import { useState, useCallback, useRef, useEffect } from "react"; import { VoiceState } from "@/types/chat"; // Web Speech API types interface SpeechRecognitionEvent extends Event { results: SpeechRecognitionResultList; } interface SpeechRecognitionErrorEvent extends Event { error: string; } interface SpeechRecognitionInstance extends EventTarget { continuous: boolean; interimResults: boolean; lang: string; start: () => void; stop: () => void; abort: () => void; onresult: ((event: SpeechRecognitionEvent) => void) | null; onerror: ((event: SpeechRecognitionErrorEvent) => void) | null; onend: (() => void) | null; } declare global { interface Window { SpeechRecognition: new () => SpeechRecognitionInstance; webkitSpeechRecognition: new () => SpeechRecognitionInstance; } } export function useVoice() { const [state, setState] = useState({ isListening: false, isSpeaking: false, transcript: "", error: null, }); const recognitionRef = useRef(null); const synthesisRef = useRef(null); // Initialize speech recognition useEffect(() => { if (typeof window !== "undefined") { const SpeechRecognition = window.SpeechRecognition || (window as any).webkitSpeechRecognition; if (SpeechRecognition) { const recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = true; recognition.lang = "en-US"; recognition.onresult = (event) => { const transcript = Array.from(event.results) .map((result) => result[0].transcript) .join(""); setState((prev) => ({ ...prev, transcript })); }; recognition.onerror = (event) => { setState((prev) => ({ ...prev, isListening: false, error: event.error, })); }; recognition.onend = () => { setState((prev) => ({ ...prev, isListening: false })); }; recognitionRef.current = recognition; } } return () => { if (recognitionRef.current) { recognitionRef.current.abort(); } }; }, []); const startListening = useCallback(() => { if (!recognitionRef.current) { setState((prev) => ({ ...prev, error: "Speech recognition not supported", })); return; } setState((prev) => ({ ...prev, isListening: true, transcript: "", error: null, })); try { recognitionRef.current.start(); } catch (error) { console.error("Failed to start recognition:", error); } }, []); const stopListening = useCallback(() => { if (recognitionRef.current) { recognitionRef.current.stop(); } setState((prev) => ({ ...prev, isListening: false })); }, []); const speak = useCallback((text: string) => { if (!window.speechSynthesis) { setState((prev) => ({ ...prev, error: "Speech synthesis not supported" })); return; } // Cancel any ongoing speech window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.rate = 1; utterance.pitch = 1; utterance.volume = 1; utterance.onstart = () => { setState((prev) => ({ ...prev, isSpeaking: true })); }; utterance.onend = () => { setState((prev) => ({ ...prev, isSpeaking: false })); }; utterance.onerror = () => { setState((prev) => ({ ...prev, isSpeaking: false })); }; synthesisRef.current = utterance; window.speechSynthesis.speak(utterance); }, []); const stopSpeaking = useCallback(() => { window.speechSynthesis.cancel(); setState((prev) => ({ ...prev, isSpeaking: false })); }, []); return { ...state, startListening, stopListening, speak, stopSpeaking, }; }