Viewing File: /home/ubuntu/voice-assistant-frontend/src/components/Voice/AddVoiceModal.jsx
import React, { useState, useEffect, useRef } from "react";
import { Form, Button, Modal, InputGroup, Image } from "react-bootstrap";
import { Formik, Form as FORM, Field, ErrorMessage } from "formik";
import { AudioRecorder, useAudioRecorder } from 'react-audio-voice-recorder';
import { getErrorNotificationMessage } from "../Helper/NotificationMessage";
import { useTranslation } from "react-multi-lang";
import { useDispatch, useSelector } from "react-redux";
import * as Yup from "yup";
import { setSelectedVoice, voiceStoreStart } from "../../store/slices/VoiceSlice";
import { v4 as uuidv4 } from 'uuid';
import { ButtonLoader } from "../Helper/Loader";
import { recordVoiceCreateStart } from "../../store/slices/VoiceSlice";
import { Link } from "react-router-dom";
import Skeleton from "react-loading-skeleton";
import SomethingWentWrong from "../Helper/SomethingWentWrong";
import { phraseListStart } from "../../store/slices/PhraseSlice";
const AddVoiceModal = (props) => {
const t = useTranslation("add_voice");
const recorderControls = useAudioRecorder()
const audioRef = useRef(null);
const dispatch = useDispatch();
const recordVoice = useSelector((state) => state.voice.recordVoice);
const phraseList = useSelector((state) => state.phrase.phraseList);
const voiceStore = useSelector((state) => state.voice.voiceStore);
const [step, setStep] = useState(1);
const [record, setRecord] = useState(false);
const [audioPlaying, setAudioPlaying] = useState(false);
const [previewState, setPreviewState] = useState("Preview");
const [currentPhraseIndex, setCurrentPhraseIndex] = useState(0);
const [voiceId, setVoice_Id] = useState(uuidv4());
const [skipRender, setSkipRender] = useState(true);
useEffect(() => {
setStep(1);
dispatch(phraseListStart())
}, []);
const addAudioElement = (data) => {
setRecord(data);
};
const stopRecording = () => {
recorderControls.stopRecording();
setStep(3);
};
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => track.stop());
if (stream) {
setStep(2);
recorderControls.startRecording();
}
} catch (error) {
getErrorNotificationMessage("MicroPhone Access denied , Please allow to continue");
}
}
const handleSubmit = () => {
const data = new File([record], `VoiceFile${currentPhraseIndex}`);
dispatch(recordVoiceCreateStart({
phrase_voice: data,
phrase_id: phraseList.data.phrases[currentPhraseIndex].id,
ai_voice_id: voiceId
}));
};
useEffect(() => {
if (
!skipRender &&
!recordVoice.loading &&
Object.keys(recordVoice.data).length > 0
)
if (currentPhraseIndex < phraseList.data.total_phrases - 1) {
setCurrentPhraseIndex(currentPhraseIndex + 1);
setStep(1);
} else {
setStep(4);
}
if (currentPhraseIndex == phraseList.data.total_phrases) {
setStep(4);
}
setSkipRender(false);
}, [recordVoice]);
useEffect(() => {
if (audioRef.current) {
audioRef.current.addEventListener('ended', () => {
setAudioPlaying(false);
setPreviewState("Preview");
audioRef.current = null
});
}
}, [audioRef.current]);
const togglePlayback = () => {
if (!audioRef.current) {
const url = URL.createObjectURL(record);
audioRef.current = new Audio(url);
}
if (!audioPlaying) {
audioRef.current.play();
setAudioPlaying(true);
setPreviewState("Pause")
} else {
audioRef.current.pause();
setAudioPlaying(false);
setPreviewState("Play")
}
}
const previewSubmit = () => {
togglePlayback();
}
const handleRepeat = () => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current = null;
setAudioPlaying(false);
setPreviewState("Preview");
}
setStep(1);
};
const AddVoiceSchema = Yup.object().shape({
name: Yup.string()
.required(t("required"))
.min(5, t("invalid")),
});
const handleAddVoice = (values) => {
dispatch(voiceStoreStart({ ...values, ai_voice_id: voiceId }))
}
useEffect(() => {
if (
!skipRender &&
!voiceStore.loading &&
Object.keys(voiceStore.data).length > 0
) {
setStep(5)
}
setSkipRender(false);
}, [voiceStore]);
return (
<>
<Modal
className="modal-dialog-center add-voice-modal"
size="md"
centered
show={props.addVoiceModal}
onHide={props.closeAddVoiceModal}
backdrop="static"
>
<Modal.Body>
<div className="hidden-recorder">
<AudioRecorder
onRecordingComplete={addAudioElement}
recorderControls={recorderControls}
audioTrackConstraints={{
noiseSuppression: true,
echoCancellation: true,
}}
// downloadOnSavePress={true}
// downloadFileExtension="webm"
/>
</div>
<h4>{t("title")}</h4>
<Button
className="modal-close"
onClick={() => {
// eslint-disable-next-line no-restricted-globals
currentPhraseIndex > 0 ? confirm("Are you sure to close? Unsaved changes will be lost.") && props.closeAddVoiceModal() : props.closeAddVoiceModal()
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="none"
viewBox="0 0 11 11"
>
<path
fill="#979BA2"
d="M10.756.252a.83.83 0 00-1.176 0L5.5 4.324 1.42.244A.83.83 0 10.244 1.42l4.08 4.08-4.08 4.08a.83.83 0 101.176 1.176l4.08-4.08 4.08 4.08a.831.831 0 101.176-1.176L6.676 5.5l4.08-4.08a.836.836 0 000-1.168z"
></path>
</svg>
</Button>
<>
{phraseList.loading ?
<div className="step-1">
<Skeleton height={20} className="mb-3" />
<Skeleton height={40} width={40} className="mb-2" />
<Skeleton height={120} className="mb-2" />
<div style={{display:"flex", alignItems:"center", justifyContent:"center"}}>
<Skeleton circle width={60} height={60} />
</div>
</div> : (Object.keys(phraseList.data).length > 0) ? <React.Fragment>
{step == 1 && (
<div className="step-1">
<div className="add-voice-sec">
<p>{t("message")}</p>
<div className="add-voice-count-sec">{currentPhraseIndex+1}/{phraseList.data.total_phrases}</div>
<div className="add-voice-text-speech">
<h3>
{phraseList.data?.phrases[currentPhraseIndex]?.phrase}
</h3>
</div>
<div className="speak-btn-sec">
<Button className="speak-btn" onClick={startRecording}>
<Image
className="speak-btn-icon"
src={
window.location.origin + "/img/speak-btn-icon-1.png"
}
type="image/png"
/>
</Button>
</div>
</div>
</div>
)}
{step == 2 && (
<div className="step-2">
<div className="add-voice-sec">
<p>{t("message")}</p>
<div className="add-voice-count-sec">{currentPhraseIndex+1}/{phraseList.data.total_phrases}</div>
<div className="add-voice-text-speech">
<h3>
{phraseList.data?.phrases[currentPhraseIndex]?.phrase}
</h3>
</div>
<div className="speak-btn-sec">
<Button className="speak-btn" onClick={stopRecording}>
<Image
className="speak-btn-icon"
src={
window.location.origin + "/img/speak-btn-icon-2.png"
}
type="image/png"
/>
</Button>
</div>
</div>
</div>
)}
{step == 3 && (
<div className="step-3">
<div className="add-voice-sec">
<p>{t("message")}</p>
<div className="add-voice-count-sec">{currentPhraseIndex+1}/{phraseList.data.total_phrases}</div>
<div className="add-voice-text-speech">
<h3>
{phraseList.data?.phrases[currentPhraseIndex]?.phrase}
</h3>
</div>
<div className="add-voice-action-btn-sec">
<div className="add-voice-action-btn-left-sec">
<Button disabled={recordVoice.buttonDisable} className="preview-btn" onClick={previewSubmit}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-player-play-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M6 4v16a1 1 0 0 0 1.524 .852l13 -8a1 1 0 0 0 0 -1.704l-13 -8a1 1 0 0 0 -1.524 .852z" stroke-width="0" fill="#5575FF" /></svg>
<span>{previewState}</span>
</Button>
<Button disabled={recordVoice.buttonDisable} className="repeat-btn" onClick={handleRepeat}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="#5575FF" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747" /><path d="M20 4v5h-5" /></svg>
<span>{t("repeat")}</span>
</Button>
</div>
<div className="add-voice-action-btn-right-sec">
<Button className="next-btn" type="submit" disabled={recordVoice.buttonDisable} onClick={handleSubmit}>
{recordVoice.buttonDisable ? (
<ButtonLoader varient="black" />
) : (
<span> {t("next")}</span>
)}
</Button>
</div>
</div>
</div>
</div>
)}
{step == 4 && (
<div className="step-4">
<div className="add-voice-sec">
<p>{t("message")}</p>
<Formik
initialValues={{
name: "",
description: "",
}}
validationSchema={AddVoiceSchema}
onSubmit={handleAddVoice}
>
{({ setFieldValue, values }) => (
<FORM className="add-voice-form-sec">
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>{t("name")}</Form.Label>
<Field
className="form-control"
placeholder={"Enter name"}
type="text"
name="name"
aria-label="Username"
aria-describedby="basic-addon1"
/>
</Form.Group>
<ErrorMessage
component={"div"}
name="name"
className="errorMsg"
/>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>{t("description")}</Form.Label>
<Field
className="form-control"
placeholder="How would you describe the voice?"
as="textarea"
name="description"
aria-label="Username"
aria-describedby="basic-addon1"
/>
</Form.Group>
<div className="add-voice-form-btn-sec">
<Button className="default-btn" type="submit" disabled={voiceStore.buttonDisable}>
{voiceStore.buttonDisable ? (
<ButtonLoader varient="black" />
) : (
<span> {t("add")}</span>
)}
</Button>
</div>
</FORM>
)}</Formik>
</div>
</div>
)}
{step == 5 && (
<div className="step-5">
<div className="add-voice-success-card">
<div className="add-voice-success-img-sec">
<Image
className="add-voice-success-img"
src={
window.location.origin + "/img/voice-success-img.png"
}
type="image/png"
/>
</div>
<h3>
{t("success")}
</h3>
<p>{t("content")}</p>
<div className="add-voice-suucess-btn-sec">
<Link to="/generate-voice" onClick={()=> dispatch(setSelectedVoice(voiceStore.data.voice))} className="default-btn">
{t("use")}
</Link>
</div>
</div>
</div>
)}
</React.Fragment> : <SomethingWentWrong handleClick={()=> dispatch(phraseListStart())} />}
</>
</Modal.Body>
</Modal>
</>
);
};
export default AddVoiceModal;
Back to Directory
File Manager