UNPKG

@assistant-ui/react

Version:

TypeScript/React library for AI Chat

152 lines 5.49 kB
const getSpeechRecognitionAPI = () => { if (typeof window === "undefined") return undefined; return window.SpeechRecognition ?? window.webkitSpeechRecognition; }; /** * WebSpeechDictationAdapter provides speech-to-text (dictation) functionality using * the browser's Web Speech API (SpeechRecognition). * * @example * ```tsx * const runtime = useChatRuntime({ * api: "/api/chat", * adapters: { * dictation: new WebSpeechDictationAdapter(), * }, * }); * ``` */ export class WebSpeechDictationAdapter { _language; _continuous; _interimResults; constructor(options = {}) { const defaultLanguage = typeof navigator !== "undefined" && navigator.language ? navigator.language : "en-US"; this._language = options.language ?? defaultLanguage; this._continuous = options.continuous ?? true; this._interimResults = options.interimResults ?? true; } /** * Check if the browser supports the Web Speech Recognition API. */ static isSupported() { return getSpeechRecognitionAPI() !== undefined; } listen() { const SpeechRecognitionAPI = getSpeechRecognitionAPI(); if (!SpeechRecognitionAPI) { throw new Error("SpeechRecognition is not supported in this browser. " + "Try using Chrome, Edge, or Safari."); } const recognition = new SpeechRecognitionAPI(); recognition.lang = this._language; recognition.continuous = this._continuous; recognition.interimResults = this._interimResults; const speechStartCallbacks = new Set(); const speechEndCallbacks = new Set(); const speechCallbacks = new Set(); let finalTranscript = ""; const session = { status: { type: "starting" }, stop: async () => { recognition.stop(); return new Promise((resolve) => { const checkEnded = () => { if (session.status.type === "ended") { resolve(); } else { setTimeout(checkEnded, 50); } }; checkEnded(); }); }, cancel: () => { recognition.abort(); }, onSpeechStart: (callback) => { speechStartCallbacks.add(callback); return () => { speechStartCallbacks.delete(callback); }; }, onSpeechEnd: (callback) => { speechEndCallbacks.add(callback); return () => { speechEndCallbacks.delete(callback); }; }, onSpeech: (callback) => { speechCallbacks.add(callback); return () => { speechCallbacks.delete(callback); }; }, }; const updateStatus = (newStatus) => { session.status = newStatus; }; recognition.addEventListener("speechstart", () => { for (const cb of speechStartCallbacks) cb(); }); recognition.addEventListener("start", () => { updateStatus({ type: "running" }); }); recognition.addEventListener("result", (event) => { const speechEvent = event; for (let i = speechEvent.resultIndex; i < speechEvent.results.length; i++) { const result = speechEvent.results[i]; if (!result) continue; const transcript = result[0]?.transcript ?? ""; if (result.isFinal) { finalTranscript += transcript; for (const cb of speechCallbacks) cb({ transcript, isFinal: true }); } else { for (const cb of speechCallbacks) cb({ transcript, isFinal: false }); } } }); recognition.addEventListener("speechend", () => { // speechend fires when user stops speaking, but 'end' handles final cleanup }); recognition.addEventListener("end", () => { const currentStatus = session.status; if (currentStatus.type !== "ended") { updateStatus({ type: "ended", reason: "stopped" }); } if (finalTranscript) { for (const cb of speechEndCallbacks) cb({ transcript: finalTranscript }); finalTranscript = ""; } }); recognition.addEventListener("error", (event) => { const errorEvent = event; if (errorEvent.error === "aborted") { updateStatus({ type: "ended", reason: "cancelled" }); } else { updateStatus({ type: "ended", reason: "error" }); console.error("Dictation error:", errorEvent.error, errorEvent.message); } }); try { recognition.start(); } catch (error) { updateStatus({ type: "ended", reason: "error" }); throw error; } return session; } } //# sourceMappingURL=WebSpeechDictationAdapter.js.map