@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 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,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;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;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
- if (e.result.text && callbacks.onPartial) {
266
- callbacks.onPartial(e.result.text);
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 (callbacks.onSegment) {
275
- callbacks.onSegment(text, {
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(callbacks);
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
- if (callbacks.onError) {
291
- callbacks.onError(new Error(`Recognition canceled: ${e.reason} - ${e.errorDetails}`));
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.scheduleSilenceCheck(callbacks);
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 (callbacks.onError) {
307
- callbacks.onError(new Error(`Failed to start recognition: ${error}`));
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,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;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"}