import { useState, useRef, useCallback } from "react"; import { Mic, Square, Loader2 } from "lucide-react"; import { Button } from "./ui/button"; interface VoiceRecordButtonProps { onTranscription: (text: string) => void; disabled?: boolean; } export function VoiceRecordButton({ onTranscription, disabled }: VoiceRecordButtonProps) { const [recording, setRecording] = useState(false); const [transcribing, setTranscribing] = useState(false); const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); const startRecording = useCallback(async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream, { mimeType: MediaRecorder.isTypeSupported("audio/webm;codecs=opus") ? "audio/webm;codecs=opus" : "audio/webm", }); chunksRef.current = []; mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) chunksRef.current.push(e.data); }; mediaRecorder.onstop = async () => { stream.getTracks().forEach((t) => t.stop()); const blob = new Blob(chunksRef.current, { type: "audio/webm" }); if (blob.size === 0) return; setTranscribing(true); try { const formData = new FormData(); formData.append("audio", blob, "recording.webm"); const res = await fetch("/api/transcribe", { method: "POST", credentials: "include", body: formData, }); if (res.ok) { const data = (await res.json()) as { text: string }; if (data.text?.trim()) { onTranscription(data.text.trim()); } } } finally { setTranscribing(false); } }; mediaRecorderRef.current = mediaRecorder; mediaRecorder.start(250); // 250ms chunks setRecording(true); } catch { // Microphone permission denied or unavailable } }, [onTranscription]); const stopRecording = useCallback(() => { if (mediaRecorderRef.current?.state === "recording") { mediaRecorderRef.current.stop(); mediaRecorderRef.current = null; } setRecording(false); }, []); if (transcribing) { return ( ); } if (recording) { return ( ); } return ( ); }