Arrakis / src /hooks /useVoice.ts
gpt-engineer-app[bot]
Changes
808fe45
Raw
History Blame Contribute Delete
3.96 kB
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<VoiceState>({
isListening: false,
isSpeaking: false,
transcript: "",
error: null,
});
const recognitionRef = useRef<SpeechRecognitionInstance | null>(null);
const synthesisRef = useRef<SpeechSynthesisUtterance | null>(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,
};
}