import { Evaluation, Exam, InteractiveSpeakingExercise, SpeakingExam, SpeakingExercise, UserSolution, WritingExam, WritingExercise, } from "@/interfaces/exam"; import axios from "axios"; import {speakingReverseMarking, writingReverseMarking} from "./score"; export const evaluateWritingAnswer = async ( exercise: WritingExercise, task: number, solution: UserSolution, id: string, ): Promise => { const response = await axios.post("/api/evaluate/writing", { question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""), answer: solution.solutions[0].solution.trim().replaceAll("\n", " "), task, id, }); if (response.status === 200) { return { ...solution, id, score: { correct: response.data ? writingReverseMarking[response.data.overall] : 0, missing: 0, total: 100, }, solutions: [{id: exercise.id, solution: solution.solutions[0].solution, evaluation: response.data}], isDisabled: true, }; } return undefined; }; export const evaluateSpeakingAnswer = async ( exercise: SpeakingExercise | InteractiveSpeakingExercise, solution: UserSolution, id: string, task: number, ): Promise => { switch (exercise?.type) { case "speaking": return {...(await evaluateSpeakingExercise(exercise, exercise.id, solution, id, task)), id} as UserSolution; case "interactiveSpeaking": return {...(await evaluateInteractiveSpeakingExercise(exercise.id, solution, id, task === 3 ? "final" : "initial")), id} as UserSolution; default: return undefined; } }; export const downloadBlob = async (url: string): Promise => { const blobResponse = await axios.get(url, {responseType: "arraybuffer"}); return Buffer.from(blobResponse.data, "binary"); }; const evaluateSpeakingExercise = async ( exercise: SpeakingExercise, exerciseId: string, solution: UserSolution, id: string, task: number, ): Promise => { const formData = new FormData(); const url = solution.solutions[0].solution.trim() as string; const audioBlob = await downloadBlob(url); const audioFile = new File([audioBlob], "audio.wav", {type: "audio/wav"}); if (url && !url.startsWith("blob")) await axios.post("/api/storage/delete", {path: url}); formData.append("audio", audioFile, "audio.wav"); const evaluationQuestion = `${exercise.text.replaceAll("\n", "")}` + (exercise.prompts.length > 0 ? `You should talk about: ${exercise.prompts.join(", ")}` : ""); formData.append("question", evaluationQuestion); formData.append("id", id); formData.append("task", task.toString()); const config = { headers: { "Content-Type": "audio/wav", }, }; const response = await axios.post("/api/evaluate/speaking", formData, config); if (response.status === 200) { return { ...solution, id, score: { correct: 0, missing: 0, total: 100, }, solutions: [{id: exerciseId, solution: response.data ? response.data.fullPath : null, evaluation: response.data}], isDisabled: true, }; } return undefined; }; 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}); return { question: x.prompt, answer: blob, }; }); const body = await Promise.all(promiseParts); const formData = new FormData(); body.forEach(({question, answer}) => { const seed = Math.random().toString().replace("0.", ""); const audioFile = new File([answer], `${seed}.wav`, {type: "audio/wav"}); formData.append(`question_${seed}`, question); formData.append(`answer_${seed}`, audioFile, `${seed}.wav`); }); formData.append("id", id); formData.append("variant", variant || "final"); const config = { headers: { "Content-Type": "audio/mp3", }, }; const response = await axios.post("/api/evaluate/interactiveSpeaking", formData, config); if (response.status === 200) { return { ...solution, id, score: { correct: 0, missing: 0, total: 100, }, module: "speaking", solutions: [{id: exerciseId, solution: response.data ? response.data.answer : null, evaluation: response.data}], isDisabled: true, }; } return undefined; };