79 lines
2.3 KiB
TypeScript
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]);
|
|
}
|