Made it so the Speaking is sent to the backend and saved to Firebase
This commit is contained in:
@@ -23,6 +23,8 @@
|
|||||||
"eslint": "8.33.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-next": "13.1.6",
|
"eslint-config-next": "13.1.6",
|
||||||
"firebase": "9.19.1",
|
"firebase": "9.19.1",
|
||||||
|
"formidable": "^3.5.0",
|
||||||
|
"formidable-serverless": "^1.1.1",
|
||||||
"framer-motion": "^9.0.2",
|
"framer-motion": "^9.0.2",
|
||||||
"iron-session": "^6.3.1",
|
"iron-session": "^6.3.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
"zustand": "^4.3.6"
|
"zustand": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/formidable": "^3.4.0",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"@types/wavesurfer.js": "^6.0.6",
|
"@types/wavesurfer.js": "^6.0.6",
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
import {SpeakingExercise} from "@/interfaces/exam";
|
||||||
import {SpeakingExercise, WritingExercise} from "@/interfaces/exam";
|
|
||||||
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
|
||||||
import Icon from "@mdi/react";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import {CommonProps} from ".";
|
import {CommonProps} from ".";
|
||||||
import {Fragment, useEffect, useState} from "react";
|
import {Fragment, useEffect, useState} from "react";
|
||||||
import {toast} from "react-toastify";
|
|
||||||
import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPlayCircle, BsTrashFill} from "react-icons/bs";
|
import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPlayCircle, BsTrashFill} from "react-icons/bs";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
|
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
|
||||||
const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), {
|
const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), {
|
||||||
@@ -33,6 +29,30 @@ export default function Speaking({id, title, text, type, prompts, onNext, onBack
|
|||||||
};
|
};
|
||||||
}, [isRecording]);
|
}, [isRecording]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const uploadFile = () => {
|
||||||
|
if (mediaBlob) {
|
||||||
|
axios.get(mediaBlob, {responseType: "arraybuffer"}).then((response) => {
|
||||||
|
const audioBlob = Buffer.from(response.data, "binary");
|
||||||
|
const audioFile = new File([audioBlob], "audio.wav", {type: "audio/wav"});
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("audio", audioFile, "audio.wav");
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "audio/mp3",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.post("/api/evaluate/speaking", formData, config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mediaBlob) uploadFile();
|
||||||
|
}, [mediaBlob]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full w-full gap-9">
|
<div className="flex flex-col h-full w-full gap-9">
|
||||||
<div className="flex flex-col w-full gap-14 bg-mti-gray-smoke rounded-xl py-8 pb-12 px-16">
|
<div className="flex flex-col w-full gap-14 bg-mti-gray-smoke rounded-xl py-8 pb-12 px-16">
|
||||||
|
|||||||
46
src/pages/api/evaluate/speaking.ts
Normal file
46
src/pages/api/evaluate/speaking.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type {NextApiRequest, NextApiResponse} from "next";
|
||||||
|
import {getFirestore, doc, getDoc} from "firebase/firestore";
|
||||||
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
|
import {sessionOptions} from "@/lib/session";
|
||||||
|
import axios from "axios";
|
||||||
|
import formidable from "formidable";
|
||||||
|
import PersistentFile from "formidable/PersistentFile";
|
||||||
|
import {getStorage, ref, uploadBytes} from "firebase/storage";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
|
|
||||||
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (!req.session.user) {
|
||||||
|
res.status(401).json({ok: false});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage = getStorage();
|
||||||
|
|
||||||
|
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 binary = fs.readFileSync((audioFile as any).filepath).buffer;
|
||||||
|
uploadBytes(audioFileRef, binary).then(async (snapshot) => {
|
||||||
|
const backendRequest = await axios.post(`${process.env.BACKEND_URL}/writing_task2`, req.body as Body, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.rmSync((audioFile as any).filepath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({ok: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -18,19 +18,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {module} = req.query as {module: string};
|
const backendRequest = await axios.post(`${process.env.BACKEND_URL}/writing_task2`, req.body as Body, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (module === "writing") {
|
res.status(backendRequest.status).json(backendRequest.data);
|
||||||
const backendRequest = await axios.post(`${process.env.BACKEND_URL}/writing_task2`, req.body as Body, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(backendRequest.status).json(backendRequest.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).json({ok: false});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ export default function Page() {
|
|||||||
const writingExam = exams.find((x) => x.id === examId)!;
|
const writingExam = exams.find((x) => x.id === examId)!;
|
||||||
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
|
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
|
||||||
|
|
||||||
const response = await axios.post<WritingEvaluation>("/api/exam/writing/evaluate", {
|
const response = await axios.post<WritingEvaluation>("/api/evaluate/writing", {
|
||||||
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
|
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
|
||||||
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
|
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default function Page() {
|
|||||||
const writingExam = exams.find((x) => x.id === examId)!;
|
const writingExam = exams.find((x) => x.id === examId)!;
|
||||||
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
|
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
|
||||||
|
|
||||||
const response = await axios.post<WritingEvaluation>("/api/exam/writing/evaluate", {
|
const response = await axios.post<WritingEvaluation>("/api/evaluate/writing", {
|
||||||
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
|
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
|
||||||
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
|
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
|
||||||
});
|
});
|
||||||
|
|||||||
48
yarn.lock
48
yarn.lock
@@ -797,6 +797,13 @@
|
|||||||
"@types/qs" "*"
|
"@types/qs" "*"
|
||||||
"@types/serve-static" "*"
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/formidable@^3.4.0":
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-3.4.0.tgz#5a671de8f88f3f313b31041f4746141e4431f82b"
|
||||||
|
integrity sha512-JXP+LsspYYBIJJxZ9VJsswb5U1hkUUhLmtAb6EB1SWcDDbJQlWRhGoZYasMSnk2NsqtUEHd3uuaiImmSys+8AQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/http-assert@*":
|
"@types/http-assert@*":
|
||||||
version "1.5.3"
|
version "1.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.3.tgz#ef8e3d1a8d46c387f04ab0f2e8ab8cb0c5078661"
|
resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.3.tgz#ef8e3d1a8d46c387f04ab0f2e8ab8cb0c5078661"
|
||||||
@@ -1126,6 +1133,11 @@ array.prototype.tosorted@^1.1.1:
|
|||||||
es-shim-unscopables "^1.0.0"
|
es-shim-unscopables "^1.0.0"
|
||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
|
|
||||||
|
asap@^2.0.0:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||||
|
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
|
||||||
|
|
||||||
asn1js@^3.0.1, asn1js@^3.0.5:
|
asn1js@^3.0.1, asn1js@^3.0.5:
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
|
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
|
||||||
@@ -1477,6 +1489,14 @@ dequal@^2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||||
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||||
|
|
||||||
|
dezalgo@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
|
||||||
|
integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==
|
||||||
|
dependencies:
|
||||||
|
asap "^2.0.0"
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
didyoumean@^1.2.2:
|
didyoumean@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||||
@@ -1994,6 +2014,27 @@ form-data@^4.0.0:
|
|||||||
combined-stream "^1.0.8"
|
combined-stream "^1.0.8"
|
||||||
mime-types "^2.1.12"
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
formidable-serverless@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/formidable-serverless/-/formidable-serverless-1.1.1.tgz#2b668d6e92d5222794a48ed485bcf7cd47efebfb"
|
||||||
|
integrity sha512-IUVI2m7d46YnWRZ9RG+wvmLoX0tTrk9P6oFc53QVjM9k0XhqzeyjwDi05PLvpGNKbyLvB3gfsAc+Lk6efM3FGQ==
|
||||||
|
dependencies:
|
||||||
|
formidable "^1.2.2"
|
||||||
|
|
||||||
|
formidable@^1.2.2:
|
||||||
|
version "1.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168"
|
||||||
|
integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==
|
||||||
|
|
||||||
|
formidable@^3.5.0:
|
||||||
|
version "3.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.0.tgz#3605a9325130d05c550d57be8e81d1757baa12d6"
|
||||||
|
integrity sha512-WwsMWvPmY+Kv37C3+KP3A+2Ym1aZoac4nz4ZEe5z0UPBoCg0O/wHay3eeYkZr4KJIbCzpSUeno+STMhde+KCfw==
|
||||||
|
dependencies:
|
||||||
|
dezalgo "^1.0.4"
|
||||||
|
hexoid "^1.0.0"
|
||||||
|
once "^1.4.0"
|
||||||
|
|
||||||
fraction.js@^4.2.0:
|
fraction.js@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||||
@@ -2218,6 +2259,11 @@ has@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
|
hexoid@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||||
|
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
|
||||||
|
|
||||||
http-parser-js@>=0.5.1:
|
http-parser-js@>=0.5.1:
|
||||||
version "0.5.8"
|
version "0.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
|
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
|
||||||
@@ -2823,7 +2869,7 @@ object.values@^1.1.6:
|
|||||||
define-properties "^1.1.4"
|
define-properties "^1.1.4"
|
||||||
es-abstract "^1.20.4"
|
es-abstract "^1.20.4"
|
||||||
|
|
||||||
once@^1.3.0:
|
once@^1.3.0, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
||||||
|
|||||||
Reference in New Issue
Block a user