Files
vocero-client/frontend-s2s/src/hooks/use-wake-word.ts
T

79 lines
2.3 KiB
TypeScript

import { useEffect } from "react";
import type { VoiceController } from "../realtime/controller";
interface Recognition {
lang: string;
continuous: boolean;
interimResults: boolean;
onresult: ((event: { resultIndex: number; results: ArrayLike<ArrayLike<{ transcript: string }>> }) => void) | null;
onerror: ((event: { error: string }) => void) | null;
onend: (() => void) | null;
start: () => void;
abort: () => void;
}
declare global {
interface Window {
SpeechRecognition?: new () => Recognition;
webkitSpeechRecognition?: new () => Recognition;
}
}
/** Optional browser wake word; never owns the microphone during a voice session. */
export function useWakeWord(enabled: boolean, controller: VoiceController) {
useEffect(() => {
const Constructor = window.SpeechRecognition ?? window.webkitSpeechRecognition;
if (!enabled || !Constructor) {
return;
}
const recognition = new Constructor();
let stopped = false;
let timer: ReturnType<typeof setTimeout> | undefined;
recognition.lang = "es-ES";
recognition.continuous = true;
recognition.interimResults = true;
const start = () => {
if (!stopped) {
try {
recognition.start();
} catch {
/* A browser may require a click first. */
}
}
};
recognition.onresult = (event) => {
for (let index = event.resultIndex; index < event.results.length; index++) {
const text =
event.results[index]?.[0]?.transcript
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") ?? "";
if (/\b(?:hola|ola)\s+(?:vocero|bocero|bosero|vocerro)\b/.test(text)) {
stopped = true;
recognition.abort();
void controller.start(false, undefined, "hola vocero");
return;
}
}
};
recognition.onerror = (event) => {
if (["not-allowed", "service-not-allowed", "audio-capture"].includes(event.error)) {
stopped = true;
}
};
recognition.onend = () => {
if (!stopped) {
timer = setTimeout(start, 1000);
}
};
start();
return () => {
stopped = true;
clearTimeout(timer);
recognition.onresult = null;
recognition.onerror = null;
recognition.onend = null;
recognition.abort();
};
}, [enabled, controller]);
}