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-config-next": "13.1.6",
|
||||
"firebase": "9.19.1",
|
||||
"formidable": "^3.5.0",
|
||||
"formidable-serverless": "^1.1.1",
|
||||
"framer-motion": "^9.0.2",
|
||||
"iron-session": "^6.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -48,6 +50,7 @@
|
||||
"zustand": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/formidable": "^3.4.0",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@types/wavesurfer.js": "^6.0.6",
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
||||
import {SpeakingExercise, WritingExercise} from "@/interfaces/exam";
|
||||
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import clsx from "clsx";
|
||||
import {SpeakingExercise} from "@/interfaces/exam";
|
||||
import {CommonProps} from ".";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPlayCircle, BsTrashFill} from "react-icons/bs";
|
||||
import dynamic from "next/dynamic";
|
||||
import Button from "../Low/Button";
|
||||
import axios from "axios";
|
||||
|
||||
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
|
||||
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]);
|
||||
|
||||
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 (
|
||||
<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">
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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") {
|
||||
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;
|
||||
res.status(backendRequest.status).json(backendRequest.data);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export default function Page() {
|
||||
const writingExam = exams.find((x) => x.id === examId)!;
|
||||
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", ""),
|
||||
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 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", ""),
|
||||
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
|
||||
});
|
||||
|
||||
48
yarn.lock
48
yarn.lock
@@ -797,6 +797,13 @@
|
||||
"@types/qs" "*"
|
||||
"@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@*":
|
||||
version "1.5.3"
|
||||
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"
|
||||
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:
|
||||
version "3.0.5"
|
||||
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"
|
||||
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:
|
||||
version "1.2.2"
|
||||
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"
|
||||
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:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
@@ -2218,6 +2259,11 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
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:
|
||||
version "0.5.8"
|
||||
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"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
once@^1.3.0:
|
||||
once@^1.3.0, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
||||
|
||||
Reference in New Issue
Block a user