diff --git a/src/assets/images/boy-running.gif b/src/assets/images/boy-running.gif new file mode 100644 index 00000000..f7d25245 Binary files /dev/null and b/src/assets/images/boy-running.gif differ diff --git a/src/components/Assesment/Assesment.jsx b/src/components/Assesment/Assesment.jsx index 01ffc63b..66ab9e7f 100644 --- a/src/components/Assesment/Assesment.jsx +++ b/src/components/Assesment/Assesment.jsx @@ -43,7 +43,7 @@ import config from "../../utils/urlConstants.json"; import panda from "../../assets/images/panda.svg"; import cryPanda from "../../assets/images/cryPanda.svg"; import { uniqueId } from "../../services/utilService"; -import CircularProgressOverlay from "../CommonComponent/CircularProgressOverlay"; +import ProgressOverlay from "../CommonComponent/ProgressOverlay"; import { end } from "../../services/telementryService"; export const LanguageModal = ({ @@ -51,6 +51,7 @@ export const LanguageModal = ({ setLang, setOpenLangModal, setLoading, + setDownloadProgress }) => { const [selectedLang, setSelectedLang] = useState(lang); const [isOfflineModel, setIsOfflineModel] = useState( @@ -66,7 +67,6 @@ export const LanguageModal = ({ let db; // Open IndexedDB - const openDB = () => { return new Promise((resolve, reject) => { const request = window.indexedDB.open(dbName, dbVersion); @@ -96,13 +96,34 @@ export const LanguageModal = ({ if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } - const modelData = await response.arrayBuffer(); - const uint8Array = new Uint8Array(modelData); - - const transaction = await db.transaction(["models"], "readwrite"); + + const reader = response.body.getReader(); + const contentLength = +response.headers.get('Content-Length'); + let receivedLength = 0; + const chunks = []; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + receivedLength += value.length; + + // Update progress + const percentage = ((receivedLength / contentLength) * 100); + setDownloadProgress(percentage.toFixed()); + } + + const modelData = new Uint8Array(receivedLength); + let position = 0; + for (let chunk of chunks) { + modelData.set(chunk, position); + position += chunk.length; + } + + const transaction = db.transaction(["models"], "readwrite"); const store = transaction.objectStore("models"); - - store.put(uint8Array, modelName); + + store.put(modelData, modelName); console.log(`Stored model ${modelName} in IndexedDB`); } catch (error) { console.error("Error storing model in IndexedDB:", error); @@ -135,7 +156,7 @@ export const LanguageModal = ({ try { window.whisperModule.FS_unlink("whisper.bin"); } catch (e) { - // ignore + console.log(e) } try { let transaction; @@ -185,6 +206,7 @@ export const LanguageModal = ({ await storeModel(modelName, modelURL); } else { console.log(`Model ${modelName} is already stored in IndexedDB`); + return; } await loadModelWhisper(modelName); } catch (error) { @@ -719,6 +741,7 @@ const Assesment = ({ discoverStart }) => { const [openLangModal, setOpenLangModal] = useState(false); const [lang, setLang] = useState(getLocalData("lang") || "en"); const [points, setPoints] = useState(0); + const [downloadProgress, setDownloadProgress] = useState(0); useEffect(() => { // const level = getLocalData('userLevel'); @@ -799,7 +822,173 @@ const Assesment = ({ discoverStart }) => { const { virtualId } = useSelector((state) => state.user); const navigate = useNavigate(); - const handleRedirect = () => { + + const dbName = "language-ai-models"; + const dbVersion = 1; + let db; + + // Function to check if the model is already stored in IndexedDB + const isModelStored = async (modelName) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction(["models"], "readonly"); + const store = transaction.objectStore("models"); + const request = store.get(modelName); + + request.onerror = function (event) { + console.error( + "Error checking model in IndexedDB:", + event.target.errorCode + ); + reject(event.target.error); + }; + + request.onsuccess = function (event) { + resolve(!!event.target.result); + }; + }); + }; + + // Function to load model in whisper cpp module + const loadModelWhisper = async (modelName) => { + try { + window.whisperModule.FS_unlink("whisper.bin"); + } catch (e) { + console.log(e) + } + try { + let transaction; + let store; + let request; + try { + transaction = await db.transaction(["models"], "readonly"); + store = transaction.objectStore("models"); + request = await store.get(modelName); + } catch (error) { + console.error("Error accessing IndexedDB:", error); + return; + } + + request.onsuccess = async () => { + const modelData = request.result; + let storeResponse = await window.whisperModule.FS_createDataFile( + "/", + "whisper.bin", + modelData, + true, + true + ); + setTimeout(console.log(window.whisperModule.init("whisper.bin")), 5000); + }; + + request.onerror = (err) => { + console.error(`Error to get model data: ${err}`); + }; + + console.log(`Stored model in whisper cpp memory`); + } catch (error) { + console.error("Error storing model in IndexedDB:", error); + } + }; + + // Function to store model in IndexedDB + const storeModel = async (modelName, modelURL) => { + try { + const response = await fetch(modelURL); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const reader = response.body.getReader(); + const contentLength = +response.headers.get('Content-Length'); + let receivedLength = 0; + const chunks = []; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + receivedLength += value.length; + + // Update progress + const percentage = ((receivedLength / contentLength) * 100); + setDownloadProgress(percentage.toFixed()); + } + + const modelData = new Uint8Array(receivedLength); + let position = 0; + for (let chunk of chunks) { + modelData.set(chunk, position); + position += chunk.length; + } + + const transaction = db.transaction(["models"], "readwrite"); + const store = transaction.objectStore("models"); + + store.put(modelData, modelName); + console.log(`Stored model ${modelName} in IndexedDB`); + } catch (error) { + console.error("Error storing model in IndexedDB:", error); + } + }; + + // Function to load model + const loadModel = async () => { + setLoading(true); + try { + await openDB(); + const modelName = "en-model"; + const modelURL = "./models/ggml-model-whisper-base.en-q5_1.bin"; + + const stored = await isModelStored(modelName); + if (!stored) { + await storeModel(modelName, modelURL); + } else { + console.log(`Model ${modelName} is already stored in IndexedDB`); + } + await loadModelWhisper(modelName); + } catch (error) { + console.log(error.message); + } finally { + setLoading(false); + } + }; + + // Open IndexedDB + + const openDB = () => { + return new Promise((resolve, reject) => { + const request = window.indexedDB.open(dbName, dbVersion); + request.onerror = (event) => { + console.error("IndexedDB error:", event.target.errorCode); + reject(event.target.error); + }; + request.onsuccess = (event) => { + db = event.target.result; + console.log("IndexedDB opened successfully"); + resolve(); + }; + request.onupgradeneeded = (event) => { + db = event.target.result; + console.log("Creating object store for models"); + if (!db.objectStoreNames.contains("models")) { + db.createObjectStore("models"); + } + }; + }); + }; + + const handleRedirect = async () => { + const modelName = "en-model"; + await openDB(); + const stored = await isModelStored(modelName); + if (stored) { + console.log(`Model ${modelName} is already stored in IndexedDB`); + } + else{ + alert(`you have to download en-offline model`) + loadModel(); + return; + } const profileName = getLocalData("profileName"); if (!username && !profileName && !virtualId && level === 0) { // alert("please add username in query param"); @@ -839,7 +1028,7 @@ const Assesment = ({ discoverStart }) => { return ( <> - {loading && } + {loading && } {!!openMessageDialog && ( { /> )} {openLangModal && ( - + )} {level > 0 ? ( diff --git a/src/components/CommonComponent/ProgressOverlay.jsx b/src/components/CommonComponent/ProgressOverlay.jsx new file mode 100644 index 00000000..6657c7d5 --- /dev/null +++ b/src/components/CommonComponent/ProgressOverlay.jsx @@ -0,0 +1,80 @@ +import React from "react"; +import { Box, CircularProgress, LinearProgress, Typography } from "@mui/material"; +import RunnungBoy from '../../assets/images/boy-running.gif' + +const ProgressOverlay = ({ + size, + color = "#ffffff", + showLinearProgress = false, + downloadProgress = 0, +}) => ( + + {showLinearProgress ? ( + + + + + + + + LOADING… {`${Math.round(Number(downloadProgress))}%`} + + + + ) : ( + + )} + +); + +export default ProgressOverlay; diff --git a/src/config/awsS3.js b/src/config/awsS3.js index 34d950f4..7bc68e0a 100644 --- a/src/config/awsS3.js +++ b/src/config/awsS3.js @@ -1,9 +1,9 @@ -// import { S3Client } from '@aws-sdk/client-s3'; +import { S3Client } from '@aws-sdk/client-s3'; -// export default new S3Client({ -// region: process.env.REACT_APP_AWS_S3_REGION, -// credentials: { -// accessKeyId: process.env.REACT_APP_AWS_ACCESS_KEY_ID, -// secretAccessKey: process.env.REACT_APP_AWS_SECRET_ACCESS_KEY, -// }, -// }); +export default new S3Client({ + region: process.env.REACT_APP_AWS_S3_REGION, + credentials: { + accessKeyId: process.env.REACT_APP_AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.REACT_APP_AWS_SECRET_ACCESS_KEY, + }, +}); diff --git a/src/utils/AudioCompare.js b/src/utils/AudioCompare.js index b229e215..562e3cb5 100644 --- a/src/utils/AudioCompare.js +++ b/src/utils/AudioCompare.js @@ -1,9 +1,9 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import AudioAnalyser from "react-audio-analyser"; -import { Box } from "@mui/material"; import { ListenButton, RetryIcon, SpeakButton, StopButton } from "./constants"; import RecordVoiceVisualizer from "./RecordVoiceVisualizer"; import useAudioDetection from "./useAudioDetection"; +import { Box, CircularProgress } from "@mui/material"; const AudioRecorderCompair = (props) => { const { startDetection, stopDetection, isSilent, isRunning, audioDetected } = @@ -11,6 +11,7 @@ const AudioRecorderCompair = (props) => { const [status, setStatus] = useState(""); const [audioSrc, setAudioSrc] = useState(""); const [recordingInitialized, setRecordingInitialized] = useState(false); + const [loader,setLoader] = useState(false); const audioType = "audio/wav"; const controlAudio = async (status) => { @@ -26,7 +27,7 @@ const AudioRecorderCompair = (props) => { setAudioSrc(""); setRecordingInitialized(false); }; - + const handleMic = async () => { if (props.setEnableNext) { props.setEnableNext(false); @@ -35,7 +36,15 @@ const AudioRecorderCompair = (props) => { resetRecording(); }; + useEffect(()=>{ + if(!!props.recordedAudio){ + setLoader(false); + } + },[props.recordedAudio]) + + const handleStop = () => { + setLoader(true); if (props.setEnableNext) { props.setEnableNext(true); } @@ -64,6 +73,7 @@ const AudioRecorderCompair = (props) => { if (props.setEnableNext) { props.setEnableNext(false); } + setLoader(false); } else { if (localStorage.getItem("isOfflineModel") === "true") { props.handleProcess(temp_audioSrc); @@ -81,6 +91,11 @@ const AudioRecorderCompair = (props) => { return (
+ {loader && ( + + + + )}
{(() => { if (status === "recording" && recordingInitialized) { @@ -115,7 +130,7 @@ const AudioRecorderCompair = (props) => { }} className="game-action-button" > - {(!props.dontShowListen || props.recordedAudio) && ( + {!loader && (!props.dontShowListen || props.recordedAudio) && ( <> {!props.pauseAudio ? (
{ sx={{ cursor: "pointer" }} onClick={handleMic} > - {!props.recordedAudio ? : } + {!loader && + (!props.recordedAudio ? ( + + ) : ( + + ))} )}
diff --git a/src/utils/VoiceAnalyser.js b/src/utils/VoiceAnalyser.js index 73fe9bf4..a0ec7b69 100644 --- a/src/utils/VoiceAnalyser.js +++ b/src/utils/VoiceAnalyser.js @@ -34,7 +34,8 @@ import { filterBadWords } from "./Badwords"; import { fetchFile } from "@ffmpeg/ffmpeg"; import useFFmpeg from "./useFFmpeg"; import * as fuzz from "fuzzball"; -// import S3Client from '../config/awsS3'; +import { PutObjectCommand } from "@aws-sdk/client-s3"; +import S3Client from '../config/awsS3'; /* eslint-disable */ const AudioPath = { @@ -165,6 +166,7 @@ function VoiceAnalyser(props) { } catch (error) { console.error("Error processing audio:", error); } + setLoader(false); }; const getResponseText = async (audioBlob) => { @@ -385,7 +387,7 @@ function VoiceAnalyser(props) { }); }); } - + useEffect(() => { if (recordedAudio !== "") { // setLoader(true); @@ -599,23 +601,24 @@ function VoiceAnalyser(props) { // TODO: Remove false when REACT_APP_AWS_S3_BUCKET_NAME and keys added var audioFileName = ""; - if (process.env.REACT_APP_CAPTURE_AUDIO === "true" && false) { + if (process.env.REACT_APP_CAPTURE_AUDIO === "true") { let getContentId = currentLine; audioFileName = `${ process.env.REACT_APP_CHANNEL }/${sessionId}-${Date.now()}-${getContentId}.wav`; - - const command = new PutObjectCommand({ - Bucket: process.env.REACT_APP_AWS_S3_BUCKET_NAME, - Key: audioFileName, - Body: Uint8Array.from(window.atob(base64Data), (c) => - c.charCodeAt(0) - ), - ContentType: "audio/wav", - }); - try { - const response = await S3Client.send(command); - } catch (err) {} +const command = new PutObjectCommand({ + Bucket: process.env.REACT_APP_AWS_S3_BUCKET_NAME, + Key: audioFileName, + Body: Uint8Array.from(window.atob(base64Data), (c) => + c.charCodeAt(0) + ), + ContentType: "audio/wav", +}); +try { + const response = await S3Client.send(command); + } catch (err) { + console.log(err); + } } response(