Exam generation rework, batch user tables, fastapi endpoint switch
This commit is contained in:
177
src/components/ExamEditor/Exercises/Speaking/index.tsx
Normal file
177
src/components/ExamEditor/Exercises/Speaking/index.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import { ModuleState } from "@/stores/examEditor/types";
|
||||
import { SpeakingExercise, InteractiveSpeakingExercise } from "@/interfaces/exam";
|
||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||
import Header from "../../Shared/Header";
|
||||
import GenLoader from "../Shared/GenLoader";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
||||
|
||||
interface Props {
|
||||
sectionId: number;
|
||||
exercise: SpeakingExercise | InteractiveSpeakingExercise;
|
||||
}
|
||||
|
||||
const Speaking: React.FC<Props> = ({ sectionId, exercise }) => {
|
||||
const { currentModule, dispatch } = useExamEditorStore();
|
||||
const { generating, genResult } = useExamEditorStore(
|
||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||
);
|
||||
const { edit } = useExamEditorStore((store) => store.modules[currentModule]);
|
||||
|
||||
const [local, setLocal] = useState(exercise);
|
||||
const [loading, setLoading] = useState(generating === "context");
|
||||
const [questions, setQuestions] = useState(() => {
|
||||
if (sectionId === 1) {
|
||||
return (exercise as SpeakingExercise).prompts || Array(5).fill("");
|
||||
} else if (sectionId === 2) {
|
||||
return [(exercise as SpeakingExercise).text || "", ...(exercise as SpeakingExercise).prompts || Array(3).fill("")];
|
||||
} else {
|
||||
return (exercise as InteractiveSpeakingExercise).prompts?.map(p => p.text) || Array(5).fill("");
|
||||
}
|
||||
});
|
||||
|
||||
const updateModule = useCallback((updates: Partial<ModuleState>) => {
|
||||
dispatch({ type: 'UPDATE_MODULE', payload: { updates } });
|
||||
}, [dispatch]);
|
||||
|
||||
const { editing, setEditing, handleSave, handleDiscard, modeHandle } = useSectionEdit({
|
||||
sectionId,
|
||||
mode: "edit",
|
||||
onSave: () => {
|
||||
let newExercise;
|
||||
if (sectionId === 1) {
|
||||
newExercise = {
|
||||
...local,
|
||||
prompts: questions
|
||||
} as SpeakingExercise;
|
||||
} else if (sectionId === 2) {
|
||||
newExercise = {
|
||||
...local,
|
||||
text: questions[0],
|
||||
prompts: questions.slice(1),
|
||||
} as SpeakingExercise;
|
||||
} else {
|
||||
// Section 3
|
||||
newExercise = {
|
||||
...local,
|
||||
prompts: questions.map(text => ({
|
||||
text,
|
||||
video_url: (local as InteractiveSpeakingExercise).prompts?.[0]?.video_url || ""
|
||||
}))
|
||||
} as InteractiveSpeakingExercise;
|
||||
}
|
||||
setEditing(false);
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: {
|
||||
sectionId: sectionId,
|
||||
update: newExercise
|
||||
}
|
||||
});
|
||||
},
|
||||
onDiscard: () => {
|
||||
setLocal(exercise);
|
||||
if (sectionId === 1) {
|
||||
setQuestions((exercise as SpeakingExercise).prompts || Array(5).fill(""));
|
||||
} else if (sectionId === 2) {
|
||||
setQuestions([(exercise as SpeakingExercise).text || "", ...(exercise as SpeakingExercise).prompts || Array(3).fill("")]);
|
||||
} else {
|
||||
setQuestions((exercise as InteractiveSpeakingExercise).prompts?.map(p => p.text) || Array(5).fill(""));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const isLoading = generating === "context";
|
||||
setLoading(isLoading);
|
||||
|
||||
if (isLoading) {
|
||||
updateModule({ edit: Array.from(new Set([...edit, sectionId])) });
|
||||
}
|
||||
}, [generating, sectionId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "context") {
|
||||
setEditing(true);
|
||||
if (sectionId === 1) {
|
||||
setQuestions(genResult[0].questions);
|
||||
} else if (sectionId === 2) {
|
||||
setQuestions([genResult[0].question, ...genResult[0].prompts]);
|
||||
} else {
|
||||
setQuestions(genResult[0].questions);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: currentModule,
|
||||
field: "genResult",
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [genResult, generating, dispatch, sectionId, setEditing, currentModule]);
|
||||
|
||||
const handleQuestionChange = (index: number, value: string) => {
|
||||
setQuestions(prev => {
|
||||
const newQuestions = [...prev];
|
||||
newQuestions[index] = value;
|
||||
return newQuestions;
|
||||
});
|
||||
};
|
||||
|
||||
const getQuestionLabel = (index: number) => {
|
||||
if (sectionId === 2 && index === 0) {
|
||||
return "Main Question";
|
||||
} else if (sectionId === 2) {
|
||||
return `Prompt ${index}`;
|
||||
} else {
|
||||
return `Question ${index + 1}`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='relative pb-4'>
|
||||
<Header
|
||||
title={`Speaking ${sectionId} Script`}
|
||||
description='Generate or write the script for the video.'
|
||||
editing={editing}
|
||||
handleSave={handleSave}
|
||||
modeHandle={modeHandle}
|
||||
handleDiscard={handleDiscard}
|
||||
/>
|
||||
</div>
|
||||
{loading ? (
|
||||
<GenLoader module={currentModule} />
|
||||
) : (
|
||||
<div className="mx-auto p-3 space-y-6">
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{questions.map((question: string, index: number) => (
|
||||
<div key={index} className="flex flex-col">
|
||||
<h2 className="font-semibold my-2">{getQuestionLabel(index)}</h2>
|
||||
<AutoExpandingTextArea
|
||||
value={question}
|
||||
onChange={(text) => handleQuestionChange(index, 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 ${getQuestionLabel(index).toLowerCase()}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Speaking;
|
||||
Reference in New Issue
Block a user