@assistant-ui/react
Version:
TypeScript/React library for AI Chat
152 lines • 5.49 kB
JavaScript
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