// lib/voice/useVoiceInput.ts // React hook around the browser SpeechRecognition API for live transcription. // Falls back gracefully where the API is unavailable (Firefox, Safari < 14.1, etc). // Push-to-talk by default — call start() / stop() from a button click. "use client"; import { useCallback, useEffect, useRef, useState } from "react"; // Minimum browser typings. SpeechRecognition is not in lib.dom.d.ts on TS < 5.4. type SpeechRecognitionEventLike = { results: { length: number; [index: number]: { isFinal: boolean; length: number; [index: number]: { transcript: string; confidence: number }; }; }; resultIndex: number; }; type SpeechRecognitionErrorEventLike = { error: string; message?: string; }; interface SpeechRecognitionLike { continuous: boolean; interimResults: boolean; lang: string; maxAlternatives: number; onresult: ((ev: SpeechRecognitionEventLike) => void) | null; onerror: ((ev: SpeechRecognitionErrorEventLike) => void) | null; onend: (() => void) | null; onstart: (() => void) | null; start: () => void; stop: () => void; abort: () => void; } function getSpeechRecognitionCtor(): (new () => SpeechRecognitionLike) | null { if (typeof window === "undefined") return null; const w = window as unknown as { SpeechRecognition?: new () => SpeechRecognitionLike; webkitSpeechRecognition?: new () => SpeechRecognitionLike; }; return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null; } export type VoiceInputState = { /** True while listening. */ listening: boolean; /** True when the browser actually supports SpeechRecognition. */ supported: boolean; /** The latest interim (not-yet-finalised) transcript; live updates while speaking. */ interim: string; /** The committed (finalised) transcript — append each final result here. */ transcript: string; /** Last error string, if any (e.g. "not-allowed", "no-speech"). */ error: string | null; }; export type VoiceInputControls = VoiceInputState & { start: () => void; stop: () => void; reset: () => void; }; export interface UseVoiceInputOptions { /** BCP-47 lang tag. Defaults to en-ZA so SA English is preferred. */ lang?: string; /** Append mode (default true) vs replace mode. */ append?: boolean; } /** * Live transcription hook. Renders a button that toggles listening; the * current recognised text (interim + final) is returned in real time. * * Example: * const { listening, transcript, interim, start, stop, supported } = useVoiceInput(); *