@myscheme/voice-navigation-sdk 0.1.6 → 0.1.7
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 +6 -0
- package/dist/microphone-handler.d.ts.map +1 -1
- package/dist/microphone-handler.js +87 -22
- package/dist/navigation-controller.d.ts +4 -0
- package/dist/navigation-controller.d.ts.map +1 -1
- package/dist/navigation-controller.js +162 -1
- 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 +122 -0
- 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/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,9 @@ export declare class MicrophoneHandler {
|
|
|
38
41
|
getAggregatedText(): string;
|
|
39
42
|
scheduleSilenceCheck(callbacks?: NavigationCallbacks): void;
|
|
40
43
|
rescheduleSilenceCheck(): void;
|
|
44
|
+
private startHealthCheck;
|
|
45
|
+
private stopHealthCheck;
|
|
46
|
+
private recoverFromStalledRecognizer;
|
|
41
47
|
sendToBedrockAgent(text: string): Promise<AgentActionResponse>;
|
|
42
48
|
disposeRecognizer(): void;
|
|
43
49
|
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;IAgB9B,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;IA4IrE,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,10 +170,59 @@ 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);
|
|
173
182
|
}
|
|
183
|
+
startHealthCheck() {
|
|
184
|
+
this.stopHealthCheck();
|
|
185
|
+
this.recognitionHealthCheckInterval = setInterval(() => {
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
const timeSinceLastRecognition = now - this.lastRecognitionTime;
|
|
188
|
+
if (this.isRecording && timeSinceLastRecognition > 60000) {
|
|
189
|
+
console.warn("[MicrophoneHandler] Recognizer appears stalled. Last recognition:", timeSinceLastRecognition / 1000, "seconds ago");
|
|
190
|
+
this.recoverFromStalledRecognizer();
|
|
191
|
+
}
|
|
192
|
+
}, 30000);
|
|
193
|
+
}
|
|
194
|
+
stopHealthCheck() {
|
|
195
|
+
if (this.recognitionHealthCheckInterval) {
|
|
196
|
+
clearInterval(this.recognitionHealthCheckInterval);
|
|
197
|
+
this.recognitionHealthCheckInterval = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async recoverFromStalledRecognizer() {
|
|
201
|
+
if (this.isRecovering) {
|
|
202
|
+
console.log("[MicrophoneHandler] Recovery already in progress, skipping");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.isRecovering = true;
|
|
206
|
+
console.log("[MicrophoneHandler] Attempting to recover from stalled recognizer");
|
|
207
|
+
this.disposeRecognizer();
|
|
208
|
+
this.isRecording = false;
|
|
209
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
210
|
+
try {
|
|
211
|
+
const restarted = await this.startRecording(this.activeCallbacks);
|
|
212
|
+
if (restarted) {
|
|
213
|
+
console.log("[MicrophoneHandler] Successfully recovered recognizer");
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
console.error("[MicrophoneHandler] Failed to recover recognizer");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
console.error("[MicrophoneHandler] Error during recovery:", error);
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
this.isRecovering = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
174
226
|
async sendToBedrockAgent(text) {
|
|
175
227
|
if (!text || !text.trim()) {
|
|
176
228
|
return { action: "unknown" };
|
|
@@ -185,16 +237,17 @@ export class MicrophoneHandler {
|
|
|
185
237
|
}
|
|
186
238
|
}
|
|
187
239
|
disposeRecognizer() {
|
|
240
|
+
this.stopHealthCheck();
|
|
188
241
|
if (this.recognizer) {
|
|
189
242
|
try {
|
|
190
243
|
this.recognizer.stopContinuousRecognitionAsync(() => {
|
|
191
|
-
console.log("Recognizer stopped");
|
|
244
|
+
console.log("[MicrophoneHandler] Recognizer stopped");
|
|
192
245
|
if (this.recognizer) {
|
|
193
246
|
this.recognizer.close();
|
|
194
247
|
this.recognizer = null;
|
|
195
248
|
}
|
|
196
249
|
}, (error) => {
|
|
197
|
-
console.error("Error stopping recognizer:", error);
|
|
250
|
+
console.error("[MicrophoneHandler] Error stopping recognizer:", error);
|
|
198
251
|
if (this.recognizer) {
|
|
199
252
|
this.recognizer.close();
|
|
200
253
|
this.recognizer = null;
|
|
@@ -202,7 +255,7 @@ export class MicrophoneHandler {
|
|
|
202
255
|
});
|
|
203
256
|
}
|
|
204
257
|
catch (error) {
|
|
205
|
-
console.error("Error disposing recognizer:", error);
|
|
258
|
+
console.error("[MicrophoneHandler] Error disposing recognizer:", error);
|
|
206
259
|
if (this.recognizer) {
|
|
207
260
|
this.recognizer.close();
|
|
208
261
|
this.recognizer = null;
|
|
@@ -221,7 +274,11 @@ export class MicrophoneHandler {
|
|
|
221
274
|
}
|
|
222
275
|
async startRecording(callbacks = {}) {
|
|
223
276
|
if (this.isRecording) {
|
|
224
|
-
console.warn("Already recording");
|
|
277
|
+
console.warn("[MicrophoneHandler] Already recording");
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (this.isRecovering) {
|
|
281
|
+
console.warn("[MicrophoneHandler] Recovery in progress, cannot start");
|
|
225
282
|
return false;
|
|
226
283
|
}
|
|
227
284
|
this.activeCallbacks = callbacks;
|
|
@@ -262,57 +319,65 @@ export class MicrophoneHandler {
|
|
|
262
319
|
return false;
|
|
263
320
|
}
|
|
264
321
|
this.recognizer.recognizing = (_s, e) => {
|
|
265
|
-
|
|
266
|
-
|
|
322
|
+
this.lastRecognitionTime = Date.now();
|
|
323
|
+
if (e.result.text && this.activeCallbacks.onPartial) {
|
|
324
|
+
this.activeCallbacks.onPartial(e.result.text);
|
|
267
325
|
}
|
|
268
326
|
};
|
|
269
327
|
this.recognizer.recognized = (_s, e) => {
|
|
328
|
+
this.lastRecognitionTime = Date.now();
|
|
270
329
|
if (e.result.reason === sdk.ResultReason.RecognizedSpeech) {
|
|
271
330
|
const text = e.result.text;
|
|
272
331
|
if (text) {
|
|
332
|
+
console.log("[MicrophoneHandler] Recognized speech:", text);
|
|
273
333
|
this.transcriptionBuffer.push(text);
|
|
274
|
-
if (
|
|
275
|
-
|
|
334
|
+
if (this.activeCallbacks.onSegment) {
|
|
335
|
+
this.activeCallbacks.onSegment(text, {
|
|
276
336
|
duration: e.result.duration,
|
|
277
337
|
offset: e.result.offset,
|
|
278
338
|
});
|
|
279
339
|
}
|
|
280
|
-
this.scheduleSilenceCheck(
|
|
340
|
+
this.scheduleSilenceCheck(this.activeCallbacks);
|
|
281
341
|
}
|
|
282
342
|
}
|
|
283
343
|
else if (e.result.reason === sdk.ResultReason.NoMatch) {
|
|
284
|
-
console.log("No speech recognized");
|
|
344
|
+
console.log("[MicrophoneHandler] No speech recognized (NoMatch)");
|
|
285
345
|
}
|
|
286
346
|
};
|
|
287
347
|
this.recognizer.canceled = (_s, e) => {
|
|
288
|
-
console.error("Recognition canceled:", e.reason, e.errorDetails);
|
|
348
|
+
console.error("[MicrophoneHandler] Recognition canceled:", e.reason, e.errorDetails);
|
|
289
349
|
this.isRecording = false;
|
|
290
|
-
|
|
291
|
-
|
|
350
|
+
this.stopHealthCheck();
|
|
351
|
+
if (this.activeCallbacks.onError) {
|
|
352
|
+
this.activeCallbacks.onError(new Error(`Recognition canceled: ${e.reason} - ${e.errorDetails}`));
|
|
292
353
|
}
|
|
293
354
|
};
|
|
294
355
|
this.recognizer.sessionStopped = (_s, _e) => {
|
|
295
|
-
console.log("Session stopped");
|
|
356
|
+
console.log("[MicrophoneHandler] Session stopped");
|
|
296
357
|
this.isRecording = false;
|
|
358
|
+
this.stopHealthCheck();
|
|
297
359
|
};
|
|
298
360
|
this.recognizer.startContinuousRecognitionAsync(() => {
|
|
299
|
-
console.log("Continuous recognition started");
|
|
361
|
+
console.log("[MicrophoneHandler] Continuous recognition started");
|
|
300
362
|
this.isRecording = true;
|
|
301
363
|
this.audioContextUnlocked = true;
|
|
302
|
-
this.
|
|
364
|
+
this.lastRecognitionTime = Date.now();
|
|
365
|
+
this.startHealthCheck();
|
|
366
|
+
this.scheduleSilenceCheck(this.activeCallbacks);
|
|
303
367
|
}, (error) => {
|
|
304
|
-
console.error("Failed to start recognition:", error);
|
|
368
|
+
console.error("[MicrophoneHandler] Failed to start recognition:", error);
|
|
305
369
|
this.isRecording = false;
|
|
306
|
-
if (
|
|
307
|
-
|
|
370
|
+
if (this.activeCallbacks.onError) {
|
|
371
|
+
this.activeCallbacks.onError(new Error(`Failed to start recognition: ${error}`));
|
|
308
372
|
}
|
|
309
373
|
});
|
|
310
374
|
return true;
|
|
311
375
|
}
|
|
312
376
|
async stopRecording() {
|
|
313
377
|
this.clearSilenceTimer();
|
|
378
|
+
this.stopHealthCheck();
|
|
314
379
|
if (!this.isRecording || !this.recognizer) {
|
|
315
|
-
console.warn("Not currently recording");
|
|
380
|
+
console.warn("[MicrophoneHandler] Not currently recording");
|
|
316
381
|
return this.getAggregatedText();
|
|
317
382
|
}
|
|
318
383
|
return new Promise((resolve) => {
|
|
@@ -321,13 +386,13 @@ export class MicrophoneHandler {
|
|
|
321
386
|
return;
|
|
322
387
|
}
|
|
323
388
|
this.recognizer.stopContinuousRecognitionAsync(() => {
|
|
324
|
-
console.log("Recognition stopped successfully");
|
|
389
|
+
console.log("[MicrophoneHandler] Recognition stopped successfully");
|
|
325
390
|
this.isRecording = false;
|
|
326
391
|
const finalText = this.getAggregatedText();
|
|
327
392
|
this.disposeRecognizer();
|
|
328
393
|
resolve(finalText);
|
|
329
394
|
}, (error) => {
|
|
330
|
-
console.error("Error stopping recognition:", error);
|
|
395
|
+
console.error("[MicrophoneHandler] Error stopping recognition:", error);
|
|
331
396
|
this.isRecording = false;
|
|
332
397
|
const finalText = this.getAggregatedText();
|
|
333
398
|
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;
|
|
@@ -37,6 +38,9 @@ export declare class VoiceNavigationController {
|
|
|
37
38
|
private processTranscript;
|
|
38
39
|
private handleAgentResult;
|
|
39
40
|
setLanguage(language: string): void;
|
|
41
|
+
setVoiceFeedback(enabled: boolean): void;
|
|
42
|
+
isVoiceFeedbackEnabled(): boolean;
|
|
43
|
+
private provideActionFeedback;
|
|
40
44
|
private executeVectorSearchAction;
|
|
41
45
|
private normalizeSearchQuery;
|
|
42
46
|
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;gBAEzB,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;IAwBhB,KAAK,CAChB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO,GACzC,OAAO,CAAC,IAAI,CAAC;IAmFH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiClC,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;YAWP,aAAa;YAOb,wBAAwB;IA2CtC,OAAO,CAAC,WAAW;YAoBL,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;IAkD/B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,wBAAwB;CAmBjC"}
|