@myscheme/voice-navigation-sdk 0.1.5 → 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.
Files changed (38) hide show
  1. package/README.md +13 -5
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +184 -44
  4. package/dist/microphone-handler.d.ts +8 -0
  5. package/dist/microphone-handler.d.ts.map +1 -1
  6. package/dist/microphone-handler.js +98 -24
  7. package/dist/navigation-controller.d.ts +4 -0
  8. package/dist/navigation-controller.d.ts.map +1 -1
  9. package/dist/navigation-controller.js +201 -7
  10. package/dist/server/azure-speech-handler.d.ts +13 -0
  11. package/dist/server/azure-speech-handler.d.ts.map +1 -0
  12. package/dist/server/azure-speech-handler.js +79 -0
  13. package/dist/server/bedrock-embedding-handler.d.ts +16 -0
  14. package/dist/server/bedrock-embedding-handler.d.ts.map +1 -0
  15. package/dist/server/bedrock-embedding-handler.js +183 -0
  16. package/dist/server/bedrock-handler.d.ts +15 -0
  17. package/dist/server/bedrock-handler.d.ts.map +1 -0
  18. package/dist/server/bedrock-handler.js +171 -0
  19. package/dist/server/index.d.ts +7 -0
  20. package/dist/server/index.d.ts.map +1 -1
  21. package/dist/server/index.js +7 -0
  22. package/dist/server/opensearch-env-handler.d.ts +22 -0
  23. package/dist/server/opensearch-env-handler.d.ts.map +1 -0
  24. package/dist/server/opensearch-env-handler.js +221 -0
  25. package/dist/services/azure-speech.d.ts +10 -2
  26. package/dist/services/azure-speech.d.ts.map +1 -1
  27. package/dist/services/azure-speech.js +66 -3
  28. package/dist/services/bedrock.d.ts +16 -4
  29. package/dist/services/bedrock.d.ts.map +1 -1
  30. package/dist/services/bedrock.js +293 -17
  31. package/dist/services/vector-search.d.ts.map +1 -1
  32. package/dist/services/vector-search.js +15 -2
  33. package/dist/services/voice-feedback.d.ts +46 -0
  34. package/dist/services/voice-feedback.d.ts.map +1 -0
  35. package/dist/services/voice-feedback.js +332 -0
  36. package/dist/types.d.ts +17 -9
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +3 -3
@@ -10,6 +10,10 @@ export class MicrophoneHandler {
10
10
  this.hasPermission = false;
11
11
  this.audioContext = null;
12
12
  this.audioContextUnlocked = false;
13
+ this.activeCallbacks = {};
14
+ this.lastRecognitionTime = 0;
15
+ this.recognitionHealthCheckInterval = null;
16
+ this.isRecovering = false;
13
17
  this.azureSpeechService = config.azureSpeechService;
14
18
  this.bedrockService = config.bedrockService;
15
19
  this.language = config.language || "en-IN";
@@ -156,13 +160,69 @@ export class MicrophoneHandler {
156
160
  }
157
161
  scheduleSilenceCheck(callbacks = {}) {
158
162
  this.clearSilenceTimer();
163
+ const callbacksToUse = Object.keys(callbacks).length > 0 ? callbacks : this.activeCallbacks;
159
164
  this.silenceTimerId = setTimeout(() => {
160
165
  console.log("Silence detected - triggering callback");
161
- if (callbacks.onSilence) {
162
- callbacks.onSilence();
166
+ if (callbacksToUse.onSilence) {
167
+ callbacksToUse.onSilence();
163
168
  }
164
169
  }, this.silenceTimeout);
165
170
  }
171
+ rescheduleSilenceCheck() {
172
+ if (!this.isRecording) {
173
+ console.warn("[MicrophoneHandler] Cannot reschedule - not recording");
174
+ return;
175
+ }
176
+ if (!this.recognizer) {
177
+ console.error("[MicrophoneHandler] Cannot reschedule - no recognizer");
178
+ return;
179
+ }
180
+ console.log("[MicrophoneHandler] Rescheduling silence check");
181
+ this.scheduleSilenceCheck(this.activeCallbacks);
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
+ }
166
226
  async sendToBedrockAgent(text) {
167
227
  if (!text || !text.trim()) {
168
228
  return { action: "unknown" };
@@ -177,16 +237,17 @@ export class MicrophoneHandler {
177
237
  }
178
238
  }
179
239
  disposeRecognizer() {
240
+ this.stopHealthCheck();
180
241
  if (this.recognizer) {
181
242
  try {
182
243
  this.recognizer.stopContinuousRecognitionAsync(() => {
183
- console.log("Recognizer stopped");
244
+ console.log("[MicrophoneHandler] Recognizer stopped");
184
245
  if (this.recognizer) {
185
246
  this.recognizer.close();
186
247
  this.recognizer = null;
187
248
  }
188
249
  }, (error) => {
189
- console.error("Error stopping recognizer:", error);
250
+ console.error("[MicrophoneHandler] Error stopping recognizer:", error);
190
251
  if (this.recognizer) {
191
252
  this.recognizer.close();
192
253
  this.recognizer = null;
@@ -194,7 +255,7 @@ export class MicrophoneHandler {
194
255
  });
195
256
  }
196
257
  catch (error) {
197
- console.error("Error disposing recognizer:", error);
258
+ console.error("[MicrophoneHandler] Error disposing recognizer:", error);
198
259
  if (this.recognizer) {
199
260
  this.recognizer.close();
200
261
  this.recognizer = null;
@@ -213,9 +274,14 @@ export class MicrophoneHandler {
213
274
  }
214
275
  async startRecording(callbacks = {}) {
215
276
  if (this.isRecording) {
216
- 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");
217
282
  return false;
218
283
  }
284
+ this.activeCallbacks = callbacks;
219
285
  if (!this.hasPermission) {
220
286
  const granted = await this.requestMicrophonePermission();
221
287
  if (!granted) {
@@ -253,57 +319,65 @@ export class MicrophoneHandler {
253
319
  return false;
254
320
  }
255
321
  this.recognizer.recognizing = (_s, e) => {
256
- if (e.result.text && callbacks.onPartial) {
257
- 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);
258
325
  }
259
326
  };
260
327
  this.recognizer.recognized = (_s, e) => {
328
+ this.lastRecognitionTime = Date.now();
261
329
  if (e.result.reason === sdk.ResultReason.RecognizedSpeech) {
262
330
  const text = e.result.text;
263
331
  if (text) {
332
+ console.log("[MicrophoneHandler] Recognized speech:", text);
264
333
  this.transcriptionBuffer.push(text);
265
- if (callbacks.onSegment) {
266
- callbacks.onSegment(text, {
334
+ if (this.activeCallbacks.onSegment) {
335
+ this.activeCallbacks.onSegment(text, {
267
336
  duration: e.result.duration,
268
337
  offset: e.result.offset,
269
338
  });
270
339
  }
271
- this.scheduleSilenceCheck(callbacks);
340
+ this.scheduleSilenceCheck(this.activeCallbacks);
272
341
  }
273
342
  }
274
343
  else if (e.result.reason === sdk.ResultReason.NoMatch) {
275
- console.log("No speech recognized");
344
+ console.log("[MicrophoneHandler] No speech recognized (NoMatch)");
276
345
  }
277
346
  };
278
347
  this.recognizer.canceled = (_s, e) => {
279
- console.error("Recognition canceled:", e.reason, e.errorDetails);
348
+ console.error("[MicrophoneHandler] Recognition canceled:", e.reason, e.errorDetails);
280
349
  this.isRecording = false;
281
- if (callbacks.onError) {
282
- 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}`));
283
353
  }
284
354
  };
285
355
  this.recognizer.sessionStopped = (_s, _e) => {
286
- console.log("Session stopped");
356
+ console.log("[MicrophoneHandler] Session stopped");
287
357
  this.isRecording = false;
358
+ this.stopHealthCheck();
288
359
  };
289
360
  this.recognizer.startContinuousRecognitionAsync(() => {
290
- console.log("Continuous recognition started");
361
+ console.log("[MicrophoneHandler] Continuous recognition started");
291
362
  this.isRecording = true;
292
363
  this.audioContextUnlocked = true;
293
- this.scheduleSilenceCheck(callbacks);
364
+ this.lastRecognitionTime = Date.now();
365
+ this.startHealthCheck();
366
+ this.scheduleSilenceCheck(this.activeCallbacks);
294
367
  }, (error) => {
295
- console.error("Failed to start recognition:", error);
368
+ console.error("[MicrophoneHandler] Failed to start recognition:", error);
296
369
  this.isRecording = false;
297
- if (callbacks.onError) {
298
- 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}`));
299
372
  }
300
373
  });
301
374
  return true;
302
375
  }
303
376
  async stopRecording() {
304
377
  this.clearSilenceTimer();
378
+ this.stopHealthCheck();
305
379
  if (!this.isRecording || !this.recognizer) {
306
- console.warn("Not currently recording");
380
+ console.warn("[MicrophoneHandler] Not currently recording");
307
381
  return this.getAggregatedText();
308
382
  }
309
383
  return new Promise((resolve) => {
@@ -312,13 +386,13 @@ export class MicrophoneHandler {
312
386
  return;
313
387
  }
314
388
  this.recognizer.stopContinuousRecognitionAsync(() => {
315
- console.log("Recognition stopped successfully");
389
+ console.log("[MicrophoneHandler] Recognition stopped successfully");
316
390
  this.isRecording = false;
317
391
  const finalText = this.getAggregatedText();
318
392
  this.disposeRecognizer();
319
393
  resolve(finalText);
320
394
  }, (error) => {
321
- console.error("Error stopping recognition:", error);
395
+ console.error("[MicrophoneHandler] Error stopping recognition:", error);
322
396
  this.isRecording = false;
323
397
  const finalText = this.getAggregatedText();
324
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;YAkFtB,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;IAuCtC,OAAO,CAAC,WAAW;YAoBL,iBAAiB;YAwBjB,iBAAiB;IA0HxB,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"}
@@ -2,6 +2,7 @@ import { MicrophoneHandler } from "./microphone-handler.js";
2
2
  import { AzureSpeechService } from "./services/azure-speech.js";
3
3
  import { BedrockService } from "./services/bedrock.js";
4
4
  import { PageRegistry } from "./services/page-registry.js";
5
+ import { VoiceFeedbackService } from "./services/voice-feedback.js";
5
6
  import { loadNavigationPages } from "./services/xml-parser.js";
6
7
  import { VectorSearchService, } from "./services/vector-search.js";
7
8
  import { performAgentAction, formatActionLabel, extractAgentAction, setPageRegistry, } from "./actions.js";
@@ -43,19 +44,47 @@ export class VoiceNavigationController {
43
44
  opensearch: openSearchConfig,
44
45
  };
45
46
  console.log("[Voice Navigation Controller] Constructor called with UI config:", this.config.ui);
46
- if (!this.config.azure?.subscriptionKey || !this.config.azure?.region) {
47
- throw new Error("Azure Speech configuration is required");
47
+ const hasAzureEndpoint = !!this.config.azure?.tokenEndpoint;
48
+ const hasAzureCredentials = !!(this.config.azure?.subscriptionKey && this.config.azure?.region);
49
+ if (!this.config.azure) {
50
+ this.config.azure = { tokenEndpoint: "/api/speech/token" };
51
+ console.log("[VoiceNavigation] Auto-configured Azure endpoint: /api/speech/token");
52
+ }
53
+ else if (!hasAzureEndpoint && !hasAzureCredentials) {
54
+ this.config.azure.tokenEndpoint = "/api/speech/token";
55
+ console.log("[VoiceNavigation] Auto-configured Azure endpoint: /api/speech/token");
56
+ }
57
+ const hasAwsEndpoint = !!this.config.aws?.bedrockEndpoint;
58
+ const hasAwsCredentials = !!(this.config.aws?.accessKeyId &&
59
+ this.config.aws?.secretAccessKey &&
60
+ this.config.aws?.modelId);
61
+ if (!this.config.aws) {
62
+ this.config.aws = {
63
+ bedrockEndpoint: "/api/speech/bedrock",
64
+ embeddingEndpoint: "/api/speech/bedrock-embedding",
65
+ };
66
+ console.log("[VoiceNavigation] Auto-configured AWS endpoints: /api/speech/bedrock, /api/speech/bedrock-embedding");
67
+ }
68
+ else if (!hasAwsEndpoint && !hasAwsCredentials) {
69
+ this.config.aws.bedrockEndpoint = "/api/speech/bedrock";
70
+ this.config.aws.embeddingEndpoint = "/api/speech/bedrock-embedding";
71
+ console.log("[VoiceNavigation] Auto-configured AWS endpoints: /api/speech/bedrock, /api/speech/bedrock-embedding");
48
72
  }
49
- if (!this.config.aws?.accessKeyId ||
50
- !this.config.aws?.secretAccessKey ||
51
- !this.config.aws?.modelId) {
52
- throw new Error("AWS Bedrock configuration is required");
73
+ if (openSearchConfig &&
74
+ !openSearchConfig.searchEndpoint &&
75
+ !openSearchConfig.node) {
76
+ openSearchConfig.searchEndpoint = "/api/opensearch/search";
77
+ openSearchConfig.suggestEndpoint = "/api/opensearch/suggest";
78
+ console.log("[VoiceNavigation] Auto-configured OpenSearch endpoints: /api/opensearch/search, /api/opensearch/suggest");
53
79
  }
54
80
  this.azureSpeechService = new AzureSpeechService({
81
+ tokenEndpoint: this.config.azure.tokenEndpoint,
55
82
  subscriptionKey: this.config.azure.subscriptionKey,
56
83
  region: this.config.azure.region,
57
84
  });
58
85
  this.bedrockService = new BedrockService({
86
+ bedrockEndpoint: this.config.aws.bedrockEndpoint,
87
+ embeddingEndpoint: this.config.aws.embeddingEndpoint,
59
88
  region: this.config.aws.region || "ap-south-1",
60
89
  accessKeyId: this.config.aws.accessKeyId,
61
90
  secretAccessKey: this.config.aws.secretAccessKey,
@@ -71,6 +100,15 @@ export class VoiceNavigationController {
71
100
  bedrockService: this.bedrockService,
72
101
  language: this.config.language || "en-IN",
73
102
  });
103
+ const voiceFeedbackEnabled = this.config.voiceFeedback?.enabled !== false;
104
+ console.log("[VoiceNavigation] Initializing voice feedback service - enabled:", voiceFeedbackEnabled);
105
+ this.voiceFeedbackService = new VoiceFeedbackService({
106
+ enabled: voiceFeedbackEnabled,
107
+ language: this.config.language || "en-IN",
108
+ azureSpeechService: this.azureSpeechService,
109
+ bedrockService: this.bedrockService,
110
+ });
111
+ console.log("[VoiceNavigation] Voice feedback service initialized successfully");
74
112
  if (openSearchConfig) {
75
113
  try {
76
114
  this.vectorSearchService = new VectorSearchService(openSearchConfig);
@@ -350,6 +388,7 @@ export class VoiceNavigationController {
350
388
  }
351
389
  updateTranscript(this.ui, "");
352
390
  this.isProcessing = false;
391
+ this.microphoneHandler.rescheduleSilenceCheck();
353
392
  }
354
393
  }
355
394
  handleError(error) {
@@ -410,6 +449,30 @@ export class VoiceNavigationController {
410
449
  query,
411
450
  originalText,
412
451
  });
452
+ if (actionResult.performed && actionResult.info?.pendingNavigation) {
453
+ console.log("[VoiceNavigation] Search result with navigation - waiting for feedback:", action);
454
+ try {
455
+ await this.provideActionFeedback(action, actionResult, originalText);
456
+ console.log("[VoiceNavigation] Search feedback complete, now navigating to:", actionResult.info.navigationUrl);
457
+ if (actionResult.info.navigationUrl) {
458
+ window.location.href = actionResult.info.navigationUrl;
459
+ actionResult.info.navigated = true;
460
+ actionResult.info.pendingNavigation = false;
461
+ }
462
+ }
463
+ catch (err) {
464
+ console.error("[VoiceNavigation] Voice feedback error (search):", err);
465
+ if (actionResult.info.navigationUrl) {
466
+ window.location.href = actionResult.info.navigationUrl;
467
+ }
468
+ }
469
+ }
470
+ else if (actionResult.performed) {
471
+ console.log("[VoiceNavigation] Search result without navigation - non-blocking feedback");
472
+ void this.provideActionFeedback(action, actionResult, originalText).catch((err) => {
473
+ console.error("[VoiceNavigation] Voice feedback error (search):", err);
474
+ });
475
+ }
413
476
  if (typeof this.config.actionHandlers?.[action] === "function") {
414
477
  try {
415
478
  this.config.actionHandlers[action]({
@@ -441,7 +504,80 @@ export class VoiceNavigationController {
441
504
  handler: this,
442
505
  onStop: () => this.stop(),
443
506
  transcript: originalText,
507
+ parameters: {
508
+ text: actionData.text || query,
509
+ query: query,
510
+ },
444
511
  });
512
+ console.log("[VoiceNavigation] Action performed:", action, "result:", actionResult.performed);
513
+ const isCriticalAction = action.startsWith("navigate_") ||
514
+ action === "focus_next" ||
515
+ action === "tab_next" ||
516
+ action === "focus_previous" ||
517
+ action === "tab_back" ||
518
+ action === "go_back" ||
519
+ action === "go_forward" ||
520
+ action === "reload_page";
521
+ if (actionResult.performed && isCriticalAction) {
522
+ console.log("[VoiceNavigation] Critical action - waiting for voice feedback to complete:", action);
523
+ try {
524
+ await this.provideActionFeedback(action, actionResult, originalText);
525
+ console.log("[VoiceNavigation] Voice feedback completed for critical action:", action);
526
+ if (actionResult.info?.pendingNavigation) {
527
+ if (actionResult.info.navigationUrl) {
528
+ console.log("[VoiceNavigation] Feedback complete, now navigating to:", actionResult.info.navigationUrl);
529
+ window.location.href = actionResult.info.navigationUrl;
530
+ actionResult.info.navigated = true;
531
+ actionResult.info.pendingNavigation = false;
532
+ }
533
+ else if (actionResult.info.navigationType === "back") {
534
+ console.log("[VoiceNavigation] Feedback complete, navigating back");
535
+ window.history.back();
536
+ actionResult.info.navigated = true;
537
+ actionResult.info.pendingNavigation = false;
538
+ }
539
+ else if (actionResult.info.navigationType === "forward") {
540
+ console.log("[VoiceNavigation] Feedback complete, navigating forward");
541
+ window.history.forward();
542
+ actionResult.info.navigated = true;
543
+ actionResult.info.pendingNavigation = false;
544
+ }
545
+ else if (actionResult.info.navigationType === "reload") {
546
+ console.log("[VoiceNavigation] Feedback complete, reloading page");
547
+ window.location.reload();
548
+ actionResult.info.reloaded = true;
549
+ actionResult.info.pendingNavigation = false;
550
+ }
551
+ }
552
+ }
553
+ catch (err) {
554
+ console.error("[VoiceNavigation] Voice feedback error (critical action):", err);
555
+ if (actionResult.info?.pendingNavigation) {
556
+ if (actionResult.info.navigationUrl) {
557
+ console.log("[VoiceNavigation] Feedback failed, but proceeding with navigation:", actionResult.info.navigationUrl);
558
+ window.location.href = actionResult.info.navigationUrl;
559
+ }
560
+ else if (actionResult.info.navigationType === "back") {
561
+ window.history.back();
562
+ }
563
+ else if (actionResult.info.navigationType === "forward") {
564
+ window.history.forward();
565
+ }
566
+ else if (actionResult.info.navigationType === "reload") {
567
+ window.location.reload();
568
+ }
569
+ }
570
+ }
571
+ }
572
+ else if (actionResult.performed) {
573
+ console.log("[VoiceNavigation] Non-critical action - providing feedback non-blocking:", action);
574
+ void this.provideActionFeedback(action, actionResult, originalText).catch((err) => {
575
+ console.error("[VoiceNavigation] Voice feedback error:", err);
576
+ });
577
+ }
578
+ else {
579
+ console.log("[VoiceNavigation] Action not performed, skipping voice feedback");
580
+ }
445
581
  if (typeof this.config.actionHandlers?.[action] === "function") {
446
582
  try {
447
583
  this.config.actionHandlers[action]({
@@ -470,6 +606,62 @@ export class VoiceNavigationController {
470
606
  }
471
607
  setLanguage(language) {
472
608
  this.microphoneHandler.setLanguage(language);
609
+ this.voiceFeedbackService.setLanguage(language);
610
+ }
611
+ setVoiceFeedback(enabled) {
612
+ this.voiceFeedbackService.setEnabled(enabled);
613
+ }
614
+ isVoiceFeedbackEnabled() {
615
+ return this.voiceFeedbackService.isEnabled();
616
+ }
617
+ async provideActionFeedback(action, actionResult, transcript) {
618
+ console.log("[VoiceNavigation] provideActionFeedback called for action:", action);
619
+ if (!this.voiceFeedbackService) {
620
+ console.error("[VoiceNavigation] Voice feedback service not initialized!");
621
+ return;
622
+ }
623
+ console.log("[VoiceNavigation] Voice feedback service exists, checking if enabled...");
624
+ console.log("[VoiceNavigation] Voice feedback enabled:", this.voiceFeedbackService.isEnabled());
625
+ try {
626
+ if (action.startsWith("navigate_") && actionResult.info?.pageName) {
627
+ console.log("[VoiceNavigation] Handling navigation feedback");
628
+ await this.voiceFeedbackService.provideFeedback({
629
+ action,
630
+ actionResult,
631
+ transcript,
632
+ });
633
+ return;
634
+ }
635
+ if ((action === "focus_next" ||
636
+ action === "tab_next" ||
637
+ action === "focus_previous" ||
638
+ action === "tab_back") &&
639
+ actionResult.performed) {
640
+ console.log("[VoiceNavigation] Handling focus feedback");
641
+ const focusedElement = document.activeElement;
642
+ if (focusedElement && focusedElement !== document.body) {
643
+ await this.voiceFeedbackService.announceFocusChange(focusedElement, actionResult.info.direction === "next" ||
644
+ actionResult.info.direction === "forward"
645
+ ? "next"
646
+ : "previous");
647
+ return;
648
+ }
649
+ }
650
+ if (actionResult.performed) {
651
+ console.log("[VoiceNavigation] Handling general action feedback");
652
+ await this.voiceFeedbackService.provideFeedback({
653
+ action,
654
+ actionResult,
655
+ transcript,
656
+ });
657
+ }
658
+ else {
659
+ console.log("[VoiceNavigation] Action not performed in feedback check");
660
+ }
661
+ }
662
+ catch (error) {
663
+ console.error("[VoiceNavigation] Failed to provide voice feedback:", error);
664
+ }
473
665
  }
474
666
  async executeVectorSearchAction(options) {
475
667
  const vectorService = this.vectorSearchService;
@@ -595,7 +787,9 @@ export class VoiceNavigationController {
595
787
  });
596
788
  this.prepareForNavigation({ target: resolvedUrl });
597
789
  baseInfo.message = "Opening the best matching result.";
598
- window.location.href = resolvedUrl;
790
+ baseInfo.pendingNavigation = true;
791
+ baseInfo.navigationUrl = resolvedUrl;
792
+ baseInfo.navigated = false;
599
793
  return {
600
794
  performed: true,
601
795
  info: baseInfo,
@@ -0,0 +1,13 @@
1
+ type NextApiRequest = {
2
+ method?: string;
3
+ body?: unknown;
4
+ };
5
+ type NextApiResponse = {
6
+ status: (code: number) => NextApiResponse;
7
+ json: (data: unknown) => void;
8
+ setHeader: (name: string, value: string | string[]) => void;
9
+ };
10
+ export declare function azureSpeechTokenHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
11
+ export declare function POST(_request: Request): Promise<Response>;
12
+ export {};
13
+ //# sourceMappingURL=azure-speech-handler.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,79 @@
1
+ async function getAzureSpeechToken() {
2
+ const key = process.env.AZURE_SPEECH_KEY;
3
+ const region = process.env.AZURE_SPEECH_REGION;
4
+ if (!key || !region) {
5
+ console.error("[VoiceNavigation] Missing Azure Speech credentials. Set AZURE_SPEECH_KEY and AZURE_SPEECH_REGION in your .env file");
6
+ return {
7
+ success: false,
8
+ statusCode: 500,
9
+ error: {
10
+ error: "Azure Speech configuration is missing",
11
+ hint: "Set AZURE_SPEECH_KEY and AZURE_SPEECH_REGION environment variables",
12
+ },
13
+ };
14
+ }
15
+ try {
16
+ const url = `https://${region}.api.cognitive.microsoft.com/sts/v1.0/issueToken`;
17
+ const response = await fetch(url, {
18
+ method: "POST",
19
+ headers: {
20
+ "Ocp-Apim-Subscription-Key": key,
21
+ },
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error(`Azure API responded with status ${response.status}`);
25
+ }
26
+ const token = await response.text();
27
+ const responseData = { token, region };
28
+ return {
29
+ success: true,
30
+ statusCode: 200,
31
+ data: responseData,
32
+ };
33
+ }
34
+ catch (error) {
35
+ console.error("[VoiceNavigation] Failed to fetch Azure speech token:", error);
36
+ return {
37
+ success: false,
38
+ statusCode: 500,
39
+ error: {
40
+ error: "Failed to get token",
41
+ message: error instanceof Error ? error.message : "Unknown error",
42
+ },
43
+ };
44
+ }
45
+ }
46
+ export async function azureSpeechTokenHandler(req, res) {
47
+ if (req.method !== "POST") {
48
+ res.setHeader("Allow", ["POST"]);
49
+ res.status(405).json({ error: "Method not allowed" });
50
+ return;
51
+ }
52
+ const result = await getAzureSpeechToken();
53
+ res.setHeader("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate");
54
+ if (result.success && result.data) {
55
+ res.status(result.statusCode).json(result.data);
56
+ }
57
+ else {
58
+ res.status(result.statusCode).json(result.error);
59
+ }
60
+ }
61
+ export async function POST(_request) {
62
+ const result = await getAzureSpeechToken();
63
+ const headers = {
64
+ "Content-Type": "application/json",
65
+ "Cache-Control": "no-store, no-cache, max-age=0, must-revalidate",
66
+ };
67
+ if (result.success && result.data) {
68
+ return new Response(JSON.stringify(result.data), {
69
+ status: result.statusCode,
70
+ headers,
71
+ });
72
+ }
73
+ else {
74
+ return new Response(JSON.stringify(result.error), {
75
+ status: result.statusCode,
76
+ headers,
77
+ });
78
+ }
79
+ }
@@ -0,0 +1,16 @@
1
+ type NextApiRequest = {
2
+ method?: string;
3
+ body?: {
4
+ text?: string | string[];
5
+ inputType?: string;
6
+ };
7
+ };
8
+ type NextApiResponse = {
9
+ status: (code: number) => NextApiResponse;
10
+ json: (data: unknown) => void;
11
+ setHeader: (name: string, value: string | string[]) => void;
12
+ };
13
+ export declare function bedrockEmbeddingHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
14
+ export declare function BedrockEmbeddingPOST(request: Request): Promise<Response>;
15
+ export {};
16
+ //# sourceMappingURL=bedrock-embedding-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bedrock-embedding-handler.d.ts","sourceRoot":"","sources":["../../src/server/bedrock-embedding-handler.ts"],"names":[],"mappings":"AA4BA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,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;AA0MF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAMD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,CAAC,CAkCnB"}