import React, { useRef, useState } from "react"; import Loader from "./Loader"; const SILENCE_THRESHOLD = 0.01; const SILENCE_DURATION = 1500; export default function VoiceBot() { const mediaRecorderRef = useRef(null); const audioChunksRef = useRef([]); const analyserRef = useRef(null); const audioContextRef = useRef(null); const silenceTimerRef = useRef(null); const streamRef = useRef(null); const listeningRef = useRef(false); const [status, setStatus] = useState("idle"); // idle | listening | thinking | speaking // ========================= // START RECORDING // ========================= const startRecording = async () => { setStatus("listening"); listeningRef.current = true; const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); streamRef.current = stream; const recorder = new MediaRecorder(stream); mediaRecorderRef.current = recorder; audioChunksRef.current = []; recorder.ondataavailable = (e) => { audioChunksRef.current.push(e.data); }; recorder.onstop = handleStop; recorder.start(); setupSilenceDetection(stream); }; // ========================= // SILENCE DETECTION // ========================= const setupSilenceDetection = (stream) => { audioContextRef.current = new AudioContext(); const source = audioContextRef.current.createMediaStreamSource(stream); analyserRef.current = audioContextRef.current.createAnalyser(); analyserRef.current.fftSize = 2048; source.connect(analyserRef.current); const data = new Uint8Array(analyserRef.current.fftSize); const checkSilence = () => { analyserRef.current.getByteTimeDomainData(data); let sum = 0; for (let i = 0; i < data.length; i++) { const v = (data[i] - 128) / 128; sum += v * v; } const volume = Math.sqrt(sum / data.length); if (volume < SILENCE_THRESHOLD) { if (!silenceTimerRef.current) { silenceTimerRef.current = setTimeout(() => { stopRecording(); }, SILENCE_DURATION); } } else { clearTimeout(silenceTimerRef.current); silenceTimerRef.current = null; } // ✅ FIX: use ref, not React state if (listeningRef.current) { requestAnimationFrame(checkSilence); } }; checkSilence(); }; // ========================= // STOP RECORDING // ========================= const stopRecording = () => { listeningRef.current = false; if (silenceTimerRef.current) { clearTimeout(silenceTimerRef.current); silenceTimerRef.current = null; } if (mediaRecorderRef.current?.state !== "inactive") { mediaRecorderRef.current.stop(); } streamRef.current?.getTracks().forEach(t => t.stop()); if ( audioContextRef.current && audioContextRef.current.state !== "closed" ) { audioContextRef.current.close(); } }; // ========================= // SEND TO BACKEND // ========================= const handleStop = async () => { setStatus("thinking"); const blob = new Blob(audioChunksRef.current, { type: "audio/wav", }); const formData = new FormData(); formData.append("audio", blob); const response = await fetch( "https://durga-7780-voicebot.hf.space/api/process-audio", { method: "POST", body: formData, } ); const data = await response.json(); if (!response.ok || !data.audio_url) { console.error("Backend error:", data); setStatus("idle"); return; } playBotAudio(data.audio_url); }; // ========================= // PLAY BOT AUDIO // ========================= const playBotAudio = (url) => { setStatus("speaking"); const audio = new Audio(url + "?t=" + Date.now()); audio.onended = () => setStatus("idle"); audio.play(); }; // ========================= // UI // ========================= return (
{status === "idle" ? ( ) : ( )}
); } const styles = { container: { height: "100vh", background: "#111", display: "flex", justifyContent: "center", alignItems: "center", }, mic: { width: 120, height: 120, borderRadius: "50%", fontSize: 40, background: "#4CAF50", color: "#fff", border: "none", cursor: "pointer", }, };