'use client'; import { useState, useCallback, useRef, type MouseEvent } from 'react'; import { fileToBase64 } from '@/lib/copilot'; interface VoiceInputProps { onVoice: (audioBase64: string, mimeType: string) => void; disabled?: boolean; } declare global { interface Window { SpeechRecognition?: new () => SpeechRecognitionLike; webkitSpeechRecognition?: new () => SpeechRecognitionLike; } } interface SpeechRecognitionLike { start: () => void; stop: () => void; onresult: ((event: SpeechRecognitionEventLike) => void) | null; onerror: (() => void) | null; onend: (() => void) | null; } interface SpeechRecognitionEventLike { results: { length: number; [key: number]: { 0: { transcript: string } } }; } export function VoiceInput({ onVoice, disabled }: VoiceInputProps) { const [recording, setRecording] = useState(false); const [supported, setSupported] = useState(true); const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); const handleStartRecording = useCallback(async () => { if (disabled) return; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const recorder = new MediaRecorder(stream); chunksRef.current = []; recorder.ondataavailable = (e) => { if (e.data.size > 0) chunksRef.current.push(e.data); }; recorder.onstop = async () => { const blob = new Blob(chunksRef.current, { type: 'audio/webm' }); const base64 = await fileToBase64(blob); onVoice(base64, 'audio/webm'); stream.getTracks().forEach((track) => track.stop()); }; recorder.start(); mediaRecorderRef.current = recorder; setRecording(true); } catch { setSupported(false); } }, [disabled, onVoice]); const handleStopRecording = useCallback(() => { if (mediaRecorderRef.current && recording) { mediaRecorderRef.current.stop(); setRecording(false); } }, [recording]); const handleToggle = useCallback( (e: MouseEvent) => { e.preventDefault(); if (recording) handleStopRecording(); else handleStartRecording(); }, [recording, handleStartRecording, handleStopRecording] ); if (!supported) { return ( ); } return ( ); }