Prepared the code to later handle the evaluation of the Interactive Speaking exercise

This commit is contained in:
Tiago Ribeiro
2023-09-17 08:56:00 +01:00
parent 161d5236b4
commit efb341355d
5 changed files with 109 additions and 131 deletions

View File

@@ -106,6 +106,19 @@ export interface SpeakingExercise {
}[]; }[];
} }
export interface InteractiveSpeakingExercise {
id: string;
type: "speaking";
title: string;
text: string;
prompts: {text: string; video_url: string}[];
userSolutions: {
id: string;
solution: string;
evaluation?: Evaluation;
}[];
}
export interface FillBlanksExercise { export interface FillBlanksExercise {
prompt: string; // *EXAMPLE: "Complete the summary below. Click a blank to select the corresponding word for it." prompt: string; // *EXAMPLE: "Complete the summary below. Click a blank to select the corresponding word for it."
type: "fillBlanks"; type: "fillBlanks";

View File

@@ -62,7 +62,7 @@ const ExamLoader = () => {
setIsLoading(true); setIsLoading(true);
if (selectedModule && examId) { if (selectedModule && examId) {
const exam = await getExamById(selectedModule, examId); const exam = await getExamById(selectedModule, examId.trim());
if (!exam) { if (!exam) {
toast.error("Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", { toast.error("Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", {
toastId: "invalid-exam-id", toastId: "invalid-exam-id",

View File

@@ -31,6 +31,7 @@ import useExamStore from "@/stores/examStore";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score"; import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
import AbandonPopup from "@/components/AbandonPopup"; import AbandonPopup from "@/components/AbandonPopup";
import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -132,67 +133,6 @@ export default function Page() {
} }
}; };
const evaluateSpeakingAnswer = async (examId: string, exerciseId: string, solution: UserSolution) => {
const speakingExam = exams.find((x) => x.id === examId)!;
const exercise = speakingExam.exercises.find((x) => x.id === exerciseId)! as SpeakingExercise;
const blobResponse = await axios.get(solution.solutions[0].solution.trim(), {responseType: "arraybuffer"});
const audioBlob = Buffer.from(blobResponse.data, "binary");
const audioFile = new File([audioBlob], "audio.wav", {type: "audio/wav"});
const formData = new FormData();
formData.append("audio", audioFile, "audio.wav");
formData.append("question", `${exercise.text.replaceAll("\n", "")} You should talk about: ${exercise.prompts.join(", ")}`);
const config = {
headers: {
"Content-Type": "audio/mp3",
},
};
const response = await axios.post("/api/evaluate/speaking", formData, config);
if (response.status === 200) {
setUserSolutions([
...userSolutions.filter((x) => x.exercise !== exerciseId),
{
...solution,
score: {
correct: speakingReverseMarking[response.data.overall] || 0,
missing: 0,
total: 100,
},
solutions: [{id: exerciseId, solution: response.data.fullPath, evaluation: response.data}],
},
]);
}
};
const evaluateWritingAnswer = async (examId: string, exerciseId: string, solution: UserSolution) => {
const writingExam = exams.find((x) => x.id === examId)!;
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
const response = await axios.post<Evaluation>("/api/evaluate/writing", {
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
});
if (response.status === 200) {
setUserSolutions([
...userSolutions.filter((x) => x.exercise !== exerciseId),
{
...solution,
score: {
correct: writingReverseMarking[response.data.overall] || 0,
missing: 0,
total: 100,
},
solutions: [{id: exerciseId, solution: solution.solutions[0].solution, evaluation: response.data}],
},
]);
}
};
const updateExamWithUserSolutions = (exam: Exam): Exam => { const updateExamWithUserSolutions = (exam: Exam): Exam => {
const exercises = exam.exercises.map((x) => const exercises = exam.exercises.map((x) =>
Object.assign(x, !x.userSolutions ? {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions} : x.userSolutions), Object.assign(x, !x.userSolutions ? {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions} : x.userSolutions),
@@ -203,18 +143,27 @@ export default function Page() {
const onFinish = (solutions: UserSolution[]) => { const onFinish = (solutions: UserSolution[]) => {
const solutionIds = solutions.map((x) => x.exercise); const solutionIds = solutions.map((x) => x.exercise);
const solutionExams = solutions.map((x) => x.exam);
if (exam && !solutionExams.includes(exam.id)) return;
if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) { if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) {
setHasBeenUploaded(true); setHasBeenUploaded(true);
setIsEvaluationLoading(true); setIsEvaluationLoading(true);
Promise.all( Promise.all(
exam.exercises.map((exercise) => exam.exercises.map(async (exercise) => {
(exam.module === "writing" ? evaluateWritingAnswer : evaluateSpeakingAnswer)( return (exam.module === "writing" ? evaluateWritingAnswer : evaluateSpeakingAnswer)(
exams,
exam.id, exam.id,
exercise.id, exercise.id,
solutions.find((x) => x.exercise === exercise.id)!, solutions.find((x) => x.exercise === exercise.id)!,
), ).then((response) => {
), if (response) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== exercise.id), response]);
}
});
}),
).finally(() => { ).finally(() => {
setIsEvaluationLoading(false); setIsEvaluationLoading(false);
setHasBeenUploaded(false); setHasBeenUploaded(false);

View File

@@ -34,6 +34,7 @@ import Layout from "@/components/High/Layout";
import {sortByModule} from "@/utils/moduleUtils"; import {sortByModule} from "@/utils/moduleUtils";
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score"; import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
import AbandonPopup from "@/components/AbandonPopup"; import AbandonPopup from "@/components/AbandonPopup";
import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -136,67 +137,6 @@ export default function Page() {
} }
}; };
const evaluateSpeakingAnswer = async (examId: string, exerciseId: string, solution: UserSolution) => {
const speakingExam = exams.find((x) => x.id === examId)!;
const exercise = speakingExam.exercises.find((x) => x.id === exerciseId)! as SpeakingExercise;
const blobResponse = await axios.get(solution.solutions[0].solution.trim(), {responseType: "arraybuffer"});
const audioBlob = Buffer.from(blobResponse.data, "binary");
const audioFile = new File([audioBlob], "audio.wav", {type: "audio/wav"});
const formData = new FormData();
formData.append("audio", audioFile, "audio.wav");
formData.append("question", `${exercise.text.replaceAll("\n", "")} You should talk about: ${exercise.prompts.join(", ")}`);
const config = {
headers: {
"Content-Type": "audio/mp3",
},
};
const response = await axios.post("/api/evaluate/speaking", formData, config);
if (response.status === 200) {
setUserSolutions([
...userSolutions.filter((x) => x.exercise !== exerciseId),
{
...solution,
score: {
correct: speakingReverseMarking[response.data.overall] || 0,
missing: 0,
total: 100,
},
solutions: [{id: exerciseId, solution: response.data.fullPath, evaluation: response.data}],
},
]);
}
};
const evaluateWritingAnswer = async (examId: string, exerciseId: string, solution: UserSolution) => {
const writingExam = exams.find((x) => x.id === examId)!;
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
const response = await axios.post<Evaluation>("/api/evaluate/writing", {
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
});
if (response.status === 200) {
setUserSolutions([
...userSolutions.filter((x) => x.exercise !== exerciseId),
{
...solution,
score: {
correct: writingReverseMarking[response.data.overall] || 0,
missing: 0,
total: 100,
},
solutions: [{id: exerciseId, solution: solution.solutions[0].solution, evaluation: response.data}],
},
]);
}
};
const updateExamWithUserSolutions = (exam: Exam): Exam => { const updateExamWithUserSolutions = (exam: Exam): Exam => {
const exercises = exam.exercises.map((x) => Object.assign(x, {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions})); const exercises = exam.exercises.map((x) => Object.assign(x, {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions}));
@@ -212,14 +152,20 @@ export default function Page() {
if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) { if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) {
setHasBeenUploaded(true); setHasBeenUploaded(true);
setIsEvaluationLoading(true); setIsEvaluationLoading(true);
Promise.all( Promise.all(
exam.exercises.map((exercise) => exam.exercises.map(async (exercise) => {
(exam.module === "writing" ? evaluateWritingAnswer : evaluateSpeakingAnswer)( return (exam.module === "writing" ? evaluateWritingAnswer : evaluateSpeakingAnswer)(
exams,
exam.id, exam.id,
exercise.id, exercise.id,
solutions.find((x) => x.exercise === exercise.id)!, solutions.find((x) => x.exercise === exercise.id)!,
), ).then((response) => {
), if (response) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== exercise.id), response]);
}
});
}),
).finally(() => { ).finally(() => {
setIsEvaluationLoading(false); setIsEvaluationLoading(false);
setHasBeenUploaded(false); setHasBeenUploaded(false);

70
src/utils/evaluation.ts Normal file
View File

@@ -0,0 +1,70 @@
import {Evaluation, Exam, SpeakingExercise, UserSolution, WritingExercise} from "@/interfaces/exam";
import axios from "axios";
import {speakingReverseMarking, writingReverseMarking} from "./score";
export const evaluateWritingAnswer = async (exams: Exam[], examId: string, exerciseId: string, solution: UserSolution) => {
const writingExam = exams.find((x) => x.id === examId)!;
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
const response = await axios.post<Evaluation>("/api/evaluate/writing", {
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
});
if (response.status === 200) {
return {
...solution,
score: {
correct: writingReverseMarking[response.data.overall] || 0,
missing: 0,
total: 100,
},
solutions: [{id: exerciseId, solution: solution.solutions[0].solution, evaluation: response.data}],
};
}
return undefined;
};
export const evaluateSpeakingAnswer = async (exams: Exam[], examId: string, exerciseId: string, solution: UserSolution) => {
const speakingExam = exams.find((x) => x.id === examId)!;
const exercise = speakingExam.exercises.find((x) => x.id === exerciseId);
if (exercise?.type === "speaking") {
return await evaluateSpeakingExercise(exercise, exerciseId, solution);
}
return undefined;
};
const evaluateSpeakingExercise = async (exercise: SpeakingExercise, exerciseId: string, solution: UserSolution) => {
const blobResponse = await axios.get(solution.solutions[0].solution.trim(), {responseType: "arraybuffer"});
const audioBlob = Buffer.from(blobResponse.data, "binary");
const audioFile = new File([audioBlob], "audio.wav", {type: "audio/wav"});
const formData = new FormData();
formData.append("audio", audioFile, "audio.wav");
formData.append("question", `${exercise.text.replaceAll("\n", "")} You should talk about: ${exercise.prompts.join(", ")}`);
const config = {
headers: {
"Content-Type": "audio/mp3",
},
};
const response = await axios.post("/api/evaluate/speaking", formData, config);
if (response.status === 200) {
return {
...solution,
score: {
correct: speakingReverseMarking[response.data.overall] || 0,
missing: 0,
total: 100,
},
solutions: [{id: exerciseId, solution: response.data.fullPath, evaluation: response.data}],
};
}
return undefined;
};