add speech recognition
Browse files- module.d.ts +1 -0
- package-lock.json +25 -0
- package.json +2 -0
- src/components/App.tsx +0 -2
- src/components/ask-ai/ask-ai.tsx +3 -0
- src/components/speech-prompt/speech-prompt.tsx +46 -0
module.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
declare module "react-speech-recognition";
|
package-lock.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
| 22 |
"react-dom": "^19.0.0",
|
| 23 |
"react-icons": "^5.5.0",
|
| 24 |
"react-markdown": "^10.1.0",
|
|
|
|
| 25 |
"react-toastify": "^11.0.5",
|
| 26 |
"react-use": "^17.6.0",
|
| 27 |
"tailwindcss": "^4.0.15"
|
|
@@ -31,6 +32,7 @@
|
|
| 31 |
"@types/express": "^5.0.1",
|
| 32 |
"@types/react": "^19.0.10",
|
| 33 |
"@types/react-dom": "^19.0.4",
|
|
|
|
| 34 |
"@vitejs/plugin-react": "^4.3.4",
|
| 35 |
"eslint": "^9.21.0",
|
| 36 |
"eslint-plugin-react-hooks": "^5.1.0",
|
|
@@ -1608,6 +1610,12 @@
|
|
| 1608 |
"@types/ms": "*"
|
| 1609 |
}
|
| 1610 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1611 |
"node_modules/@types/estree": {
|
| 1612 |
"version": "1.0.6",
|
| 1613 |
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
|
@@ -1730,6 +1738,15 @@
|
|
| 1730 |
"@types/react": "^19.0.0"
|
| 1731 |
}
|
| 1732 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1733 |
"node_modules/@types/send": {
|
| 1734 |
"version": "0.17.4",
|
| 1735 |
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
|
@@ -5370,6 +5387,14 @@
|
|
| 5370 |
"node": ">=0.10.0"
|
| 5371 |
}
|
| 5372 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5373 |
"node_modules/react-toastify": {
|
| 5374 |
"version": "11.0.5",
|
| 5375 |
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
|
|
|
|
| 22 |
"react-dom": "^19.0.0",
|
| 23 |
"react-icons": "^5.5.0",
|
| 24 |
"react-markdown": "^10.1.0",
|
| 25 |
+
"react-speech-recognition": "^4.0.0",
|
| 26 |
"react-toastify": "^11.0.5",
|
| 27 |
"react-use": "^17.6.0",
|
| 28 |
"tailwindcss": "^4.0.15"
|
|
|
|
| 32 |
"@types/express": "^5.0.1",
|
| 33 |
"@types/react": "^19.0.10",
|
| 34 |
"@types/react-dom": "^19.0.4",
|
| 35 |
+
"@types/react-speech-recognition": "^3.9.6",
|
| 36 |
"@vitejs/plugin-react": "^4.3.4",
|
| 37 |
"eslint": "^9.21.0",
|
| 38 |
"eslint-plugin-react-hooks": "^5.1.0",
|
|
|
|
| 1610 |
"@types/ms": "*"
|
| 1611 |
}
|
| 1612 |
},
|
| 1613 |
+
"node_modules/@types/dom-speech-recognition": {
|
| 1614 |
+
"version": "0.0.6",
|
| 1615 |
+
"resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.6.tgz",
|
| 1616 |
+
"integrity": "sha512-o7pAVq9UQPJL5RDjO1f/fcpfFHdgiMnR4PoIU2N/ZQrYOS3C5rzdOJMsrpqeBCbii2EE9mERXgqspQqPDdPahw==",
|
| 1617 |
+
"dev": true
|
| 1618 |
+
},
|
| 1619 |
"node_modules/@types/estree": {
|
| 1620 |
"version": "1.0.6",
|
| 1621 |
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
|
|
|
| 1738 |
"@types/react": "^19.0.0"
|
| 1739 |
}
|
| 1740 |
},
|
| 1741 |
+
"node_modules/@types/react-speech-recognition": {
|
| 1742 |
+
"version": "3.9.6",
|
| 1743 |
+
"resolved": "https://registry.npmjs.org/@types/react-speech-recognition/-/react-speech-recognition-3.9.6.tgz",
|
| 1744 |
+
"integrity": "sha512-cdzwXIZXWyp8zfM2XI7APDW1rZf4Nz73T4SIS2y+cC7zHnZluCdumYKH6HacxgxJH+zemAq2oXbHWXcyW0eT3A==",
|
| 1745 |
+
"dev": true,
|
| 1746 |
+
"dependencies": {
|
| 1747 |
+
"@types/dom-speech-recognition": "*"
|
| 1748 |
+
}
|
| 1749 |
+
},
|
| 1750 |
"node_modules/@types/send": {
|
| 1751 |
"version": "0.17.4",
|
| 1752 |
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
|
|
|
| 5387 |
"node": ">=0.10.0"
|
| 5388 |
}
|
| 5389 |
},
|
| 5390 |
+
"node_modules/react-speech-recognition": {
|
| 5391 |
+
"version": "4.0.0",
|
| 5392 |
+
"resolved": "https://registry.npmjs.org/react-speech-recognition/-/react-speech-recognition-4.0.0.tgz",
|
| 5393 |
+
"integrity": "sha512-hz1OsRhjAW70rOMVXN84PR+1L2I1j8xS1TXpwpd4vlDaRY9i/LbAaxEklqscgObECTTuyxNeGBdVdcq/pX3bqQ==",
|
| 5394 |
+
"peerDependencies": {
|
| 5395 |
+
"react": ">=16.8.0"
|
| 5396 |
+
}
|
| 5397 |
+
},
|
| 5398 |
"node_modules/react-toastify": {
|
| 5399 |
"version": "11.0.5",
|
| 5400 |
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
|
package.json
CHANGED
|
@@ -25,6 +25,7 @@
|
|
| 25 |
"react-dom": "^19.0.0",
|
| 26 |
"react-icons": "^5.5.0",
|
| 27 |
"react-markdown": "^10.1.0",
|
|
|
|
| 28 |
"react-toastify": "^11.0.5",
|
| 29 |
"react-use": "^17.6.0",
|
| 30 |
"tailwindcss": "^4.0.15"
|
|
@@ -34,6 +35,7 @@
|
|
| 34 |
"@types/express": "^5.0.1",
|
| 35 |
"@types/react": "^19.0.10",
|
| 36 |
"@types/react-dom": "^19.0.4",
|
|
|
|
| 37 |
"@vitejs/plugin-react": "^4.3.4",
|
| 38 |
"eslint": "^9.21.0",
|
| 39 |
"eslint-plugin-react-hooks": "^5.1.0",
|
|
|
|
| 25 |
"react-dom": "^19.0.0",
|
| 26 |
"react-icons": "^5.5.0",
|
| 27 |
"react-markdown": "^10.1.0",
|
| 28 |
+
"react-speech-recognition": "^4.0.0",
|
| 29 |
"react-toastify": "^11.0.5",
|
| 30 |
"react-use": "^17.6.0",
|
| 31 |
"tailwindcss": "^4.0.15"
|
|
|
|
| 35 |
"@types/express": "^5.0.1",
|
| 36 |
"@types/react": "^19.0.10",
|
| 37 |
"@types/react-dom": "^19.0.4",
|
| 38 |
+
"@types/react-speech-recognition": "^3.9.6",
|
| 39 |
"@vitejs/plugin-react": "^4.3.4",
|
| 40 |
"eslint": "^9.21.0",
|
| 41 |
"eslint-plugin-react-hooks": "^5.1.0",
|
src/components/App.tsx
CHANGED
|
@@ -23,8 +23,6 @@ function App() {
|
|
| 23 |
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
|
| 24 |
const remix = useSearchParam("remix");
|
| 25 |
|
| 26 |
-
console.log("REMIX => ", remix);
|
| 27 |
-
|
| 28 |
const preview = useRef<HTMLDivElement>(null);
|
| 29 |
const editor = useRef<HTMLDivElement>(null);
|
| 30 |
const resizer = useRef<HTMLDivElement>(null);
|
|
|
|
| 23 |
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
|
| 24 |
const remix = useSearchParam("remix");
|
| 25 |
|
|
|
|
|
|
|
| 26 |
const preview = useRef<HTMLDivElement>(null);
|
| 27 |
const editor = useRef<HTMLDivElement>(null);
|
| 28 |
const resizer = useRef<HTMLDivElement>(null);
|
src/components/ask-ai/ask-ai.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import { useState } from "react";
|
| 2 |
import { RiSparkling2Fill } from "react-icons/ri";
|
| 3 |
import { GrSend } from "react-icons/gr";
|
|
@@ -10,6 +11,7 @@ import Login from "../login/login";
|
|
| 10 |
import { defaultHTML } from "./../../../utils/consts";
|
| 11 |
import SuccessSound from "./../../assets/success.mp3";
|
| 12 |
import Settings from "../settings/settings";
|
|
|
|
| 13 |
|
| 14 |
function AskAI({
|
| 15 |
html,
|
|
@@ -166,6 +168,7 @@ function AskAI({
|
|
| 166 |
}}
|
| 167 |
/>
|
| 168 |
<div className="flex items-center justify-end gap-2">
|
|
|
|
| 169 |
<Settings
|
| 170 |
provider={provider as string}
|
| 171 |
onChange={setProvider}
|
|
|
|
| 1 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import { useState } from "react";
|
| 3 |
import { RiSparkling2Fill } from "react-icons/ri";
|
| 4 |
import { GrSend } from "react-icons/gr";
|
|
|
|
| 11 |
import { defaultHTML } from "./../../../utils/consts";
|
| 12 |
import SuccessSound from "./../../assets/success.mp3";
|
| 13 |
import Settings from "../settings/settings";
|
| 14 |
+
import SpeechPrompt from "../speech-prompt/speech-prompt";
|
| 15 |
|
| 16 |
function AskAI({
|
| 17 |
html,
|
|
|
|
| 168 |
}}
|
| 169 |
/>
|
| 170 |
<div className="flex items-center justify-end gap-2">
|
| 171 |
+
<SpeechPrompt setPrompt={setPrompt} />
|
| 172 |
<Settings
|
| 173 |
provider={provider as string}
|
| 174 |
onChange={setProvider}
|
src/components/speech-prompt/speech-prompt.tsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import classNames from "classnames";
|
| 2 |
+
import { FaMicrophone } from "react-icons/fa";
|
| 3 |
+
import SpeechRecognition, {
|
| 4 |
+
useSpeechRecognition,
|
| 5 |
+
} from "react-speech-recognition";
|
| 6 |
+
import { useUpdateEffect } from "react-use";
|
| 7 |
+
|
| 8 |
+
function SpeechPrompt({
|
| 9 |
+
setPrompt,
|
| 10 |
+
}: {
|
| 11 |
+
setPrompt: React.Dispatch<React.SetStateAction<string>>;
|
| 12 |
+
}) {
|
| 13 |
+
const { transcript, listening, browserSupportsSpeechRecognition } =
|
| 14 |
+
useSpeechRecognition();
|
| 15 |
+
|
| 16 |
+
const startListening = () =>
|
| 17 |
+
SpeechRecognition.startListening({ continuous: true });
|
| 18 |
+
|
| 19 |
+
if (!browserSupportsSpeechRecognition) {
|
| 20 |
+
return null;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
| 24 |
+
useUpdateEffect(() => {
|
| 25 |
+
if (transcript) setPrompt(transcript);
|
| 26 |
+
}, [transcript]);
|
| 27 |
+
|
| 28 |
+
return (
|
| 29 |
+
<button
|
| 30 |
+
className={classNames(
|
| 31 |
+
"flex cursor-pointer flex-none items-center justify-center rounded-full text-sm font-semibold size-8 text-center bg-gray-800 hover:bg-gray-700 text-white shadow-sm dark:shadow-highlight/20 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300",
|
| 32 |
+
{
|
| 33 |
+
"animate-pulse !bg-orange-500": listening,
|
| 34 |
+
}
|
| 35 |
+
)}
|
| 36 |
+
onTouchStart={startListening}
|
| 37 |
+
onMouseDown={startListening}
|
| 38 |
+
onTouchEnd={SpeechRecognition.stopListening}
|
| 39 |
+
onMouseUp={SpeechRecognition.stopListening}
|
| 40 |
+
>
|
| 41 |
+
<FaMicrophone className="text-base" />
|
| 42 |
+
</button>
|
| 43 |
+
);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
export default SpeechPrompt;
|