@myscheme/voice-navigation-sdk 0.1.6 → 0.1.8
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 +13 -5
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +172 -32
- package/dist/microphone-handler.d.ts +7 -0
- package/dist/microphone-handler.d.ts.map +1 -1
- package/dist/microphone-handler.js +106 -22
- package/dist/navigation-controller.d.ts +9 -1
- package/dist/navigation-controller.d.ts.map +1 -1
- package/dist/navigation-controller.js +205 -6
- package/dist/server/azure-speech-handler.d.ts +1 -0
- package/dist/server/azure-speech-handler.d.ts.map +1 -1
- package/dist/server/azure-speech-handler.js +55 -16
- package/dist/server/bedrock-embedding-handler.d.ts +1 -0
- package/dist/server/bedrock-embedding-handler.d.ts.map +1 -1
- package/dist/server/bedrock-embedding-handler.js +84 -24
- package/dist/server/bedrock-handler.d.ts +1 -0
- package/dist/server/bedrock-handler.d.ts.map +1 -1
- package/dist/server/bedrock-handler.js +79 -22
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/services/bedrock.d.ts +3 -0
- package/dist/services/bedrock.d.ts.map +1 -1
- package/dist/services/bedrock.js +135 -1
- package/dist/services/voice-feedback.d.ts +46 -0
- package/dist/services/voice-feedback.d.ts.map +1 -0
- package/dist/services/voice-feedback.js +332 -0
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +32 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,19 @@ The following packages are automatically installed:
|
|
|
30
30
|
- `@aws-sdk/client-bedrock-runtime` - AWS Bedrock integration
|
|
31
31
|
- `microsoft-cognitiveservices-speech-sdk` - Azure Speech-to-Text
|
|
32
32
|
|
|
33
|
+
## 📚 Next.js Integration
|
|
34
|
+
|
|
35
|
+
For Next.js projects, we recommend using **server-side API routes** for secure credential management:
|
|
36
|
+
|
|
37
|
+
📖 **[Complete Next.js Setup Guide →](./NEXT_JS_SETUP.md)**
|
|
38
|
+
|
|
39
|
+
The guide covers:
|
|
40
|
+
|
|
41
|
+
- ✅ Pages Router (Next.js 12 and below)
|
|
42
|
+
- ✅ App Router (Next.js 13+)
|
|
43
|
+
- ✅ Secure server-side token handling
|
|
44
|
+
- ✅ Troubleshooting common errors
|
|
45
|
+
|
|
33
46
|
## 🎯 Quick Start
|
|
34
47
|
|
|
35
48
|
### Basic Setup
|
|
@@ -858,7 +871,6 @@ console.log("New zoom:", result.info.newZoom);
|
|
|
858
871
|
**Solutions:**
|
|
859
872
|
|
|
860
873
|
1. **Check XML file exists:**
|
|
861
|
-
|
|
862
874
|
- File should be in `public/` folder
|
|
863
875
|
- Verify URL: `http://localhost:3000/navigation-pages.xml`
|
|
864
876
|
|
|
@@ -874,7 +886,6 @@ console.log("New zoom:", result.info.newZoom);
|
|
|
874
886
|
```
|
|
875
887
|
|
|
876
888
|
3. **Check browser console:**
|
|
877
|
-
|
|
878
889
|
- Look for `[VoiceNavigation]` messages
|
|
879
890
|
- Should see: "✓ Page registry initialized with X pages"
|
|
880
891
|
|
|
@@ -946,7 +957,6 @@ console.log("New zoom:", result.info.newZoom);
|
|
|
946
957
|
```
|
|
947
958
|
|
|
948
959
|
2. **Use temporary credentials:**
|
|
949
|
-
|
|
950
960
|
- AWS Cognito for user-specific tokens
|
|
951
961
|
- STS for temporary access keys
|
|
952
962
|
- API Gateway with authorization
|
|
@@ -1001,14 +1011,12 @@ console.log("New zoom:", result.info.newZoom);
|
|
|
1001
1011
|
⚠️ **Important Security Notes:**
|
|
1002
1012
|
|
|
1003
1013
|
1. **Credential Protection:**
|
|
1004
|
-
|
|
1005
1014
|
- Never hardcode AWS/Azure credentials
|
|
1006
1015
|
- Use environment variables
|
|
1007
1016
|
- Rotate keys regularly
|
|
1008
1017
|
- Use least-privilege IAM policies
|
|
1009
1018
|
|
|
1010
1019
|
2. **Production Setup:**
|
|
1011
|
-
|
|
1012
1020
|
- Implement AWS Cognito for temporary credentials
|
|
1013
1021
|
- Use API Gateway for request authorization
|
|
1014
1022
|
- Proxy sensitive operations through backend
|
package/dist/actions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAEhB,aAAa,EACb,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAEhB,aAAa,EACb,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AA4DhE,eAAO,MAAM,eAAe,GAAI,UAAU,YAAY,KAAG,IAOxD,CAAC;AAKF,eAAO,MAAM,eAAe,QAAO,YAAY,GAAG,IAEjD,CAAC;AAgGF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAS5C,CAAC;AAKF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAM1C,CAAC;AAKF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,MAElD,CAAC;AAKF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,gBAAgB,EACxB,UAAS,aAAkB,KAC1B,YAg7BF,CAAC;AAKF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,KAAG,mBAAmB,GAAG,IAiCtE,CAAC;AAKF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MAAM,IAAI,gBAY1D,CAAC"}
|
package/dist/actions.js
CHANGED
|
@@ -28,9 +28,12 @@ const CORE_ACTIONS = new Set([
|
|
|
28
28
|
"quit",
|
|
29
29
|
"tab_next",
|
|
30
30
|
"tab_back",
|
|
31
|
+
"focus_next",
|
|
32
|
+
"focus_previous",
|
|
31
33
|
"click",
|
|
32
34
|
"enter",
|
|
33
35
|
"submit",
|
|
36
|
+
"activate",
|
|
34
37
|
"move_left",
|
|
35
38
|
"move_right",
|
|
36
39
|
"move_up",
|
|
@@ -66,6 +69,46 @@ const notifyNavigationIntent = (context, target) => {
|
|
|
66
69
|
handler.prepareForNavigation({ target });
|
|
67
70
|
}
|
|
68
71
|
};
|
|
72
|
+
const getFullscreenElement = () => {
|
|
73
|
+
const doc = document;
|
|
74
|
+
return (doc.fullscreenElement ||
|
|
75
|
+
doc.webkitFullscreenElement ||
|
|
76
|
+
doc.mozFullScreenElement ||
|
|
77
|
+
doc.msFullscreenElement ||
|
|
78
|
+
null);
|
|
79
|
+
};
|
|
80
|
+
const requestFullscreen = (element) => {
|
|
81
|
+
const el = element;
|
|
82
|
+
if (el.requestFullscreen) {
|
|
83
|
+
return el.requestFullscreen();
|
|
84
|
+
}
|
|
85
|
+
else if (el.webkitRequestFullscreen) {
|
|
86
|
+
return el.webkitRequestFullscreen();
|
|
87
|
+
}
|
|
88
|
+
else if (el.mozRequestFullScreen) {
|
|
89
|
+
return el.mozRequestFullScreen();
|
|
90
|
+
}
|
|
91
|
+
else if (el.msRequestFullscreen) {
|
|
92
|
+
return el.msRequestFullscreen();
|
|
93
|
+
}
|
|
94
|
+
return Promise.reject(new Error("Fullscreen API not available"));
|
|
95
|
+
};
|
|
96
|
+
const exitFullscreen = () => {
|
|
97
|
+
const doc = document;
|
|
98
|
+
if (doc.exitFullscreen) {
|
|
99
|
+
return doc.exitFullscreen();
|
|
100
|
+
}
|
|
101
|
+
else if (doc.webkitExitFullscreen) {
|
|
102
|
+
return doc.webkitExitFullscreen();
|
|
103
|
+
}
|
|
104
|
+
else if (doc.mozCancelFullScreen) {
|
|
105
|
+
return doc.mozCancelFullScreen();
|
|
106
|
+
}
|
|
107
|
+
else if (doc.msExitFullscreen) {
|
|
108
|
+
return doc.msExitFullscreen();
|
|
109
|
+
}
|
|
110
|
+
return Promise.reject(new Error("Fullscreen API not available"));
|
|
111
|
+
};
|
|
69
112
|
const ensureZoomBaseline = () => {
|
|
70
113
|
const root = document.documentElement;
|
|
71
114
|
if (!root.dataset.navigateZoom) {
|
|
@@ -184,26 +227,29 @@ export const performAgentAction = (action, context = {}) => {
|
|
|
184
227
|
case "go_back": {
|
|
185
228
|
if (typeof window !== "undefined" && window.history.length > 1) {
|
|
186
229
|
notifyNavigationIntent(context);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
info.
|
|
230
|
+
console.log("Preparing to navigate back");
|
|
231
|
+
info.navigated = false;
|
|
232
|
+
info.pendingNavigation = true;
|
|
233
|
+
info.navigationType = "back";
|
|
190
234
|
performed = true;
|
|
191
235
|
}
|
|
192
236
|
break;
|
|
193
237
|
}
|
|
194
238
|
case "go_forward": {
|
|
195
239
|
notifyNavigationIntent(context);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
info.
|
|
240
|
+
console.log("Preparing to navigate forward");
|
|
241
|
+
info.navigated = false;
|
|
242
|
+
info.pendingNavigation = true;
|
|
243
|
+
info.navigationType = "forward";
|
|
199
244
|
performed = true;
|
|
200
245
|
break;
|
|
201
246
|
}
|
|
202
247
|
case "reload_page": {
|
|
203
248
|
notifyNavigationIntent(context, window.location.href);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
info.
|
|
249
|
+
console.log("Preparing to reload page");
|
|
250
|
+
info.reloaded = false;
|
|
251
|
+
info.pendingNavigation = true;
|
|
252
|
+
info.navigationType = "reload";
|
|
207
253
|
performed = true;
|
|
208
254
|
break;
|
|
209
255
|
}
|
|
@@ -280,31 +326,40 @@ export const performAgentAction = (action, context = {}) => {
|
|
|
280
326
|
break;
|
|
281
327
|
}
|
|
282
328
|
case "toggle_fullscreen": {
|
|
283
|
-
const element =
|
|
329
|
+
const element = getFullscreenElement();
|
|
284
330
|
if (element) {
|
|
285
|
-
|
|
286
|
-
.
|
|
287
|
-
|
|
288
|
-
|
|
331
|
+
exitFullscreen()
|
|
332
|
+
.then(() => {
|
|
333
|
+
console.log("Exited fullscreen");
|
|
334
|
+
})
|
|
335
|
+
.catch((error) => {
|
|
336
|
+
console.warn("Failed to exit fullscreen", error);
|
|
337
|
+
});
|
|
289
338
|
info.fullscreen = false;
|
|
290
339
|
performed = true;
|
|
291
340
|
}
|
|
292
|
-
else
|
|
293
|
-
document.documentElement
|
|
294
|
-
.
|
|
295
|
-
|
|
296
|
-
|
|
341
|
+
else {
|
|
342
|
+
requestFullscreen(document.documentElement)
|
|
343
|
+
.then(() => {
|
|
344
|
+
console.log("Entered fullscreen");
|
|
345
|
+
})
|
|
346
|
+
.catch((error) => {
|
|
347
|
+
console.warn("Failed to enter fullscreen", error);
|
|
348
|
+
});
|
|
297
349
|
info.fullscreen = true;
|
|
298
350
|
performed = true;
|
|
299
351
|
}
|
|
300
352
|
break;
|
|
301
353
|
}
|
|
302
354
|
case "exit_fullscreen": {
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
.
|
|
306
|
-
|
|
307
|
-
|
|
355
|
+
if (getFullscreenElement()) {
|
|
356
|
+
exitFullscreen()
|
|
357
|
+
.then(() => {
|
|
358
|
+
console.log("Exited fullscreen");
|
|
359
|
+
})
|
|
360
|
+
.catch((error) => {
|
|
361
|
+
console.warn("Failed to exit fullscreen", error);
|
|
362
|
+
});
|
|
308
363
|
info.fullscreen = false;
|
|
309
364
|
performed = true;
|
|
310
365
|
}
|
|
@@ -417,15 +472,99 @@ export const performAgentAction = (action, context = {}) => {
|
|
|
417
472
|
}
|
|
418
473
|
break;
|
|
419
474
|
}
|
|
475
|
+
case "focus_next": {
|
|
476
|
+
const focusableElements = Array.from(document.querySelectorAll('a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])')).filter((el) => {
|
|
477
|
+
const rect = el.getBoundingClientRect();
|
|
478
|
+
return (!el.hasAttribute("disabled") &&
|
|
479
|
+
rect.width > 0 &&
|
|
480
|
+
rect.height > 0 &&
|
|
481
|
+
window.getComputedStyle(el).visibility !== "hidden");
|
|
482
|
+
});
|
|
483
|
+
const currentIndex = focusableElements.indexOf(document.activeElement);
|
|
484
|
+
const nextIndex = (currentIndex + 1) % focusableElements.length;
|
|
485
|
+
if (focusableElements[nextIndex]) {
|
|
486
|
+
focusableElements[nextIndex].focus();
|
|
487
|
+
focusableElements[nextIndex].scrollIntoView({
|
|
488
|
+
behavior: "smooth",
|
|
489
|
+
block: "center",
|
|
490
|
+
});
|
|
491
|
+
console.log("Shifted focus to next element");
|
|
492
|
+
info.focused = true;
|
|
493
|
+
info.direction = "next";
|
|
494
|
+
info.elementTag = focusableElements[nextIndex].tagName;
|
|
495
|
+
performed = true;
|
|
496
|
+
}
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "focus_previous": {
|
|
500
|
+
const focusableElements = Array.from(document.querySelectorAll('a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])')).filter((el) => {
|
|
501
|
+
const rect = el.getBoundingClientRect();
|
|
502
|
+
return (!el.hasAttribute("disabled") &&
|
|
503
|
+
rect.width > 0 &&
|
|
504
|
+
rect.height > 0 &&
|
|
505
|
+
window.getComputedStyle(el).visibility !== "hidden");
|
|
506
|
+
});
|
|
507
|
+
const currentIndex = focusableElements.indexOf(document.activeElement);
|
|
508
|
+
const prevIndex = currentIndex <= 0 ? focusableElements.length - 1 : currentIndex - 1;
|
|
509
|
+
if (focusableElements[prevIndex]) {
|
|
510
|
+
focusableElements[prevIndex].focus();
|
|
511
|
+
focusableElements[prevIndex].scrollIntoView({
|
|
512
|
+
behavior: "smooth",
|
|
513
|
+
block: "center",
|
|
514
|
+
});
|
|
515
|
+
console.log("Shifted focus to previous element");
|
|
516
|
+
info.focused = true;
|
|
517
|
+
info.direction = "previous";
|
|
518
|
+
info.elementTag = focusableElements[prevIndex].tagName;
|
|
519
|
+
performed = true;
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
420
523
|
case "click":
|
|
421
524
|
case "enter":
|
|
422
|
-
case "submit":
|
|
525
|
+
case "submit":
|
|
526
|
+
case "activate": {
|
|
423
527
|
const activeElement = document.activeElement;
|
|
424
528
|
if (activeElement && activeElement !== document.body) {
|
|
425
|
-
activeElement
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
529
|
+
if (activeElement instanceof HTMLInputElement ||
|
|
530
|
+
activeElement instanceof HTMLTextAreaElement) {
|
|
531
|
+
const form = activeElement.closest("form");
|
|
532
|
+
if (form) {
|
|
533
|
+
form.requestSubmit();
|
|
534
|
+
console.log(`Submitted form for ${activeElement.tagName}`);
|
|
535
|
+
info.submitted = true;
|
|
536
|
+
performed = true;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
const enterEvent = new KeyboardEvent("keydown", {
|
|
540
|
+
key: "Enter",
|
|
541
|
+
code: "Enter",
|
|
542
|
+
keyCode: 13,
|
|
543
|
+
which: 13,
|
|
544
|
+
bubbles: true,
|
|
545
|
+
cancelable: true,
|
|
546
|
+
});
|
|
547
|
+
activeElement.dispatchEvent(enterEvent);
|
|
548
|
+
const keypressEvent = new KeyboardEvent("keypress", {
|
|
549
|
+
key: "Enter",
|
|
550
|
+
code: "Enter",
|
|
551
|
+
keyCode: 13,
|
|
552
|
+
which: 13,
|
|
553
|
+
bubbles: true,
|
|
554
|
+
cancelable: true,
|
|
555
|
+
});
|
|
556
|
+
activeElement.dispatchEvent(keypressEvent);
|
|
557
|
+
console.log(`Triggered Enter key on ${activeElement.tagName} input`);
|
|
558
|
+
info.keyPressed = "Enter";
|
|
559
|
+
performed = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
activeElement.click();
|
|
564
|
+
console.log(`Clicked on focused element: ${activeElement.tagName}`);
|
|
565
|
+
info.clicked = true;
|
|
566
|
+
performed = true;
|
|
567
|
+
}
|
|
429
568
|
}
|
|
430
569
|
else {
|
|
431
570
|
console.log("No element is currently focused");
|
|
@@ -753,9 +892,10 @@ export const performAgentAction = (action, context = {}) => {
|
|
|
753
892
|
const page = globalPageRegistry.getPageById(pageId);
|
|
754
893
|
if (page) {
|
|
755
894
|
notifyNavigationIntent(context, page.path);
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
info.
|
|
895
|
+
console.log(`Preparing to navigate to ${page.name} (${page.path})`);
|
|
896
|
+
info.navigated = false;
|
|
897
|
+
info.pendingNavigation = true;
|
|
898
|
+
info.navigationUrl = page.path;
|
|
759
899
|
info.pageName = page.name;
|
|
760
900
|
info.pagePath = page.path;
|
|
761
901
|
performed = true;
|
|
@@ -23,6 +23,9 @@ export declare class MicrophoneHandler {
|
|
|
23
23
|
private audioContext;
|
|
24
24
|
private audioContextUnlocked;
|
|
25
25
|
private activeCallbacks;
|
|
26
|
+
private lastRecognitionTime;
|
|
27
|
+
private recognitionHealthCheckInterval;
|
|
28
|
+
private isRecovering;
|
|
26
29
|
constructor(config: MicrophoneConfig);
|
|
27
30
|
hasMicrophonePermission(): boolean;
|
|
28
31
|
hasUnlockedAudioContext(): boolean;
|
|
@@ -38,6 +41,10 @@ export declare class MicrophoneHandler {
|
|
|
38
41
|
getAggregatedText(): string;
|
|
39
42
|
scheduleSilenceCheck(callbacks?: NavigationCallbacks): void;
|
|
40
43
|
rescheduleSilenceCheck(): void;
|
|
44
|
+
private verifyRecognizerHealth;
|
|
45
|
+
private startHealthCheck;
|
|
46
|
+
private stopHealthCheck;
|
|
47
|
+
private recoverFromStalledRecognizer;
|
|
41
48
|
sendToBedrockAgent(text: string): Promise<AgentActionResponse>;
|
|
42
49
|
disposeRecognizer(): void;
|
|
43
50
|
createRecognizer(): Promise<sdk.SpeechRecognizer>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"microphone-handler.d.ts","sourceRoot":"","sources":["../src/microphone-handler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,GAAG,MAAM,wCAAwC,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,UAAU,gBAAgB;IACxB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAqC;IACvD,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,oBAAoB,CAAkB;IAC9C,OAAO,CAAC,eAAe,CAA2B;
|
|
1
|
+
{"version":3,"file":"microphone-handler.d.ts","sourceRoot":"","sources":["../src/microphone-handler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,GAAG,MAAM,wCAAwC,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,UAAU,gBAAgB;IACxB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAqC;IACvD,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,oBAAoB,CAAkB;IAC9C,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,mBAAmB,CAAa;IACxC,OAAO,CAAC,8BAA8B,CAEtB;IAChB,OAAO,CAAC,YAAY,CAAkB;gBAE1B,MAAM,EAAE,gBAAgB;IAUpC,uBAAuB,IAAI,OAAO;IAIlC,uBAAuB,IAAI,OAAO;IAO5B,kBAAkB,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAoB1D,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBzC,kBAAkB,CAAC,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAwDzD,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC;IA2BrD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYnC,wBAAwB,IAAI,IAAI;IAO1B,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBnC,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBzC,iBAAiB,IAAI,IAAI;IAUzB,iBAAiB,IAAI,MAAM;IAO3B,oBAAoB,CAAC,SAAS,GAAE,mBAAwB,GAAG,IAAI;IAiB/D,sBAAsB,IAAI,IAAI;IAmB9B,OAAO,CAAC,sBAAsB;IA4B9B,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,eAAe;YAUT,4BAA4B;IAoCpC,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAiBpE,iBAAiB,IAAI,IAAI;IAqCnB,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAmBjD,cAAc,CAAC,SAAS,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoJrE,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;WAwCzB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC;CAYvD"}
|
|
@@ -11,6 +11,9 @@ export class MicrophoneHandler {
|
|
|
11
11
|
this.audioContext = null;
|
|
12
12
|
this.audioContextUnlocked = false;
|
|
13
13
|
this.activeCallbacks = {};
|
|
14
|
+
this.lastRecognitionTime = 0;
|
|
15
|
+
this.recognitionHealthCheckInterval = null;
|
|
16
|
+
this.isRecovering = false;
|
|
14
17
|
this.azureSpeechService = config.azureSpeechService;
|
|
15
18
|
this.bedrockService = config.bedrockService;
|
|
16
19
|
this.language = config.language || "en-IN";
|
|
@@ -167,9 +170,73 @@ export class MicrophoneHandler {
|
|
|
167
170
|
}
|
|
168
171
|
rescheduleSilenceCheck() {
|
|
169
172
|
if (!this.isRecording) {
|
|
173
|
+
console.warn("[MicrophoneHandler] Cannot reschedule - not recording");
|
|
170
174
|
return;
|
|
171
175
|
}
|
|
176
|
+
if (!this.recognizer) {
|
|
177
|
+
console.error("[MicrophoneHandler] Cannot reschedule - no recognizer");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
console.log("[MicrophoneHandler] Rescheduling silence check");
|
|
172
181
|
this.scheduleSilenceCheck(this.activeCallbacks);
|
|
182
|
+
this.verifyRecognizerHealth();
|
|
183
|
+
}
|
|
184
|
+
verifyRecognizerHealth() {
|
|
185
|
+
if (!this.isRecording || !this.recognizer) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
const timeSinceLastRecognition = now - this.lastRecognitionTime;
|
|
190
|
+
if (timeSinceLastRecognition > 15000) {
|
|
191
|
+
console.warn("[MicrophoneHandler] Recognizer may be stalled - last event was", timeSinceLastRecognition / 1000, "seconds ago. Triggering recovery.");
|
|
192
|
+
void this.recoverFromStalledRecognizer();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
console.log("[MicrophoneHandler] Recognizer health OK - last event", (timeSinceLastRecognition / 1000).toFixed(1), "seconds ago");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
startHealthCheck() {
|
|
199
|
+
this.stopHealthCheck();
|
|
200
|
+
this.recognitionHealthCheckInterval = setInterval(() => {
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const timeSinceLastRecognition = now - this.lastRecognitionTime;
|
|
203
|
+
if (this.isRecording && timeSinceLastRecognition > 30000) {
|
|
204
|
+
console.warn("[MicrophoneHandler] Recognizer appears stalled. Last recognition:", timeSinceLastRecognition / 1000, "seconds ago");
|
|
205
|
+
this.recoverFromStalledRecognizer();
|
|
206
|
+
}
|
|
207
|
+
}, 30000);
|
|
208
|
+
}
|
|
209
|
+
stopHealthCheck() {
|
|
210
|
+
if (this.recognitionHealthCheckInterval) {
|
|
211
|
+
clearInterval(this.recognitionHealthCheckInterval);
|
|
212
|
+
this.recognitionHealthCheckInterval = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async recoverFromStalledRecognizer() {
|
|
216
|
+
if (this.isRecovering) {
|
|
217
|
+
console.log("[MicrophoneHandler] Recovery already in progress, skipping");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.isRecovering = true;
|
|
221
|
+
console.log("[MicrophoneHandler] Attempting to recover from stalled recognizer");
|
|
222
|
+
this.disposeRecognizer();
|
|
223
|
+
this.isRecording = false;
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
225
|
+
try {
|
|
226
|
+
const restarted = await this.startRecording(this.activeCallbacks);
|
|
227
|
+
if (restarted) {
|
|
228
|
+
console.log("[MicrophoneHandler] Successfully recovered recognizer");
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.error("[MicrophoneHandler] Failed to recover recognizer");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error("[MicrophoneHandler] Error during recovery:", error);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
this.isRecovering = false;
|
|
239
|
+
}
|
|
173
240
|
}
|
|
174
241
|
async sendToBedrockAgent(text) {
|
|
175
242
|
if (!text || !text.trim()) {
|
|
@@ -185,16 +252,17 @@ export class MicrophoneHandler {
|
|
|
185
252
|
}
|
|
186
253
|
}
|
|
187
254
|
disposeRecognizer() {
|
|
255
|
+
this.stopHealthCheck();
|
|
188
256
|
if (this.recognizer) {
|
|
189
257
|
try {
|
|
190
258
|
this.recognizer.stopContinuousRecognitionAsync(() => {
|
|
191
|
-
console.log("Recognizer stopped");
|
|
259
|
+
console.log("[MicrophoneHandler] Recognizer stopped");
|
|
192
260
|
if (this.recognizer) {
|
|
193
261
|
this.recognizer.close();
|
|
194
262
|
this.recognizer = null;
|
|
195
263
|
}
|
|
196
264
|
}, (error) => {
|
|
197
|
-
console.error("Error stopping recognizer:", error);
|
|
265
|
+
console.error("[MicrophoneHandler] Error stopping recognizer:", error);
|
|
198
266
|
if (this.recognizer) {
|
|
199
267
|
this.recognizer.close();
|
|
200
268
|
this.recognizer = null;
|
|
@@ -202,7 +270,7 @@ export class MicrophoneHandler {
|
|
|
202
270
|
});
|
|
203
271
|
}
|
|
204
272
|
catch (error) {
|
|
205
|
-
console.error("Error disposing recognizer:", error);
|
|
273
|
+
console.error("[MicrophoneHandler] Error disposing recognizer:", error);
|
|
206
274
|
if (this.recognizer) {
|
|
207
275
|
this.recognizer.close();
|
|
208
276
|
this.recognizer = null;
|
|
@@ -221,7 +289,11 @@ export class MicrophoneHandler {
|
|
|
221
289
|
}
|
|
222
290
|
async startRecording(callbacks = {}) {
|
|
223
291
|
if (this.isRecording) {
|
|
224
|
-
console.warn("Already recording");
|
|
292
|
+
console.warn("[MicrophoneHandler] Already recording");
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (this.isRecovering) {
|
|
296
|
+
console.warn("[MicrophoneHandler] Recovery in progress, cannot start");
|
|
225
297
|
return false;
|
|
226
298
|
}
|
|
227
299
|
this.activeCallbacks = callbacks;
|
|
@@ -262,57 +334,69 @@ export class MicrophoneHandler {
|
|
|
262
334
|
return false;
|
|
263
335
|
}
|
|
264
336
|
this.recognizer.recognizing = (_s, e) => {
|
|
265
|
-
|
|
266
|
-
|
|
337
|
+
this.lastRecognitionTime = Date.now();
|
|
338
|
+
if (e.result.text && this.activeCallbacks.onPartial) {
|
|
339
|
+
this.activeCallbacks.onPartial(e.result.text);
|
|
267
340
|
}
|
|
268
341
|
};
|
|
269
342
|
this.recognizer.recognized = (_s, e) => {
|
|
343
|
+
this.lastRecognitionTime = Date.now();
|
|
270
344
|
if (e.result.reason === sdk.ResultReason.RecognizedSpeech) {
|
|
271
345
|
const text = e.result.text;
|
|
272
346
|
if (text) {
|
|
347
|
+
console.log("[MicrophoneHandler] Recognized speech:", text);
|
|
273
348
|
this.transcriptionBuffer.push(text);
|
|
274
|
-
if (
|
|
275
|
-
|
|
349
|
+
if (this.activeCallbacks.onSegment) {
|
|
350
|
+
this.activeCallbacks.onSegment(text, {
|
|
276
351
|
duration: e.result.duration,
|
|
277
352
|
offset: e.result.offset,
|
|
278
353
|
});
|
|
279
354
|
}
|
|
280
|
-
this.scheduleSilenceCheck(
|
|
355
|
+
this.scheduleSilenceCheck(this.activeCallbacks);
|
|
281
356
|
}
|
|
282
357
|
}
|
|
283
358
|
else if (e.result.reason === sdk.ResultReason.NoMatch) {
|
|
284
|
-
|
|
359
|
+
this.lastRecognitionTime = Date.now();
|
|
360
|
+
console.log("[MicrophoneHandler] No speech recognized (NoMatch)");
|
|
285
361
|
}
|
|
286
362
|
};
|
|
287
363
|
this.recognizer.canceled = (_s, e) => {
|
|
288
|
-
console.error("Recognition canceled:", e.reason, e.errorDetails);
|
|
364
|
+
console.error("[MicrophoneHandler] Recognition canceled:", e.reason, e.errorDetails);
|
|
289
365
|
this.isRecording = false;
|
|
290
|
-
|
|
291
|
-
|
|
366
|
+
this.stopHealthCheck();
|
|
367
|
+
if (this.activeCallbacks.onError) {
|
|
368
|
+
this.activeCallbacks.onError(new Error(`Recognition canceled: ${e.reason} - ${e.errorDetails}`));
|
|
292
369
|
}
|
|
293
370
|
};
|
|
294
371
|
this.recognizer.sessionStopped = (_s, _e) => {
|
|
295
|
-
console.log("Session stopped");
|
|
372
|
+
console.log("[MicrophoneHandler] Session stopped unexpectedly by SDK");
|
|
296
373
|
this.isRecording = false;
|
|
374
|
+
this.stopHealthCheck();
|
|
375
|
+
if (this.activeCallbacks.onError) {
|
|
376
|
+
this.activeCallbacks.onError(new Error("Speech recognition session stopped unexpectedly"));
|
|
377
|
+
}
|
|
297
378
|
};
|
|
298
379
|
this.recognizer.startContinuousRecognitionAsync(() => {
|
|
299
|
-
console.log("Continuous recognition started");
|
|
380
|
+
console.log("[MicrophoneHandler] Continuous recognition started");
|
|
300
381
|
this.isRecording = true;
|
|
301
382
|
this.audioContextUnlocked = true;
|
|
302
|
-
this.
|
|
383
|
+
this.lastRecognitionTime = Date.now();
|
|
384
|
+
this.startHealthCheck();
|
|
385
|
+
this.scheduleSilenceCheck(this.activeCallbacks);
|
|
303
386
|
}, (error) => {
|
|
304
|
-
console.error("Failed to start recognition:", error);
|
|
387
|
+
console.error("[MicrophoneHandler] Failed to start recognition:", error);
|
|
305
388
|
this.isRecording = false;
|
|
306
|
-
if (
|
|
307
|
-
|
|
389
|
+
if (this.activeCallbacks.onError) {
|
|
390
|
+
this.activeCallbacks.onError(new Error(`Failed to start recognition: ${error}`));
|
|
308
391
|
}
|
|
309
392
|
});
|
|
310
393
|
return true;
|
|
311
394
|
}
|
|
312
395
|
async stopRecording() {
|
|
313
396
|
this.clearSilenceTimer();
|
|
397
|
+
this.stopHealthCheck();
|
|
314
398
|
if (!this.isRecording || !this.recognizer) {
|
|
315
|
-
console.warn("Not currently recording");
|
|
399
|
+
console.warn("[MicrophoneHandler] Not currently recording");
|
|
316
400
|
return this.getAggregatedText();
|
|
317
401
|
}
|
|
318
402
|
return new Promise((resolve) => {
|
|
@@ -321,13 +405,13 @@ export class MicrophoneHandler {
|
|
|
321
405
|
return;
|
|
322
406
|
}
|
|
323
407
|
this.recognizer.stopContinuousRecognitionAsync(() => {
|
|
324
|
-
console.log("Recognition stopped successfully");
|
|
408
|
+
console.log("[MicrophoneHandler] Recognition stopped successfully");
|
|
325
409
|
this.isRecording = false;
|
|
326
410
|
const finalText = this.getAggregatedText();
|
|
327
411
|
this.disposeRecognizer();
|
|
328
412
|
resolve(finalText);
|
|
329
413
|
}, (error) => {
|
|
330
|
-
console.error("Error stopping recognition:", error);
|
|
414
|
+
console.error("[MicrophoneHandler] Error stopping recognition:", error);
|
|
331
415
|
this.isRecording = false;
|
|
332
416
|
const finalText = this.getAggregatedText();
|
|
333
417
|
this.disposeRecognizer();
|
|
@@ -4,6 +4,7 @@ export declare class VoiceNavigationController {
|
|
|
4
4
|
private microphoneHandler;
|
|
5
5
|
private azureSpeechService;
|
|
6
6
|
private bedrockService;
|
|
7
|
+
private voiceFeedbackService;
|
|
7
8
|
private pageRegistry;
|
|
8
9
|
private ui;
|
|
9
10
|
private lastTranscript;
|
|
@@ -15,6 +16,7 @@ export declare class VoiceNavigationController {
|
|
|
15
16
|
private vectorSearchService;
|
|
16
17
|
private pagesInitialized;
|
|
17
18
|
private uiInitialized;
|
|
19
|
+
private isSyncingButtonState;
|
|
18
20
|
constructor(config: NavigationConfig);
|
|
19
21
|
private initializeUIAsync;
|
|
20
22
|
private shouldAutoStart;
|
|
@@ -28,7 +30,10 @@ export declare class VoiceNavigationController {
|
|
|
28
30
|
start(options?: {
|
|
29
31
|
reason?: "auto" | "user";
|
|
30
32
|
}): Promise<void>;
|
|
31
|
-
stop(
|
|
33
|
+
stop(options?: {
|
|
34
|
+
triggeredByUser?: boolean;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
private syncCustomButtonState;
|
|
32
37
|
private handlePartial;
|
|
33
38
|
private handleSegment;
|
|
34
39
|
private handleSilence;
|
|
@@ -37,6 +42,9 @@ export declare class VoiceNavigationController {
|
|
|
37
42
|
private processTranscript;
|
|
38
43
|
private handleAgentResult;
|
|
39
44
|
setLanguage(language: string): void;
|
|
45
|
+
setVoiceFeedback(enabled: boolean): void;
|
|
46
|
+
isVoiceFeedbackEnabled(): boolean;
|
|
47
|
+
private provideActionFeedback;
|
|
40
48
|
private executeVectorSearchAction;
|
|
41
49
|
private normalizeSearchQuery;
|
|
42
50
|
private normalizeIndianSchemeName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation-controller.d.ts","sourceRoot":"","sources":["../src/navigation-controller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,
|
|
1
|
+
{"version":3,"file":"navigation-controller.d.ts","sourceRoot":"","sources":["../src/navigation-controller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAMjB,MAAM,YAAY,CAAC;AA4DpB,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,qBAAqB,CAAkB;IAC/C,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,+BAA+B,CAAkB;IACzD,OAAO,CAAC,wBAAwB,CAA6B;IAC7D,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAkB;gBAElC,MAAM,EAAE,gBAAgB;YAqJtB,iBAAiB;IAiD/B,OAAO,CAAC,eAAe;IAoBhB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAepC,oBAAoB,CAAC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,IAAI;YAOtD,eAAe;IAmE7B,OAAO,CAAC,kBAAkB;YASZ,eAAe;IA8BhB,KAAK,CAChB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO,GACzC,OAAO,CAAC,IAAI,CAAC;IAsFH,IAAI,CACf,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,OAAO,CAAC,IAAI,CAAC;IAoDhB,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;YAWP,aAAa;YAOb,wBAAwB;IA2CtC,OAAO,CAAC,WAAW;YAuBL,iBAAiB;YAwBjB,iBAAiB;IAoRxB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQnC,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAOxC,sBAAsB,IAAI,OAAO;YAO1B,qBAAqB;YAgFrB,yBAAyB;IAmMvC,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,yBAAyB;IAoCjC,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,4BAA4B;IAiB7B,OAAO,IAAI,IAAI;IAgBtB,OAAO,CAAC,kBAAkB;IA8B1B,OAAO,CAAC,kBAAkB;IAwD1B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,uBAAuB;IAqD/B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,wBAAwB;CAmBjC"}
|