From cb49e15cb05a78eb6c734bf0e4d8498f95b09535 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Tue, 18 Jun 2024 10:02:03 +0100 Subject: [PATCH] Updated the speaking and interactive speaking to the new format --- .../Exercises/InteractiveSpeaking.tsx | 30 ++----------------- src/exams/Speaking.tsx | 5 ++-- src/interfaces/exam.ts | 5 ++++ src/pages/(exam)/ExamPage.tsx | 12 ++++++-- src/pages/(generation)/SpeakingGeneration.tsx | 22 +++++++++++--- src/pages/api/evaluate/interactiveSpeaking.ts | 6 ++-- src/pages/api/evaluate/speaking.ts | 2 +- src/utils/evaluation.ts | 10 +++++-- 8 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/components/Exercises/InteractiveSpeaking.tsx b/src/components/Exercises/InteractiveSpeaking.tsx index 3b3e65aa..b1a02924 100644 --- a/src/components/Exercises/InteractiveSpeaking.tsx +++ b/src/components/Exercises/InteractiveSpeaking.tsx @@ -16,8 +16,9 @@ const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mo export default function InteractiveSpeaking({ id, title, + first_title, + second_title, examID, - text, type, prompts, userSolutions, @@ -35,31 +36,6 @@ export default function InteractiveSpeaking({ const hasExamEnded = useExamStore((state) => state.hasExamEnded); - const saveToStorage = async (previousURL?: string) => { - if (mediaBlob && mediaBlob.startsWith("blob")) { - const blobBuffer = await downloadBlob(mediaBlob); - const audioFile = new File([blobBuffer], "audio.wav", {type: "audio/wav"}); - - const seed = Math.random().toString().replace("0.", ""); - - const formData = new FormData(); - formData.append("audio", audioFile, `${seed}.wav`); - formData.append("root", "speaking_recordings"); - - const config = { - headers: { - "Content-Type": "audio/wav", - }, - }; - - const response = await axios.post<{path: string}>("/api/storage/insert", formData, config); - if (previousURL && !previousURL.startsWith("blob")) await axios.post("/api/storage/delete", {path: previousURL}); - return response.data.path; - } - - return undefined; - }; - const back = async () => { setIsLoading(true); @@ -177,7 +153,7 @@ export default function InteractiveSpeaking({
- {title} + {!!first_title && !!second_title ? `${first_title} & ${second_title}` : title}
{prompts && prompts.length > 0 && (
diff --git a/src/exams/Speaking.tsx b/src/exams/Speaking.tsx index 8dcd8709..7d6271db 100644 --- a/src/exams/Speaking.tsx +++ b/src/exams/Speaking.tsx @@ -2,7 +2,7 @@ import {renderExercise} from "@/components/Exercises"; import ModuleTitle from "@/components/Medium/ModuleTitle"; import {renderSolution} from "@/components/Solutions"; import {infoButtonStyle} from "@/constants/buttonStyles"; -import {UserSolution, SpeakingExam} from "@/interfaces/exam"; +import {UserSolution, SpeakingExam, SpeakingExercise, InteractiveSpeakingExercise} from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; import {defaultUserSolutions} from "@/utils/exams"; import {countExercises} from "@/utils/moduleUtils"; @@ -64,8 +64,9 @@ export default function Speaking({exam, showSolutions = false, onFinish}: Props) const exercise = exam.exercises[exerciseIndex]; return { ...exercise, + variant: exerciseIndex < 2 && exercise.type === "interactiveSpeaking" ? "initial" : undefined, userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [], - }; + } as SpeakingExercise | InteractiveSpeakingExercise; }; return ( diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts index 9b4b255a..50031470 100644 --- a/src/interfaces/exam.ts +++ b/src/interfaces/exam.ts @@ -177,6 +177,8 @@ export interface InteractiveSpeakingExercise { id: string; type: "interactiveSpeaking"; title: string; + first_title?: string; + second_title?: string; text: string; prompts: {text: string; video_url: string}[]; userSolutions: { @@ -185,6 +187,9 @@ export interface InteractiveSpeakingExercise { evaluation?: InteractiveSpeakingEvaluation; }[]; topic?: string; + first_topic?: string; + second_topic?: string; + variant?: "initial" | "final"; } export interface FillBlanksExercise { diff --git a/src/pages/(exam)/ExamPage.tsx b/src/pages/(exam)/ExamPage.tsx index 367cfa10..52a81e5e 100644 --- a/src/pages/(exam)/ExamPage.tsx +++ b/src/pages/(exam)/ExamPage.tsx @@ -133,14 +133,22 @@ export default function ExamPage({page}: Props) { !!exam && timeSpent > 0 && !showSolutions && - moduleIndex < selectedModules.length + moduleIndex < selectedModules.length && + selectedModules[moduleIndex] !== "speaking" ) saveSession(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [assignment, exam, exams, moduleIndex, selectedModules, sessionId, userSolutions, user, exerciseIndex, partIndex, questionIndex]); useEffect(() => { - if (timeSpent % 20 === 0 && timeSpent > 0 && moduleIndex < selectedModules.length && !showSolutions) saveSession(); + if ( + timeSpent % 20 === 0 && + timeSpent > 0 && + moduleIndex < selectedModules.length && + selectedModules[moduleIndex] !== "speaking" && + !showSolutions + ) + saveSession(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [timeSpent]); diff --git a/src/pages/(generation)/SpeakingGeneration.tsx b/src/pages/(generation)/SpeakingGeneration.tsx index 25d3e70d..e0d1134a 100644 --- a/src/pages/(generation)/SpeakingGeneration.tsx +++ b/src/pages/(generation)/SpeakingGeneration.tsx @@ -45,6 +45,7 @@ const PartTab = ({ .then((result) => { playSound(typeof result.data === "string" ? "error" : "check"); if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again."); + console.log(result.data); setPart(result.data); }) .catch((error) => { @@ -54,7 +55,7 @@ const PartTab = ({ .finally(() => setIsLoading(false)); }; - const generateVideo = () => { + const generateVideo = async () => { if (!part) return toast.error("Please generate the first part before generating the video!"); toast.info("This will take quite a while, please do not leave this page or close the tab/window."); @@ -64,11 +65,12 @@ const PartTab = ({ const initialTime = moment(); axios - .post(`/api/exam/speaking/generate/speaking/generate_${index === 3 ? "interactive" : "speaking"}_video`, {...part, avatar: avatar?.id}) + .post(`/api/exam/speaking/generate/speaking/generate_video_${index}`, {...part, avatar: avatar?.id}) .then((result) => { const isError = typeof result.data === "string" || moment().diff(initialTime, "seconds") < 60; playSound(isError ? "error" : "check"); + console.log(result.data); if (isError) return toast.error("Something went wrong, please try to generate the video again."); setPart({...part, result: {...result.data, topic: part?.topic}, gender, avatar}); }) @@ -139,7 +141,9 @@ const PartTab = ({ )} {part && !isLoading && (
-

{part.topic}

+

+ {!!part.first_topic && !!part.second_topic ? `${part.first_topic} & ${part.second_topic}` : part.topic} +

{part.question && {part.question}} {part.questions && (
@@ -177,6 +181,8 @@ interface SpeakingPart { question?: string; questions?: string[]; topic: string; + first_topic?: string; + second_topic?: string; result?: SpeakingExercise | InteractiveSpeakingExercise; gender?: "male" | "female"; avatar?: (typeof AVATARS)[number]; @@ -208,10 +214,18 @@ const SpeakingGeneration = () => { const genders = [part1?.gender, part2?.gender, part3?.gender].filter((x) => !!x); + const exercises = [part1?.result, part2?.result, part3?.result] + .filter((x) => !!x) + .map((x) => ({ + ...x, + first_title: x?.type === "interactiveSpeaking" ? x.first_topic : undefined, + second_title: x?.type === "interactiveSpeaking" ? x.second_topic : undefined, + })); + const exam: SpeakingExam = { id: v4(), isDiagnostic: false, - exercises: [part1?.result, part2?.result, part3?.result].filter((x) => !!x) as (SpeakingExercise | InteractiveSpeakingExercise)[], + exercises: exercises as (SpeakingExercise | InteractiveSpeakingExercise)[], minTimer, variant: minTimer >= 14 ? "full" : "partial", module: "speaking", diff --git a/src/pages/api/evaluate/interactiveSpeaking.ts b/src/pages/api/evaluate/interactiveSpeaking.ts index 9612d5a7..6f38eac7 100644 --- a/src/pages/api/evaluate/interactiveSpeaking.ts +++ b/src/pages/api/evaluate/interactiveSpeaking.ts @@ -47,7 +47,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { res.status(200).json(null); console.log("🌱 - Still processing"); - const backendRequest = await evaluate({answers: uploadingAudios}); + const backendRequest = await evaluate({answers: uploadingAudios}, fields.variant); console.log("🌱 - Process complete"); const correspondingStat = await getCorrespondingStat(fields.id, 1); @@ -79,8 +79,8 @@ async function getCorrespondingStat(id: string, index: number): Promise { return getCorrespondingStat(id, index + 1); } -async function evaluate(body: {answers: object[]}): Promise { - const backendRequest = await axios.post(`${process.env.BACKEND_URL}/speaking_task_3`, body, { +async function evaluate(body: {answers: object[]}, variant?: "initial" | "final"): Promise { + const backendRequest = await axios.post(`${process.env.BACKEND_URL}/speaking_task_${variant === "initial" ? "1" : "3"}`, body, { headers: { Authorization: `Bearer ${process.env.BACKEND_JWT}`, }, diff --git a/src/pages/api/evaluate/speaking.ts b/src/pages/api/evaluate/speaking.ts index 3f9431b9..382ddc02 100644 --- a/src/pages/api/evaluate/speaking.ts +++ b/src/pages/api/evaluate/speaking.ts @@ -78,7 +78,7 @@ async function getCorrespondingStat(id: string, index: number): Promise { } async function evaluate(body: {answer: string; question: string}, task: number): Promise { - const backendRequest = await axios.post(`${process.env.BACKEND_URL}/speaking_task_${task}`, body, { + const backendRequest = await axios.post(`${process.env.BACKEND_URL}/speaking_task_2`, body, { headers: { Authorization: `Bearer ${process.env.BACKEND_JWT}`, }, diff --git a/src/utils/evaluation.ts b/src/utils/evaluation.ts index 82283a4b..c1e950b3 100644 --- a/src/utils/evaluation.ts +++ b/src/utils/evaluation.ts @@ -51,7 +51,7 @@ export const evaluateSpeakingAnswer = async ( case "speaking": return {...(await evaluateSpeakingExercise(exercise, exercise.id, solution, id, task)), id} as UserSolution; case "interactiveSpeaking": - return {...(await evaluateInteractiveSpeakingExercise(exercise.id, solution, id)), id} as UserSolution; + return {...(await evaluateInteractiveSpeakingExercise(exercise.id, solution, id, exercise.variant)), id} as UserSolution; default: return undefined; } @@ -110,7 +110,12 @@ const evaluateSpeakingExercise = async ( return undefined; }; -const evaluateInteractiveSpeakingExercise = async (exerciseId: string, solution: UserSolution, id: string): Promise => { +const evaluateInteractiveSpeakingExercise = async ( + exerciseId: string, + solution: UserSolution, + id: string, + variant?: "initial" | "final", +): Promise => { const promiseParts = solution.solutions.map(async (x: {prompt: string; blob: string}) => { const blob = await downloadBlob(x.blob); if (!x.blob.startsWith("blob")) await axios.post("/api/storage/delete", {path: x.blob}); @@ -132,6 +137,7 @@ const evaluateInteractiveSpeakingExercise = async (exerciseId: string, solution: formData.append(`answer_${seed}`, audioFile, `${seed}.wav`); }); formData.append("id", id); + formData.append("variant", variant || "final"); const config = { headers: {