Finallyyyyyy finished the whole Speaking flow along with the solution page
This commit is contained in:
@@ -15,7 +15,7 @@ export default function App({Component, pageProps}: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
if (router.pathname !== "/exercises") reset();
|
||||
}, [router.pathname, reset]);
|
||||
|
||||
return <Component {...pageProps} />;
|
||||
|
||||
@@ -20,26 +20,25 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const storage = getStorage(app);
|
||||
|
||||
const form = formidable({keepExtensions: true, uploadDir: "./"});
|
||||
form.parse(req, (err, fields, files) => {
|
||||
const audioFile = (files.audio as unknown as PersistentFile[])[0];
|
||||
const audioFileRef = ref(storage, `speaking_recordings/${(audioFile as any).newFilename}`);
|
||||
const [fields, files] = await form.parse(req);
|
||||
const audioFile = (files.audio as unknown as PersistentFile[])[0];
|
||||
const audioFileRef = ref(storage, `speaking_recordings/${(audioFile as any).newFilename}`);
|
||||
|
||||
const binary = fs.readFileSync((audioFile as any).filepath).buffer;
|
||||
uploadBytes(audioFileRef, binary).then(async (snapshot) => {
|
||||
const backendRequest = await axios.post(
|
||||
`${process.env.BACKEND_URL}/speaking_task_1`,
|
||||
{question: (fields.question as string[]).join(""), answer: snapshot.metadata.fullPath},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const binary = fs.readFileSync((audioFile as any).filepath).buffer;
|
||||
const snapshot = await uploadBytes(audioFileRef, binary);
|
||||
|
||||
fs.rmSync((audioFile as any).filepath);
|
||||
res.status(200).json({...backendRequest.data, fullPath: snapshot.metadata.fullPath});
|
||||
});
|
||||
});
|
||||
const backendRequest = await axios.post(
|
||||
`${process.env.BACKEND_URL}/speaking_task_1`,
|
||||
{question: (fields.question as string[]).join(""), answer: snapshot.metadata.fullPath},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
fs.rmSync((audioFile as any).filepath);
|
||||
res.status(200).json({...backendRequest.data, fullPath: snapshot.metadata.fullPath});
|
||||
}
|
||||
|
||||
export const config = {
|
||||
|
||||
@@ -4,19 +4,23 @@ import {withIronSessionApiRoute} from "iron-session/next";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import {getDownloadURL, getStorage, ref} from "firebase/storage";
|
||||
import {app} from "@/firebase";
|
||||
import axios from "axios";
|
||||
|
||||
// export default withIronSessionApiRoute(handler, sessionOptions);
|
||||
export default handler;
|
||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// if (!req.session.user) {
|
||||
// res.status(401).json({ok: false});
|
||||
// return;
|
||||
// }
|
||||
if (!req.session.user) {
|
||||
res.status(401).json({ok: false});
|
||||
return;
|
||||
}
|
||||
|
||||
const storage = getStorage(app);
|
||||
const {path} = req.body as {path: string};
|
||||
|
||||
const pathReference = ref(storage, path);
|
||||
getDownloadURL(pathReference).then((url) => res.status(200).json({url}));
|
||||
const url = await getDownloadURL(pathReference);
|
||||
|
||||
const response = await axios.get(url, {responseType: "arraybuffer"});
|
||||
|
||||
res.status(200).send(response.data);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export default function Page() {
|
||||
const speakingExam = exams.find((x) => x.id === examId)!;
|
||||
const exercise = speakingExam.exercises.find((x) => x.id === exerciseId)! as SpeakingExercise;
|
||||
|
||||
const blobResponse = await axios.get(exercise.userSolutions[0].solution.trim());
|
||||
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"});
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function Page() {
|
||||
{
|
||||
...solution,
|
||||
score: {
|
||||
correct: speakingReverseMarking[response.data.overall],
|
||||
correct: speakingReverseMarking[response.data.overall] || 0,
|
||||
missing: 0,
|
||||
total: 100,
|
||||
},
|
||||
@@ -180,7 +180,7 @@ export default function Page() {
|
||||
{
|
||||
...solution,
|
||||
score: {
|
||||
correct: writingReverseMarking[response.data.overall],
|
||||
correct: writingReverseMarking[response.data.overall] || 0,
|
||||
missing: 0,
|
||||
total: 100,
|
||||
},
|
||||
@@ -294,11 +294,6 @@ export default function Page() {
|
||||
return <Writing exam={exam} onFinish={onFinish} showSolutions={showSolutions} />;
|
||||
}
|
||||
|
||||
if (exam && exam.module === "speaking" && showSolutions) {
|
||||
setModuleIndex((prev) => prev + 1);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (exam && exam.module === "speaking") {
|
||||
return <Speaking exam={exam} onFinish={onFinish} showSolutions={showSolutions} />;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,17 @@ import {Module} from "@/interfaces";
|
||||
|
||||
import Selection from "@/exams/Selection";
|
||||
import Reading from "@/exams/Reading";
|
||||
import {Exam, ListeningExam, ReadingExam, SpeakingExam, UserSolution, Evaluation, WritingExam, WritingExercise} from "@/interfaces/exam";
|
||||
import {
|
||||
Exam,
|
||||
ListeningExam,
|
||||
ReadingExam,
|
||||
SpeakingExam,
|
||||
UserSolution,
|
||||
Evaluation,
|
||||
WritingExam,
|
||||
WritingExercise,
|
||||
SpeakingExercise,
|
||||
} from "@/interfaces/exam";
|
||||
import Listening from "@/exams/Listening";
|
||||
import Writing from "@/exams/Writing";
|
||||
import {ToastContainer, toast} from "react-toastify";
|
||||
@@ -22,7 +32,7 @@ import useExamStore from "@/stores/examStore";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import {sortByModule} from "@/utils/moduleUtils";
|
||||
import {writingReverseMarking} from "@/utils/score";
|
||||
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||
const user = req.session.user;
|
||||
@@ -122,6 +132,42 @@ 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;
|
||||
@@ -137,7 +183,7 @@ export default function Page() {
|
||||
{
|
||||
...solution,
|
||||
score: {
|
||||
correct: writingReverseMarking[response.data.overall],
|
||||
correct: writingReverseMarking[response.data.overall] || 0,
|
||||
missing: 0,
|
||||
total: 100,
|
||||
},
|
||||
@@ -148,9 +194,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const updateExamWithUserSolutions = (exam: Exam): Exam => {
|
||||
const exercises = exam.exercises.map((x) =>
|
||||
Object.assign(x, !x.userSolutions ? {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions} : x.userSolutions),
|
||||
);
|
||||
const exercises = exam.exercises.map((x) => Object.assign(x, {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions}));
|
||||
|
||||
return Object.assign(exam, exercises);
|
||||
};
|
||||
@@ -158,11 +202,17 @@ export default function Page() {
|
||||
const onFinish = (solutions: UserSolution[]) => {
|
||||
const solutionIds = solutions.map((x) => x.exercise);
|
||||
|
||||
if (exam && exam.module === "writing" && solutions.length > 0 && !showSolutions) {
|
||||
if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) {
|
||||
setHasBeenUploaded(true);
|
||||
setIsEvaluationLoading(true);
|
||||
Promise.all(
|
||||
exam.exercises.map((exercise) => evaluateWritingAnswer(exam.id, exercise.id, solutions.find((x) => x.exercise === exercise.id)!)),
|
||||
exam.exercises.map((exercise) =>
|
||||
(exam.module === "writing" ? evaluateWritingAnswer : evaluateSpeakingAnswer)(
|
||||
exam.id,
|
||||
exercise.id,
|
||||
solutions.find((x) => x.exercise === exercise.id)!,
|
||||
),
|
||||
),
|
||||
).finally(() => {
|
||||
setIsEvaluationLoading(false);
|
||||
setHasBeenUploaded(false);
|
||||
@@ -245,11 +295,6 @@ export default function Page() {
|
||||
return <Writing exam={exam} onFinish={onFinish} showSolutions={showSolutions} />;
|
||||
}
|
||||
|
||||
if (exam && exam.module === "speaking" && showSolutions) {
|
||||
setModuleIndex((prev) => prev + 1);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (exam && exam.module === "speaking") {
|
||||
return <Speaking exam={exam} onFinish={onFinish} showSolutions={showSolutions} />;
|
||||
}
|
||||
@@ -260,7 +305,7 @@ export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Exam | IELTS GPT</title>
|
||||
<title>Exercises | IELTS GPT</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
||||
|
||||
@@ -152,7 +152,7 @@ export default function History({user}: {user: User}) {
|
||||
.sort(sortByModule)
|
||||
.map((x) => x!.module),
|
||||
);
|
||||
router.push("/exam");
|
||||
router.push("/exercises");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user