Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
latenssi committed Sep 5, 2024
0 parents commit b85c9a2
Show file tree
Hide file tree
Showing 8 changed files with 3,338 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
140 changes: 140 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

export type OnUpdateFunc = ({
transcript,
interimTranscript,
isFinal,
}: {
transcript: string;
interimTranscript: string;
isFinal: boolean;
}) => void;

export type OnErrorFunc = ({
error,
}: {
error: SpeechRecognitionErrorEvent | null;
}) => void;

export interface Options {
lang?: string;
continuous?: boolean;
timeout?: number;
onUpdate?: OnUpdateFunc;
onError?: OnErrorFunc;
}

export function useSpeechRecognition({
lang,
continuous,
timeout,
onUpdate,
onError,
}: Options) {
const [transcript, setTranscript] = useState("");
const [interimTranscript, setInterimTranscript] = useState("");
const [isFinal, setIsFinal] = useState(false);
const [isListening, setIsListening] = useState(false);
const [error, setError] = useState<SpeechRecognitionErrorEvent | null>(null);

const recognitionRef = useRef<SpeechRecognition | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const SpeechRecognition = useMemo(() => {
if (typeof window === "undefined") {
return null;
}
return window.SpeechRecognition || window.webkitSpeechRecognition;
}, []);

useEffect(() => {
if (!SpeechRecognition) return;

const rec = new SpeechRecognition();
recognitionRef.current = rec;

rec.lang = lang ?? "en-US";
rec.continuous = continuous ?? timeout !== undefined;
rec.interimResults = true;

rec.onstart = () => {
setError(null);
setInterimTranscript("");
setTranscript("");
setIsFinal(false);
setIsListening(true);
};

rec.onend = () => {
setIsFinal(true);
setIsListening(false);
};

rec.onerror = (event) => {
console.error(`Speech recognition error: ${event.error}`);
setError(event);
};

rec.onresult = (event) => {
// Handle custom timeout
if (!continuous && timeout !== undefined) {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => rec.stop(), timeout);
}

// Parse the transcript from results
let final = "";
let interim = "";
for (let i = 0; i < event.results.length; i++) {
const s = event.results[i]?.[0]?.transcript.toLocaleLowerCase();
if (event.results[i]?.isFinal) {
final += s;
} else {
interim += s;
}
}
setInterimTranscript(interim);
setTranscript(final);
};

return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (recognitionRef.current) {
recognitionRef.current.abort();
recognitionRef.current = null;
}
};
}, [SpeechRecognition, continuous, lang, timeout]);

useEffect(() => {
if (onUpdate) onUpdate({ transcript, interimTranscript, isFinal });
}, [onUpdate, transcript, interimTranscript, isFinal]);

useEffect(() => {
if (onError) onError({ error });
}, [onError, error]);

const start = useCallback(() => {
if (!recognitionRef.current) return;
recognitionRef.current.start();
}, []);

const stop = useCallback(() => {
if (!recognitionRef.current) return;
recognitionRef.current.stop();
}, []);

return {
transcript,
interimTranscript,
isListening,
isFinal,
isSupported: !!SpeechRecognition,
start,
stop,
error,
};
}
Loading

0 comments on commit b85c9a2

Please sign in to comment.