@myscheme/voice-navigation-sdk 0.1.8 → 0.1.10

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.
Files changed (48) hide show
  1. package/README.md +130 -17
  2. package/dist/actions.d.ts +1 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +51 -1
  5. package/dist/index.d.ts +38 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +158 -1
  8. package/dist/languages.d.ts +17 -0
  9. package/dist/languages.d.ts.map +1 -0
  10. package/dist/languages.js +86 -0
  11. package/dist/microphone-handler.d.ts +9 -2
  12. package/dist/microphone-handler.d.ts.map +1 -1
  13. package/dist/microphone-handler.js +104 -6
  14. package/dist/navigation-controller.d.ts +5 -0
  15. package/dist/navigation-controller.d.ts.map +1 -1
  16. package/dist/navigation-controller.js +149 -35
  17. package/dist/server/azure-speech-handler.d.ts +3 -2
  18. package/dist/server/azure-speech-handler.d.ts.map +1 -1
  19. package/dist/server/azure-speech-handler.js +26 -2
  20. package/dist/server/bedrock-embedding-handler.d.ts +2 -1
  21. package/dist/server/bedrock-embedding-handler.d.ts.map +1 -1
  22. package/dist/server/bedrock-embedding-handler.js +73 -19
  23. package/dist/server/bedrock-handler.d.ts +2 -1
  24. package/dist/server/bedrock-handler.d.ts.map +1 -1
  25. package/dist/server/bedrock-handler.js +124 -19
  26. package/dist/server/index.d.ts +5 -0
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/index.js +3 -0
  29. package/dist/server/opensearch-env-handler.d.ts +2 -1
  30. package/dist/server/opensearch-env-handler.d.ts.map +1 -1
  31. package/dist/server/opensearch-env-handler.js +23 -53
  32. package/dist/server/sarvam-stt-relay.d.ts +8 -0
  33. package/dist/server/sarvam-stt-relay.d.ts.map +1 -0
  34. package/dist/server/sarvam-stt-relay.js +111 -0
  35. package/dist/server/security.d.ts +26 -0
  36. package/dist/server/security.d.ts.map +1 -0
  37. package/dist/server/security.js +89 -0
  38. package/dist/services/bedrock.d.ts.map +1 -1
  39. package/dist/services/bedrock.js +13 -0
  40. package/dist/services/sarvam-speech.d.ts +47 -0
  41. package/dist/services/sarvam-speech.d.ts.map +1 -0
  42. package/dist/services/sarvam-speech.js +418 -0
  43. package/dist/services/voice-feedback.d.ts +7 -2
  44. package/dist/services/voice-feedback.d.ts.map +1 -1
  45. package/dist/services/voice-feedback.js +45 -5
  46. package/dist/types.d.ts +11 -0
  47. package/dist/types.d.ts.map +1 -1
  48. package/package.json +4 -2
