react-voice-action-router 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/dist/index.js +24 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
# React Voice Action Router
|
|
2
2
|
|
|
3
|
-
> Don't build a chatbot. Build a hands-free interface
|
|
3
|
+
> **Don't just build a chatbot. Build a complete hands-free interface.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-voice-action-router)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
8
|
[**Read the Documentation**](https://nouman64-cat.github.io/react-voice-action-router/)
|
|
9
9
|
|
|
10
|
-
A
|
|
10
|
+
A full-stack voice control library for React. It combines a robust **Speech Recognition Engine** with an **AI-Powered Intent Router** to give your app voice capabilities in minutes, not days.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
12
|
+
## What's New in v2.0?
|
|
13
|
+
|
|
14
|
+
- **Built-in Speech Engine:** No more manual `SpeechRecognition` setup. It just works.
|
|
15
|
+
- **Dictation Mode:** Seamlessly switch between executing commands and typing text.
|
|
16
|
+
- **Offline Fallback:** Core commands work even if your AI API goes down.
|
|
17
|
+
- **Auto-Healing:** Automatically restarts the microphone if the browser stops it.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Headless & Zero UI:** We provide the logic; you build the interface (or keep it invisible).
|
|
24
|
+
- **Context-Aware:** The AI only knows about commands on the _current_ screen, reducing costs and hallucinations.
|
|
25
|
+
- **Universal Adapter:** Works with OpenAI, Anthropic, Gemini, local LLMs (Ollama), or any custom backend.
|
|
26
|
+
- **Latency-First:** Instant execution for exact phrases, falling back to AI for natural language understanding.
|
|
27
|
+
|
|
28
|
+
---
|
|
16
29
|
|
|
17
30
|
## Installation
|
|
18
31
|
|
package/dist/index.js
CHANGED
|
@@ -209,13 +209,8 @@ var VoiceControlProvider = ({
|
|
|
209
209
|
},
|
|
210
210
|
[adapter, enableOfflineFallback]
|
|
211
211
|
);
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
start: engineStart,
|
|
215
|
-
stop: engineStop
|
|
216
|
-
} = useSpeechRecognition({
|
|
217
|
-
disabled: disableSpeechEngine,
|
|
218
|
-
onResult: (transcript, isFinal) => {
|
|
212
|
+
const handleResult = react.useCallback(
|
|
213
|
+
(transcript, isFinal) => {
|
|
219
214
|
var _a, _b;
|
|
220
215
|
if (isDictatingRef.current && dictationOptionsRef.current) {
|
|
221
216
|
const options = dictationOptionsRef.current;
|
|
@@ -226,15 +221,33 @@ var VoiceControlProvider = ({
|
|
|
226
221
|
processTranscript(transcript);
|
|
227
222
|
return;
|
|
228
223
|
}
|
|
229
|
-
|
|
224
|
+
if (isFinal) {
|
|
225
|
+
options.onFinal(transcript);
|
|
226
|
+
} else {
|
|
227
|
+
(_b = options.onInterim) == null ? void 0 : _b.call(options, transcript);
|
|
228
|
+
}
|
|
230
229
|
return;
|
|
231
230
|
}
|
|
232
|
-
if (isFinal)
|
|
231
|
+
if (isFinal) {
|
|
232
|
+
processTranscript(transcript);
|
|
233
|
+
}
|
|
233
234
|
},
|
|
234
|
-
|
|
235
|
+
[processTranscript, stopDictation]
|
|
236
|
+
);
|
|
237
|
+
const handleError = react.useCallback((err) => {
|
|
238
|
+
setState((prev) => ({
|
|
235
239
|
...prev,
|
|
236
240
|
error: typeof err === "string" ? err : "Speech Error"
|
|
237
|
-
}))
|
|
241
|
+
}));
|
|
242
|
+
}, []);
|
|
243
|
+
const {
|
|
244
|
+
isListening: engineListening,
|
|
245
|
+
start: engineStart,
|
|
246
|
+
stop: engineStop
|
|
247
|
+
} = useSpeechRecognition({
|
|
248
|
+
disabled: disableSpeechEngine,
|
|
249
|
+
onResult: handleResult,
|
|
250
|
+
onError: handleError
|
|
238
251
|
});
|
|
239
252
|
react.useEffect(() => {
|
|
240
253
|
setState((prev) => ({ ...prev, isListening: engineListening }));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSpeechRecognition.ts","../src/core/localMatcher.ts","../src/components/VoiceContext.tsx","../src/hooks/useVoiceCommand.ts","../src/core/prompt.ts","../src/adapters/openai.ts","../src/adapters/gemini.ts","../src/adapters/claude.ts"],"names":["useState","useRef","useEffect","useCallback","createContext","jsx","useContext"],"mappings":";;;;;;AAqBO,IAAM,uBAAuB,CAAC;AAAA,EACjC,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW;AACf,CAAA,KAAiC;AAE7B,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAA6B,IAAI,CAAA;AAC3D,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,IAAI,CAAA;AAGnD,EAAA,MAAM,cAAA,GAAiBC,aAAiC,IAAI,CAAA;AAC5D,EAAA,MAAM,aAAA,GAAgBA,aAAO,KAAK,CAAA;AAGlC,EAAAC,eAAA,CAAU,MAAM;AACZ,IAAA,IAAI,QAAA,EAAU;AAEd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,IAAA,MAAM,iBAAA,GAAoB,MAAA,CAAO,iBAAA,IAAqB,MAAA,CAAO,uBAAA;AAE7D,IAAA,IAAI,CAAC,iBAAA,EAAmB;AACpB,MAAA,OAAA,CAAQ,MAAM,8EAA8E,CAAA;AAC5F,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,WAAA,GAAc,IAAI,iBAAA,EAAkB;AAC1C,IAAA,WAAA,CAAY,UAAA,GAAa,IAAA;AACzB,IAAA,WAAA,CAAY,cAAA,GAAiB,IAAA;AAC7B,IAAA,WAAA,CAAY,IAAA,GAAO,OAAA;AAInB,IAAA,WAAA,CAAY,QAAA,GAAW,CAAC,KAAA,KAAkC;AAEtD,MAAA,MAAM,SAAS,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAC,CAAA;AACrD,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,CAAC,CAAA,CAAE,UAAA;AAC7B,MAAA,MAAM,UAAU,MAAA,CAAO,OAAA;AAGvB,MAAA,QAAA,CAAS,YAAY,OAAO,CAAA;AAAA,IAChC,CAAA;AAEA,IAAA,WAAA,CAAY,OAAA,GAAU,CAAC,KAAA,KAAuC;AAE1D,MAAA,IAAI,KAAA,CAAM,UAAU,WAAA,EAAa;AAC7B,QAAA;AAAA,MACJ;AAEA,MAAA,OAAA,CAAQ,IAAA,CAAK,2BAAA,EAA6B,KAAA,CAAM,KAAK,CAAA;AACrD,MAAA,QAAA,CAAS,MAAM,KAAK,CAAA;AACpB,MAAA,IAAI,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA;AAGhC,MAAA,IAAI,KAAA,CAAM,UAAU,aAAA,EAAe;AAC/B,QAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AACxB,QAAA,cAAA,CAAe,KAAK,CAAA;AAAA,MACxB;AAAA,IACJ,CAAA;AAEA,IAAA,WAAA,CAAY,QAAQ,MAAM;AAEtB,MAAA,IAAI,cAAc,OAAA,EAAS;AAEvB,QAAA,IAAI;AACA,UAAA,WAAA,CAAY,KAAA,EAAM;AAAA,QACtB,SAAS,CAAA,EAAG;AAER,UAAA,UAAA,CAAW,MAAM;AACb,YAAA,IAAI,aAAA,CAAc,OAAA,EAAS,WAAA,CAAY,KAAA,EAAM;AAAA,UACjD,GAAG,GAAG,CAAA;AAAA,QACV;AAAA,MACJ,CAAA,MAAO;AACH,QAAA,cAAA,CAAe,KAAK,CAAA;AAAA,MACxB;AAAA,IACJ,CAAA;AAEA,IAAA,cAAA,CAAe,OAAA,GAAU,WAAA;AAGzB,IAAA,OAAO,MAAM;AACT,MAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AACxB,MAAA,WAAA,CAAY,IAAA,EAAK;AAAA,IACrB,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,QAAA,EAAU,QAAA,EAAU,OAAO,CAAC,CAAA;AAGhC,EAAA,MAAM,KAAA,GAAQC,kBAAY,MAAM;AAC5B,IAAA,IAAI,CAAC,cAAA,CAAe,OAAA,IAAW,CAAC,WAAA,EAAa;AAE7C,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,IAAI;AACA,MAAA,cAAA,CAAe,QAAQ,KAAA,EAAM;AAC7B,MAAA,cAAA,CAAe,IAAI,CAAA;AAAA,IACvB,SAAS,CAAA,EAAG;AAAA,IAEZ;AAAA,EACJ,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAM;AAC3B,IAAA,IAAI,CAAC,eAAe,OAAA,EAAS;AAE7B,IAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AACxB,IAAA,cAAA,CAAe,QAAQ,IAAA,EAAK;AAC5B,IAAA,cAAA,CAAe,KAAK,CAAA;AAAA,EACxB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAASA,kBAAY,MAAM;AAC7B,IAAA,IAAI,aAAa,IAAA,EAAK;AAAA,SACjB,KAAA,EAAM;AAAA,EACf,CAAA,EAAG,CAAC,WAAA,EAAa,KAAA,EAAO,IAAI,CAAC,CAAA;AAE7B,EAAA,OAAO;AAAA,IACH,WAAA;AAAA,IACA,WAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACJ;AACJ,CAAA;;;AC1IO,IAAM,aAAA,GAAgB,CAAC,UAAA,EAAoB,QAAA,KAA4C;AAC1F,EAAA,MAAM,eAAA,GAAkB,UAAA,CAAW,WAAA,EAAY,CAAE,IAAA,EAAK;AACtD,EAAA,MAAM,eAAA,GAAkB,gBAAgB,KAAA,CAAM,KAAK,EAAE,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA;AAE7E,EAAA,IAAI,WAAA,GAA6B,IAAA;AACjC,EAAA,IAAI,QAAA,GAAW,CAAA;AAEf,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AACxB,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,WAAA,CAAY,WAAA,EAAY;AACzC,IAAA,MAAM,EAAA,GAAK,GAAA,CAAI,EAAA,CAAG,WAAA,EAAY;AAG9B,IAAA,IAAI,eAAA,CAAgB,QAAA,CAAS,EAAE,CAAA,EAAG;AAC9B,MAAA,KAAA,IAAS,CAAA;AAAA,IACb;AAIA,IAAA,KAAA,MAAW,QAAQ,eAAA,EAAiB;AAChC,MAAA,IAAI,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG;AACrB,QAAA,KAAA,IAAS,CAAA;AAAA,MACb;AAAA,IACJ;AAGA,IAAA,IAAI,KAAA,GAAQ,QAAA,IAAY,KAAA,IAAS,CAAA,EAAG;AAChC,MAAA,QAAA,GAAW,KAAA;AACX,MAAA,WAAA,GAAc,GAAA,CAAI,EAAA;AAAA,IACtB;AAAA,EACJ;AAEA,EAAA,OAAO,WAAA;AACX,CAAA;ACZA,IAAM,YAAA,GAAeC,oBAAwC,IAAI,CAAA;AAE1D,IAAM,uBAAqD,CAAC;AAAA,EACjE,QAAA;AAAA,EACA,OAAA;AAAA,EACA,mBAAA,GAAsB,KAAA;AAAA,EACtB,qBAAA,GAAwB;AAAA;AAC1B,CAAA,KAAM;AACJ,EAAA,MAAM,WAAA,GAAcH,YAAAA,iBAAkC,IAAI,GAAA,EAAK,CAAA;AAC/D,EAAA,MAAM,WAAA,GAAcA,aAAO,KAAK,CAAA;AAChC,EAAA,MAAM,cAAA,GAAiBA,aAAO,KAAK,CAAA;AACnC,EAAA,MAAM,mBAAA,GAAsBA,aAAgC,IAAI,CAAA;AAEhE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAID,cAAAA,CAA4B;AAAA,IACpD,WAAA,EAAa,KAAA;AAAA,IACb,YAAA,EAAc,KAAA;AAAA,IACd,cAAA,EAAgB,IAAA;AAAA,IAChB,gBAAgB,EAAC;AAAA,IACjB,QAAA,EAAU,KAAA;AAAA,IACV,KAAA,EAAO,IAAA;AAAA,IACP,WAAA,EAAa;AAAA,GACd,CAAA;AAED,EAAA,MAAM,SAAA,GAAYG,iBAAAA,CAAY,CAAC,MAAA,KAAoB;AACjD,IAAA,WAAA,CAAY,OAAA,GAAU,MAAA;AACtB,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,QAAO,CAAE,CAAA;AAAA,EACpD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,cAAA,GAAiBA,iBAAAA,CAAY,CAAC,OAAA,KAA8B;AAChE,IAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,IAAA,mBAAA,CAAoB,OAAA,GAAU,OAAA;AAC9B,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,MAAK,CAAE,CAAA;AAAA,EACrD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,kBAAY,MAAM;AACtC,IAAA,cAAA,CAAe,OAAA,GAAU,KAAA;AACzB,IAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAC9B,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,OAAM,CAAE,CAAA;AAAA,EACtD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,iBAAA,GAAoBA,iBAAAA;AAAA,IACxB,OAAO,UAAA,KAAuB;AAC5B,MAAA,IAAI,YAAY,OAAA,EAAS;AAEzB,MAAA,MAAM,SAAA,GAAY,UAAA,CAAW,IAAA,EAAK,CAAE,WAAA,EAAY;AAChD,MAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,MAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,QAClB,GAAG,IAAA;AAAA,QACH,YAAA,EAAc,IAAA;AAAA,QACd,cAAA,EAAgB;AAAA,OAClB,CAAE,CAAA;AACF,MAAA,MAAM,cAAc,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,QAAQ,CAAA;AAG3D,MAAA,MAAM,aAAa,WAAA,CAAY,IAAA;AAAA,QAC7B,CAAC,CAAA,KAAG;AAnFZ,UAAA,IAAA,EAAA;AAmFe,UAAA,OAAA,CAAA,CAAA,EAAA,GAAA,CAAA,CAAE,MAAA,KAAF,mBAAU,WAAA,EAAA,MAAkB,SAAA;AAAA,QAAA;AAAA,OACrC;AACA,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,eAAA,EAAa,SAAS,CAAA,CAAA,CAAG,CAAA;AACrC,QAAA,UAAA,CAAW,MAAA,EAAO;AAClB,QAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,YAAA,EAAc,OAAM,CAAE,CAAA;AACrD,QAAA;AAAA,MACF;AAGA,MAAA,IAAI;AACF,QAAA,MAAM,mBAAmB,WAAA,CAAY,GAAA;AAAA,UACnC,CAAC,EAAE,EAAA,EAAI,WAAA,EAAa,QAAO,MAAO,EAAE,EAAA,EAAI,WAAA,EAAa,MAAA,EAAO;AAAA,SAC9D;AAEA,QAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,SAAA,EAAW,gBAAgB,CAAA;AAExD,QAAA,IAAI,OAAO,SAAA,EAAW;AACpB,UAAA,MAAM,GAAA,GAAM,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,OAAO,SAAS,CAAA;AACpD,UAAA,IAAI,GAAA,EAAK;AACP,YAAA,GAAA,CAAI,MAAA,EAAO;AAAA,UACb;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,KAAA,CAAM,sBAAsB,KAAK,CAAA;AAGzC,QAAA,IAAI,qBAAA,EAAuB;AACzB,UAAA,OAAA,CAAQ,KAAK,0CAAmC,CAAA;AAChD,UAAA,MAAM,UAAA,GAAa,aAAA,CAAc,SAAA,EAAW,WAAW,CAAA;AAEvD,UAAA,IAAI,UAAA,EAAY;AACd,YAAA,MAAM,GAAA,GAAM,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA;AAC9C,YAAA,IAAI,GAAA,EAAK;AACP,cAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,sBAAA,EAAoB,GAAA,CAAI,EAAE,CAAA,CAAE,CAAA;AACxC,cAAA,GAAA,CAAI,MAAA,EAAO;AAAA,YACb;AAAA,UACF,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,IAAI,gCAA2B,CAAA;AAAA,UACzC;AAAA,QACF;AAAA,MACF,CAAA,SAAE;AACA,QAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,YAAA,EAAc,OAAM,CAAE,CAAA;AAAA,MACvD;AAAA,IACF,CAAA;AAAA,IACA,CAAC,SAAS,qBAAqB;AAAA,GACjC;AAGA,EAAA,MAAM;AAAA,IACJ,WAAA,EAAa,eAAA;AAAA,IACb,KAAA,EAAO,WAAA;AAAA,IACP,IAAA,EAAM;AAAA,MACJ,oBAAA,CAAqB;AAAA,IACvB,QAAA,EAAU,mBAAA;AAAA,IACV,QAAA,EAAU,CAAC,UAAA,EAAY,OAAA,KAAY;AA1IvC,MAAA,IAAA,EAAA,EAAA,EAAA;AA2IM,MAAA,IAAI,cAAA,CAAe,OAAA,IAAW,mBAAA,CAAoB,OAAA,EAAS;AACzD,QAAA,MAAM,UAAU,mBAAA,CAAoB,OAAA;AACpC,QAAA,IACE,OAAA,KAAA,CACA,EAAA,GAAA,OAAA,CAAQ,YAAA,KAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAsB,IAAA;AAAA,UAAK,CAAC,GAAA,KAC1B,UAAA,CAAW,WAAA,EAAY,CAAE,SAAS,GAAG;AAAA,SAAA,CAAA,EAEvC;AACA,UAAA,aAAA,EAAc;AACd,UAAA,iBAAA,CAAkB,UAAU,CAAA;AAC5B,UAAA;AAAA,QACF;AACA,QAAA,OAAA,GAAU,QAAQ,OAAA,CAAQ,UAAU,CAAA,GAAA,CAAI,EAAA,GAAA,OAAA,CAAQ,cAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,OAAA,EAAoB,UAAA,CAAA;AAC5D,QAAA;AAAA,MACF;AACA,MAAA,IAAI,OAAA,oBAA2B,UAAU,CAAA;AAAA,IAC3C,CAAA;AAAA,IACA,OAAA,EAAS,CAAC,GAAA,KACR,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,MAClB,GAAG,IAAA;AAAA,MACH,KAAA,EAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM;AAAA,KACzC,CAAE;AAAA,GACL,CAAA;AAED,EAAAD,gBAAU,MAAM;AACd,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,iBAAgB,CAAE,CAAA;AAAA,EAChE,CAAA,EAAG,CAAC,eAAe,CAAC,CAAA;AAEpB,EAAA,MAAM,QAAA,GAAWC,iBAAAA,CAAY,CAAC,GAAA,KAAsB;AAClD,IAAA,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,GAAG,CAAA;AACnC,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,MAClB,GAAG,IAAA;AAAA,MACH,gBAAgB,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,QAAQ;AAAA,KACzD,CAAE,CAAA;AAAA,EACJ,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,iBAAAA,CAAY,CAAC,EAAA,KAAe;AAC7C,IAAA,WAAA,CAAY,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC7B,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,MAClB,GAAG,IAAA;AAAA,MACH,gBAAgB,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,QAAQ;AAAA,KACzD,CAAE,CAAA;AAAA,EACJ,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,uBACEE,cAAA;AAAA,IAAC,YAAA,CAAa,QAAA;AAAA,IAAb;AAAA,MACC,KAAA,EAAO;AAAA,QACL,GAAG,KAAA;AAAA,QACH,QAAA;AAAA,QACA,UAAA;AAAA,QACA,iBAAA;AAAA,QACA,SAAA;AAAA,QACA,cAAA,EAAgB,WAAA;AAAA,QAChB,aAAA,EAAe,UAAA;AAAA,QACf,cAAA;AAAA,QACA;AAAA,OACF;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ;AAEO,IAAM,kBAAkB,MAAM;AACnC,EAAA,MAAM,GAAA,GAAMC,iBAAW,YAAY,CAAA;AACnC,EAAA,IAAI,CAAC,GAAA;AACH,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AACF,EAAA,OAAO,GAAA;AACT;AC7MO,IAAM,eAAA,GAAkB,CAAC,OAAA,KAA0B;AACxD,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,eAAA,EAAgB;AAKjD,EAAA,MAAM,UAAA,GAAaL,aAAO,OAAO,CAAA;AAGjC,EAAAC,gBAAU,MAAM;AACd,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAAA,EACvB,CAAC,CAAA;AAED,EAAAA,gBAAU,MAAM;AAId,IAAA,MAAM,YAAA,GAA6B;AAAA,MACjC,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,QAAQ,MAAM;AAEZ,QAAA,UAAA,CAAW,QAAQ,MAAA,EAAO;AAAA,MAC5B;AAAA,KACF;AAEA,IAAA,QAAA,CAAS,YAAY,CAAA;AAErB,IAAA,OAAO,MAAM;AACX,MAAA,UAAA,CAAW,aAAa,EAAE,CAAA;AAAA,IAC5B,CAAA;AAAA,EAGF,GAAG,CAAC,QAAA,EAAU,UAAA,EAAY,OAAA,CAAQ,EAAE,CAAC,CAAA;AACvC;;;ACjCO,IAAM,kBAAA,GAAqB,CAAC,QAAA,KAA6C;AAE5E,EAAA,MAAM,cAAc,QAAA,CAAS,GAAA;AAAA,IAAI,SAC7B,CAAA,OAAA,EAAU,GAAA,CAAI,EAAE,CAAA,kBAAA,EAAqB,IAAI,WAAW,CAAA,CAAA;AAAA,GACxD,CAAE,KAAK,IAAI,CAAA;AAEX,EAAA,OAAO;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,EAYT,WAAW;;AAAA;AAAA;AAAA,CAAA;AAKb,CAAA;;;AChBO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAAqC;AACrE,EAAA,OAAO,OAAO,YAAY,QAAA,KAAa;AAGnC,IAAA,MAAM,YAAA,GAAe,mBAAmB,QAAQ,CAAA;AAGhD,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,4CAAA,EAA8C;AAAA,MACvE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACL,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA;AAAA,OAC5C;AAAA,MACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACjB,KAAA,EAAO,OAAO,KAAA,IAAS,aAAA;AAAA,QACvB,QAAA,EAAU;AAAA,UACN,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,YAAA,EAAa;AAAA,UACxC,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,UAAA;AAAW,SACxC;AAAA,QACA,WAAA,EAAa,CAAA;AAAA;AAAA,QACb,eAAA,EAAiB,EAAE,IAAA,EAAM,aAAA;AAAc;AAAA,OAC1C;AAAA,KACJ,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,uBAAA,EAA0B,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,IACnE;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAGjC,IAAA,IAAI;AACA,MAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAC,CAAA,CAAE,QAAQ,OAAO,CAAA;AACzD,MAAA,OAAO,EAAE,SAAA,EAAW,MAAA,CAAO,SAAA,EAAU;AAAA,IACzC,SAAS,CAAA,EAAG;AACR,MAAA,OAAA,CAAQ,KAAA,CAAM,gCAAgC,CAAC,CAAA;AAC/C,MAAA,OAAO,EAAE,WAAW,IAAA,EAAK;AAAA,IAC7B;AAAA,EACJ,CAAA;AACJ;;;ACtCO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAAqC;AACvE,EAAA,OAAO,OAAO,YAAY,QAAA,KAAa;AAfzC,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAkBI,IAAA,MAAM,WAAA,GAAc,QAAA,CACjB,GAAA,CAAI,CAAC,MAAM,CAAA,GAAA,EAAM,CAAA,CAAE,EAAE,CAAA,GAAA,EAAM,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC1C,KAAK,IAAI,CAAA;AAEZ,IAAA,MAAM,MAAA,GAAS;AAAA;AAAA,sBAAA,EAEK,UAAU,CAAA;;AAAA;AAAA,MAAA,EAG1B,WAAW;;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAQf,IAAA,MAAM,MAAM,CAAA,wDAAA,EACV,MAAA,CAAO,SAAS,kBAClB,CAAA,qBAAA,EAAwB,OAAO,MAAM,CAAA,CAAA;AAErC,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA,SAClB;AAAA,QACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,QAAA,EAAU;AAAA,YACR;AAAA,cACE,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,QAAQ;AAAA;AAC1B;AACF,SACD;AAAA,OACF,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,MAC5D;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAIjC,MAAA,MAAM,YAAA,GAAA,CAAe,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,CAAK,UAAA,KAAL,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAkB,CAAA,CAAA,KAAlB,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAsB,OAAA,KAAtB,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAA+B,KAAA,KAA/B,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAuC,CAAA,CAAA,KAAvC,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAA2C,IAAA;AAEhE,MAAA,IAAI,CAAC,YAAA,EAAc,OAAO,EAAE,WAAW,IAAA,EAAK;AAG5C,MAAA,MAAM,YAAY,YAAA,CAAa,OAAA,CAAQ,cAAA,EAAgB,EAAE,EAAE,IAAA,EAAK;AAEhE,MAAA,OAAO,IAAA,CAAK,MAAM,SAAS,CAAA;AAAA,IAC7B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AACjD,MAAA,OAAO,EAAE,WAAW,IAAA,EAAK;AAAA,IAC3B;AAAA,EACF,CAAA;AACF;;;AC5DO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAAqC;AACvE,EAAA,OAAO,OAAO,YAAY,QAAA,KAAa;AAhBzC,IAAA,IAAA,EAAA,EAAA,EAAA;AAiBI,IAAA,MAAM,QAAA,GAAW,OAAO,QAAA,IAAY,uCAAA;AACpC,IAAA,MAAM,KAAA,GAAQ,OAAO,KAAA,IAAS,4BAAA;AAG9B,IAAA,MAAM,WAAA,GAAc,QAAA,CACjB,GAAA,CAAI,CAAC,MAAM,CAAA,GAAA,EAAM,CAAA,CAAE,EAAE,CAAA,GAAA,EAAM,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC1C,KAAK,IAAI,CAAA;AAEZ,IAAA,MAAM,YAAA,GAAe;AAAA;AAAA;AAAA,MAAA,EAGjB,WAAW;;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAQf,IAAA,IAAI;AAEF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,QAAA,EAAU;AAAA,QACrC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,aAAa,MAAA,CAAO,MAAA;AAAA,UACpB,mBAAA,EAAqB,YAAA;AAAA,UACrB,cAAA,EAAgB;AAAA;AAAA,SAElB;AAAA,QACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,KAAA;AAAA,UACA,UAAA,EAAY,GAAA;AAAA,UACZ,MAAA,EAAQ,YAAA;AAAA,UACR,UAAU,CAAC,EAAE,MAAM,MAAA,EAAQ,OAAA,EAAS,YAAY;AAAA,SACjD;AAAA,OACF,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,GAAA,GAAM,MAAM,QAAA,CAAS,IAAA,EAAK;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,SAAS,MAAM,CAAA,GAAA,EAAM,GAAG,CAAA,CAAE,CAAA;AAAA,MACjE;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAIjC,MAAA,MAAM,YAAA,GAAA,CAAe,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,CAAK,OAAA,KAAL,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAe,OAAf,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAmB,IAAA;AAExC,MAAA,IAAI,CAAC,YAAA,EAAc,OAAO,EAAE,WAAW,IAAA,EAAK;AAG5C,MAAA,MAAM,YAAY,YAAA,CAAa,OAAA,CAAQ,cAAA,EAAgB,EAAE,EAAE,IAAA,EAAK;AAChE,MAAA,OAAO,IAAA,CAAK,MAAM,SAAS,CAAA;AAAA,IAC7B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AACjD,MAAA,OAAO,EAAE,WAAW,IAAA,EAAK;AAAA,IAC3B;AAAA,EACF,CAAA;AACF","file":"index.js","sourcesContent":["// src/hooks/useSpeechRecognition.ts\r\nimport { useState, useEffect, useRef, useCallback } from 'react';\r\n\r\n// Define the supported error types\r\nexport type SpeechError =\r\n | 'not-allowed'\r\n | 'no-speech'\r\n | 'network'\r\n | 'aborted'\r\n | 'service-not-allowed'\r\n | string;\r\n\r\ninterface UseSpeechRecognitionProps {\r\n onResult: (transcript: string, isFinal: boolean) => void;\r\n onError?: (error: SpeechError) => void;\r\n /**\r\n * If true, the engine will not initialize (backward compatibility)\r\n */\r\n disabled?: boolean;\r\n}\r\n\r\nexport const useSpeechRecognition = ({\r\n onResult,\r\n onError,\r\n disabled = false\r\n}: UseSpeechRecognitionProps) => {\r\n // UI State\r\n const [isListening, setIsListening] = useState(false);\r\n const [error, setError] = useState<SpeechError | null>(null);\r\n const [isSupported, setIsSupported] = useState(true);\r\n\r\n // Internal Refs\r\n const recognitionRef = useRef<SpeechRecognition | null>(null);\r\n const intendedState = useRef(false); // Tracks if we *want* to be listening\r\n\r\n // 1. Initialize the API\r\n useEffect(() => {\r\n if (disabled) return;\r\n\r\n if (typeof window === 'undefined') return; // SSR check\r\n\r\n const SpeechConstructor = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n\r\n if (!SpeechConstructor) {\r\n console.error(\"React Voice Action Router: Speech Recognition not supported in this browser.\");\r\n setIsSupported(false);\r\n return;\r\n }\r\n\r\n const recognition = new SpeechConstructor();\r\n recognition.continuous = true; // We want continuous stream\r\n recognition.interimResults = true; // We need real-time feedback\r\n recognition.lang = 'en-US'; // Default to English (configurable later)\r\n\r\n // --- EVENT HANDLERS ---\r\n\r\n recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n // Get the latest result\r\n const result = event.results[event.results.length - 1];\r\n const transcript = result[0].transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Pass to parent\r\n onResult(transcript, isFinal);\r\n };\r\n\r\n recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n // 'no-speech' is common and usually ignorable (just silence)\r\n if (event.error === 'no-speech') {\r\n return;\r\n }\r\n\r\n console.warn(\"Speech Recognition Error:\", event.error);\r\n setError(event.error);\r\n if (onError) onError(event.error);\r\n\r\n // If permission denied, stop trying\r\n if (event.error === 'not-allowed') {\r\n intendedState.current = false;\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognition.onend = () => {\r\n // The Critical \"Auto-Restart\" Logic\r\n if (intendedState.current) {\r\n // If we still want to be listening, restart immediately\r\n try {\r\n recognition.start();\r\n } catch (e) {\r\n // Sometimes it needs a tiny delay if the browser is busy\r\n setTimeout(() => {\r\n if (intendedState.current) recognition.start();\r\n }, 100);\r\n }\r\n } else {\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognitionRef.current = recognition;\r\n\r\n // Cleanup\r\n return () => {\r\n intendedState.current = false;\r\n recognition.stop();\r\n };\r\n }, [disabled, onResult, onError]);\r\n\r\n // 2. Control Methods\r\n const start = useCallback(() => {\r\n if (!recognitionRef.current || !isSupported) return;\r\n\r\n setError(null);\r\n intendedState.current = true;\r\n try {\r\n recognitionRef.current.start();\r\n setIsListening(true);\r\n } catch (e) {\r\n // Already started, ignore\r\n }\r\n }, [isSupported]);\r\n\r\n const stop = useCallback(() => {\r\n if (!recognitionRef.current) return;\r\n\r\n intendedState.current = false;\r\n recognitionRef.current.stop();\r\n setIsListening(false);\r\n }, []);\r\n\r\n const toggle = useCallback(() => {\r\n if (isListening) stop();\r\n else start();\r\n }, [isListening, start, stop]);\r\n\r\n return {\r\n isListening,\r\n isSupported,\r\n error,\r\n start,\r\n stop,\r\n toggle\r\n };\r\n};","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * A simple fallback matcher that scores commands based on keyword overlap.\r\n * Used when the AI adapter fails.\r\n */\r\nexport const findBestMatch = (transcript: string, commands: VoiceCommand[]): string | null => {\r\n const lowerTranscript = transcript.toLowerCase().trim();\r\n const transcriptWords = lowerTranscript.split(/\\s+/).filter(w => w.length > 2); // Ignore short words\r\n\r\n let bestMatchId: string | null = null;\r\n let maxScore = 0;\r\n\r\n for (const cmd of commands) {\r\n let score = 0;\r\n const desc = cmd.description.toLowerCase();\r\n const id = cmd.id.toLowerCase();\r\n\r\n // 1. Direct ID Match (Strong signal)\r\n if (lowerTranscript.includes(id)) {\r\n score += 3;\r\n }\r\n\r\n // 2. Description Keyword Match\r\n // If the user says \"Navigate settings\", and description is \"Navigate to settings page\"\r\n for (const word of transcriptWords) {\r\n if (desc.includes(word)) {\r\n score += 1;\r\n }\r\n }\r\n\r\n // Update best match if score is significant\r\n if (score > maxScore && score >= 2) { // Threshold of 2 ensures at least some relevance\r\n maxScore = score;\r\n bestMatchId = cmd.id;\r\n }\r\n }\r\n\r\n return bestMatchId;\r\n};","import React, {\r\n createContext,\r\n useContext,\r\n useState,\r\n useCallback,\r\n useRef,\r\n useEffect,\r\n} from \"react\";\r\nimport {\r\n VoiceCommand,\r\n VoiceControlState,\r\n VoiceProviderProps,\r\n DictationOptions,\r\n} from \"../types\";\r\nimport { useSpeechRecognition } from \"../hooks/useSpeechRecognition\";\r\nimport { findBestMatch } from \"../core/localMatcher\";\r\ninterface VoiceContextValue extends VoiceControlState {\r\n register: (cmd: VoiceCommand) => void;\r\n unregister: (id: string) => void;\r\n processTranscript: (text: string) => Promise<void>;\r\n setPaused: (paused: boolean) => void;\r\n startListening: () => void;\r\n stopListening: () => void;\r\n startDictation: (options: DictationOptions) => void;\r\n stopDictation: () => void;\r\n}\r\n\r\nconst VoiceContext = createContext<VoiceContextValue | null>(null);\r\n\r\nexport const VoiceControlProvider: React.FC<VoiceProviderProps> = ({\r\n children,\r\n adapter,\r\n disableSpeechEngine = false,\r\n enableOfflineFallback = true, // <--- Default to true\r\n}) => {\r\n const commandsRef = useRef<Map<string, VoiceCommand>>(new Map());\r\n const isPausedRef = useRef(false);\r\n const isDictatingRef = useRef(false);\r\n const dictationOptionsRef = useRef<DictationOptions | null>(null);\r\n\r\n const [state, setState] = useState<VoiceControlState>({\r\n isListening: false,\r\n isProcessing: false,\r\n lastTranscript: null,\r\n activeCommands: [],\r\n isPaused: false,\r\n error: null,\r\n isDictating: false,\r\n });\r\n\r\n const setPaused = useCallback((paused: boolean) => {\r\n isPausedRef.current = paused;\r\n setState((prev) => ({ ...prev, isPaused: paused }));\r\n }, []);\r\n\r\n const startDictation = useCallback((options: DictationOptions) => {\r\n isDictatingRef.current = true;\r\n dictationOptionsRef.current = options;\r\n setState((prev) => ({ ...prev, isDictating: true }));\r\n }, []);\r\n\r\n const stopDictation = useCallback(() => {\r\n isDictatingRef.current = false;\r\n dictationOptionsRef.current = null;\r\n setState((prev) => ({ ...prev, isDictating: false }));\r\n }, []);\r\n\r\n const processTranscript = useCallback(\r\n async (transcript: string) => {\r\n if (isPausedRef.current) return;\r\n\r\n const cleanText = transcript.trim().toLowerCase();\r\n if (!cleanText) return;\r\n\r\n setState((prev) => ({\r\n ...prev,\r\n isProcessing: true,\r\n lastTranscript: cleanText,\r\n }));\r\n const allCommands = Array.from(commandsRef.current.values());\r\n\r\n // 1. Exact Match (Fastest)\r\n const exactMatch = allCommands.find(\r\n (c) => c.phrase?.toLowerCase() === cleanText\r\n );\r\n if (exactMatch) {\r\n console.log(`⚡ Match: \"${cleanText}\"`);\r\n exactMatch.action();\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n return;\r\n }\r\n\r\n // 2. AI Fuzzy Match (Smartest)\r\n try {\r\n const commandListForAI = allCommands.map(\r\n ({ id, description, phrase }) => ({ id, description, phrase })\r\n );\r\n\r\n const result = await adapter(cleanText, commandListForAI);\r\n\r\n if (result.commandId) {\r\n const cmd = commandsRef.current.get(result.commandId);\r\n if (cmd) {\r\n cmd.action();\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"AI Adapter Failed:\", error);\r\n\r\n // 3. Offline Fallback (Safety Net)\r\n if (enableOfflineFallback) {\r\n console.warn(\"🌐 Attempting Offline Fallback...\");\r\n const fallbackId = findBestMatch(cleanText, allCommands);\r\n\r\n if (fallbackId) {\r\n const cmd = commandsRef.current.get(fallbackId);\r\n if (cmd) {\r\n console.log(`✅ Offline Match: ${cmd.id}`);\r\n cmd.action();\r\n }\r\n } else {\r\n console.log(\"❌ No offline match found.\");\r\n }\r\n }\r\n } finally {\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n }\r\n },\r\n [adapter, enableOfflineFallback]\r\n );\r\n\r\n // ... (keep useSpeechRecognition hook and register/unregister)\r\n const {\r\n isListening: engineListening,\r\n start: engineStart,\r\n stop: engineStop,\r\n } = useSpeechRecognition({\r\n disabled: disableSpeechEngine,\r\n onResult: (transcript, isFinal) => {\r\n if (isDictatingRef.current && dictationOptionsRef.current) {\r\n const options = dictationOptionsRef.current;\r\n if (\r\n isFinal &&\r\n options.exitCommands?.some((cmd) =>\r\n transcript.toLowerCase().includes(cmd)\r\n )\r\n ) {\r\n stopDictation();\r\n processTranscript(transcript);\r\n return;\r\n }\r\n isFinal ? options.onFinal(transcript) : options.onInterim?.(transcript);\r\n return;\r\n }\r\n if (isFinal) processTranscript(transcript);\r\n },\r\n onError: (err) =>\r\n setState((prev) => ({\r\n ...prev,\r\n error: typeof err === \"string\" ? err : \"Speech Error\",\r\n })),\r\n });\r\n\r\n useEffect(() => {\r\n setState((prev) => ({ ...prev, isListening: engineListening }));\r\n }, [engineListening]);\r\n\r\n const register = useCallback((cmd: VoiceCommand) => {\r\n commandsRef.current.set(cmd.id, cmd);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n const unregister = useCallback((id: string) => {\r\n commandsRef.current.delete(id);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n return (\r\n <VoiceContext.Provider\r\n value={{\r\n ...state,\r\n register,\r\n unregister,\r\n processTranscript,\r\n setPaused,\r\n startListening: engineStart,\r\n stopListening: engineStop,\r\n startDictation,\r\n stopDictation,\r\n }}\r\n >\r\n {children}\r\n </VoiceContext.Provider>\r\n );\r\n};\r\n\r\nexport const useVoiceContext = () => {\r\n const ctx = useContext(VoiceContext);\r\n if (!ctx)\r\n throw new Error(\r\n \"useVoiceContext must be used within a VoiceControlProvider\"\r\n );\r\n return ctx;\r\n};\r\n","import { useEffect, useRef } from \"react\";\r\nimport { useVoiceContext } from \"../components/VoiceContext\";\r\nimport { VoiceCommand } from \"../types\";\r\n\r\nexport const useVoiceCommand = (command: VoiceCommand) => {\r\n const { register, unregister } = useVoiceContext();\r\n\r\n // 1. Keep a \"Ref\" to the latest command.\r\n // This allows the action function to change (e.g. updating state)\r\n // WITHOUT forcing us to unregister/re-register the command.\r\n const commandRef = useRef(command);\r\n\r\n // Always update the ref on every render\r\n useEffect(() => {\r\n commandRef.current = command;\r\n });\r\n\r\n useEffect(() => {\r\n // 2. Register a \"Proxy Command\"\r\n // Instead of registering the raw command, we register a proxy\r\n // that always calls the LATEST version stored in the ref.\r\n const proxyCommand: VoiceCommand = {\r\n id: command.id,\r\n description: command.description,\r\n phrase: command.phrase,\r\n action: () => {\r\n // When triggered, execute whatever the current action is\r\n commandRef.current.action();\r\n },\r\n };\r\n\r\n register(proxyCommand);\r\n\r\n return () => {\r\n unregister(proxyCommand.id);\r\n };\r\n // 3. Only re-register if the ID changes.\r\n // We intentionally ignore 'command' or 'command.action' changes here.\r\n }, [register, unregister, command.id]);\r\n};\r\n","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * Generates the System Prompt for the LLM.\r\n * This ensures consistent behavior across different AI providers.\r\n */\r\nexport const createSystemPrompt = (commands: Omit<VoiceCommand, 'action'>[]) => {\r\n // Format the list of commands for the AI to read\r\n const commandList = commands.map(cmd =>\r\n `- ID: \"${cmd.id}\" | Description: \"${cmd.description}\"`\r\n ).join('\\n');\r\n\r\n return `\r\nYou are a precise Voice Command Router.\r\nYour goal is to map the user's spoken input to the correct Command ID from the list below.\r\n\r\nRULES:\r\n1. Analyze the user's input and find the intent.\r\n2. Match it to the command with the most relevant \"Description\".\r\n3. Use fuzzy matching (e.g., \"Dark mode\" matches \"Toggle Theme\").\r\n4. If NO command matches the intent, return null.\r\n5. IMPORTANT: Output ONLY valid JSON. Do not include markdown formatting.\r\n\r\nAVAILABLE COMMANDS:\r\n${commandList}\r\n\r\nRESPONSE FORMAT:\r\n{ \"commandId\": \"string_id_or_null\" }\r\n`;\r\n};\r\n\r\n/**\r\n * Standardizes how the user's voice transcript is presented to the AI.\r\n */\r\nexport const createUserPrompt = (transcript: string) => {\r\n return `User Input: \"${transcript}\"`;\r\n};","import { LLMAdapter } from '../types';\r\nimport { createSystemPrompt } from '../core/prompt';\r\n\r\ninterface OpenAIConfig {\r\n apiKey: string;\r\n /** @default \"gpt-4o-mini\" */\r\n model?: string;\r\n}\r\n\r\n/**\r\n * A Factory that creates an Adapter for OpenAI.\r\n * Users call this: createOpenAIAdapter({ apiKey: '...' })\r\n */\r\nexport const createOpenAIAdapter = (config: OpenAIConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n\r\n // 1. Generate the optimized system instructions\r\n const systemPrompt = createSystemPrompt(commands);\r\n\r\n // 2. Call the API\r\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`\r\n },\r\n body: JSON.stringify({\r\n model: config.model || \"gpt-4o-mini\",\r\n messages: [\r\n { role: \"system\", content: systemPrompt },\r\n { role: \"user\", content: transcript }\r\n ],\r\n temperature: 0, // Deterministic results\r\n response_format: { type: \"json_object\" } // Force JSON mode\r\n })\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`OpenAI Adapter Failed: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse the result\r\n try {\r\n const parsed = JSON.parse(data.choices[0].message.content);\r\n return { commandId: parsed.commandId };\r\n } catch (e) {\r\n console.error(\"Failed to parse LLM response\", e);\r\n return { commandId: null };\r\n }\r\n };\r\n};","import { LLMAdapter } from \"../types\";\r\n\r\ninterface GeminiConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"gemini-1.5-flash\"\r\n */\r\n model?: string;\r\n /**\r\n * System instruction to guide the style of response (Optional)\r\n */\r\n systemInstruction?: string;\r\n}\r\n\r\nexport const createGeminiAdapter = (config: GeminiConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n // 1. Prepare the System Prompt\r\n // We explain the task to Gemini and list the available commands\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const prompt = `\r\n You are a voice command router.\r\n The user said: \"${transcript}\"\r\n\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Return ONLY the JSON object. No markdown, no explanation.\r\n 2. Format: { \"commandId\": \"id_here\" } or { \"commandId\": null } if no match.\r\n `;\r\n\r\n // 2. Call Google Gemini API\r\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${\r\n config.model || \"gemini-1.5-flash\"\r\n }:generateContent?key=${config.apiKey}`;\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({\r\n contents: [\r\n {\r\n parts: [{ text: prompt }],\r\n },\r\n ],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Gemini API Error: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Gemini returns nested objects: candidates[0].content.parts[0].text\r\n const textResponse = data.candidates?.[0]?.content?.parts?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up markdown code blocks if Gemini adds them (e.g. ```json ... ```)\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Gemini:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n","import { LLMAdapter } from \"../types\";\r\n\r\ninterface ClaudeConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"claude-3-5-sonnet-20240620\"\r\n */\r\n model?: string;\r\n /**\r\n * Optional custom endpoint if using a proxy (Recommended for CORS)\r\n * @default \"https://api.anthropic.com/v1/messages\"\r\n */\r\n endpoint?: string;\r\n}\r\n\r\nexport const createClaudeAdapter = (config: ClaudeConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n const endpoint = config.endpoint || \"https://api.anthropic.com/v1/messages\";\r\n const model = config.model || \"claude-3-5-sonnet-20240620\";\r\n\r\n // 1. Prepare Command List\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const systemPrompt = `\r\n You are a voice command router.\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Analyze the user's transcript.\r\n 2. Return a JSON object: { \"commandId\": \"id\" } or { \"commandId\": null }.\r\n 3. No markdown, no conversational text. ONLY JSON.\r\n `;\r\n\r\n try {\r\n // 2. Call Anthropic API\r\n const response = await fetch(endpoint, {\r\n method: \"POST\",\r\n headers: {\r\n \"x-api-key\": config.apiKey,\r\n \"anthropic-version\": \"2023-06-01\",\r\n \"content-type\": \"application/json\",\r\n // 'dangerously-allow-browser': 'true' // Anthropic client SDK uses this, but fetch doesn't need it.\r\n },\r\n body: JSON.stringify({\r\n model: model,\r\n max_tokens: 100,\r\n system: systemPrompt,\r\n messages: [{ role: \"user\", content: transcript }],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const err = await response.text();\r\n throw new Error(`Claude API Error: ${response.status} - ${err}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Claude returns: content: [ { type: 'text', text: '...' } ]\r\n const textResponse = data.content?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up potential markdown formatting\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Claude:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSpeechRecognition.ts","../src/core/localMatcher.ts","../src/components/VoiceContext.tsx","../src/hooks/useVoiceCommand.ts","../src/core/prompt.ts","../src/adapters/openai.ts","../src/adapters/gemini.ts","../src/adapters/claude.ts"],"names":["useState","useRef","useEffect","useCallback","createContext","jsx","useContext"],"mappings":";;;;;;AAqBO,IAAM,uBAAuB,CAAC;AAAA,EACjC,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW;AACf,CAAA,KAAiC;AAE7B,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAA6B,IAAI,CAAA;AAC3D,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,IAAI,CAAA;AAGnD,EAAA,MAAM,cAAA,GAAiBC,aAAiC,IAAI,CAAA;AAC5D,EAAA,MAAM,aAAA,GAAgBA,aAAO,KAAK,CAAA;AAGlC,EAAAC,eAAA,CAAU,MAAM;AACZ,IAAA,IAAI,QAAA,EAAU;AAEd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,IAAA,MAAM,iBAAA,GAAoB,MAAA,CAAO,iBAAA,IAAqB,MAAA,CAAO,uBAAA;AAE7D,IAAA,IAAI,CAAC,iBAAA,EAAmB;AACpB,MAAA,OAAA,CAAQ,MAAM,8EAA8E,CAAA;AAC5F,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,WAAA,GAAc,IAAI,iBAAA,EAAkB;AAC1C,IAAA,WAAA,CAAY,UAAA,GAAa,IAAA;AACzB,IAAA,WAAA,CAAY,cAAA,GAAiB,IAAA;AAC7B,IAAA,WAAA,CAAY,IAAA,GAAO,OAAA;AAInB,IAAA,WAAA,CAAY,QAAA,GAAW,CAAC,KAAA,KAAkC;AAEtD,MAAA,MAAM,SAAS,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAC,CAAA;AACrD,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,CAAC,CAAA,CAAE,UAAA;AAC7B,MAAA,MAAM,UAAU,MAAA,CAAO,OAAA;AAGvB,MAAA,QAAA,CAAS,YAAY,OAAO,CAAA;AAAA,IAChC,CAAA;AAEA,IAAA,WAAA,CAAY,OAAA,GAAU,CAAC,KAAA,KAAuC;AAE1D,MAAA,IAAI,KAAA,CAAM,UAAU,WAAA,EAAa;AAC7B,QAAA;AAAA,MACJ;AAEA,MAAA,OAAA,CAAQ,IAAA,CAAK,2BAAA,EAA6B,KAAA,CAAM,KAAK,CAAA;AACrD,MAAA,QAAA,CAAS,MAAM,KAAK,CAAA;AACpB,MAAA,IAAI,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA;AAGhC,MAAA,IAAI,KAAA,CAAM,UAAU,aAAA,EAAe;AAC/B,QAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AACxB,QAAA,cAAA,CAAe,KAAK,CAAA;AAAA,MACxB;AAAA,IACJ,CAAA;AAEA,IAAA,WAAA,CAAY,QAAQ,MAAM;AAEtB,MAAA,IAAI,cAAc,OAAA,EAAS;AAEvB,QAAA,IAAI;AACA,UAAA,WAAA,CAAY,KAAA,EAAM;AAAA,QACtB,SAAS,CAAA,EAAG;AAER,UAAA,UAAA,CAAW,MAAM;AACb,YAAA,IAAI,aAAA,CAAc,OAAA,EAAS,WAAA,CAAY,KAAA,EAAM;AAAA,UACjD,GAAG,GAAG,CAAA;AAAA,QACV;AAAA,MACJ,CAAA,MAAO;AACH,QAAA,cAAA,CAAe,KAAK,CAAA;AAAA,MACxB;AAAA,IACJ,CAAA;AAEA,IAAA,cAAA,CAAe,OAAA,GAAU,WAAA;AAGzB,IAAA,OAAO,MAAM;AACT,MAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AACxB,MAAA,WAAA,CAAY,IAAA,EAAK;AAAA,IACrB,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,QAAA,EAAU,QAAA,EAAU,OAAO,CAAC,CAAA;AAGhC,EAAA,MAAM,KAAA,GAAQC,kBAAY,MAAM;AAC5B,IAAA,IAAI,CAAC,cAAA,CAAe,OAAA,IAAW,CAAC,WAAA,EAAa;AAE7C,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,IAAI;AACA,MAAA,cAAA,CAAe,QAAQ,KAAA,EAAM;AAC7B,MAAA,cAAA,CAAe,IAAI,CAAA;AAAA,IACvB,SAAS,CAAA,EAAG;AAAA,IAEZ;AAAA,EACJ,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAM;AAC3B,IAAA,IAAI,CAAC,eAAe,OAAA,EAAS;AAE7B,IAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AACxB,IAAA,cAAA,CAAe,QAAQ,IAAA,EAAK;AAC5B,IAAA,cAAA,CAAe,KAAK,CAAA;AAAA,EACxB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAASA,kBAAY,MAAM;AAC7B,IAAA,IAAI,aAAa,IAAA,EAAK;AAAA,SACjB,KAAA,EAAM;AAAA,EACf,CAAA,EAAG,CAAC,WAAA,EAAa,KAAA,EAAO,IAAI,CAAC,CAAA;AAE7B,EAAA,OAAO;AAAA,IACH,WAAA;AAAA,IACA,WAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACJ;AACJ,CAAA;;;AC1IO,IAAM,aAAA,GAAgB,CAAC,UAAA,EAAoB,QAAA,KAA4C;AAC1F,EAAA,MAAM,eAAA,GAAkB,UAAA,CAAW,WAAA,EAAY,CAAE,IAAA,EAAK;AACtD,EAAA,MAAM,eAAA,GAAkB,gBAAgB,KAAA,CAAM,KAAK,EAAE,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA;AAE7E,EAAA,IAAI,WAAA,GAA6B,IAAA;AACjC,EAAA,IAAI,QAAA,GAAW,CAAA;AAEf,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AACxB,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,WAAA,CAAY,WAAA,EAAY;AACzC,IAAA,MAAM,EAAA,GAAK,GAAA,CAAI,EAAA,CAAG,WAAA,EAAY;AAG9B,IAAA,IAAI,eAAA,CAAgB,QAAA,CAAS,EAAE,CAAA,EAAG;AAC9B,MAAA,KAAA,IAAS,CAAA;AAAA,IACb;AAIA,IAAA,KAAA,MAAW,QAAQ,eAAA,EAAiB;AAChC,MAAA,IAAI,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG;AACrB,QAAA,KAAA,IAAS,CAAA;AAAA,MACb;AAAA,IACJ;AAGA,IAAA,IAAI,KAAA,GAAQ,QAAA,IAAY,KAAA,IAAS,CAAA,EAAG;AAChC,MAAA,QAAA,GAAW,KAAA;AACX,MAAA,WAAA,GAAc,GAAA,CAAI,EAAA;AAAA,IACtB;AAAA,EACJ;AAEA,EAAA,OAAO,WAAA;AACX,CAAA;ACZA,IAAM,YAAA,GAAeC,oBAAwC,IAAI,CAAA;AAE1D,IAAM,uBAAqD,CAAC;AAAA,EACjE,QAAA;AAAA,EACA,OAAA;AAAA,EACA,mBAAA,GAAsB,KAAA;AAAA,EACtB,qBAAA,GAAwB;AAAA;AAC1B,CAAA,KAAM;AACJ,EAAA,MAAM,WAAA,GAAcH,YAAAA,iBAAkC,IAAI,GAAA,EAAK,CAAA;AAC/D,EAAA,MAAM,WAAA,GAAcA,aAAO,KAAK,CAAA;AAChC,EAAA,MAAM,cAAA,GAAiBA,aAAO,KAAK,CAAA;AACnC,EAAA,MAAM,mBAAA,GAAsBA,aAAgC,IAAI,CAAA;AAEhE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAID,cAAAA,CAA4B;AAAA,IACpD,WAAA,EAAa,KAAA;AAAA,IACb,YAAA,EAAc,KAAA;AAAA,IACd,cAAA,EAAgB,IAAA;AAAA,IAChB,gBAAgB,EAAC;AAAA,IACjB,QAAA,EAAU,KAAA;AAAA,IACV,KAAA,EAAO,IAAA;AAAA,IACP,WAAA,EAAa;AAAA,GACd,CAAA;AAED,EAAA,MAAM,SAAA,GAAYG,iBAAAA,CAAY,CAAC,MAAA,KAAoB;AACjD,IAAA,WAAA,CAAY,OAAA,GAAU,MAAA;AACtB,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,QAAO,CAAE,CAAA;AAAA,EACpD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,cAAA,GAAiBA,iBAAAA,CAAY,CAAC,OAAA,KAA8B;AAChE,IAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,IAAA,mBAAA,CAAoB,OAAA,GAAU,OAAA;AAC9B,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,MAAK,CAAE,CAAA;AAAA,EACrD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,kBAAY,MAAM;AACtC,IAAA,cAAA,CAAe,OAAA,GAAU,KAAA;AACzB,IAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAC9B,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,OAAM,CAAE,CAAA;AAAA,EACtD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,iBAAA,GAAoBA,iBAAAA;AAAA,IACxB,OAAO,UAAA,KAAuB;AAC5B,MAAA,IAAI,YAAY,OAAA,EAAS;AAEzB,MAAA,MAAM,SAAA,GAAY,UAAA,CAAW,IAAA,EAAK,CAAE,WAAA,EAAY;AAChD,MAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,MAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,QAClB,GAAG,IAAA;AAAA,QACH,YAAA,EAAc,IAAA;AAAA,QACd,cAAA,EAAgB;AAAA,OAClB,CAAE,CAAA;AACF,MAAA,MAAM,cAAc,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,QAAQ,CAAA;AAG3D,MAAA,MAAM,aAAa,WAAA,CAAY,IAAA;AAAA,QAC7B,CAAC,CAAA,KAAG;AAnFZ,UAAA,IAAA,EAAA;AAmFe,UAAA,OAAA,CAAA,CAAA,EAAA,GAAA,CAAA,CAAE,MAAA,KAAF,mBAAU,WAAA,EAAA,MAAkB,SAAA;AAAA,QAAA;AAAA,OACrC;AACA,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,eAAA,EAAa,SAAS,CAAA,CAAA,CAAG,CAAA;AACrC,QAAA,UAAA,CAAW,MAAA,EAAO;AAClB,QAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,YAAA,EAAc,OAAM,CAAE,CAAA;AACrD,QAAA;AAAA,MACF;AAGA,MAAA,IAAI;AACF,QAAA,MAAM,mBAAmB,WAAA,CAAY,GAAA;AAAA,UACnC,CAAC,EAAE,EAAA,EAAI,WAAA,EAAa,QAAO,MAAO,EAAE,EAAA,EAAI,WAAA,EAAa,MAAA,EAAO;AAAA,SAC9D;AAEA,QAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,SAAA,EAAW,gBAAgB,CAAA;AAExD,QAAA,IAAI,OAAO,SAAA,EAAW;AACpB,UAAA,MAAM,GAAA,GAAM,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,OAAO,SAAS,CAAA;AACpD,UAAA,IAAI,GAAA,EAAK;AACP,YAAA,GAAA,CAAI,MAAA,EAAO;AAAA,UACb;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,KAAA,CAAM,sBAAsB,KAAK,CAAA;AAGzC,QAAA,IAAI,qBAAA,EAAuB;AACzB,UAAA,OAAA,CAAQ,KAAK,0CAAmC,CAAA;AAChD,UAAA,MAAM,UAAA,GAAa,aAAA,CAAc,SAAA,EAAW,WAAW,CAAA;AAEvD,UAAA,IAAI,UAAA,EAAY;AACd,YAAA,MAAM,GAAA,GAAM,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA;AAC9C,YAAA,IAAI,GAAA,EAAK;AACP,cAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,sBAAA,EAAoB,GAAA,CAAI,EAAE,CAAA,CAAE,CAAA;AACxC,cAAA,GAAA,CAAI,MAAA,EAAO;AAAA,YACb;AAAA,UACF,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,IAAI,gCAA2B,CAAA;AAAA,UACzC;AAAA,QACF;AAAA,MACF,CAAA,SAAE;AACA,QAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,YAAA,EAAc,OAAM,CAAE,CAAA;AAAA,MACvD;AAAA,IACF,CAAA;AAAA,IACA,CAAC,SAAS,qBAAqB;AAAA,GACjC;AAGA,EAAA,MAAM,YAAA,GAAeA,iBAAAA;AAAA,IACnB,CAAC,YAAoB,OAAA,KAAqB;AArI9C,MAAA,IAAA,EAAA,EAAA,EAAA;AAsIM,MAAA,IAAI,cAAA,CAAe,OAAA,IAAW,mBAAA,CAAoB,OAAA,EAAS;AACzD,QAAA,MAAM,UAAU,mBAAA,CAAoB,OAAA;AAGpC,QAAA,IACE,OAAA,KAAA,CACA,EAAA,GAAA,OAAA,CAAQ,YAAA,KAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAsB,IAAA;AAAA,UAAK,CAAC,GAAA,KAC1B,UAAA,CAAW,WAAA,EAAY,CAAE,SAAS,GAAG;AAAA,SAAA,CAAA,EAEvC;AACA,UAAA,aAAA,EAAc;AACd,UAAA,iBAAA,CAAkB,UAAU,CAAA;AAC5B,UAAA;AAAA,QACF;AAGA,QAAA,IAAI,OAAA,EAAS;AACX,UAAA,OAAA,CAAQ,QAAQ,UAAU,CAAA;AAAA,QAC5B,CAAA,MAAO;AACL,UAAA,CAAA,EAAA,GAAA,OAAA,CAAQ,cAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,OAAA,EAAoB,UAAA,CAAA;AAAA,QACtB;AACA,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,iBAAA,CAAkB,UAAU,CAAA;AAAA,MAC9B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,mBAAmB,aAAa;AAAA,GACnC;AAEA,EAAA,MAAM,WAAA,GAAcA,iBAAAA,CAAY,CAAC,GAAA,KAAgB;AAC/C,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,MAClB,GAAG,IAAA;AAAA,MACH,KAAA,EAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM;AAAA,KACzC,CAAE,CAAA;AAAA,EACJ,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM;AAAA,IACJ,WAAA,EAAa,eAAA;AAAA,IACb,KAAA,EAAO,WAAA;AAAA,IACP,IAAA,EAAM;AAAA,MACJ,oBAAA,CAAqB;AAAA,IACvB,QAAA,EAAU,mBAAA;AAAA,IACV,QAAA,EAAU,YAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACV,CAAA;AAED,EAAAD,gBAAU,MAAM;AACd,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,iBAAgB,CAAE,CAAA;AAAA,EAChE,CAAA,EAAG,CAAC,eAAe,CAAC,CAAA;AAEpB,EAAA,MAAM,QAAA,GAAWC,iBAAAA,CAAY,CAAC,GAAA,KAAsB;AAClD,IAAA,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,GAAG,CAAA;AACnC,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,MAClB,GAAG,IAAA;AAAA,MACH,gBAAgB,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,QAAQ;AAAA,KACzD,CAAE,CAAA;AAAA,EACJ,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,iBAAAA,CAAY,CAAC,EAAA,KAAe;AAC7C,IAAA,WAAA,CAAY,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC7B,IAAA,QAAA,CAAS,CAAC,IAAA,MAAU;AAAA,MAClB,GAAG,IAAA;AAAA,MACH,gBAAgB,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,QAAQ;AAAA,KACzD,CAAE,CAAA;AAAA,EACJ,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,uBACEE,cAAA;AAAA,IAAC,YAAA,CAAa,QAAA;AAAA,IAAb;AAAA,MACC,KAAA,EAAO;AAAA,QACL,GAAG,KAAA;AAAA,QACH,QAAA;AAAA,QACA,UAAA;AAAA,QACA,iBAAA;AAAA,QACA,SAAA;AAAA,QACA,cAAA,EAAgB,WAAA;AAAA,QAChB,aAAA,EAAe,UAAA;AAAA,QACf,cAAA;AAAA,QACA;AAAA,OACF;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ;AAEO,IAAM,kBAAkB,MAAM;AACnC,EAAA,MAAM,GAAA,GAAMC,iBAAW,YAAY,CAAA;AACnC,EAAA,IAAI,CAAC,GAAA;AACH,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AACF,EAAA,OAAO,GAAA;AACT;ACjOO,IAAM,eAAA,GAAkB,CAAC,OAAA,KAA0B;AACxD,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,eAAA,EAAgB;AAKjD,EAAA,MAAM,UAAA,GAAaL,aAAO,OAAO,CAAA;AAGjC,EAAAC,gBAAU,MAAM;AACd,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAAA,EACvB,CAAC,CAAA;AAED,EAAAA,gBAAU,MAAM;AAId,IAAA,MAAM,YAAA,GAA6B;AAAA,MACjC,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,QAAQ,MAAM;AAEZ,QAAA,UAAA,CAAW,QAAQ,MAAA,EAAO;AAAA,MAC5B;AAAA,KACF;AAEA,IAAA,QAAA,CAAS,YAAY,CAAA;AAErB,IAAA,OAAO,MAAM;AACX,MAAA,UAAA,CAAW,aAAa,EAAE,CAAA;AAAA,IAC5B,CAAA;AAAA,EAGF,GAAG,CAAC,QAAA,EAAU,UAAA,EAAY,OAAA,CAAQ,EAAE,CAAC,CAAA;AACvC;;;ACjCO,IAAM,kBAAA,GAAqB,CAAC,QAAA,KAA6C;AAE5E,EAAA,MAAM,cAAc,QAAA,CAAS,GAAA;AAAA,IAAI,SAC7B,CAAA,OAAA,EAAU,GAAA,CAAI,EAAE,CAAA,kBAAA,EAAqB,IAAI,WAAW,CAAA,CAAA;AAAA,GACxD,CAAE,KAAK,IAAI,CAAA;AAEX,EAAA,OAAO;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,EAYT,WAAW;;AAAA;AAAA;AAAA,CAAA;AAKb,CAAA;;;AChBO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAAqC;AACrE,EAAA,OAAO,OAAO,YAAY,QAAA,KAAa;AAGnC,IAAA,MAAM,YAAA,GAAe,mBAAmB,QAAQ,CAAA;AAGhD,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,4CAAA,EAA8C;AAAA,MACvE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACL,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA;AAAA,OAC5C;AAAA,MACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACjB,KAAA,EAAO,OAAO,KAAA,IAAS,aAAA;AAAA,QACvB,QAAA,EAAU;AAAA,UACN,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,YAAA,EAAa;AAAA,UACxC,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,UAAA;AAAW,SACxC;AAAA,QACA,WAAA,EAAa,CAAA;AAAA;AAAA,QACb,eAAA,EAAiB,EAAE,IAAA,EAAM,aAAA;AAAc;AAAA,OAC1C;AAAA,KACJ,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,uBAAA,EAA0B,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,IACnE;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAGjC,IAAA,IAAI;AACA,MAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAC,CAAA,CAAE,QAAQ,OAAO,CAAA;AACzD,MAAA,OAAO,EAAE,SAAA,EAAW,MAAA,CAAO,SAAA,EAAU;AAAA,IACzC,SAAS,CAAA,EAAG;AACR,MAAA,OAAA,CAAQ,KAAA,CAAM,gCAAgC,CAAC,CAAA;AAC/C,MAAA,OAAO,EAAE,WAAW,IAAA,EAAK;AAAA,IAC7B;AAAA,EACJ,CAAA;AACJ;;;ACtCO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAAqC;AACvE,EAAA,OAAO,OAAO,YAAY,QAAA,KAAa;AAfzC,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAkBI,IAAA,MAAM,WAAA,GAAc,QAAA,CACjB,GAAA,CAAI,CAAC,MAAM,CAAA,GAAA,EAAM,CAAA,CAAE,EAAE,CAAA,GAAA,EAAM,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC1C,KAAK,IAAI,CAAA;AAEZ,IAAA,MAAM,MAAA,GAAS;AAAA;AAAA,sBAAA,EAEK,UAAU,CAAA;;AAAA;AAAA,MAAA,EAG1B,WAAW;;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAQf,IAAA,MAAM,MAAM,CAAA,wDAAA,EACV,MAAA,CAAO,SAAS,kBAClB,CAAA,qBAAA,EAAwB,OAAO,MAAM,CAAA,CAAA;AAErC,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA,SAClB;AAAA,QACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,QAAA,EAAU;AAAA,YACR;AAAA,cACE,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,QAAQ;AAAA;AAC1B;AACF,SACD;AAAA,OACF,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,MAC5D;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAIjC,MAAA,MAAM,YAAA,GAAA,CAAe,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,CAAK,UAAA,KAAL,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAkB,CAAA,CAAA,KAAlB,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAsB,OAAA,KAAtB,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAA+B,KAAA,KAA/B,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAuC,CAAA,CAAA,KAAvC,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAA2C,IAAA;AAEhE,MAAA,IAAI,CAAC,YAAA,EAAc,OAAO,EAAE,WAAW,IAAA,EAAK;AAG5C,MAAA,MAAM,YAAY,YAAA,CAAa,OAAA,CAAQ,cAAA,EAAgB,EAAE,EAAE,IAAA,EAAK;AAEhE,MAAA,OAAO,IAAA,CAAK,MAAM,SAAS,CAAA;AAAA,IAC7B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AACjD,MAAA,OAAO,EAAE,WAAW,IAAA,EAAK;AAAA,IAC3B;AAAA,EACF,CAAA;AACF;;;AC5DO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAAqC;AACvE,EAAA,OAAO,OAAO,YAAY,QAAA,KAAa;AAhBzC,IAAA,IAAA,EAAA,EAAA,EAAA;AAiBI,IAAA,MAAM,QAAA,GAAW,OAAO,QAAA,IAAY,uCAAA;AACpC,IAAA,MAAM,KAAA,GAAQ,OAAO,KAAA,IAAS,4BAAA;AAG9B,IAAA,MAAM,WAAA,GAAc,QAAA,CACjB,GAAA,CAAI,CAAC,MAAM,CAAA,GAAA,EAAM,CAAA,CAAE,EAAE,CAAA,GAAA,EAAM,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC1C,KAAK,IAAI,CAAA;AAEZ,IAAA,MAAM,YAAA,GAAe;AAAA;AAAA;AAAA,MAAA,EAGjB,WAAW;;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAQf,IAAA,IAAI;AAEF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,QAAA,EAAU;AAAA,QACrC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,aAAa,MAAA,CAAO,MAAA;AAAA,UACpB,mBAAA,EAAqB,YAAA;AAAA,UACrB,cAAA,EAAgB;AAAA;AAAA,SAElB;AAAA,QACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,KAAA;AAAA,UACA,UAAA,EAAY,GAAA;AAAA,UACZ,MAAA,EAAQ,YAAA;AAAA,UACR,UAAU,CAAC,EAAE,MAAM,MAAA,EAAQ,OAAA,EAAS,YAAY;AAAA,SACjD;AAAA,OACF,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,GAAA,GAAM,MAAM,QAAA,CAAS,IAAA,EAAK;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,SAAS,MAAM,CAAA,GAAA,EAAM,GAAG,CAAA,CAAE,CAAA;AAAA,MACjE;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAIjC,MAAA,MAAM,YAAA,GAAA,CAAe,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,CAAK,OAAA,KAAL,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAe,OAAf,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAmB,IAAA;AAExC,MAAA,IAAI,CAAC,YAAA,EAAc,OAAO,EAAE,WAAW,IAAA,EAAK;AAG5C,MAAA,MAAM,YAAY,YAAA,CAAa,OAAA,CAAQ,cAAA,EAAgB,EAAE,EAAE,IAAA,EAAK;AAChE,MAAA,OAAO,IAAA,CAAK,MAAM,SAAS,CAAA;AAAA,IAC7B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AACjD,MAAA,OAAO,EAAE,WAAW,IAAA,EAAK;AAAA,IAC3B;AAAA,EACF,CAAA;AACF","file":"index.js","sourcesContent":["// src/hooks/useSpeechRecognition.ts\r\nimport { useState, useEffect, useRef, useCallback } from 'react';\r\n\r\n// Define the supported error types\r\nexport type SpeechError =\r\n | 'not-allowed'\r\n | 'no-speech'\r\n | 'network'\r\n | 'aborted'\r\n | 'service-not-allowed'\r\n | string;\r\n\r\ninterface UseSpeechRecognitionProps {\r\n onResult: (transcript: string, isFinal: boolean) => void;\r\n onError?: (error: SpeechError) => void;\r\n /**\r\n * If true, the engine will not initialize (backward compatibility)\r\n */\r\n disabled?: boolean;\r\n}\r\n\r\nexport const useSpeechRecognition = ({\r\n onResult,\r\n onError,\r\n disabled = false\r\n}: UseSpeechRecognitionProps) => {\r\n // UI State\r\n const [isListening, setIsListening] = useState(false);\r\n const [error, setError] = useState<SpeechError | null>(null);\r\n const [isSupported, setIsSupported] = useState(true);\r\n\r\n // Internal Refs\r\n const recognitionRef = useRef<SpeechRecognition | null>(null);\r\n const intendedState = useRef(false); // Tracks if we *want* to be listening\r\n\r\n // 1. Initialize the API\r\n useEffect(() => {\r\n if (disabled) return;\r\n\r\n if (typeof window === 'undefined') return; // SSR check\r\n\r\n const SpeechConstructor = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n\r\n if (!SpeechConstructor) {\r\n console.error(\"React Voice Action Router: Speech Recognition not supported in this browser.\");\r\n setIsSupported(false);\r\n return;\r\n }\r\n\r\n const recognition = new SpeechConstructor();\r\n recognition.continuous = true; // We want continuous stream\r\n recognition.interimResults = true; // We need real-time feedback\r\n recognition.lang = 'en-US'; // Default to English (configurable later)\r\n\r\n // --- EVENT HANDLERS ---\r\n\r\n recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n // Get the latest result\r\n const result = event.results[event.results.length - 1];\r\n const transcript = result[0].transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Pass to parent\r\n onResult(transcript, isFinal);\r\n };\r\n\r\n recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n // 'no-speech' is common and usually ignorable (just silence)\r\n if (event.error === 'no-speech') {\r\n return;\r\n }\r\n\r\n console.warn(\"Speech Recognition Error:\", event.error);\r\n setError(event.error);\r\n if (onError) onError(event.error);\r\n\r\n // If permission denied, stop trying\r\n if (event.error === 'not-allowed') {\r\n intendedState.current = false;\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognition.onend = () => {\r\n // The Critical \"Auto-Restart\" Logic\r\n if (intendedState.current) {\r\n // If we still want to be listening, restart immediately\r\n try {\r\n recognition.start();\r\n } catch (e) {\r\n // Sometimes it needs a tiny delay if the browser is busy\r\n setTimeout(() => {\r\n if (intendedState.current) recognition.start();\r\n }, 100);\r\n }\r\n } else {\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognitionRef.current = recognition;\r\n\r\n // Cleanup\r\n return () => {\r\n intendedState.current = false;\r\n recognition.stop();\r\n };\r\n }, [disabled, onResult, onError]);\r\n\r\n // 2. Control Methods\r\n const start = useCallback(() => {\r\n if (!recognitionRef.current || !isSupported) return;\r\n\r\n setError(null);\r\n intendedState.current = true;\r\n try {\r\n recognitionRef.current.start();\r\n setIsListening(true);\r\n } catch (e) {\r\n // Already started, ignore\r\n }\r\n }, [isSupported]);\r\n\r\n const stop = useCallback(() => {\r\n if (!recognitionRef.current) return;\r\n\r\n intendedState.current = false;\r\n recognitionRef.current.stop();\r\n setIsListening(false);\r\n }, []);\r\n\r\n const toggle = useCallback(() => {\r\n if (isListening) stop();\r\n else start();\r\n }, [isListening, start, stop]);\r\n\r\n return {\r\n isListening,\r\n isSupported,\r\n error,\r\n start,\r\n stop,\r\n toggle\r\n };\r\n};","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * A simple fallback matcher that scores commands based on keyword overlap.\r\n * Used when the AI adapter fails.\r\n */\r\nexport const findBestMatch = (transcript: string, commands: VoiceCommand[]): string | null => {\r\n const lowerTranscript = transcript.toLowerCase().trim();\r\n const transcriptWords = lowerTranscript.split(/\\s+/).filter(w => w.length > 2); // Ignore short words\r\n\r\n let bestMatchId: string | null = null;\r\n let maxScore = 0;\r\n\r\n for (const cmd of commands) {\r\n let score = 0;\r\n const desc = cmd.description.toLowerCase();\r\n const id = cmd.id.toLowerCase();\r\n\r\n // 1. Direct ID Match (Strong signal)\r\n if (lowerTranscript.includes(id)) {\r\n score += 3;\r\n }\r\n\r\n // 2. Description Keyword Match\r\n // If the user says \"Navigate settings\", and description is \"Navigate to settings page\"\r\n for (const word of transcriptWords) {\r\n if (desc.includes(word)) {\r\n score += 1;\r\n }\r\n }\r\n\r\n // Update best match if score is significant\r\n if (score > maxScore && score >= 2) { // Threshold of 2 ensures at least some relevance\r\n maxScore = score;\r\n bestMatchId = cmd.id;\r\n }\r\n }\r\n\r\n return bestMatchId;\r\n};","import React, {\r\n createContext,\r\n useContext,\r\n useState,\r\n useCallback,\r\n useRef,\r\n useEffect,\r\n} from \"react\";\r\nimport {\r\n VoiceCommand,\r\n VoiceControlState,\r\n VoiceProviderProps,\r\n DictationOptions,\r\n} from \"../types\";\r\nimport { useSpeechRecognition } from \"../hooks/useSpeechRecognition\";\r\nimport { findBestMatch } from \"../core/localMatcher\";\r\ninterface VoiceContextValue extends VoiceControlState {\r\n register: (cmd: VoiceCommand) => void;\r\n unregister: (id: string) => void;\r\n processTranscript: (text: string) => Promise<void>;\r\n setPaused: (paused: boolean) => void;\r\n startListening: () => void;\r\n stopListening: () => void;\r\n startDictation: (options: DictationOptions) => void;\r\n stopDictation: () => void;\r\n}\r\n\r\nconst VoiceContext = createContext<VoiceContextValue | null>(null);\r\n\r\nexport const VoiceControlProvider: React.FC<VoiceProviderProps> = ({\r\n children,\r\n adapter,\r\n disableSpeechEngine = false,\r\n enableOfflineFallback = true, // <--- Default to true\r\n}) => {\r\n const commandsRef = useRef<Map<string, VoiceCommand>>(new Map());\r\n const isPausedRef = useRef(false);\r\n const isDictatingRef = useRef(false);\r\n const dictationOptionsRef = useRef<DictationOptions | null>(null);\r\n\r\n const [state, setState] = useState<VoiceControlState>({\r\n isListening: false,\r\n isProcessing: false,\r\n lastTranscript: null,\r\n activeCommands: [],\r\n isPaused: false,\r\n error: null,\r\n isDictating: false,\r\n });\r\n\r\n const setPaused = useCallback((paused: boolean) => {\r\n isPausedRef.current = paused;\r\n setState((prev) => ({ ...prev, isPaused: paused }));\r\n }, []);\r\n\r\n const startDictation = useCallback((options: DictationOptions) => {\r\n isDictatingRef.current = true;\r\n dictationOptionsRef.current = options;\r\n setState((prev) => ({ ...prev, isDictating: true }));\r\n }, []);\r\n\r\n const stopDictation = useCallback(() => {\r\n isDictatingRef.current = false;\r\n dictationOptionsRef.current = null;\r\n setState((prev) => ({ ...prev, isDictating: false }));\r\n }, []);\r\n\r\n const processTranscript = useCallback(\r\n async (transcript: string) => {\r\n if (isPausedRef.current) return;\r\n\r\n const cleanText = transcript.trim().toLowerCase();\r\n if (!cleanText) return;\r\n\r\n setState((prev) => ({\r\n ...prev,\r\n isProcessing: true,\r\n lastTranscript: cleanText,\r\n }));\r\n const allCommands = Array.from(commandsRef.current.values());\r\n\r\n // 1. Exact Match (Fastest)\r\n const exactMatch = allCommands.find(\r\n (c) => c.phrase?.toLowerCase() === cleanText\r\n );\r\n if (exactMatch) {\r\n console.log(`⚡ Match: \"${cleanText}\"`);\r\n exactMatch.action();\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n return;\r\n }\r\n\r\n // 2. AI Fuzzy Match (Smartest)\r\n try {\r\n const commandListForAI = allCommands.map(\r\n ({ id, description, phrase }) => ({ id, description, phrase })\r\n );\r\n\r\n const result = await adapter(cleanText, commandListForAI);\r\n\r\n if (result.commandId) {\r\n const cmd = commandsRef.current.get(result.commandId);\r\n if (cmd) {\r\n cmd.action();\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"AI Adapter Failed:\", error);\r\n\r\n // 3. Offline Fallback (Safety Net)\r\n if (enableOfflineFallback) {\r\n console.warn(\"🌐 Attempting Offline Fallback...\");\r\n const fallbackId = findBestMatch(cleanText, allCommands);\r\n\r\n if (fallbackId) {\r\n const cmd = commandsRef.current.get(fallbackId);\r\n if (cmd) {\r\n console.log(`✅ Offline Match: ${cmd.id}`);\r\n cmd.action();\r\n }\r\n } else {\r\n console.log(\"❌ No offline match found.\");\r\n }\r\n }\r\n } finally {\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n }\r\n },\r\n [adapter, enableOfflineFallback]\r\n );\r\n\r\n // 3. INTERNAL SPEECH ENGINE\r\n const handleResult = useCallback(\r\n (transcript: string, isFinal: boolean) => {\r\n if (isDictatingRef.current && dictationOptionsRef.current) {\r\n const options = dictationOptionsRef.current;\r\n\r\n // Check for exit commands\r\n if (\r\n isFinal &&\r\n options.exitCommands?.some((cmd) =>\r\n transcript.toLowerCase().includes(cmd)\r\n )\r\n ) {\r\n stopDictation();\r\n processTranscript(transcript);\r\n return;\r\n }\r\n\r\n // Handle dictation input\r\n if (isFinal) {\r\n options.onFinal(transcript);\r\n } else {\r\n options.onInterim?.(transcript);\r\n }\r\n return;\r\n }\r\n\r\n // Normal Command Mode\r\n if (isFinal) {\r\n processTranscript(transcript);\r\n }\r\n },\r\n [processTranscript, stopDictation]\r\n );\r\n\r\n const handleError = useCallback((err: string) => {\r\n setState((prev) => ({\r\n ...prev,\r\n error: typeof err === \"string\" ? err : \"Speech Error\",\r\n }));\r\n }, []);\r\n\r\n const {\r\n isListening: engineListening,\r\n start: engineStart,\r\n stop: engineStop,\r\n } = useSpeechRecognition({\r\n disabled: disableSpeechEngine,\r\n onResult: handleResult,\r\n onError: handleError,\r\n });\r\n\r\n useEffect(() => {\r\n setState((prev) => ({ ...prev, isListening: engineListening }));\r\n }, [engineListening]);\r\n\r\n const register = useCallback((cmd: VoiceCommand) => {\r\n commandsRef.current.set(cmd.id, cmd);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n const unregister = useCallback((id: string) => {\r\n commandsRef.current.delete(id);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n return (\r\n <VoiceContext.Provider\r\n value={{\r\n ...state,\r\n register,\r\n unregister,\r\n processTranscript,\r\n setPaused,\r\n startListening: engineStart,\r\n stopListening: engineStop,\r\n startDictation,\r\n stopDictation,\r\n }}\r\n >\r\n {children}\r\n </VoiceContext.Provider>\r\n );\r\n};\r\n\r\nexport const useVoiceContext = () => {\r\n const ctx = useContext(VoiceContext);\r\n if (!ctx)\r\n throw new Error(\r\n \"useVoiceContext must be used within a VoiceControlProvider\"\r\n );\r\n return ctx;\r\n};\r\n","import { useEffect, useRef } from \"react\";\r\nimport { useVoiceContext } from \"../components/VoiceContext\";\r\nimport { VoiceCommand } from \"../types\";\r\n\r\nexport const useVoiceCommand = (command: VoiceCommand) => {\r\n const { register, unregister } = useVoiceContext();\r\n\r\n // 1. Keep a \"Ref\" to the latest command.\r\n // This allows the action function to change (e.g. updating state)\r\n // WITHOUT forcing us to unregister/re-register the command.\r\n const commandRef = useRef(command);\r\n\r\n // Always update the ref on every render\r\n useEffect(() => {\r\n commandRef.current = command;\r\n });\r\n\r\n useEffect(() => {\r\n // 2. Register a \"Proxy Command\"\r\n // Instead of registering the raw command, we register a proxy\r\n // that always calls the LATEST version stored in the ref.\r\n const proxyCommand: VoiceCommand = {\r\n id: command.id,\r\n description: command.description,\r\n phrase: command.phrase,\r\n action: () => {\r\n // When triggered, execute whatever the current action is\r\n commandRef.current.action();\r\n },\r\n };\r\n\r\n register(proxyCommand);\r\n\r\n return () => {\r\n unregister(proxyCommand.id);\r\n };\r\n // 3. Only re-register if the ID changes.\r\n // We intentionally ignore 'command' or 'command.action' changes here.\r\n }, [register, unregister, command.id]);\r\n};\r\n","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * Generates the System Prompt for the LLM.\r\n * This ensures consistent behavior across different AI providers.\r\n */\r\nexport const createSystemPrompt = (commands: Omit<VoiceCommand, 'action'>[]) => {\r\n // Format the list of commands for the AI to read\r\n const commandList = commands.map(cmd =>\r\n `- ID: \"${cmd.id}\" | Description: \"${cmd.description}\"`\r\n ).join('\\n');\r\n\r\n return `\r\nYou are a precise Voice Command Router.\r\nYour goal is to map the user's spoken input to the correct Command ID from the list below.\r\n\r\nRULES:\r\n1. Analyze the user's input and find the intent.\r\n2. Match it to the command with the most relevant \"Description\".\r\n3. Use fuzzy matching (e.g., \"Dark mode\" matches \"Toggle Theme\").\r\n4. If NO command matches the intent, return null.\r\n5. IMPORTANT: Output ONLY valid JSON. Do not include markdown formatting.\r\n\r\nAVAILABLE COMMANDS:\r\n${commandList}\r\n\r\nRESPONSE FORMAT:\r\n{ \"commandId\": \"string_id_or_null\" }\r\n`;\r\n};\r\n\r\n/**\r\n * Standardizes how the user's voice transcript is presented to the AI.\r\n */\r\nexport const createUserPrompt = (transcript: string) => {\r\n return `User Input: \"${transcript}\"`;\r\n};","import { LLMAdapter } from '../types';\r\nimport { createSystemPrompt } from '../core/prompt';\r\n\r\ninterface OpenAIConfig {\r\n apiKey: string;\r\n /** @default \"gpt-4o-mini\" */\r\n model?: string;\r\n}\r\n\r\n/**\r\n * A Factory that creates an Adapter for OpenAI.\r\n * Users call this: createOpenAIAdapter({ apiKey: '...' })\r\n */\r\nexport const createOpenAIAdapter = (config: OpenAIConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n\r\n // 1. Generate the optimized system instructions\r\n const systemPrompt = createSystemPrompt(commands);\r\n\r\n // 2. Call the API\r\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`\r\n },\r\n body: JSON.stringify({\r\n model: config.model || \"gpt-4o-mini\",\r\n messages: [\r\n { role: \"system\", content: systemPrompt },\r\n { role: \"user\", content: transcript }\r\n ],\r\n temperature: 0, // Deterministic results\r\n response_format: { type: \"json_object\" } // Force JSON mode\r\n })\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`OpenAI Adapter Failed: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse the result\r\n try {\r\n const parsed = JSON.parse(data.choices[0].message.content);\r\n return { commandId: parsed.commandId };\r\n } catch (e) {\r\n console.error(\"Failed to parse LLM response\", e);\r\n return { commandId: null };\r\n }\r\n };\r\n};","import { LLMAdapter } from \"../types\";\r\n\r\ninterface GeminiConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"gemini-1.5-flash\"\r\n */\r\n model?: string;\r\n /**\r\n * System instruction to guide the style of response (Optional)\r\n */\r\n systemInstruction?: string;\r\n}\r\n\r\nexport const createGeminiAdapter = (config: GeminiConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n // 1. Prepare the System Prompt\r\n // We explain the task to Gemini and list the available commands\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const prompt = `\r\n You are a voice command router.\r\n The user said: \"${transcript}\"\r\n\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Return ONLY the JSON object. No markdown, no explanation.\r\n 2. Format: { \"commandId\": \"id_here\" } or { \"commandId\": null } if no match.\r\n `;\r\n\r\n // 2. Call Google Gemini API\r\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${\r\n config.model || \"gemini-1.5-flash\"\r\n }:generateContent?key=${config.apiKey}`;\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({\r\n contents: [\r\n {\r\n parts: [{ text: prompt }],\r\n },\r\n ],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Gemini API Error: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Gemini returns nested objects: candidates[0].content.parts[0].text\r\n const textResponse = data.candidates?.[0]?.content?.parts?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up markdown code blocks if Gemini adds them (e.g. ```json ... ```)\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Gemini:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n","import { LLMAdapter } from \"../types\";\r\n\r\ninterface ClaudeConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"claude-3-5-sonnet-20240620\"\r\n */\r\n model?: string;\r\n /**\r\n * Optional custom endpoint if using a proxy (Recommended for CORS)\r\n * @default \"https://api.anthropic.com/v1/messages\"\r\n */\r\n endpoint?: string;\r\n}\r\n\r\nexport const createClaudeAdapter = (config: ClaudeConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n const endpoint = config.endpoint || \"https://api.anthropic.com/v1/messages\";\r\n const model = config.model || \"claude-3-5-sonnet-20240620\";\r\n\r\n // 1. Prepare Command List\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const systemPrompt = `\r\n You are a voice command router.\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Analyze the user's transcript.\r\n 2. Return a JSON object: { \"commandId\": \"id\" } or { \"commandId\": null }.\r\n 3. No markdown, no conversational text. ONLY JSON.\r\n `;\r\n\r\n try {\r\n // 2. Call Anthropic API\r\n const response = await fetch(endpoint, {\r\n method: \"POST\",\r\n headers: {\r\n \"x-api-key\": config.apiKey,\r\n \"anthropic-version\": \"2023-06-01\",\r\n \"content-type\": \"application/json\",\r\n // 'dangerously-allow-browser': 'true' // Anthropic client SDK uses this, but fetch doesn't need it.\r\n },\r\n body: JSON.stringify({\r\n model: model,\r\n max_tokens: 100,\r\n system: systemPrompt,\r\n messages: [{ role: \"user\", content: transcript }],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const err = await response.text();\r\n throw new Error(`Claude API Error: ${response.status} - ${err}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Claude returns: content: [ { type: 'text', text: '...' } ]\r\n const textResponse = data.content?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up potential markdown formatting\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Claude:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -207,13 +207,8 @@ var VoiceControlProvider = ({
|
|
|
207
207
|
},
|
|
208
208
|
[adapter, enableOfflineFallback]
|
|
209
209
|
);
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
start: engineStart,
|
|
213
|
-
stop: engineStop
|
|
214
|
-
} = useSpeechRecognition({
|
|
215
|
-
disabled: disableSpeechEngine,
|
|
216
|
-
onResult: (transcript, isFinal) => {
|
|
210
|
+
const handleResult = useCallback(
|
|
211
|
+
(transcript, isFinal) => {
|
|
217
212
|
var _a, _b;
|
|
218
213
|
if (isDictatingRef.current && dictationOptionsRef.current) {
|
|
219
214
|
const options = dictationOptionsRef.current;
|
|
@@ -224,15 +219,33 @@ var VoiceControlProvider = ({
|
|
|
224
219
|
processTranscript(transcript);
|
|
225
220
|
return;
|
|
226
221
|
}
|
|
227
|
-
|
|
222
|
+
if (isFinal) {
|
|
223
|
+
options.onFinal(transcript);
|
|
224
|
+
} else {
|
|
225
|
+
(_b = options.onInterim) == null ? void 0 : _b.call(options, transcript);
|
|
226
|
+
}
|
|
228
227
|
return;
|
|
229
228
|
}
|
|
230
|
-
if (isFinal)
|
|
229
|
+
if (isFinal) {
|
|
230
|
+
processTranscript(transcript);
|
|
231
|
+
}
|
|
231
232
|
},
|
|
232
|
-
|
|
233
|
+
[processTranscript, stopDictation]
|
|
234
|
+
);
|
|
235
|
+
const handleError = useCallback((err) => {
|
|
236
|
+
setState((prev) => ({
|
|
233
237
|
...prev,
|
|
234
238
|
error: typeof err === "string" ? err : "Speech Error"
|
|
235
|
-
}))
|
|
239
|
+
}));
|
|
240
|
+
}, []);
|
|
241
|
+
const {
|
|
242
|
+
isListening: engineListening,
|
|
243
|
+
start: engineStart,
|
|
244
|
+
stop: engineStop
|
|
245
|
+
} = useSpeechRecognition({
|
|
246
|
+
disabled: disableSpeechEngine,
|
|
247
|
+
onResult: handleResult,
|
|
248
|
+
onError: handleError
|
|
236
249
|
});
|
|
237
250
|
useEffect(() => {
|
|
238
251
|
setState((prev) => ({ ...prev, isListening: engineListening }));
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSpeechRecognition.ts","../src/core/localMatcher.ts","../src/components/VoiceContext.tsx","../src/hooks/useVoiceCommand.ts","../src/core/prompt.ts","../src/adapters/openai.ts","../src/adapters/gemini.ts","../src/adapters/claude.ts"],"names":["useRef","useState","useCallback","useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["// src/hooks/useSpeechRecognition.ts\r\nimport { useState, useEffect, useRef, useCallback } from 'react';\r\n\r\n// Define the supported error types\r\nexport type SpeechError =\r\n | 'not-allowed'\r\n | 'no-speech'\r\n | 'network'\r\n | 'aborted'\r\n | 'service-not-allowed'\r\n | string;\r\n\r\ninterface UseSpeechRecognitionProps {\r\n onResult: (transcript: string, isFinal: boolean) => void;\r\n onError?: (error: SpeechError) => void;\r\n /**\r\n * If true, the engine will not initialize (backward compatibility)\r\n */\r\n disabled?: boolean;\r\n}\r\n\r\nexport const useSpeechRecognition = ({\r\n onResult,\r\n onError,\r\n disabled = false\r\n}: UseSpeechRecognitionProps) => {\r\n // UI State\r\n const [isListening, setIsListening] = useState(false);\r\n const [error, setError] = useState<SpeechError | null>(null);\r\n const [isSupported, setIsSupported] = useState(true);\r\n\r\n // Internal Refs\r\n const recognitionRef = useRef<SpeechRecognition | null>(null);\r\n const intendedState = useRef(false); // Tracks if we *want* to be listening\r\n\r\n // 1. Initialize the API\r\n useEffect(() => {\r\n if (disabled) return;\r\n\r\n if (typeof window === 'undefined') return; // SSR check\r\n\r\n const SpeechConstructor = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n\r\n if (!SpeechConstructor) {\r\n console.error(\"React Voice Action Router: Speech Recognition not supported in this browser.\");\r\n setIsSupported(false);\r\n return;\r\n }\r\n\r\n const recognition = new SpeechConstructor();\r\n recognition.continuous = true; // We want continuous stream\r\n recognition.interimResults = true; // We need real-time feedback\r\n recognition.lang = 'en-US'; // Default to English (configurable later)\r\n\r\n // --- EVENT HANDLERS ---\r\n\r\n recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n // Get the latest result\r\n const result = event.results[event.results.length - 1];\r\n const transcript = result[0].transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Pass to parent\r\n onResult(transcript, isFinal);\r\n };\r\n\r\n recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n // 'no-speech' is common and usually ignorable (just silence)\r\n if (event.error === 'no-speech') {\r\n return;\r\n }\r\n\r\n console.warn(\"Speech Recognition Error:\", event.error);\r\n setError(event.error);\r\n if (onError) onError(event.error);\r\n\r\n // If permission denied, stop trying\r\n if (event.error === 'not-allowed') {\r\n intendedState.current = false;\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognition.onend = () => {\r\n // The Critical \"Auto-Restart\" Logic\r\n if (intendedState.current) {\r\n // If we still want to be listening, restart immediately\r\n try {\r\n recognition.start();\r\n } catch (e) {\r\n // Sometimes it needs a tiny delay if the browser is busy\r\n setTimeout(() => {\r\n if (intendedState.current) recognition.start();\r\n }, 100);\r\n }\r\n } else {\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognitionRef.current = recognition;\r\n\r\n // Cleanup\r\n return () => {\r\n intendedState.current = false;\r\n recognition.stop();\r\n };\r\n }, [disabled, onResult, onError]);\r\n\r\n // 2. Control Methods\r\n const start = useCallback(() => {\r\n if (!recognitionRef.current || !isSupported) return;\r\n\r\n setError(null);\r\n intendedState.current = true;\r\n try {\r\n recognitionRef.current.start();\r\n setIsListening(true);\r\n } catch (e) {\r\n // Already started, ignore\r\n }\r\n }, [isSupported]);\r\n\r\n const stop = useCallback(() => {\r\n if (!recognitionRef.current) return;\r\n\r\n intendedState.current = false;\r\n recognitionRef.current.stop();\r\n setIsListening(false);\r\n }, []);\r\n\r\n const toggle = useCallback(() => {\r\n if (isListening) stop();\r\n else start();\r\n }, [isListening, start, stop]);\r\n\r\n return {\r\n isListening,\r\n isSupported,\r\n error,\r\n start,\r\n stop,\r\n toggle\r\n };\r\n};","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * A simple fallback matcher that scores commands based on keyword overlap.\r\n * Used when the AI adapter fails.\r\n */\r\nexport const findBestMatch = (transcript: string, commands: VoiceCommand[]): string | null => {\r\n const lowerTranscript = transcript.toLowerCase().trim();\r\n const transcriptWords = lowerTranscript.split(/\\s+/).filter(w => w.length > 2); // Ignore short words\r\n\r\n let bestMatchId: string | null = null;\r\n let maxScore = 0;\r\n\r\n for (const cmd of commands) {\r\n let score = 0;\r\n const desc = cmd.description.toLowerCase();\r\n const id = cmd.id.toLowerCase();\r\n\r\n // 1. Direct ID Match (Strong signal)\r\n if (lowerTranscript.includes(id)) {\r\n score += 3;\r\n }\r\n\r\n // 2. Description Keyword Match\r\n // If the user says \"Navigate settings\", and description is \"Navigate to settings page\"\r\n for (const word of transcriptWords) {\r\n if (desc.includes(word)) {\r\n score += 1;\r\n }\r\n }\r\n\r\n // Update best match if score is significant\r\n if (score > maxScore && score >= 2) { // Threshold of 2 ensures at least some relevance\r\n maxScore = score;\r\n bestMatchId = cmd.id;\r\n }\r\n }\r\n\r\n return bestMatchId;\r\n};","import React, {\r\n createContext,\r\n useContext,\r\n useState,\r\n useCallback,\r\n useRef,\r\n useEffect,\r\n} from \"react\";\r\nimport {\r\n VoiceCommand,\r\n VoiceControlState,\r\n VoiceProviderProps,\r\n DictationOptions,\r\n} from \"../types\";\r\nimport { useSpeechRecognition } from \"../hooks/useSpeechRecognition\";\r\nimport { findBestMatch } from \"../core/localMatcher\";\r\ninterface VoiceContextValue extends VoiceControlState {\r\n register: (cmd: VoiceCommand) => void;\r\n unregister: (id: string) => void;\r\n processTranscript: (text: string) => Promise<void>;\r\n setPaused: (paused: boolean) => void;\r\n startListening: () => void;\r\n stopListening: () => void;\r\n startDictation: (options: DictationOptions) => void;\r\n stopDictation: () => void;\r\n}\r\n\r\nconst VoiceContext = createContext<VoiceContextValue | null>(null);\r\n\r\nexport const VoiceControlProvider: React.FC<VoiceProviderProps> = ({\r\n children,\r\n adapter,\r\n disableSpeechEngine = false,\r\n enableOfflineFallback = true, // <--- Default to true\r\n}) => {\r\n const commandsRef = useRef<Map<string, VoiceCommand>>(new Map());\r\n const isPausedRef = useRef(false);\r\n const isDictatingRef = useRef(false);\r\n const dictationOptionsRef = useRef<DictationOptions | null>(null);\r\n\r\n const [state, setState] = useState<VoiceControlState>({\r\n isListening: false,\r\n isProcessing: false,\r\n lastTranscript: null,\r\n activeCommands: [],\r\n isPaused: false,\r\n error: null,\r\n isDictating: false,\r\n });\r\n\r\n const setPaused = useCallback((paused: boolean) => {\r\n isPausedRef.current = paused;\r\n setState((prev) => ({ ...prev, isPaused: paused }));\r\n }, []);\r\n\r\n const startDictation = useCallback((options: DictationOptions) => {\r\n isDictatingRef.current = true;\r\n dictationOptionsRef.current = options;\r\n setState((prev) => ({ ...prev, isDictating: true }));\r\n }, []);\r\n\r\n const stopDictation = useCallback(() => {\r\n isDictatingRef.current = false;\r\n dictationOptionsRef.current = null;\r\n setState((prev) => ({ ...prev, isDictating: false }));\r\n }, []);\r\n\r\n const processTranscript = useCallback(\r\n async (transcript: string) => {\r\n if (isPausedRef.current) return;\r\n\r\n const cleanText = transcript.trim().toLowerCase();\r\n if (!cleanText) return;\r\n\r\n setState((prev) => ({\r\n ...prev,\r\n isProcessing: true,\r\n lastTranscript: cleanText,\r\n }));\r\n const allCommands = Array.from(commandsRef.current.values());\r\n\r\n // 1. Exact Match (Fastest)\r\n const exactMatch = allCommands.find(\r\n (c) => c.phrase?.toLowerCase() === cleanText\r\n );\r\n if (exactMatch) {\r\n console.log(`⚡ Match: \"${cleanText}\"`);\r\n exactMatch.action();\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n return;\r\n }\r\n\r\n // 2. AI Fuzzy Match (Smartest)\r\n try {\r\n const commandListForAI = allCommands.map(\r\n ({ id, description, phrase }) => ({ id, description, phrase })\r\n );\r\n\r\n const result = await adapter(cleanText, commandListForAI);\r\n\r\n if (result.commandId) {\r\n const cmd = commandsRef.current.get(result.commandId);\r\n if (cmd) {\r\n cmd.action();\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"AI Adapter Failed:\", error);\r\n\r\n // 3. Offline Fallback (Safety Net)\r\n if (enableOfflineFallback) {\r\n console.warn(\"🌐 Attempting Offline Fallback...\");\r\n const fallbackId = findBestMatch(cleanText, allCommands);\r\n\r\n if (fallbackId) {\r\n const cmd = commandsRef.current.get(fallbackId);\r\n if (cmd) {\r\n console.log(`✅ Offline Match: ${cmd.id}`);\r\n cmd.action();\r\n }\r\n } else {\r\n console.log(\"❌ No offline match found.\");\r\n }\r\n }\r\n } finally {\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n }\r\n },\r\n [adapter, enableOfflineFallback]\r\n );\r\n\r\n // ... (keep useSpeechRecognition hook and register/unregister)\r\n const {\r\n isListening: engineListening,\r\n start: engineStart,\r\n stop: engineStop,\r\n } = useSpeechRecognition({\r\n disabled: disableSpeechEngine,\r\n onResult: (transcript, isFinal) => {\r\n if (isDictatingRef.current && dictationOptionsRef.current) {\r\n const options = dictationOptionsRef.current;\r\n if (\r\n isFinal &&\r\n options.exitCommands?.some((cmd) =>\r\n transcript.toLowerCase().includes(cmd)\r\n )\r\n ) {\r\n stopDictation();\r\n processTranscript(transcript);\r\n return;\r\n }\r\n isFinal ? options.onFinal(transcript) : options.onInterim?.(transcript);\r\n return;\r\n }\r\n if (isFinal) processTranscript(transcript);\r\n },\r\n onError: (err) =>\r\n setState((prev) => ({\r\n ...prev,\r\n error: typeof err === \"string\" ? err : \"Speech Error\",\r\n })),\r\n });\r\n\r\n useEffect(() => {\r\n setState((prev) => ({ ...prev, isListening: engineListening }));\r\n }, [engineListening]);\r\n\r\n const register = useCallback((cmd: VoiceCommand) => {\r\n commandsRef.current.set(cmd.id, cmd);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n const unregister = useCallback((id: string) => {\r\n commandsRef.current.delete(id);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n return (\r\n <VoiceContext.Provider\r\n value={{\r\n ...state,\r\n register,\r\n unregister,\r\n processTranscript,\r\n setPaused,\r\n startListening: engineStart,\r\n stopListening: engineStop,\r\n startDictation,\r\n stopDictation,\r\n }}\r\n >\r\n {children}\r\n </VoiceContext.Provider>\r\n );\r\n};\r\n\r\nexport const useVoiceContext = () => {\r\n const ctx = useContext(VoiceContext);\r\n if (!ctx)\r\n throw new Error(\r\n \"useVoiceContext must be used within a VoiceControlProvider\"\r\n );\r\n return ctx;\r\n};\r\n","import { useEffect, useRef } from \"react\";\r\nimport { useVoiceContext } from \"../components/VoiceContext\";\r\nimport { VoiceCommand } from \"../types\";\r\n\r\nexport const useVoiceCommand = (command: VoiceCommand) => {\r\n const { register, unregister } = useVoiceContext();\r\n\r\n // 1. Keep a \"Ref\" to the latest command.\r\n // This allows the action function to change (e.g. updating state)\r\n // WITHOUT forcing us to unregister/re-register the command.\r\n const commandRef = useRef(command);\r\n\r\n // Always update the ref on every render\r\n useEffect(() => {\r\n commandRef.current = command;\r\n });\r\n\r\n useEffect(() => {\r\n // 2. Register a \"Proxy Command\"\r\n // Instead of registering the raw command, we register a proxy\r\n // that always calls the LATEST version stored in the ref.\r\n const proxyCommand: VoiceCommand = {\r\n id: command.id,\r\n description: command.description,\r\n phrase: command.phrase,\r\n action: () => {\r\n // When triggered, execute whatever the current action is\r\n commandRef.current.action();\r\n },\r\n };\r\n\r\n register(proxyCommand);\r\n\r\n return () => {\r\n unregister(proxyCommand.id);\r\n };\r\n // 3. Only re-register if the ID changes.\r\n // We intentionally ignore 'command' or 'command.action' changes here.\r\n }, [register, unregister, command.id]);\r\n};\r\n","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * Generates the System Prompt for the LLM.\r\n * This ensures consistent behavior across different AI providers.\r\n */\r\nexport const createSystemPrompt = (commands: Omit<VoiceCommand, 'action'>[]) => {\r\n // Format the list of commands for the AI to read\r\n const commandList = commands.map(cmd =>\r\n `- ID: \"${cmd.id}\" | Description: \"${cmd.description}\"`\r\n ).join('\\n');\r\n\r\n return `\r\nYou are a precise Voice Command Router.\r\nYour goal is to map the user's spoken input to the correct Command ID from the list below.\r\n\r\nRULES:\r\n1. Analyze the user's input and find the intent.\r\n2. Match it to the command with the most relevant \"Description\".\r\n3. Use fuzzy matching (e.g., \"Dark mode\" matches \"Toggle Theme\").\r\n4. If NO command matches the intent, return null.\r\n5. IMPORTANT: Output ONLY valid JSON. Do not include markdown formatting.\r\n\r\nAVAILABLE COMMANDS:\r\n${commandList}\r\n\r\nRESPONSE FORMAT:\r\n{ \"commandId\": \"string_id_or_null\" }\r\n`;\r\n};\r\n\r\n/**\r\n * Standardizes how the user's voice transcript is presented to the AI.\r\n */\r\nexport const createUserPrompt = (transcript: string) => {\r\n return `User Input: \"${transcript}\"`;\r\n};","import { LLMAdapter } from '../types';\r\nimport { createSystemPrompt } from '../core/prompt';\r\n\r\ninterface OpenAIConfig {\r\n apiKey: string;\r\n /** @default \"gpt-4o-mini\" */\r\n model?: string;\r\n}\r\n\r\n/**\r\n * A Factory that creates an Adapter for OpenAI.\r\n * Users call this: createOpenAIAdapter({ apiKey: '...' })\r\n */\r\nexport const createOpenAIAdapter = (config: OpenAIConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n\r\n // 1. Generate the optimized system instructions\r\n const systemPrompt = createSystemPrompt(commands);\r\n\r\n // 2. Call the API\r\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`\r\n },\r\n body: JSON.stringify({\r\n model: config.model || \"gpt-4o-mini\",\r\n messages: [\r\n { role: \"system\", content: systemPrompt },\r\n { role: \"user\", content: transcript }\r\n ],\r\n temperature: 0, // Deterministic results\r\n response_format: { type: \"json_object\" } // Force JSON mode\r\n })\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`OpenAI Adapter Failed: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse the result\r\n try {\r\n const parsed = JSON.parse(data.choices[0].message.content);\r\n return { commandId: parsed.commandId };\r\n } catch (e) {\r\n console.error(\"Failed to parse LLM response\", e);\r\n return { commandId: null };\r\n }\r\n };\r\n};","import { LLMAdapter } from \"../types\";\r\n\r\ninterface GeminiConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"gemini-1.5-flash\"\r\n */\r\n model?: string;\r\n /**\r\n * System instruction to guide the style of response (Optional)\r\n */\r\n systemInstruction?: string;\r\n}\r\n\r\nexport const createGeminiAdapter = (config: GeminiConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n // 1. Prepare the System Prompt\r\n // We explain the task to Gemini and list the available commands\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const prompt = `\r\n You are a voice command router.\r\n The user said: \"${transcript}\"\r\n\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Return ONLY the JSON object. No markdown, no explanation.\r\n 2. Format: { \"commandId\": \"id_here\" } or { \"commandId\": null } if no match.\r\n `;\r\n\r\n // 2. Call Google Gemini API\r\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${\r\n config.model || \"gemini-1.5-flash\"\r\n }:generateContent?key=${config.apiKey}`;\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({\r\n contents: [\r\n {\r\n parts: [{ text: prompt }],\r\n },\r\n ],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Gemini API Error: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Gemini returns nested objects: candidates[0].content.parts[0].text\r\n const textResponse = data.candidates?.[0]?.content?.parts?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up markdown code blocks if Gemini adds them (e.g. ```json ... ```)\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Gemini:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n","import { LLMAdapter } from \"../types\";\r\n\r\ninterface ClaudeConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"claude-3-5-sonnet-20240620\"\r\n */\r\n model?: string;\r\n /**\r\n * Optional custom endpoint if using a proxy (Recommended for CORS)\r\n * @default \"https://api.anthropic.com/v1/messages\"\r\n */\r\n endpoint?: string;\r\n}\r\n\r\nexport const createClaudeAdapter = (config: ClaudeConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n const endpoint = config.endpoint || \"https://api.anthropic.com/v1/messages\";\r\n const model = config.model || \"claude-3-5-sonnet-20240620\";\r\n\r\n // 1. Prepare Command List\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const systemPrompt = `\r\n You are a voice command router.\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Analyze the user's transcript.\r\n 2. Return a JSON object: { \"commandId\": \"id\" } or { \"commandId\": null }.\r\n 3. No markdown, no conversational text. ONLY JSON.\r\n `;\r\n\r\n try {\r\n // 2. Call Anthropic API\r\n const response = await fetch(endpoint, {\r\n method: \"POST\",\r\n headers: {\r\n \"x-api-key\": config.apiKey,\r\n \"anthropic-version\": \"2023-06-01\",\r\n \"content-type\": \"application/json\",\r\n // 'dangerously-allow-browser': 'true' // Anthropic client SDK uses this, but fetch doesn't need it.\r\n },\r\n body: JSON.stringify({\r\n model: model,\r\n max_tokens: 100,\r\n system: systemPrompt,\r\n messages: [{ role: \"user\", content: transcript }],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const err = await response.text();\r\n throw new Error(`Claude API Error: ${response.status} - ${err}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Claude returns: content: [ { type: 'text', text: '...' } ]\r\n const textResponse = data.content?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up potential markdown formatting\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Claude:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSpeechRecognition.ts","../src/core/localMatcher.ts","../src/components/VoiceContext.tsx","../src/hooks/useVoiceCommand.ts","../src/core/prompt.ts","../src/adapters/openai.ts","../src/adapters/gemini.ts","../src/adapters/claude.ts"],"names":["useRef","useState","useCallback","useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["// src/hooks/useSpeechRecognition.ts\r\nimport { useState, useEffect, useRef, useCallback } from 'react';\r\n\r\n// Define the supported error types\r\nexport type SpeechError =\r\n | 'not-allowed'\r\n | 'no-speech'\r\n | 'network'\r\n | 'aborted'\r\n | 'service-not-allowed'\r\n | string;\r\n\r\ninterface UseSpeechRecognitionProps {\r\n onResult: (transcript: string, isFinal: boolean) => void;\r\n onError?: (error: SpeechError) => void;\r\n /**\r\n * If true, the engine will not initialize (backward compatibility)\r\n */\r\n disabled?: boolean;\r\n}\r\n\r\nexport const useSpeechRecognition = ({\r\n onResult,\r\n onError,\r\n disabled = false\r\n}: UseSpeechRecognitionProps) => {\r\n // UI State\r\n const [isListening, setIsListening] = useState(false);\r\n const [error, setError] = useState<SpeechError | null>(null);\r\n const [isSupported, setIsSupported] = useState(true);\r\n\r\n // Internal Refs\r\n const recognitionRef = useRef<SpeechRecognition | null>(null);\r\n const intendedState = useRef(false); // Tracks if we *want* to be listening\r\n\r\n // 1. Initialize the API\r\n useEffect(() => {\r\n if (disabled) return;\r\n\r\n if (typeof window === 'undefined') return; // SSR check\r\n\r\n const SpeechConstructor = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n\r\n if (!SpeechConstructor) {\r\n console.error(\"React Voice Action Router: Speech Recognition not supported in this browser.\");\r\n setIsSupported(false);\r\n return;\r\n }\r\n\r\n const recognition = new SpeechConstructor();\r\n recognition.continuous = true; // We want continuous stream\r\n recognition.interimResults = true; // We need real-time feedback\r\n recognition.lang = 'en-US'; // Default to English (configurable later)\r\n\r\n // --- EVENT HANDLERS ---\r\n\r\n recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n // Get the latest result\r\n const result = event.results[event.results.length - 1];\r\n const transcript = result[0].transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Pass to parent\r\n onResult(transcript, isFinal);\r\n };\r\n\r\n recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n // 'no-speech' is common and usually ignorable (just silence)\r\n if (event.error === 'no-speech') {\r\n return;\r\n }\r\n\r\n console.warn(\"Speech Recognition Error:\", event.error);\r\n setError(event.error);\r\n if (onError) onError(event.error);\r\n\r\n // If permission denied, stop trying\r\n if (event.error === 'not-allowed') {\r\n intendedState.current = false;\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognition.onend = () => {\r\n // The Critical \"Auto-Restart\" Logic\r\n if (intendedState.current) {\r\n // If we still want to be listening, restart immediately\r\n try {\r\n recognition.start();\r\n } catch (e) {\r\n // Sometimes it needs a tiny delay if the browser is busy\r\n setTimeout(() => {\r\n if (intendedState.current) recognition.start();\r\n }, 100);\r\n }\r\n } else {\r\n setIsListening(false);\r\n }\r\n };\r\n\r\n recognitionRef.current = recognition;\r\n\r\n // Cleanup\r\n return () => {\r\n intendedState.current = false;\r\n recognition.stop();\r\n };\r\n }, [disabled, onResult, onError]);\r\n\r\n // 2. Control Methods\r\n const start = useCallback(() => {\r\n if (!recognitionRef.current || !isSupported) return;\r\n\r\n setError(null);\r\n intendedState.current = true;\r\n try {\r\n recognitionRef.current.start();\r\n setIsListening(true);\r\n } catch (e) {\r\n // Already started, ignore\r\n }\r\n }, [isSupported]);\r\n\r\n const stop = useCallback(() => {\r\n if (!recognitionRef.current) return;\r\n\r\n intendedState.current = false;\r\n recognitionRef.current.stop();\r\n setIsListening(false);\r\n }, []);\r\n\r\n const toggle = useCallback(() => {\r\n if (isListening) stop();\r\n else start();\r\n }, [isListening, start, stop]);\r\n\r\n return {\r\n isListening,\r\n isSupported,\r\n error,\r\n start,\r\n stop,\r\n toggle\r\n };\r\n};","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * A simple fallback matcher that scores commands based on keyword overlap.\r\n * Used when the AI adapter fails.\r\n */\r\nexport const findBestMatch = (transcript: string, commands: VoiceCommand[]): string | null => {\r\n const lowerTranscript = transcript.toLowerCase().trim();\r\n const transcriptWords = lowerTranscript.split(/\\s+/).filter(w => w.length > 2); // Ignore short words\r\n\r\n let bestMatchId: string | null = null;\r\n let maxScore = 0;\r\n\r\n for (const cmd of commands) {\r\n let score = 0;\r\n const desc = cmd.description.toLowerCase();\r\n const id = cmd.id.toLowerCase();\r\n\r\n // 1. Direct ID Match (Strong signal)\r\n if (lowerTranscript.includes(id)) {\r\n score += 3;\r\n }\r\n\r\n // 2. Description Keyword Match\r\n // If the user says \"Navigate settings\", and description is \"Navigate to settings page\"\r\n for (const word of transcriptWords) {\r\n if (desc.includes(word)) {\r\n score += 1;\r\n }\r\n }\r\n\r\n // Update best match if score is significant\r\n if (score > maxScore && score >= 2) { // Threshold of 2 ensures at least some relevance\r\n maxScore = score;\r\n bestMatchId = cmd.id;\r\n }\r\n }\r\n\r\n return bestMatchId;\r\n};","import React, {\r\n createContext,\r\n useContext,\r\n useState,\r\n useCallback,\r\n useRef,\r\n useEffect,\r\n} from \"react\";\r\nimport {\r\n VoiceCommand,\r\n VoiceControlState,\r\n VoiceProviderProps,\r\n DictationOptions,\r\n} from \"../types\";\r\nimport { useSpeechRecognition } from \"../hooks/useSpeechRecognition\";\r\nimport { findBestMatch } from \"../core/localMatcher\";\r\ninterface VoiceContextValue extends VoiceControlState {\r\n register: (cmd: VoiceCommand) => void;\r\n unregister: (id: string) => void;\r\n processTranscript: (text: string) => Promise<void>;\r\n setPaused: (paused: boolean) => void;\r\n startListening: () => void;\r\n stopListening: () => void;\r\n startDictation: (options: DictationOptions) => void;\r\n stopDictation: () => void;\r\n}\r\n\r\nconst VoiceContext = createContext<VoiceContextValue | null>(null);\r\n\r\nexport const VoiceControlProvider: React.FC<VoiceProviderProps> = ({\r\n children,\r\n adapter,\r\n disableSpeechEngine = false,\r\n enableOfflineFallback = true, // <--- Default to true\r\n}) => {\r\n const commandsRef = useRef<Map<string, VoiceCommand>>(new Map());\r\n const isPausedRef = useRef(false);\r\n const isDictatingRef = useRef(false);\r\n const dictationOptionsRef = useRef<DictationOptions | null>(null);\r\n\r\n const [state, setState] = useState<VoiceControlState>({\r\n isListening: false,\r\n isProcessing: false,\r\n lastTranscript: null,\r\n activeCommands: [],\r\n isPaused: false,\r\n error: null,\r\n isDictating: false,\r\n });\r\n\r\n const setPaused = useCallback((paused: boolean) => {\r\n isPausedRef.current = paused;\r\n setState((prev) => ({ ...prev, isPaused: paused }));\r\n }, []);\r\n\r\n const startDictation = useCallback((options: DictationOptions) => {\r\n isDictatingRef.current = true;\r\n dictationOptionsRef.current = options;\r\n setState((prev) => ({ ...prev, isDictating: true }));\r\n }, []);\r\n\r\n const stopDictation = useCallback(() => {\r\n isDictatingRef.current = false;\r\n dictationOptionsRef.current = null;\r\n setState((prev) => ({ ...prev, isDictating: false }));\r\n }, []);\r\n\r\n const processTranscript = useCallback(\r\n async (transcript: string) => {\r\n if (isPausedRef.current) return;\r\n\r\n const cleanText = transcript.trim().toLowerCase();\r\n if (!cleanText) return;\r\n\r\n setState((prev) => ({\r\n ...prev,\r\n isProcessing: true,\r\n lastTranscript: cleanText,\r\n }));\r\n const allCommands = Array.from(commandsRef.current.values());\r\n\r\n // 1. Exact Match (Fastest)\r\n const exactMatch = allCommands.find(\r\n (c) => c.phrase?.toLowerCase() === cleanText\r\n );\r\n if (exactMatch) {\r\n console.log(`⚡ Match: \"${cleanText}\"`);\r\n exactMatch.action();\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n return;\r\n }\r\n\r\n // 2. AI Fuzzy Match (Smartest)\r\n try {\r\n const commandListForAI = allCommands.map(\r\n ({ id, description, phrase }) => ({ id, description, phrase })\r\n );\r\n\r\n const result = await adapter(cleanText, commandListForAI);\r\n\r\n if (result.commandId) {\r\n const cmd = commandsRef.current.get(result.commandId);\r\n if (cmd) {\r\n cmd.action();\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"AI Adapter Failed:\", error);\r\n\r\n // 3. Offline Fallback (Safety Net)\r\n if (enableOfflineFallback) {\r\n console.warn(\"🌐 Attempting Offline Fallback...\");\r\n const fallbackId = findBestMatch(cleanText, allCommands);\r\n\r\n if (fallbackId) {\r\n const cmd = commandsRef.current.get(fallbackId);\r\n if (cmd) {\r\n console.log(`✅ Offline Match: ${cmd.id}`);\r\n cmd.action();\r\n }\r\n } else {\r\n console.log(\"❌ No offline match found.\");\r\n }\r\n }\r\n } finally {\r\n setState((prev) => ({ ...prev, isProcessing: false }));\r\n }\r\n },\r\n [adapter, enableOfflineFallback]\r\n );\r\n\r\n // 3. INTERNAL SPEECH ENGINE\r\n const handleResult = useCallback(\r\n (transcript: string, isFinal: boolean) => {\r\n if (isDictatingRef.current && dictationOptionsRef.current) {\r\n const options = dictationOptionsRef.current;\r\n\r\n // Check for exit commands\r\n if (\r\n isFinal &&\r\n options.exitCommands?.some((cmd) =>\r\n transcript.toLowerCase().includes(cmd)\r\n )\r\n ) {\r\n stopDictation();\r\n processTranscript(transcript);\r\n return;\r\n }\r\n\r\n // Handle dictation input\r\n if (isFinal) {\r\n options.onFinal(transcript);\r\n } else {\r\n options.onInterim?.(transcript);\r\n }\r\n return;\r\n }\r\n\r\n // Normal Command Mode\r\n if (isFinal) {\r\n processTranscript(transcript);\r\n }\r\n },\r\n [processTranscript, stopDictation]\r\n );\r\n\r\n const handleError = useCallback((err: string) => {\r\n setState((prev) => ({\r\n ...prev,\r\n error: typeof err === \"string\" ? err : \"Speech Error\",\r\n }));\r\n }, []);\r\n\r\n const {\r\n isListening: engineListening,\r\n start: engineStart,\r\n stop: engineStop,\r\n } = useSpeechRecognition({\r\n disabled: disableSpeechEngine,\r\n onResult: handleResult,\r\n onError: handleError,\r\n });\r\n\r\n useEffect(() => {\r\n setState((prev) => ({ ...prev, isListening: engineListening }));\r\n }, [engineListening]);\r\n\r\n const register = useCallback((cmd: VoiceCommand) => {\r\n commandsRef.current.set(cmd.id, cmd);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n const unregister = useCallback((id: string) => {\r\n commandsRef.current.delete(id);\r\n setState((prev) => ({\r\n ...prev,\r\n activeCommands: Array.from(commandsRef.current.values()),\r\n }));\r\n }, []);\r\n\r\n return (\r\n <VoiceContext.Provider\r\n value={{\r\n ...state,\r\n register,\r\n unregister,\r\n processTranscript,\r\n setPaused,\r\n startListening: engineStart,\r\n stopListening: engineStop,\r\n startDictation,\r\n stopDictation,\r\n }}\r\n >\r\n {children}\r\n </VoiceContext.Provider>\r\n );\r\n};\r\n\r\nexport const useVoiceContext = () => {\r\n const ctx = useContext(VoiceContext);\r\n if (!ctx)\r\n throw new Error(\r\n \"useVoiceContext must be used within a VoiceControlProvider\"\r\n );\r\n return ctx;\r\n};\r\n","import { useEffect, useRef } from \"react\";\r\nimport { useVoiceContext } from \"../components/VoiceContext\";\r\nimport { VoiceCommand } from \"../types\";\r\n\r\nexport const useVoiceCommand = (command: VoiceCommand) => {\r\n const { register, unregister } = useVoiceContext();\r\n\r\n // 1. Keep a \"Ref\" to the latest command.\r\n // This allows the action function to change (e.g. updating state)\r\n // WITHOUT forcing us to unregister/re-register the command.\r\n const commandRef = useRef(command);\r\n\r\n // Always update the ref on every render\r\n useEffect(() => {\r\n commandRef.current = command;\r\n });\r\n\r\n useEffect(() => {\r\n // 2. Register a \"Proxy Command\"\r\n // Instead of registering the raw command, we register a proxy\r\n // that always calls the LATEST version stored in the ref.\r\n const proxyCommand: VoiceCommand = {\r\n id: command.id,\r\n description: command.description,\r\n phrase: command.phrase,\r\n action: () => {\r\n // When triggered, execute whatever the current action is\r\n commandRef.current.action();\r\n },\r\n };\r\n\r\n register(proxyCommand);\r\n\r\n return () => {\r\n unregister(proxyCommand.id);\r\n };\r\n // 3. Only re-register if the ID changes.\r\n // We intentionally ignore 'command' or 'command.action' changes here.\r\n }, [register, unregister, command.id]);\r\n};\r\n","import { VoiceCommand } from '../types';\r\n\r\n/**\r\n * Generates the System Prompt for the LLM.\r\n * This ensures consistent behavior across different AI providers.\r\n */\r\nexport const createSystemPrompt = (commands: Omit<VoiceCommand, 'action'>[]) => {\r\n // Format the list of commands for the AI to read\r\n const commandList = commands.map(cmd =>\r\n `- ID: \"${cmd.id}\" | Description: \"${cmd.description}\"`\r\n ).join('\\n');\r\n\r\n return `\r\nYou are a precise Voice Command Router.\r\nYour goal is to map the user's spoken input to the correct Command ID from the list below.\r\n\r\nRULES:\r\n1. Analyze the user's input and find the intent.\r\n2. Match it to the command with the most relevant \"Description\".\r\n3. Use fuzzy matching (e.g., \"Dark mode\" matches \"Toggle Theme\").\r\n4. If NO command matches the intent, return null.\r\n5. IMPORTANT: Output ONLY valid JSON. Do not include markdown formatting.\r\n\r\nAVAILABLE COMMANDS:\r\n${commandList}\r\n\r\nRESPONSE FORMAT:\r\n{ \"commandId\": \"string_id_or_null\" }\r\n`;\r\n};\r\n\r\n/**\r\n * Standardizes how the user's voice transcript is presented to the AI.\r\n */\r\nexport const createUserPrompt = (transcript: string) => {\r\n return `User Input: \"${transcript}\"`;\r\n};","import { LLMAdapter } from '../types';\r\nimport { createSystemPrompt } from '../core/prompt';\r\n\r\ninterface OpenAIConfig {\r\n apiKey: string;\r\n /** @default \"gpt-4o-mini\" */\r\n model?: string;\r\n}\r\n\r\n/**\r\n * A Factory that creates an Adapter for OpenAI.\r\n * Users call this: createOpenAIAdapter({ apiKey: '...' })\r\n */\r\nexport const createOpenAIAdapter = (config: OpenAIConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n\r\n // 1. Generate the optimized system instructions\r\n const systemPrompt = createSystemPrompt(commands);\r\n\r\n // 2. Call the API\r\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`\r\n },\r\n body: JSON.stringify({\r\n model: config.model || \"gpt-4o-mini\",\r\n messages: [\r\n { role: \"system\", content: systemPrompt },\r\n { role: \"user\", content: transcript }\r\n ],\r\n temperature: 0, // Deterministic results\r\n response_format: { type: \"json_object\" } // Force JSON mode\r\n })\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`OpenAI Adapter Failed: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse the result\r\n try {\r\n const parsed = JSON.parse(data.choices[0].message.content);\r\n return { commandId: parsed.commandId };\r\n } catch (e) {\r\n console.error(\"Failed to parse LLM response\", e);\r\n return { commandId: null };\r\n }\r\n };\r\n};","import { LLMAdapter } from \"../types\";\r\n\r\ninterface GeminiConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"gemini-1.5-flash\"\r\n */\r\n model?: string;\r\n /**\r\n * System instruction to guide the style of response (Optional)\r\n */\r\n systemInstruction?: string;\r\n}\r\n\r\nexport const createGeminiAdapter = (config: GeminiConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n // 1. Prepare the System Prompt\r\n // We explain the task to Gemini and list the available commands\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const prompt = `\r\n You are a voice command router.\r\n The user said: \"${transcript}\"\r\n\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Return ONLY the JSON object. No markdown, no explanation.\r\n 2. Format: { \"commandId\": \"id_here\" } or { \"commandId\": null } if no match.\r\n `;\r\n\r\n // 2. Call Google Gemini API\r\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${\r\n config.model || \"gemini-1.5-flash\"\r\n }:generateContent?key=${config.apiKey}`;\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({\r\n contents: [\r\n {\r\n parts: [{ text: prompt }],\r\n },\r\n ],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Gemini API Error: ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Gemini returns nested objects: candidates[0].content.parts[0].text\r\n const textResponse = data.candidates?.[0]?.content?.parts?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up markdown code blocks if Gemini adds them (e.g. ```json ... ```)\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Gemini:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n","import { LLMAdapter } from \"../types\";\r\n\r\ninterface ClaudeConfig {\r\n apiKey: string;\r\n /**\r\n * @default \"claude-3-5-sonnet-20240620\"\r\n */\r\n model?: string;\r\n /**\r\n * Optional custom endpoint if using a proxy (Recommended for CORS)\r\n * @default \"https://api.anthropic.com/v1/messages\"\r\n */\r\n endpoint?: string;\r\n}\r\n\r\nexport const createClaudeAdapter = (config: ClaudeConfig): LLMAdapter => {\r\n return async (transcript, commands) => {\r\n const endpoint = config.endpoint || \"https://api.anthropic.com/v1/messages\";\r\n const model = config.model || \"claude-3-5-sonnet-20240620\";\r\n\r\n // 1. Prepare Command List\r\n const commandList = commands\r\n .map((c) => `- \"${c.id}\": ${c.description}`)\r\n .join(\"\\n\");\r\n\r\n const systemPrompt = `\r\n You are a voice command router.\r\n Available commands:\r\n ${commandList}\r\n\r\n Rules:\r\n 1. Analyze the user's transcript.\r\n 2. Return a JSON object: { \"commandId\": \"id\" } or { \"commandId\": null }.\r\n 3. No markdown, no conversational text. ONLY JSON.\r\n `;\r\n\r\n try {\r\n // 2. Call Anthropic API\r\n const response = await fetch(endpoint, {\r\n method: \"POST\",\r\n headers: {\r\n \"x-api-key\": config.apiKey,\r\n \"anthropic-version\": \"2023-06-01\",\r\n \"content-type\": \"application/json\",\r\n // 'dangerously-allow-browser': 'true' // Anthropic client SDK uses this, but fetch doesn't need it.\r\n },\r\n body: JSON.stringify({\r\n model: model,\r\n max_tokens: 100,\r\n system: systemPrompt,\r\n messages: [{ role: \"user\", content: transcript }],\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const err = await response.text();\r\n throw new Error(`Claude API Error: ${response.status} - ${err}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n // 3. Parse Response\r\n // Claude returns: content: [ { type: 'text', text: '...' } ]\r\n const textResponse = data.content?.[0]?.text;\r\n\r\n if (!textResponse) return { commandId: null };\r\n\r\n // Clean up potential markdown formatting\r\n const cleanJson = textResponse.replace(/```json|```/g, \"\").trim();\r\n return JSON.parse(cleanJson);\r\n } catch (error) {\r\n console.error(\"Error routing with Claude:\", error);\r\n return { commandId: null };\r\n }\r\n };\r\n};\r\n"]}
|