/** * Speech-to-text for the assistant composer (Workstream 2 of the AI Assistant * V2 multimodal effort). * * Input-only, browser-native (Web Speech API). It degrades gracefully: * `isSupported` is false on engines without SpeechRecognition (notably Firefox), * and the composer hides the mic entirely in that case rather than showing a * dead control. * * ⚠️ This does NOT mean the audio stays on the device. Chrome's * `webkitSpeechRecognition` is a NETWORK service — the audio is streamed to the * browser vendor for recognition, and the supported language list is theirs, not * ours. We neither receive nor store it, which is all an earlier version of this * comment ("no audio ever leaves the device through us") actually claimed; read * literally it implied something stronger and untrue. Whether recording farmers' * speech this way needs disclosure or consent is a product/legal call, not a * code one. * * Output (reading replies aloud) is a separate concern and lives in * `AssistantSpeechProvider`; an earlier version of this note said the assistant * does not speak, which stopped being true when BOFF-6290 shipped. * * Differences from the fe-libs `assistantWidget/useVoiceInput` this is modelled * on, all of which the composer needs: * - **interim results are exposed**, so the user sees words appear while * speaking instead of staring at a silent button until a phrase finalises; * - **errors are surfaced** (`error`) so a denied mic permission can be * explained rather than looking like a no-op; * - **the recogniser language follows the app locale** (a Tamil or Hindi * farmer dictating into an `en-US` recogniser gets garbage); * - **`stop()` resolves the final transcript** through `onFinalize`, so the * composer can commit exactly once when the user stops. */ export type VoiceInputError = 'not-allowed' | 'no-speech' | 'audio-capture' | 'network' | 'language-not-supported' | 'unknown'; export interface UseVoiceInputOptions { /** * Called with each newly finalised phrase. The composer appends it to the * draft; keeping the committed text in the caller (rather than accumulating * here) means the user can freely edit while dictation continues. */ onFinalize: (text: string) => void; /** * BCP-47 tag to listen for. Omit to follow the app locale, which is what this * hook did unconditionally before (BOFF-7108). * * The two are genuinely independent: a farmer whose interface is English may * well speak Tamil, and deriving the recogniser language from the UI meant the * only way to dictate Tamil was to translate the entire app. */ language?: string; } export interface UseVoiceInputResult { isSupported: boolean; isListening: boolean; /** Requested, not yet capturing. Distinguishes starting up from listening. */ isStarting: boolean; /** * Starting OR listening — "the microphone is busy". * * ★ Most callers mean THIS and were testing `isListening`, which was the same * thing only while capture was assumed the instant start() returned. Exposing * it once stops four call sites disagreeing about what counts as active. */ isActive: boolean; /** Words recognised but not yet finalised — render as a live hint. */ interim: string; error: VoiceInputError | null; /** * BCP-47 tag the recogniser was asked for. Exposed so error copy can name the * language that failed ("Tamil dictation is not supported…") rather than * leaving the user to guess which language the browser rejected. */ language: string; start: () => void; stop: () => void; /** Start if idle, stop if listening — what the mic button binds to. */ toggle: () => void; clearError: () => void; } export declare function useVoiceInput({ onFinalize, language: requested }: UseVoiceInputOptions): UseVoiceInputResult; //# sourceMappingURL=useVoiceInput.d.ts.map