import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea"; import { Card, CardContent } from "@/components/ui/card"; import { BiQuestionMark } from 'react-icons/bi'; import { AiOutlineUnorderedList, AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai'; import { Tooltip } from "react-tooltip"; import Header from "../../Shared/Header"; import GenLoader from "../Shared/GenLoader"; import { useCallback, useEffect, useState } from "react"; import useSectionEdit from "../../Hooks/useSectionEdit"; import useExamEditorStore from "@/stores/examEditor"; import { Difficulty, InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam"; import { BsFileText } from "react-icons/bs"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa6"; import { RiVideoLine } from "react-icons/ri"; import { Module } from "@/interfaces"; interface Props { sectionId: number; exercise: InteractiveSpeakingExercise; module?: Module; } const InteractiveSpeaking: React.FC = ({ sectionId, exercise, module = "speaking" }) => { const { currentModule, dispatch } = useExamEditorStore(); const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty); const [local, setLocal] = useState(exercise); const [currentVideoIndex, setCurrentVideoIndex] = useState(0); const { generating, genResult, state, levelGenResults, levelGenerating } = useExamEditorStore( (state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)! ); const { editing, setEditing, handleSave, handleDiscard, handleEdit, handlePractice } = useSectionEdit({ sectionId, onSave: () => { setEditing(false); if (module === "level") { const updatedState = { ...state, exercises: (state as LevelPart).exercises.map((ex) => ex.id === local.id ? local : ex ) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: updatedState, module } }); } else { dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: local, module } }); } if (genResult) { dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "genResult", value: undefined } }); } const speakingScript = levelGenResults?.find((res) => res.generating === `${local.id}-speakingScript`); if (module === "level" && speakingScript) { dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, field: "levelGenResults", value: levelGenResults.filter((res) => res.generating !== `${local.id}-speakingScript`), module } }); } }, onDiscard: () => { setLocal(exercise); }, onPractice: () => { const updatedLocal = { ...local, isPractice: !local.isPractice }; setLocal(updatedLocal); if (module === "level") { const updatedState = { ...state, exercises: (state as LevelPart).exercises.map((ex) => ex.id === local.id ? updatedLocal : ex ) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: updatedState, module } }); } else { dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: updatedLocal, module } }); } }, }); useEffect(() => { if (genResult && generating === "speakingScript") { if (!difficulty.includes(genResult.result[0].difficulty)) { dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, genResult.result[0].difficulty]} } }); } const updatedLocal = { ...local, title: genResult.result[0].title, prompts: genResult.result[0].prompts.map((item: any) => ({ text: item || "", video_url: "" })), difficulty: genResult.result[0].difficulty }; setEditing(true); setLocal(updatedLocal); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "generating", value: undefined } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [genResult, generating]); useEffect(() => { if (genResult && generating === "video") { const updatedLocal = { ...local, prompts: genResult.result[0].prompts }; setLocal(updatedLocal); dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: updatedLocal, module } }); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module, field: "generating", value: undefined } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [genResult, generating]); useEffect(() => { const speakingScript = levelGenResults?.find((res) => res.generating === `${local.id}-speakingScript`); const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`); if (speakingScript && isGenerating) { if (!difficulty.includes(speakingScript.result[0].difficulty)) { dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, speakingScript.result[0].difficulty]} } }); } const updatedLocal = { ...local, title: speakingScript.result[0].title, prompts: speakingScript.result[0].prompts.map((item: any) => ({ text: item || "", video_url: "" })), difficulty: speakingScript.result[0].difficulty }; setEditing(true); setLocal(updatedLocal); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, field: "levelGenerating", value: levelGenerating.filter((g) => g !== `${local.id}-speakingScript`), module } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [levelGenResults, levelGenerating]); useEffect(() => { const speakingVideo = levelGenResults?.find((res) => res.generating === `${local.id}-video`); const isGenerating = levelGenerating?.includes(`${local.id}-video`); if (speakingVideo && isGenerating) { const updatedLocal = { ...local, prompts: speakingVideo.result[0].prompts }; setLocal(updatedLocal); const updatedState = { ...state, exercises: (state as LevelPart).exercises.map((ex) => ex.id === local.id ? updatedLocal : ex ) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: updatedState, module } }); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, field: "levelGenerating", value: levelGenerating.filter((g) => g !== `${local.id}-video`), module } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [levelGenResults, levelGenerating]); const addPrompt = () => { setLocal(prev => ({ ...prev, prompts: [...prev.prompts, { text: "", video_url: "" }] })); }; const removePrompt = (index: number) => { setLocal(prev => ({ ...prev, prompts: prev.prompts.filter((_, i) => i !== index) })); }; const updatePrompt = (index: number, text: string) => { setLocal(prev => { const newPrompts = [...prev.prompts]; newPrompts[index] = { ...newPrompts[index], text }; return { ...prev, prompts: newPrompts }; }); }; const isUnedited = local.prompts.length === 0; useEffect(() => { if (genResult && generating === "video") { setLocal({ ...local, prompts: genResult.result[0].prompts }); dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: { ...local, prompts: genResult.result[0].prompts }, module: module } }); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: module, field: "generating", value: undefined } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [genResult, generating]); const handlePrevVideo = () => { setCurrentVideoIndex((prev) => (prev > 0 ? prev - 1 : prev)); }; const handleNextVideo = () => { setCurrentVideoIndex((prev) => (prev < local.prompts.length - 1 ? prev + 1 : prev) ); }; const saveDifficulty = useCallback((diff: Difficulty)=> { if (!difficulty.includes(diff)) { dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } }); } if (module !== "level") { const updatedExercise = { ...exercise, difficulty: diff }; dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: currentModule } }); } else { const updatedExercise = { ...exercise, difficulty: diff }; const newState = { ...state as LevelPart }; newState.exercises = (newState as LevelPart).exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex ); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); } }, [currentModule, difficulty, dispatch, exercise, module, sectionId, state]); return ( <>
{(generating && generating === "speakingScript") || (levelGenerating.find((g) => g === `${local.id}-speakingScript`)) ? ( ) : ( <> {editing ? ( <> {local.prompts.every((p) => p.video_url !== "") && (

Videos

{currentVideoIndex + 1} / {local.prompts.length}
)} {(generating && generating === "video") || levelGenerating.find((g) => g === `${local.id}-video`) && }

Title

setLocal(prev => ({ ...prev, title: text }))} className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] transition-all" placeholder="Enter the title" />

Questions

{local.prompts.length === 0 ? (

No questions added yet

) : ( local.prompts.map((prompt, index) => (

Question {index + 1}

updatePrompt(index, text)} className="w-full p-3 border border-gray-200 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] transition-all bg-white" placeholder={`Enter question ${index + 1}`} />
)) )}
) : isUnedited ? (

Generate or edit the questions!

) : (

Title

{local.title || 'Untitled'}

Questions

{local.prompts .filter(prompt => prompt.text !== "") .map((prompt, index) => (

Question {index + 1}

{prompt.text}

)) }
)} )} ); }; export default InteractiveSpeaking;