@myscheme/voice-navigation-sdk 0.1.9 → 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.
- package/README.md +130 -17
- package/dist/actions.d.ts +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +51 -1
- package/dist/index.d.ts +38 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -1
- package/dist/languages.d.ts +17 -0
- package/dist/languages.d.ts.map +1 -0
- package/dist/languages.js +86 -0
- package/dist/microphone-handler.d.ts +8 -2
- package/dist/microphone-handler.d.ts.map +1 -1
- package/dist/microphone-handler.js +89 -2
- package/dist/navigation-controller.d.ts +2 -0
- package/dist/navigation-controller.d.ts.map +1 -1
- package/dist/navigation-controller.js +58 -22
- package/dist/server/azure-speech-handler.d.ts +3 -2
- package/dist/server/azure-speech-handler.d.ts.map +1 -1
- package/dist/server/azure-speech-handler.js +26 -2
- package/dist/server/bedrock-embedding-handler.d.ts +2 -1
- package/dist/server/bedrock-embedding-handler.d.ts.map +1 -1
- package/dist/server/bedrock-embedding-handler.js +73 -19
- package/dist/server/bedrock-handler.d.ts +2 -1
- package/dist/server/bedrock-handler.d.ts.map +1 -1
- package/dist/server/bedrock-handler.js +124 -19
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/opensearch-env-handler.d.ts +2 -1
- package/dist/server/opensearch-env-handler.d.ts.map +1 -1
- package/dist/server/opensearch-env-handler.js +23 -53
- package/dist/server/sarvam-stt-relay.d.ts +8 -0
- package/dist/server/sarvam-stt-relay.d.ts.map +1 -0
- package/dist/server/sarvam-stt-relay.js +111 -0
- package/dist/server/security.d.ts +26 -0
- package/dist/server/security.d.ts.map +1 -0
- package/dist/server/security.js +89 -0
- package/dist/services/bedrock.d.ts.map +1 -1
- package/dist/services/bedrock.js +13 -0
- package/dist/services/sarvam-speech.d.ts +47 -0
- package/dist/services/sarvam-speech.d.ts.map +1 -0
- package/dist/services/sarvam-speech.js +418 -0
- package/dist/services/voice-feedback.d.ts +7 -2
- package/dist/services/voice-feedback.d.ts.map +1 -1
- package/dist/services/voice-feedback.js +45 -5
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- 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,
|
|
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"}
|
|
@@ -15,7 +15,15 @@ export class MicrophoneHandler {
|
|
|
15
15
|
this.recognitionHealthCheckInterval = null;
|
|
16
16
|
this.isRecovering = false;
|
|
17
17
|
this.isStoppingIntentionally = false;
|
|
18
|
-
this.
|
|
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
|
+
}
|
|
19
27
|
this.bedrockService = config.bedrockService;
|
|
20
28
|
this.language = config.language || "en-IN";
|
|
21
29
|
this.silenceTimeout = config.silenceTimeout || 2000;
|
|
@@ -127,6 +135,9 @@ export class MicrophoneHandler {
|
|
|
127
135
|
this.transcriptionBuffer = [];
|
|
128
136
|
}
|
|
129
137
|
async fetchAzureToken() {
|
|
138
|
+
if (!this.azureSpeechService) {
|
|
139
|
+
throw new Error("Azure speech service is not configured");
|
|
140
|
+
}
|
|
130
141
|
try {
|
|
131
142
|
const response = await this.azureSpeechService.fetchToken();
|
|
132
143
|
this.token = response.token;
|
|
@@ -174,7 +185,7 @@ export class MicrophoneHandler {
|
|
|
174
185
|
console.warn("[MicrophoneHandler] Cannot reschedule - not recording");
|
|
175
186
|
return;
|
|
176
187
|
}
|
|
177
|
-
if (!this.recognizer) {
|
|
188
|
+
if (this.speechProvider === "azure" && !this.recognizer) {
|
|
178
189
|
console.error("[MicrophoneHandler] Cannot reschedule - no recognizer");
|
|
179
190
|
return;
|
|
180
191
|
}
|
|
@@ -220,6 +231,9 @@ export class MicrophoneHandler {
|
|
|
220
231
|
}
|
|
221
232
|
this.isRecovering = true;
|
|
222
233
|
console.log("[MicrophoneHandler] Attempting to recover from stalled recognizer");
|
|
234
|
+
if (this.speechProvider === "sarvam" && this.sarvamSpeechService) {
|
|
235
|
+
await this.sarvamSpeechService.stopListening();
|
|
236
|
+
}
|
|
223
237
|
this.disposeRecognizer();
|
|
224
238
|
this.isRecording = false;
|
|
225
239
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -254,6 +268,14 @@ export class MicrophoneHandler {
|
|
|
254
268
|
}
|
|
255
269
|
disposeRecognizer() {
|
|
256
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
|
+
}
|
|
257
279
|
if (this.recognizer) {
|
|
258
280
|
try {
|
|
259
281
|
this.recognizer.stopContinuousRecognitionAsync(() => {
|
|
@@ -308,6 +330,9 @@ export class MicrophoneHandler {
|
|
|
308
330
|
return false;
|
|
309
331
|
}
|
|
310
332
|
}
|
|
333
|
+
if (this.speechProvider === "sarvam") {
|
|
334
|
+
return this.startSarvamRecording(callbacks);
|
|
335
|
+
}
|
|
311
336
|
const sdkReady = await this.ensureSpeechSdk();
|
|
312
337
|
if (!sdkReady) {
|
|
313
338
|
if (callbacks.onError) {
|
|
@@ -398,10 +423,72 @@ export class MicrophoneHandler {
|
|
|
398
423
|
});
|
|
399
424
|
return true;
|
|
400
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
|
+
}
|
|
401
471
|
async stopRecording() {
|
|
402
472
|
this.clearSilenceTimer();
|
|
403
473
|
this.stopHealthCheck();
|
|
404
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
|
+
}
|
|
405
492
|
if (!this.isRecording || !this.recognizer) {
|
|
406
493
|
console.warn("[MicrophoneHandler] Not currently recording");
|
|
407
494
|
this.isStoppingIntentionally = false;
|
|
@@ -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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation-controller.d.ts","sourceRoot":"","sources":["../src/navigation-controller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,
|
|
1
|
+
{"version":3,"file":"navigation-controller.d.ts","sourceRoot":"","sources":["../src/navigation-controller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,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";
|
|
@@ -45,17 +47,28 @@ export class VoiceNavigationController {
|
|
|
45
47
|
this.config = {
|
|
46
48
|
...config,
|
|
47
49
|
opensearch: openSearchConfig,
|
|
50
|
+
speechProvider: config.speechProvider ?? "sarvam",
|
|
48
51
|
};
|
|
52
|
+
this.speechProvider = this.config.speechProvider ?? "sarvam";
|
|
49
53
|
console.log("[Voice Navigation Controller] Constructor called with UI config:", this.config.ui);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.config.azure
|
|
54
|
-
|
|
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
|
+
}
|
|
55
65
|
}
|
|
56
|
-
|
|
57
|
-
this.config.
|
|
58
|
-
|
|
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
|
+
};
|
|
59
72
|
}
|
|
60
73
|
const hasAwsEndpoint = !!this.config.aws?.bedrockEndpoint;
|
|
61
74
|
const hasAwsCredentials = !!(this.config.aws?.accessKeyId &&
|
|
@@ -80,11 +93,29 @@ export class VoiceNavigationController {
|
|
|
80
93
|
openSearchConfig.suggestEndpoint = "/api/opensearch/suggest";
|
|
81
94
|
console.log("[VoiceNavigation] Auto-configured OpenSearch endpoints: /api/opensearch/search, /api/opensearch/suggest");
|
|
82
95
|
}
|
|
83
|
-
this.azureSpeechService =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
}
|
|
88
119
|
this.bedrockService = new BedrockService({
|
|
89
120
|
bedrockEndpoint: this.config.aws.bedrockEndpoint,
|
|
90
121
|
embeddingEndpoint: this.config.aws.embeddingEndpoint,
|
|
@@ -99,16 +130,20 @@ export class VoiceNavigationController {
|
|
|
99
130
|
this.pageRegistry = new PageRegistry([]);
|
|
100
131
|
this.pagesInitialized = this.initializePages();
|
|
101
132
|
this.microphoneHandler = new MicrophoneHandler({
|
|
102
|
-
|
|
133
|
+
speechProvider: this.speechProvider,
|
|
134
|
+
azureSpeechService: this.azureSpeechService ?? undefined,
|
|
135
|
+
sarvamSpeechService: this.sarvamSpeechService ?? undefined,
|
|
103
136
|
bedrockService: this.bedrockService,
|
|
104
|
-
language: this.config.language
|
|
137
|
+
language: normalizeLanguageCode(this.config.language),
|
|
105
138
|
});
|
|
106
139
|
const voiceFeedbackEnabled = this.config.voiceFeedback?.enabled !== false;
|
|
107
140
|
console.log("[VoiceNavigation] Initializing voice feedback service - enabled:", voiceFeedbackEnabled);
|
|
108
141
|
this.voiceFeedbackService = new VoiceFeedbackService({
|
|
109
142
|
enabled: voiceFeedbackEnabled,
|
|
110
|
-
language: this.config.language
|
|
111
|
-
|
|
143
|
+
language: normalizeLanguageCode(this.config.language),
|
|
144
|
+
speechProvider: this.speechProvider,
|
|
145
|
+
azureSpeechService: this.azureSpeechService ?? undefined,
|
|
146
|
+
sarvamSpeechService: this.sarvamSpeechService ?? undefined,
|
|
112
147
|
bedrockService: this.bedrockService,
|
|
113
148
|
});
|
|
114
149
|
console.log("[VoiceNavigation] Voice feedback service initialized successfully");
|
|
@@ -143,7 +178,7 @@ export class VoiceNavigationController {
|
|
|
143
178
|
customButtonSelector,
|
|
144
179
|
buttonPlacement,
|
|
145
180
|
});
|
|
146
|
-
if (
|
|
181
|
+
if (customButtonSelector) {
|
|
147
182
|
console.log(`[Voice Navigation] Attempting to use custom button: ${customButtonSelector}`);
|
|
148
183
|
try {
|
|
149
184
|
this.ui = await attachToCustomButton(customButtonSelector);
|
|
@@ -151,11 +186,11 @@ export class VoiceNavigationController {
|
|
|
151
186
|
}
|
|
152
187
|
catch (error) {
|
|
153
188
|
console.error("Failed to attach to custom button:", error);
|
|
154
|
-
console.warn("Falling back to default button
|
|
189
|
+
console.warn("Falling back to default button - custom button selector was not found in the DOM");
|
|
155
190
|
this.ui = createFloatingControl(buttonPlacement);
|
|
156
191
|
}
|
|
157
192
|
}
|
|
158
|
-
else if (!showDefaultButton
|
|
193
|
+
else if (!showDefaultButton) {
|
|
159
194
|
console.warn("showDefaultButton is false but no customButtonSelector provided. Creating default button.");
|
|
160
195
|
this.ui = createFloatingControl(buttonPlacement);
|
|
161
196
|
}
|
|
@@ -719,8 +754,9 @@ export class VoiceNavigationController {
|
|
|
719
754
|
});
|
|
720
755
|
}
|
|
721
756
|
setLanguage(language) {
|
|
722
|
-
|
|
723
|
-
this.
|
|
757
|
+
const normalized = normalizeLanguageCode(language);
|
|
758
|
+
this.microphoneHandler.setLanguage(normalized);
|
|
759
|
+
this.voiceFeedbackService.setLanguage(normalized);
|
|
724
760
|
}
|
|
725
761
|
setVoiceFeedback(enabled) {
|
|
726
762
|
this.voiceFeedbackService.setEnabled(enabled);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
type
|
|
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(
|
|
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":"
|
|
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(
|
|
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,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bedrock-embedding-handler.d.ts","sourceRoot":"","sources":["../../src/server/bedrock-embedding-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bedrock-embedding-handler.d.ts","sourceRoot":"","sources":["../../src/server/bedrock-embedding-handler.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnE,KAAK,cAAc,GAAG,eAAe,GAAG;IACtC,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;AA0OF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAMD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,CAAC,CAsDnB"}
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
|
+
import { evaluateGate } from "./security.js";
|
|
3
|
+
const MAX_EMBEDDING_TEXTS = 16;
|
|
4
|
+
const MAX_EMBEDDING_TEXT_CHARS = 2000;
|
|
5
|
+
const ALLOWED_INPUT_TYPES = new Set([
|
|
6
|
+
"search_query",
|
|
7
|
+
"search_document",
|
|
8
|
+
"classification",
|
|
9
|
+
"clustering",
|
|
10
|
+
]);
|
|
11
|
+
function sanitizeEmbeddingInput(text, inputType) {
|
|
12
|
+
const rawTexts = Array.isArray(text) ? text : text != null ? [text] : [];
|
|
13
|
+
const texts = [];
|
|
14
|
+
for (const entry of rawTexts.slice(0, MAX_EMBEDDING_TEXTS)) {
|
|
15
|
+
if (typeof entry === "string" && entry.trim().length > 0) {
|
|
16
|
+
texts.push(entry.slice(0, MAX_EMBEDDING_TEXT_CHARS));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (texts.length === 0) {
|
|
20
|
+
return { ok: false };
|
|
21
|
+
}
|
|
22
|
+
const resolvedType = typeof inputType === "string" && ALLOWED_INPUT_TYPES.has(inputType)
|
|
23
|
+
? inputType
|
|
24
|
+
: "search_query";
|
|
25
|
+
return { ok: true, texts, inputType: resolvedType };
|
|
26
|
+
}
|
|
2
27
|
function sign(key, msg) {
|
|
3
28
|
return crypto.createHmac("sha256", key).update(msg, "utf8").digest();
|
|
4
29
|
}
|
|
@@ -141,43 +166,72 @@ export async function bedrockEmbeddingHandler(req, res) {
|
|
|
141
166
|
res.status(405).json({ error: "Method not allowed" });
|
|
142
167
|
return;
|
|
143
168
|
}
|
|
144
|
-
const {
|
|
145
|
-
|
|
169
|
+
const gate = evaluateGate(req, { maxRequests: 30, windowMs: 60 * 1000, bucket: "embed" });
|
|
170
|
+
if (!gate.ok) {
|
|
171
|
+
res.status(gate.status ?? 403).json({ error: gate.error });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const { text, inputType } = req.body || {};
|
|
175
|
+
const sanitized = sanitizeEmbeddingInput(text, inputType);
|
|
176
|
+
if (!sanitized.ok) {
|
|
177
|
+
res.status(400).json({ error: "Invalid text parameter" });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const result = await generateBedrockEmbedding(sanitized.texts, sanitized.inputType);
|
|
146
181
|
if (result.success && result.data) {
|
|
147
182
|
res.status(result.statusCode).json(result.data);
|
|
148
183
|
}
|
|
149
184
|
else {
|
|
150
|
-
res
|
|
185
|
+
res
|
|
186
|
+
.status(result.statusCode >= 500 ? 502 : result.statusCode)
|
|
187
|
+
.json({ error: "Embedding request failed" });
|
|
151
188
|
}
|
|
152
189
|
}
|
|
153
190
|
export async function BedrockEmbeddingPOST(request) {
|
|
191
|
+
const headers = { "Content-Type": "application/json" };
|
|
192
|
+
const gateReq = {
|
|
193
|
+
method: request.method,
|
|
194
|
+
headers: {
|
|
195
|
+
origin: request.headers.get("origin") ?? undefined,
|
|
196
|
+
referer: request.headers.get("referer") ?? undefined,
|
|
197
|
+
host: request.headers.get("host") ?? undefined,
|
|
198
|
+
"x-forwarded-for": request.headers.get("x-forwarded-for") ?? undefined,
|
|
199
|
+
"x-real-ip": request.headers.get("x-real-ip") ?? undefined,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
const gate = evaluateGate(gateReq, { maxRequests: 30, windowMs: 60 * 1000, bucket: "embed" });
|
|
203
|
+
if (!gate.ok) {
|
|
204
|
+
return new Response(JSON.stringify({ error: gate.error }), {
|
|
205
|
+
status: gate.status ?? 403,
|
|
206
|
+
headers,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
154
209
|
try {
|
|
155
210
|
const body = await request.json();
|
|
156
|
-
const { text, inputType
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (result.success && result.data) {
|
|
162
|
-
return new Response(JSON.stringify(result.data), {
|
|
163
|
-
status: result.statusCode,
|
|
211
|
+
const { text, inputType } = body || {};
|
|
212
|
+
const sanitized = sanitizeEmbeddingInput(text, inputType);
|
|
213
|
+
if (!sanitized.ok) {
|
|
214
|
+
return new Response(JSON.stringify({ error: "Invalid text parameter" }), {
|
|
215
|
+
status: 400,
|
|
164
216
|
headers,
|
|
165
217
|
});
|
|
166
218
|
}
|
|
167
|
-
|
|
168
|
-
|
|
219
|
+
const result = await generateBedrockEmbedding(sanitized.texts, sanitized.inputType);
|
|
220
|
+
if (result.success && result.data) {
|
|
221
|
+
return new Response(JSON.stringify(result.data), {
|
|
169
222
|
status: result.statusCode,
|
|
170
223
|
headers,
|
|
171
224
|
});
|
|
172
225
|
}
|
|
226
|
+
return new Response(JSON.stringify({ error: "Embedding request failed" }), {
|
|
227
|
+
status: result.statusCode >= 500 ? 502 : result.statusCode,
|
|
228
|
+
headers,
|
|
229
|
+
});
|
|
173
230
|
}
|
|
174
|
-
catch
|
|
175
|
-
return new Response(JSON.stringify({
|
|
176
|
-
error: "Invalid request",
|
|
177
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
178
|
-
}), {
|
|
231
|
+
catch {
|
|
232
|
+
return new Response(JSON.stringify({ error: "Invalid request" }), {
|
|
179
233
|
status: 400,
|
|
180
|
-
headers
|
|
234
|
+
headers,
|
|
181
235
|
});
|
|
182
236
|
}
|
|
183
237
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bedrock-handler.d.ts","sourceRoot":"","sources":["../../src/server/bedrock-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bedrock-handler.d.ts","sourceRoot":"","sources":["../../src/server/bedrock-handler.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnE,KAAK,cAAc,GAAG,eAAe,GAAG;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,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;AAyRF,wBAAsB,cAAc,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAMD,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmDrE"}
|