@@ -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;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"}
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,EACV,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,UAAU,gBAAgB;IACxB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,mBAAmB,CAA6B;IACxD,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;IACtC,OAAO,CAAC,uBAAuB,CAAkB;gBAErC,MAAM,EAAE,gBAAgB;IAqBpC,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;IAoBnC,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;IAwCpC,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAiBpE,iBAAiB,IAAI,IAAI;IAiDnB,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAmBjD,cAAc,CAAC,SAAS,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC;YA8J7D,oBAAoB;IAqD5B,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;WAgEzB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC;CAYvD"}
@@ -14,7 +14,16 @@ export class MicrophoneHandler {
14
14
  this.lastRecognitionTime = 0;
15
15
  this.recognitionHealthCheckInterval = null;
16
16
  this.isRecovering = false;
17
- this.azureSpeechService = config.azureSpeechService;
17
+ this.isStoppingIntentionally = false;
18
+ this.speechProvider = config.speechProvider ?? "sarvam";
19
+ this.azureSpeechService = config.azureSpeechService ?? null;
20
+ this.sarvamSpeechService = config.sarvamSpeechService ?? null;
21
+ if (this.speechProvider === "azure" && !this.azureSpeechService) {
22
+ throw new Error("Azure speech service is required for azure provider");
23
+ }
24
+ if (this.speechProvider === "sarvam" && !this.sarvamSpeechService) {
25
+ throw new Error("Sarvam speech service is required for sarvam provider");
26
+ }
18
27
  this.bedrockService = config.bedrockService;
19
28
  this.language = config.language || "en-IN";
20
29
  this.silenceTimeout = config.silenceTimeout || 2000;
@@ -126,6 +135,9 @@ export class MicrophoneHandler {
126
135
  this.transcriptionBuffer = [];
127
136
  }
128
137
  async fetchAzureToken() {
138
+ if (!this.azureSpeechService) {
139
+ throw new Error("Azure speech service is not configured");
140
+ }
129
141
  try {
130
142
  const response = await this.azureSpeechService.fetchToken();
131
143
  this.token = response.token;
@@ -173,7 +185,7 @@ export class MicrophoneHandler {
173
185
  console.warn("[MicrophoneHandler] Cannot reschedule - not recording");
174
186
  return;
175
187
  }
176
- if (!this.recognizer) {
188
+ if (this.speechProvider === "azure" && !this.recognizer) {
177
189
  console.error("[MicrophoneHandler] Cannot reschedule - no recognizer");
178
190
  return;
179
191
  }
@@ -219,6 +231,9 @@ export class MicrophoneHandler {
219
231
  }
220
232
  this.isRecovering = true;
221
233
  console.log("[MicrophoneHandler] Attempting to recover from stalled recognizer");
234
+ if (this.speechProvider === "sarvam" && this.sarvamSpeechService) {
235
+ await this.sarvamSpeechService.stopListening();
236
+ }
222
237
  this.disposeRecognizer();
223
238
  this.isRecording = false;
224
239
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -253,6 +268,14 @@ export class MicrophoneHandler {
253
268
  }
254
269
  disposeRecognizer() {
255
270
  this.stopHealthCheck();
271
+ if (this.speechProvider === "sarvam") {
272
+ if (this.sarvamSpeechService) {
273
+ void this.sarvamSpeechService.stopListening().catch((error) => {
274
+ console.error("[MicrophoneHandler] Error stopping Sarvam STT:", error);
275
+ });
276
+ }
277
+ return;
278
+ }
256
279
  if (this.recognizer) {
257
280
  try {
258
281
  this.recognizer.stopContinuousRecognitionAsync(() => {
@@ -296,6 +319,7 @@ export class MicrophoneHandler {
296
319
  console.warn("[MicrophoneHandler] Recovery in progress, cannot start");
297
320
  return false;
298
321
  }
322
+ this.isStoppingIntentionally = false;
299
323
  this.activeCallbacks = callbacks;
300
324
  if (!this.hasPermission) {
301
325
  const granted = await this.requestMicrophonePermission();
@@ -306,6 +330,9 @@ export class MicrophoneHandler {
306
330
  return false;
307
331
  }
308
332
  }
333
+ if (this.speechProvider === "sarvam") {
334
+ return this.startSarvamRecording(callbacks);
335
+ }
309
336
  const sdkReady = await this.ensureSpeechSdk();
310
337
  if (!sdkReady) {
311
338
  if (callbacks.onError) {
@@ -369,12 +396,16 @@ export class MicrophoneHandler {
369
396
  }
370
397
  };
371
398
  this.recognizer.sessionStopped = (_s, _e) => {
372
- console.log("[MicrophoneHandler] Session stopped unexpectedly by SDK");
399
+ if (this.isStoppingIntentionally) {
400
+ console.log("[MicrophoneHandler] Session stopped (user-initiated)");
401
+ this.isRecording = false;
402
+ this.stopHealthCheck();
403
+ return;
404
+ }
405
+ console.warn("[MicrophoneHandler] Session stopped unexpectedly by SDK - attempting auto-restart");
373
406
  this.isRecording = false;
374
407
  this.stopHealthCheck();
375
- if (this.activeCallbacks.onError) {
376
- this.activeCallbacks.onError(new Error("Speech recognition session stopped unexpectedly"));
377
- }
408
+ void this.recoverFromStalledRecognizer();
378
409
  };
379
410
  this.recognizer.startContinuousRecognitionAsync(() => {
380
411
  console.log("[MicrophoneHandler] Continuous recognition started");
@@ -392,27 +423,94 @@ export class MicrophoneHandler {
392
423
  });
393
424
  return true;
394
425
  }
426
+ async startSarvamRecording(callbacks) {
427
+ if (!this.sarvamSpeechService) {
428
+ callbacks.onError?.(new Error("Sarvam speech service not configured"));
429
+ return false;
430
+ }
431
+ this.resetTranscriptionBuffer();
432
+ try {
433
+ await this.sarvamSpeechService.startListening(this.language, {
434
+ shouldSendAudio: () => this.isRecording,
435
+ onPartial: (text) => {
436
+ this.lastRecognitionTime = Date.now();
437
+ this.activeCallbacks.onPartial?.(text);
438
+ },
439
+ onFinal: (text) => {
440
+ this.lastRecognitionTime = Date.now();
441
+ this.transcriptionBuffer.push(text);
442
+ this.activeCallbacks.onSegment?.(text, {});
443
+ this.scheduleSilenceCheck(this.activeCallbacks);
444
+ },
445
+ onSpeechStart: () => {
446
+ this.lastRecognitionTime = Date.now();
447
+ },
448
+ onSpeechEnd: () => {
449
+ this.lastRecognitionTime = Date.now();
450
+ this.scheduleSilenceCheck(this.activeCallbacks);
451
+ },
452
+ onError: (error) => {
453
+ this.isRecording = false;
454
+ this.stopHealthCheck();
455
+ this.activeCallbacks.onError?.(error);
456
+ },
457
+ });
458
+ this.isRecording = true;
459
+ this.audioContextUnlocked = true;
460
+ this.lastRecognitionTime = Date.now();
461
+ this.startHealthCheck();
462
+ this.scheduleSilenceCheck(this.activeCallbacks);
463
+ return true;
464
+ }
465
+ catch (error) {
466
+ this.isRecording = false;
467
+ callbacks.onError?.(error);
468
+ return false;
469
+ }
470
+ }
395
471
  async stopRecording() {
396
472
  this.clearSilenceTimer();
397
473
  this.stopHealthCheck();
474
+ this.isStoppingIntentionally = true;
475
+ if (this.speechProvider === "sarvam") {
476
+ if (!this.isRecording || !this.sarvamSpeechService) {
477
+ console.warn("[MicrophoneHandler] Not currently recording");
478
+ this.isStoppingIntentionally = false;
479
+ return this.getAggregatedText();
480
+ }
481
+ try {
482
+ await this.sarvamSpeechService.stopListening();
483
+ }
484
+ catch (error) {
485
+ console.error("[MicrophoneHandler] Error stopping Sarvam STT:", error);
486
+ }
487
+ this.isRecording = false;
488
+ this.isStoppingIntentionally = false;
489
+ const finalText = this.getAggregatedText();
490
+ return finalText;
491
+ }
398
492
  if (!this.isRecording || !this.recognizer) {
399
493
  console.warn("[MicrophoneHandler] Not currently recording");
494
+ this.isStoppingIntentionally = false;
400
495
  return this.getAggregatedText();
401
496
  }
402
497
  return new Promise((resolve) => {
403
498
  if (!this.recognizer) {
499
+ this.isStoppingIntentionally = false;
404
500
  resolve(this.getAggregatedText());
405
501
  return;
406
502
  }
407
503
  this.recognizer.stopContinuousRecognitionAsync(() => {
408
504
  console.log("[MicrophoneHandler] Recognition stopped successfully");
409
505
  this.isRecording = false;
506
+ this.isStoppingIntentionally = false;
410
507
  const finalText = this.getAggregatedText();
411
508
  this.disposeRecognizer();
412
509
  resolve(finalText);
413
510
  }, (error) => {
414
511
  console.error("[MicrophoneHandler] Error stopping recognition:", error);
415
512
  this.isRecording = false;
513
+ this.isStoppingIntentionally = false;
416
514
  const finalText = this.getAggregatedText();
417
515
  this.disposeRecognizer();
418
516
  resolve(finalText);
@@ -1,8 +1,10 @@
1
1
  import type { NavigationConfig } from "./types.js";
2
2
  export declare class VoiceNavigationController {
3
3
  private config;
4
+ private speechProvider;
4
5
  private microphoneHandler;
5
6
  private azureSpeechService;
7
+ private sarvamSpeechService;
6
8
  private bedrockService;
7
9
  private voiceFeedbackService;
8
10
  private pageRegistry;
@@ -17,6 +19,8 @@ export declare class VoiceNavigationController {
17
19
  private pagesInitialized;
18
20
  private uiInitialized;
19
21
  private isSyncingButtonState;
22
+ private hasNavigatedAway;
23
+ private removeNavigationListener;
20
24
  constructor(config: NavigationConfig);
21
25
  private initializeUIAsync;
22
26
  private shouldAutoStart;
@@ -33,6 +37,7 @@ export declare class VoiceNavigationController {
33
37
  stop(options?: {
34
38
  triggeredByUser?: boolean;
35
39
  }): Promise<void>;
40
+ private trackNavigationAway;
36
41
  private syncCustomButtonState;
37
42
  private handlePartial;
38
43
  private handleSegment;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"navigation-controller.d.ts","sourceRoot":"","sources":["../src/navigation-controller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAOjB,MAAM,YAAY,CAAC;AA8DpB,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,mBAAmB,CAA6B;IACxD,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;IAC9C,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,wBAAwB,CAA6B;gBAEjD,MAAM,EAAE,gBAAgB;YAgMtB,iBAAiB;IAoD/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;IAuFH,IAAI,CACf,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,OAAO,CAAC,IAAI,CAAC;IAqEhB,OAAO,CAAC,mBAAmB;IAoD3B,OAAO,CAAC,qBAAqB;IA4C7B,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;YAWP,aAAa;YAOb,wBAAwB;IA2CtC,OAAO,CAAC,WAAW;YAuBL,iBAAiB;YAwBjB,iBAAiB;IA6RxB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IASnC,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"}
@@ -1,9 +1,11 @@
1
1
  import { MicrophoneHandler } from "./microphone-handler.js";
2
2
  import { AzureSpeechService } from "./services/azure-speech.js";
3
3
  import { BedrockService } from "./services/bedrock.js";
4
+ import { SarvamSpeechService } from "./services/sarvam-speech.js";
4
5
  import { PageRegistry } from "./services/page-registry.js";
5
6
  import { VoiceFeedbackService } from "./services/voice-feedback.js";
6
7
  import { loadNavigationPages } from "./services/xml-parser.js";
8
+ import { normalizeLanguageCode } from "./languages.js";
7
9
  import { VectorSearchService, } from "./services/vector-search.js";
8
10
  import { performAgentAction, formatActionLabel, extractAgentAction, setPageRegistry, } from "./actions.js";
9
11
  import { createFloatingControl, attachToCustomButton, setState, updateStatus, updateTranscript, updateActionIndicator, showError, emitEvent, } from "./ui.js";
@@ -39,21 +41,34 @@ export class VoiceNavigationController {
39
41
  this.removeAutoStartListeners = null;
40
42
  this.vectorSearchService = null;
41
43
  this.isSyncingButtonState = false;
44
+ this.hasNavigatedAway = false;
45
+ this.removeNavigationListener = null;
42
46
  const openSearchConfig = resolveOpenSearchConfig(config);
43
47
  this.config = {
44
48
  ...config,
45
49
  opensearch: openSearchConfig,
50
+ speechProvider: config.speechProvider ?? "sarvam",
46
51
  };
52
+ this.speechProvider = this.config.speechProvider ?? "sarvam";
47
53
  console.log("[Voice Navigation Controller] Constructor called with UI config:", this.config.ui);
48
- const hasAzureEndpoint = !!this.config.azure?.tokenEndpoint;
49
- const hasAzureCredentials = !!(this.config.azure?.subscriptionKey && this.config.azure?.region);
50
- if (!this.config.azure) {
51
- this.config.azure = { tokenEndpoint: "/api/speech/token" };
52
- console.log("[VoiceNavigation] Auto-configured Azure endpoint: /api/speech/token");
54
+ if (this.speechProvider === "azure") {
55
+ const hasAzureEndpoint = !!this.config.azure?.tokenEndpoint;
56
+ const hasAzureCredentials = !!(this.config.azure?.subscriptionKey && this.config.azure?.region);
57
+ if (!this.config.azure) {
58
+ this.config.azure = { tokenEndpoint: "/api/speech/token" };
59
+ console.log("[VoiceNavigation] Auto-configured Azure endpoint: /api/speech/token");
60
+ }
61
+ else if (!hasAzureEndpoint && !hasAzureCredentials) {
62
+ this.config.azure.tokenEndpoint = "/api/speech/token";
63
+ console.log("[VoiceNavigation] Auto-configured Azure endpoint: /api/speech/token");
64
+ }
53
65
  }
54
- else if (!hasAzureEndpoint && !hasAzureCredentials) {
55
- this.config.azure.tokenEndpoint = "/api/speech/token";
56
- console.log("[VoiceNavigation] Auto-configured Azure endpoint: /api/speech/token");
66
+ if (this.speechProvider === "sarvam") {
67
+ this.config.sarvam = {
68
+ ...(this.config.sarvam ?? {}),
69
+ ttsUrl: this.config.sarvam?.ttsUrl ?? "/api/speech/sarvam",
70
+ sttUrl: this.config.sarvam?.sttUrl ?? "/api/speech/sarvam-stt",
71
+ };
57
72
  }
58
73
  const hasAwsEndpoint = !!this.config.aws?.bedrockEndpoint;
59
74
  const hasAwsCredentials = !!(this.config.aws?.accessKeyId &&
@@ -78,11 +93,29 @@ export class VoiceNavigationController {
78
93
  openSearchConfig.suggestEndpoint = "/api/opensearch/suggest";
79
94
  console.log("[VoiceNavigation] Auto-configured OpenSearch endpoints: /api/opensearch/search, /api/opensearch/suggest");
80
95
  }
81
- this.azureSpeechService = new AzureSpeechService({
82
- tokenEndpoint: this.config.azure.tokenEndpoint,
83
- subscriptionKey: this.config.azure.subscriptionKey,
84
- region: this.config.azure.region,
85
- });
96
+ this.azureSpeechService = null;
97
+ this.sarvamSpeechService = null;
98
+ if (this.speechProvider === "azure") {
99
+ if (!this.config.azure) {
100
+ throw new Error("Azure configuration is required for azure provider");
101
+ }
102
+ this.azureSpeechService = new AzureSpeechService({
103
+ tokenEndpoint: this.config.azure.tokenEndpoint,
104
+ subscriptionKey: this.config.azure.subscriptionKey,
105
+ region: this.config.azure.region,
106
+ });
107
+ }
108
+ else {
109
+ this.sarvamSpeechService = new SarvamSpeechService({
110
+ ttsUrl: this.config.sarvam?.ttsUrl,
111
+ sttUrl: this.config.sarvam?.sttUrl,
112
+ sttModel: this.config.sarvam?.sttModel,
113
+ ttsModel: this.config.sarvam?.ttsModel,
114
+ speaker: this.config.sarvam?.speaker,
115
+ sttSampleRate: this.config.sarvam?.sttSampleRate,
116
+ ttsSampleRate: this.config.sarvam?.ttsSampleRate,
117
+ });
118
+ }
86
119
  this.bedrockService = new BedrockService({
87
120
  bedrockEndpoint: this.config.aws.bedrockEndpoint,
88
121
  embeddingEndpoint: this.config.aws.embeddingEndpoint,
@@ -97,16 +130,20 @@ export class VoiceNavigationController {
97
130
  this.pageRegistry = new PageRegistry([]);
98
131
  this.pagesInitialized = this.initializePages();
99
132
  this.microphoneHandler = new MicrophoneHandler({
100
- azureSpeechService: this.azureSpeechService,
133
+ speechProvider: this.speechProvider,
134
+ azureSpeechService: this.azureSpeechService ?? undefined,
135
+ sarvamSpeechService: this.sarvamSpeechService ?? undefined,
101
136
  bedrockService: this.bedrockService,
102
- language: this.config.language || "en-IN",
137
+ language: normalizeLanguageCode(this.config.language),
103
138
  });
104
139
  const voiceFeedbackEnabled = this.config.voiceFeedback?.enabled !== false;
105
140
  console.log("[VoiceNavigation] Initializing voice feedback service - enabled:", voiceFeedbackEnabled);
106
141
  this.voiceFeedbackService = new VoiceFeedbackService({
107
142
  enabled: voiceFeedbackEnabled,
108
- language: this.config.language || "en-IN",
109
- azureSpeechService: this.azureSpeechService,
143
+ language: normalizeLanguageCode(this.config.language),
144
+ speechProvider: this.speechProvider,
145
+ azureSpeechService: this.azureSpeechService ?? undefined,
146
+ sarvamSpeechService: this.sarvamSpeechService ?? undefined,
110
147
  bedrockService: this.bedrockService,
111
148
  });
112
149
  console.log("[VoiceNavigation] Voice feedback service initialized successfully");
@@ -120,6 +157,9 @@ export class VoiceNavigationController {
120
157
  }
121
158
  }
122
159
  this.resumeAfterNavigation = this.consumeResumeState();
160
+ if (this.resumeAfterNavigation) {
161
+ this.hasNavigatedAway = true;
162
+ }
123
163
  this.uiInitialized = this.initializeUIAsync();
124
164
  if (this.resumeAfterNavigation) {
125
165
  void this.start({ reason: "auto" });
@@ -138,7 +178,7 @@ export class VoiceNavigationController {
138
178
  customButtonSelector,
139
179
  buttonPlacement,
140
180
  });
141
- if (!showDefaultButton && customButtonSelector) {
181
+ if (customButtonSelector) {
142
182
  console.log(`[Voice Navigation] Attempting to use custom button: ${customButtonSelector}`);
143
183
  try {
144
184
  this.ui = await attachToCustomButton(customButtonSelector);
@@ -146,11 +186,11 @@ export class VoiceNavigationController {
146
186
  }
147
187
  catch (error) {
148
188
  console.error("Failed to attach to custom button:", error);
149
- console.warn("Falling back to default button due to custom button error");
189
+ console.warn("Falling back to default button - custom button selector was not found in the DOM");
150
190
  this.ui = createFloatingControl(buttonPlacement);
151
191
  }
152
192
  }
153
- else if (!showDefaultButton && !customButtonSelector) {
193
+ else if (!showDefaultButton) {
154
194
  console.warn("showDefaultButton is false but no customButtonSelector provided. Creating default button.");
155
195
  this.ui = createFloatingControl(buttonPlacement);
156
196
  }
@@ -309,6 +349,7 @@ export class VoiceNavigationController {
309
349
  });
310
350
  if (started) {
311
351
  this.clearAutoStartFallback();
352
+ this.trackNavigationAway();
312
353
  setState(this.ui, "listening");
313
354
  emitEvent("state-change", { state: "listening" });
314
355
  }
@@ -344,8 +385,13 @@ export class VoiceNavigationController {
344
385
  emitEvent("state-change", { state: "idle" });
345
386
  updateTranscript(this.ui, "");
346
387
  if (!triggeredByUser && this.ui.isCustomButton) {
347
- console.log("[VoiceNavigation] Programmatically clicking custom button to sync external UI state");
348
- this.syncCustomButtonState();
388
+ if (!this.hasNavigatedAway) {
389
+ console.log("[VoiceNavigation] Programmatically clicking custom button to sync external UI state");
390
+ this.syncCustomButtonState();
391
+ }
392
+ else {
393
+ console.log("[VoiceNavigation] Skipping custom button click - user has navigated away from start page");
394
+ }
349
395
  }
350
396
  }
351
397
  catch (error) {
@@ -354,27 +400,80 @@ export class VoiceNavigationController {
354
400
  setState(this.ui, "error");
355
401
  emitEvent("error", { error });
356
402
  if (!triggeredByUser && this.ui.isCustomButton) {
357
- console.log("[VoiceNavigation] Programmatically clicking custom button after error");
358
- this.syncCustomButtonState();
403
+ if (!this.hasNavigatedAway) {
404
+ console.log("[VoiceNavigation] Programmatically clicking custom button after error");
405
+ this.syncCustomButtonState();
406
+ }
407
+ else {
408
+ console.log("[VoiceNavigation] Skipping custom button click after error - user has navigated away from start page");
409
+ }
359
410
  }
360
411
  }
361
412
  }
413
+ trackNavigationAway() {
414
+ if (typeof window === "undefined") {
415
+ return;
416
+ }
417
+ if (this.removeNavigationListener) {
418
+ this.removeNavigationListener();
419
+ this.removeNavigationListener = null;
420
+ }
421
+ const startUrl = window.location.href;
422
+ const markNavigatedAway = () => {
423
+ if (window.location.href !== startUrl) {
424
+ this.hasNavigatedAway = true;
425
+ console.log("[VoiceNavigation] User navigated away from start page - custom button sync disabled");
426
+ if (this.removeNavigationListener) {
427
+ this.removeNavigationListener();
428
+ this.removeNavigationListener = null;
429
+ }
430
+ }
431
+ };
432
+ window.addEventListener("popstate", markNavigatedAway);
433
+ const originalPushState = history.pushState.bind(history);
434
+ const originalReplaceState = history.replaceState.bind(history);
435
+ history.pushState = (...args) => {
436
+ originalPushState(...args);
437
+ markNavigatedAway();
438
+ };
439
+ history.replaceState = (...args) => {
440
+ originalReplaceState(...args);
441
+ markNavigatedAway();
442
+ };
443
+ this.removeNavigationListener = () => {
444
+ window.removeEventListener("popstate", markNavigatedAway);
445
+ history.pushState = originalPushState;
446
+ history.replaceState = originalReplaceState;
447
+ };
448
+ }
362
449
  syncCustomButtonState() {
363
- if (!this.ui.isCustomButton) {
450
+ if (!this.ui.isCustomButton || !this.ui.button) {
364
451
  return;
365
452
  }
453
+ console.log("[VoiceNavigation] Syncing custom button state");
366
454
  try {
367
455
  this.isSyncingButtonState = true;
456
+ this.ui.button.setAttribute("aria-pressed", "false");
457
+ this.ui.button.setAttribute("data-voice-state", "idle");
458
+ this.ui.button.classList.remove("active", "recording", "listening");
459
+ this.ui.button.classList.add("inactive", "stopped");
460
+ console.log("[VoiceNavigation] Updated button attributes");
461
+ const event = new CustomEvent("voiceNavigation:stopped", {
462
+ bubbles: true,
463
+ detail: { stopped: true },
464
+ });
465
+ this.ui.button.dispatchEvent(event);
466
+ console.log("[VoiceNavigation] Dispatched voiceNavigation:stopped event");
368
467
  this.ui.button.click();
369
- console.log("[VoiceNavigation] Custom button clicked to sync external UI state");
370
- }
371
- catch (error) {
372
- console.error("[VoiceNavigation] Failed to click custom button:", error);
373
- }
374
- finally {
468
+ console.log("[VoiceNavigation] Clicked button once to sync state");
375
469
  setTimeout(() => {
376
470
  this.isSyncingButtonState = false;
377
- }, 100);
471
+ console.log("[VoiceNavigation] Button state sync complete");
472
+ }, 500);
473
+ }
474
+ catch (error) {
475
+ console.error("[VoiceNavigation] Error syncing button state:", error);
476
+ this.isSyncingButtonState = false;
378
477
  }
379
478
  }
380
479
  handlePartial(text) {
@@ -491,6 +590,8 @@ export class VoiceNavigationController {
491
590
  await this.provideActionFeedback(action, actionResult, originalText);
492
591
  console.log("[VoiceNavigation] Search feedback complete, now navigating to:", actionResult.info.navigationUrl);
493
592
  if (actionResult.info.navigationUrl) {
593
+ if (this.ui.isCustomButton)
594
+ this.syncCustomButtonState();
494
595
  window.location.href = actionResult.info.navigationUrl;
495
596
  actionResult.info.navigated = true;
496
597
  actionResult.info.pendingNavigation = false;
@@ -499,6 +600,8 @@ export class VoiceNavigationController {
499
600
  catch (err) {
500
601
  console.error("[VoiceNavigation] Voice feedback error (search):", err);
501
602
  if (actionResult.info.navigationUrl) {
603
+ if (this.ui.isCustomButton)
604
+ this.syncCustomButtonState();
502
605
  window.location.href = actionResult.info.navigationUrl;
503
606
  }
504
607
  }
@@ -538,7 +641,7 @@ export class VoiceNavigationController {
538
641
  }
539
642
  const actionResult = performAgentAction(action, {
540
643
  handler: this,
541
- onStop: () => this.stop(),
644
+ onStop: () => this.stop({ triggeredByUser: false }),
542
645
  transcript: originalText,
543
646
  parameters: {
544
647
  text: actionData.text || query,
@@ -562,24 +665,32 @@ export class VoiceNavigationController {
562
665
  if (actionResult.info?.pendingNavigation) {
563
666
  if (actionResult.info.navigationUrl) {
564
667
  console.log("[VoiceNavigation] Feedback complete, now navigating to:", actionResult.info.navigationUrl);
668
+ if (this.ui.isCustomButton)
669
+ this.syncCustomButtonState();
565
670
  window.location.href = actionResult.info.navigationUrl;
566
671
  actionResult.info.navigated = true;
567
672
  actionResult.info.pendingNavigation = false;
568
673
  }
569
674
  else if (actionResult.info.navigationType === "back") {
570
675
  console.log("[VoiceNavigation] Feedback complete, navigating back");
676
+ if (this.ui.isCustomButton)
677
+ this.syncCustomButtonState();
571
678
  window.history.back();
572
679
  actionResult.info.navigated = true;
573
680
  actionResult.info.pendingNavigation = false;
574
681
  }
575
682
  else if (actionResult.info.navigationType === "forward") {
576
683
  console.log("[VoiceNavigation] Feedback complete, navigating forward");
684
+ if (this.ui.isCustomButton)
685
+ this.syncCustomButtonState();
577
686
  window.history.forward();
578
687
  actionResult.info.navigated = true;
579
688
  actionResult.info.pendingNavigation = false;
580
689
  }
581
690
  else if (actionResult.info.navigationType === "reload") {
582
691
  console.log("[VoiceNavigation] Feedback complete, reloading page");
692
+ if (this.ui.isCustomButton)
693
+ this.syncCustomButtonState();
583
694
  window.location.reload();
584
695
  actionResult.info.reloaded = true;
585
696
  actionResult.info.pendingNavigation = false;
@@ -589,6 +700,8 @@ export class VoiceNavigationController {
589
700
  catch (err) {
590
701
  console.error("[VoiceNavigation] Voice feedback error (critical action):", err);
591
702
  if (actionResult.info?.pendingNavigation) {
703
+ if (this.ui.isCustomButton)
704
+ this.syncCustomButtonState();
592
705
  if (actionResult.info.navigationUrl) {
593
706
  console.log("[VoiceNavigation] Feedback failed, but proceeding with navigation:", actionResult.info.navigationUrl);
594
707
  window.location.href = actionResult.info.navigationUrl;
@@ -641,8 +754,9 @@ export class VoiceNavigationController {
641
754
  });
642
755
  }
643
756
  setLanguage(language) {
644
- this.microphoneHandler.setLanguage(language);
645
- this.voiceFeedbackService.setLanguage(language);
757
+ const normalized = normalizeLanguageCode(language);
758
+ this.microphoneHandler.setLanguage(normalized);
759
+ this.voiceFeedbackService.setLanguage(normalized);
646
760
  }
647
761
  setVoiceFeedback(enabled) {
648
762
  this.voiceFeedbackService.setEnabled(enabled);
@@ -1,4 +1,5 @@
1
- type NextApiRequest = {
1
+ import { type GateRequestLike } from "./security.js";
2
+ type NextApiRequest = GateRequestLike & {
2
3
  method?: string;
3
4
  body?: unknown;
4
5
  };
@@ -8,6 +9,6 @@ type NextApiResponse = {
8
9
  setHeader: (name: string, value: string | string[]) => void;
9
10
  };
10
11
  export declare function azureSpeechTokenHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
11
- export declare function POST(_request: Request): Promise<Response>;
12
+ export declare function POST(request: Request): Promise<Response>;
12
13
  export {};
13
14
  //# sourceMappingURL=azure-speech-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"azure-speech-handler.d.ts","sourceRoot":"","sources":["../../src/server/azure-speech-handler.ts"],"names":[],"mappings":"AAwBA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC1C,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7D,CAAC;AA6EF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAMD,wBAAsB,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmB/D"}
1
+ {"version":3,"file":"azure-speech-handler.d.ts","sourceRoot":"","sources":["../../src/server/azure-speech-handler.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGnE,KAAK,cAAc,GAAG,eAAe,GAAG;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC1C,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7D,CAAC;AA6EF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAMD,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqC9D"}
@@ -1,3 +1,4 @@
1
+ import { evaluateGate } from "./security.js";
1
2
  async function getAzureSpeechToken() {
2
3
  const key = process.env.AZURE_SPEECH_KEY;
3
4
  const region = process.env.AZURE_SPEECH_REGION;
@@ -49,6 +50,12 @@ export async function azureSpeechTokenHandler(req, res) {
49
50
  res.status(405).json({ error: "Method not allowed" });
50
51
  return;
51
52
  }
53
+ const gate = evaluateGate(req, { maxRequests: 20, windowMs: 60 * 1000, bucket: "aztoken" });
54
+ if (!gate.ok) {
55
+ res.setHeader("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate");
56
+ res.status(gate.status ?? 403).json({ error: gate.error });
57
+ return;
58
+ }
52
59
  const result = await getAzureSpeechToken();
53
60
  res.setHeader("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate");
54
61
  if (result.success && result.data) {
@@ -58,12 +65,29 @@ export async function azureSpeechTokenHandler(req, res) {
58
65
  res.status(result.statusCode).json(result.error);
59
66
  }
60
67
  }
61
- export async function POST(_request) {
62
- const result = await getAzureSpeechToken();
68
+ export async function POST(request) {
63
69
  const headers = {
64
70
  "Content-Type": "application/json",
65
71
  "Cache-Control": "no-store, no-cache, max-age=0, must-revalidate",
66
72
  };
73
+ const gateReq = {
74
+ method: request.method,
75
+ headers: {
76
+ origin: request.headers.get("origin") ?? undefined,
77
+ referer: request.headers.get("referer") ?? undefined,
78
+ host: request.headers.get("host") ?? undefined,
79
+ "x-forwarded-for": request.headers.get("x-forwarded-for") ?? undefined,
80
+ "x-real-ip": request.headers.get("x-real-ip") ?? undefined,
81
+ },
82
+ };
83
+ const gate = evaluateGate(gateReq, { maxRequests: 20, windowMs: 60 * 1000, bucket: "aztoken" });
84
+ if (!gate.ok) {
85
+ return new Response(JSON.stringify({ error: gate.error }), {
86
+ status: gate.status ?? 403,
87
+ headers,
88
+ });
89
+ }
90
+ const result = await getAzureSpeechToken();
67
91
  if (result.success && result.data) {
68
92
  return new Response(JSON.stringify(result.data), {
69
93
  status: result.statusCode,