@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 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
@@ -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;AAyDhE,eAAO,MAAM,eAAe,GAAI,UAAU,YAAY,KAAG,IAOxD,CAAC;AAKF,eAAO,MAAM,eAAe,QAAO,YAAY,GAAG,IAEjD,CAAC;AAkCF,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,YA4yBF,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"}
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
- window.history.back();
188
- console.log("Navigated back");
189
- info.navigated = true;
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
- window.history.forward();
197
- console.log("Navigated forward");
198
- info.navigated = true;
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
- window.location.reload();
205
- console.log("Page reloaded");
206
- info.reloaded = true;
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 = document.fullscreenElement;
329
+ const element = getFullscreenElement();
284
330
  if (element) {
285
- document
286
- .exitFullscreen()
287
- .then(() => console.log("Exited fullscreen"))
288
- .catch((error) => console.warn("Failed to exit fullscreen", error));
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 if (document.documentElement.requestFullscreen) {
293
- document.documentElement
294
- .requestFullscreen()
295
- .then(() => console.log("Entered fullscreen"))
296
- .catch((error) => console.warn("Failed to enter fullscreen", error));
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 (document.fullscreenElement) {
304
- document
305
- .exitFullscreen()
306
- .then(() => console.log("Exited fullscreen"))
307
- .catch((error) => console.warn("Failed to exit fullscreen", error));
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.click();
426
- console.log(`Clicked on focused element: ${activeElement.tagName}`);
427
- info.clicked = true;
428
- performed = true;
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
- window.location.href = page.path;
757
- console.log(`Navigating to ${page.name} (${page.path})`);
758
- info.navigated = true;
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;gBAEtC,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;IAUxB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAiBpE,iBAAiB,IAAI,IAAI;IAgCnB,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAmBjD,cAAc,CAAC,SAAS,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoHrE,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;WAoCzB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC;CAYvD"}
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
- if (e.result.text && callbacks.onPartial) {
266
- callbacks.onPartial(e.result.text);
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 (callbacks.onSegment) {
275
- callbacks.onSegment(text, {
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(callbacks);
355
+ this.scheduleSilenceCheck(this.activeCallbacks);
281
356
  }
282
357
  }
283
358
  else if (e.result.reason === sdk.ResultReason.NoMatch) {
284
- console.log("No speech recognized");
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
- if (callbacks.onError) {
291
- callbacks.onError(new Error(`Recognition canceled: ${e.reason} - ${e.errorDetails}`));
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.scheduleSilenceCheck(callbacks);
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 (callbacks.onError) {
307
- callbacks.onError(new Error(`Failed to start recognition: ${error}`));
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(): Promise<void>;
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,EAKjB,MAAM,YAAY,CAAC;AA2DpB,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,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;YAmItB,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;IA8HxB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;YAI5B,yBAAyB;IAgMvC,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"}
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"}