@myscheme/voice-navigation-sdk 0.1.0

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 (41) hide show
  1. package/README.md +359 -0
  2. package/dist/actions.d.ts +8 -0
  3. package/dist/actions.d.ts.map +1 -0
  4. package/dist/actions.js +478 -0
  5. package/dist/constants.d.ts +2 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/constants.js +1 -0
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +156 -0
  11. package/dist/microphone-handler.d.ts +47 -0
  12. package/dist/microphone-handler.d.ts.map +1 -0
  13. package/dist/microphone-handler.js +341 -0
  14. package/dist/navigation-controller.d.ts +50 -0
  15. package/dist/navigation-controller.d.ts.map +1 -0
  16. package/dist/navigation-controller.js +782 -0
  17. package/dist/server/index.d.ts +3 -0
  18. package/dist/server/index.d.ts.map +1 -0
  19. package/dist/server/index.js +1 -0
  20. package/dist/server/opensearch-handler.d.ts +52 -0
  21. package/dist/server/opensearch-handler.d.ts.map +1 -0
  22. package/dist/server/opensearch-handler.js +279 -0
  23. package/dist/services/azure-speech.d.ts +13 -0
  24. package/dist/services/azure-speech.d.ts.map +1 -0
  25. package/dist/services/azure-speech.js +33 -0
  26. package/dist/services/bedrock.d.ts +18 -0
  27. package/dist/services/bedrock.d.ts.map +1 -0
  28. package/dist/services/bedrock.js +132 -0
  29. package/dist/services/schemes.d.ts +2 -0
  30. package/dist/services/schemes.d.ts.map +1 -0
  31. package/dist/services/schemes.js +1 -0
  32. package/dist/services/vector-search.d.ts +21 -0
  33. package/dist/services/vector-search.d.ts.map +1 -0
  34. package/dist/services/vector-search.js +181 -0
  35. package/dist/types.d.ts +107 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +1 -0
  38. package/dist/ui.d.ts +10 -0
  39. package/dist/ui.d.ts.map +1 -0
  40. package/dist/ui.js +225 -0
  41. package/package.json +55 -0
@@ -0,0 +1,181 @@
1
+ import { DEFAULT_VECTOR_SEARCH_API_PATH } from "../constants.js";
2
+ export class VectorSearchService {
3
+ constructor(config) {
4
+ const size = typeof config.size === "number" && config.size > 0 ? config.size : 5;
5
+ const minScore = typeof config.minScore === "number" && Number.isFinite(config.minScore)
6
+ ? config.minScore
7
+ : 0;
8
+ const numCandidates = typeof config.numCandidates === "number" && config.numCandidates >= size
9
+ ? config.numCandidates
10
+ : Math.max(size * 4, 20);
11
+ const requestTimeoutMs = typeof config.requestTimeoutMs === "number" && config.requestTimeoutMs > 0
12
+ ? Math.floor(config.requestTimeoutMs)
13
+ : 15000;
14
+ this.config = {
15
+ ...config,
16
+ vectorField: config.vectorField || "embedding",
17
+ size,
18
+ minScore,
19
+ numCandidates,
20
+ requestTimeoutMs,
21
+ sourceFields: config.sourceFields,
22
+ apiPath: config.apiPath || DEFAULT_VECTOR_SEARCH_API_PATH,
23
+ };
24
+ this.endpoint = this.resolveEndpoint(this.config.apiPath);
25
+ }
26
+ async search(vector, queryText) {
27
+ if (!Array.isArray(vector) || vector.length === 0) {
28
+ return [];
29
+ }
30
+ if (typeof fetch !== "function") {
31
+ throw new Error("Vector search requires Fetch API support in this environment");
32
+ }
33
+ const payload = this.buildRequestPayload(vector, queryText);
34
+ const timeoutMs = this.config.requestTimeoutMs;
35
+ const abortController = typeof AbortController !== "undefined" ? new AbortController() : null;
36
+ const timeoutId = abortController
37
+ ? setTimeout(() => abortController.abort(), timeoutMs)
38
+ : null;
39
+ let response;
40
+ try {
41
+ response = await fetch(this.endpoint, {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ },
46
+ body: JSON.stringify(payload),
47
+ credentials: "include",
48
+ signal: abortController?.signal,
49
+ });
50
+ }
51
+ catch (error) {
52
+ if (abortController?.signal.aborted ||
53
+ (error.name === "AbortError" && timeoutMs > 0)) {
54
+ throw new Error(`Vector search request timed out after ${timeoutMs}ms while calling ${this.endpoint}.`);
55
+ }
56
+ throw error;
57
+ }
58
+ finally {
59
+ if (timeoutId !== null) {
60
+ clearTimeout(timeoutId);
61
+ }
62
+ }
63
+ if (!response.ok) {
64
+ const errorText = await this.safeReadText(response);
65
+ if (response.status === 404) {
66
+ throw new Error("OpenSearch proxy endpoint not found. Please mount createOpenSearchProxyHandler at the configured apiPath.");
67
+ }
68
+ throw new Error(`OpenSearch proxy request failed with status ${response.status}: ${errorText || "no response body"}`);
69
+ }
70
+ const data = (await response.json());
71
+ const hits = Array.isArray(data.hits) ? data.hits : [];
72
+ return hits.map((hit) => {
73
+ const id = typeof hit.id === "string"
74
+ ? hit.id
75
+ : typeof hit._id === "string"
76
+ ? hit._id
77
+ : "";
78
+ const score = typeof hit.score === "number"
79
+ ? hit.score
80
+ : typeof hit._score === "number"
81
+ ? hit._score
82
+ : 0;
83
+ const source = hit.source ??
84
+ hit._source ??
85
+ {};
86
+ return {
87
+ id,
88
+ score,
89
+ source,
90
+ };
91
+ });
92
+ }
93
+ getSourceUrl(hit) {
94
+ const source = hit.source;
95
+ if (!source || typeof source !== "object") {
96
+ return null;
97
+ }
98
+ const metadata = source.metadata;
99
+ if (metadata) {
100
+ const candidates = [
101
+ metadata.source_url,
102
+ metadata.sourceUrl,
103
+ metadata.url,
104
+ ];
105
+ for (const candidate of candidates) {
106
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
107
+ return candidate.trim();
108
+ }
109
+ }
110
+ }
111
+ const candidates = [
112
+ source.source_url,
113
+ source.sourceUrl,
114
+ source.url,
115
+ ];
116
+ for (const candidate of candidates) {
117
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
118
+ return candidate.trim();
119
+ }
120
+ }
121
+ return null;
122
+ }
123
+ findBestMatchWithUrl(hits) {
124
+ for (const hit of hits) {
125
+ if (typeof hit.score === "number" && hit.score < this.config.minScore) {
126
+ continue;
127
+ }
128
+ const url = this.getSourceUrl(hit);
129
+ if (url) {
130
+ return { hit, url };
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ buildRequestPayload(vector, queryText) {
136
+ return {
137
+ vector,
138
+ queryText,
139
+ params: {
140
+ index: this.config.index,
141
+ vectorField: this.config.vectorField,
142
+ size: this.config.size,
143
+ numCandidates: this.config.numCandidates,
144
+ minScore: this.config.minScore,
145
+ sourceFields: this.config.sourceFields,
146
+ queryText,
147
+ },
148
+ credentials: {
149
+ node: this.config.node,
150
+ username: this.config.username,
151
+ password: this.config.password,
152
+ ssl: this.config.ssl,
153
+ },
154
+ };
155
+ }
156
+ resolveEndpoint(path) {
157
+ if (!path) {
158
+ return DEFAULT_VECTOR_SEARCH_API_PATH;
159
+ }
160
+ if (typeof window === "undefined") {
161
+ return path;
162
+ }
163
+ try {
164
+ const url = new URL(path, window.location.origin);
165
+ return url.toString();
166
+ }
167
+ catch (error) {
168
+ console.warn("Failed to resolve OpenSearch proxy path, using as-is", error);
169
+ return path;
170
+ }
171
+ }
172
+ async safeReadText(response) {
173
+ try {
174
+ return await response.text();
175
+ }
176
+ catch (error) {
177
+ console.warn("Failed to read error response body:", error);
178
+ return "";
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,107 @@
1
+ export type NavigationAction = "zoom_in" | "zoom_out" | "scroll_up" | "scroll_down" | "scroll_left" | "scroll_right" | "page_up" | "page_down" | "scroll_top" | "scroll_bottom" | "go_back" | "go_forward" | "reload_page" | "print_page" | "copy_url" | "open_menu" | "close_menu" | "focus_search" | "toggle_fullscreen" | "exit_fullscreen" | "play_media" | "pause_media" | "mute_media" | "unmute_media" | "navigate_home" | "search_content" | "navigate_search" | "navigate_faqs" | "navigate_help" | "navigate_contact" | "navigate_about" | "navigate_screen_reader" | "navigate_accessibility" | "navigate_disclaimer" | "navigate_terms_conditions" | "stop" | "unknown";
2
+ export interface NavigationConfig {
3
+ aws?: {
4
+ region?: string;
5
+ accessKeyId?: string;
6
+ secretAccessKey?: string;
7
+ modelId?: string;
8
+ embeddingModelId?: string | null;
9
+ };
10
+ azure?: {
11
+ subscriptionKey?: string;
12
+ region?: string;
13
+ };
14
+ language?: string;
15
+ autoStart?: boolean;
16
+ actionHandlers?: Partial<Record<NavigationAction, (context?: ActionContext) => void>>;
17
+ opensearch?: OpenSearchConfig;
18
+ openSearch?: OpenSearchConfig;
19
+ }
20
+ export interface OpenSearchConfig {
21
+ node: string;
22
+ username: string;
23
+ password: string;
24
+ index: string;
25
+ vectorField?: string;
26
+ size?: number;
27
+ minScore?: number;
28
+ numCandidates?: number;
29
+ requestTimeoutMs?: number;
30
+ sourceFields?: string[];
31
+ ssl?: {
32
+ rejectUnauthorized?: boolean;
33
+ };
34
+ apiPath?: string;
35
+ }
36
+ export interface ActionContext {
37
+ controller?: any;
38
+ handler?: any;
39
+ onStop?: () => void;
40
+ transcript?: string;
41
+ timestamp?: number;
42
+ actionResult?: ActionResult;
43
+ [key: string]: any;
44
+ }
45
+ export interface ActionResult {
46
+ performed: boolean;
47
+ info: {
48
+ newZoom?: number;
49
+ scrolled?: boolean;
50
+ direction?: string;
51
+ navigated?: boolean;
52
+ reloaded?: boolean;
53
+ printed?: boolean;
54
+ copied?: boolean;
55
+ fullscreen?: boolean;
56
+ mediaState?: "playing" | "paused";
57
+ muted?: boolean;
58
+ [key: string]: any;
59
+ url?: string;
60
+ query?: string;
61
+ score?: number;
62
+ external?: boolean;
63
+ message?: string;
64
+ reason?: string;
65
+ };
66
+ }
67
+ export interface AgentActionResponse {
68
+ action: NavigationAction;
69
+ query?: string;
70
+ }
71
+ export interface BedrockResponse {
72
+ content?: Array<{
73
+ text?: string;
74
+ }>;
75
+ stop_reason?: string;
76
+ }
77
+ export interface AzureTokenResponse {
78
+ token: string;
79
+ region: string;
80
+ }
81
+ export interface NavigationCallbacks {
82
+ onPartial?: (text: string) => void;
83
+ onSegment?: (text: string, info?: any) => void;
84
+ onSilence?: () => void;
85
+ onAction?: (action: NavigationAction, performed: boolean, info?: any) => void;
86
+ onError?: (error: Error) => void;
87
+ onStateChange?: (state: NavigationState) => void;
88
+ }
89
+ export type NavigationState = "idle" | "loading" | "listening" | "thinking" | "error";
90
+ export interface UIElements {
91
+ root: HTMLElement;
92
+ button: HTMLButtonElement;
93
+ label: HTMLElement;
94
+ status: HTMLElement;
95
+ transcript: HTMLElement;
96
+ action: HTMLElement;
97
+ }
98
+ export interface NavigationEvent extends CustomEvent {
99
+ detail: {
100
+ state?: NavigationState;
101
+ action?: NavigationAction;
102
+ transcript?: string;
103
+ error?: Error;
104
+ [key: string]: any;
105
+ };
106
+ }
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/ui.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { UIElements, NavigationState } from "./types.js";
2
+ export declare const injectFloatingControlStyles: () => void;
3
+ export declare const createFloatingControl: () => UIElements;
4
+ export declare const updateStatus: (ui: UIElements, text: string) => void;
5
+ export declare const setState: (ui: UIElements, state: NavigationState) => void;
6
+ export declare const updateTranscript: (ui: UIElements, text: string) => void;
7
+ export declare const updateActionIndicator: (ui: UIElements, actionName: string, performed: boolean, info?: any) => void;
8
+ export declare const showError: (ui: UIElements, error: Error) => void;
9
+ export declare const emitEvent: (type: string, detail?: any) => void;
10
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAK9D,eAAO,MAAM,2BAA2B,QAAO,IAmH9C,CAAC;AAKF,eAAO,MAAM,qBAAqB,QAAO,UAqDxC,CAAC;AAKF,eAAO,MAAM,YAAY,GAAI,IAAI,UAAU,EAAE,MAAM,MAAM,KAAG,IAE3D,CAAC;AAKF,eAAO,MAAM,QAAQ,GAAI,IAAI,UAAU,EAAE,OAAO,eAAe,KAAG,IA+BjE,CAAC;AAKF,eAAO,MAAM,gBAAgB,GAAI,IAAI,UAAU,EAAE,MAAM,MAAM,KAAG,IAE/D,CAAC;AAKF,eAAO,MAAM,qBAAqB,GAChC,IAAI,UAAU,EACd,YAAY,MAAM,EAClB,WAAW,OAAO,EAClB,OAAM,GAAQ,KACb,IAkBF,CAAC;AAKF,eAAO,MAAM,SAAS,GAAI,IAAI,UAAU,EAAE,OAAO,KAAK,KAAG,IAGxD,CAAC;AAKF,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,SAAQ,GAAQ,KAAG,IAU1D,CAAC"}
package/dist/ui.js ADDED
@@ -0,0 +1,225 @@
1
+ export const injectFloatingControlStyles = () => {
2
+ if (document.getElementById("navigate-control-styles")) {
3
+ return;
4
+ }
5
+ const style = document.createElement("style");
6
+ style.id = "navigate-control-styles";
7
+ style.textContent = `
8
+ .navigate-control {
9
+ position: fixed;
10
+ bottom: 24px;
11
+ right: 24px;
12
+ display: flex;
13
+ gap: 12px;
14
+ align-items: flex-end;
15
+ z-index: 2147483647;
16
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
17
+ color: #0f172a;
18
+ pointer-events: auto;
19
+ }
20
+
21
+ .navigate-control__button {
22
+ width: 60px;
23
+ height: 60px;
24
+ border-radius: 50%;
25
+ border: none;
26
+ background: #1d4ed8;
27
+ color: #fff;
28
+ font-weight: 600;
29
+ cursor: pointer;
30
+ box-shadow: 0 14px 24px rgba(15, 23, 42, 0.25);
31
+ transition: transform 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
32
+ }
33
+
34
+ .navigate-control__button:hover {
35
+ transform: translateY(-2px);
36
+ }
37
+
38
+ .navigate-control__button:focus {
39
+ outline: 2px solid #38bdf8;
40
+ outline-offset: 3px;
41
+ }
42
+
43
+ .navigate-control__panel {
44
+ min-width: 220px;
45
+ max-width: 320px;
46
+ padding: 12px 16px;
47
+ border-radius: 14px;
48
+ background: rgba(15, 23, 42, 0.92);
49
+ color: #f8fafc;
50
+ box-shadow: 0 18px 30px rgba(15, 23, 42, 0.35);
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 6px;
54
+ backdrop-filter: blur(6px);
55
+ }
56
+
57
+ .navigate-control__status {
58
+ font-size: 0.85rem;
59
+ letter-spacing: 0.02em;
60
+ text-transform: uppercase;
61
+ color: #bae6fd;
62
+ }
63
+
64
+ .navigate-control__transcript {
65
+ font-size: 0.95rem;
66
+ line-height: 1.4;
67
+ color: #e2e8f0;
68
+ max-height: 120px;
69
+ overflow-y: auto;
70
+ }
71
+
72
+ .navigate-control__transcript:empty,
73
+ .navigate-control__action:empty {
74
+ display: none;
75
+ }
76
+
77
+ .navigate-control__action {
78
+ font-size: 0.85rem;
79
+ color: #a5f3fc;
80
+ }
81
+
82
+ .navigate-control[data-state="loading"] .navigate-control__button,
83
+ .navigate-control[data-state="thinking"] .navigate-control__button {
84
+ cursor: progress;
85
+ box-shadow: 0 12px 20px rgba(15, 23, 42, 0.2);
86
+ }
87
+
88
+ .navigate-control[data-state="loading"] .navigate-control__button {
89
+ background: #64748b;
90
+ width: 200px;
91
+ border-radius: 14px;
92
+ }
93
+
94
+ .navigate-control[data-state="listening"] .navigate-control__button {
95
+ background: #dc2626;
96
+ }
97
+
98
+ .navigate-control[data-state="thinking"] .navigate-control__button {
99
+ background: #7c3aed;
100
+ }
101
+
102
+ @media (max-width: 640px) {
103
+ .navigate-control {
104
+ right: 16px;
105
+ bottom: 16px;
106
+ }
107
+
108
+ .navigate-control__panel {
109
+ min-width: 180px;
110
+ }
111
+ }
112
+ `;
113
+ document.head.appendChild(style);
114
+ };
115
+ export const createFloatingControl = () => {
116
+ injectFloatingControlStyles();
117
+ let root = document.getElementById("navigate-control");
118
+ if (!root) {
119
+ root = document.createElement("div");
120
+ root.id = "navigate-control";
121
+ root.className = "navigate-control";
122
+ }
123
+ root.dataset.state = root.dataset.state || "idle";
124
+ root.innerHTML = `
125
+ <button type="button" class="navigate-control__button" aria-pressed="false" aria-label="Start voice control">
126
+ <span class="navigate-control__label">Start</span>
127
+ </button>
128
+ <div class="navigate-control__panel">
129
+ <div class="navigate-control__status">Tap to start voice control</div>
130
+ <div class="navigate-control__transcript" role="status" aria-live="polite"></div>
131
+ <div class="navigate-control__action" aria-live="polite"></div>
132
+ </div>
133
+ `;
134
+ const appendToBody = () => {
135
+ if (!document.body) {
136
+ return;
137
+ }
138
+ if (!document.body.contains(root)) {
139
+ document.body.appendChild(root);
140
+ }
141
+ };
142
+ appendToBody();
143
+ if (document.readyState === "loading") {
144
+ document.addEventListener("DOMContentLoaded", appendToBody, {
145
+ once: true,
146
+ });
147
+ }
148
+ return {
149
+ root,
150
+ button: root.querySelector(".navigate-control__button"),
151
+ label: root.querySelector(".navigate-control__label"),
152
+ status: root.querySelector(".navigate-control__status"),
153
+ transcript: root.querySelector(".navigate-control__transcript"),
154
+ action: root.querySelector(".navigate-control__action"),
155
+ };
156
+ };
157
+ export const updateStatus = (ui, text) => {
158
+ ui.status.textContent = text;
159
+ };
160
+ export const setState = (ui, state) => {
161
+ ui.root.dataset.state = state;
162
+ switch (state) {
163
+ case "idle":
164
+ ui.label.textContent = "Start";
165
+ ui.button.setAttribute("aria-pressed", "false");
166
+ ui.button.setAttribute("aria-label", "Start voice control");
167
+ updateStatus(ui, "Tap to start voice control");
168
+ break;
169
+ case "loading":
170
+ ui.label.textContent = "Loading...";
171
+ ui.button.setAttribute("aria-label", "Loading voice control");
172
+ updateStatus(ui, "Loading...");
173
+ break;
174
+ case "listening":
175
+ ui.label.textContent = "Stop";
176
+ ui.button.setAttribute("aria-pressed", "true");
177
+ ui.button.setAttribute("aria-label", "Stop voice control");
178
+ updateStatus(ui, "Listening...");
179
+ break;
180
+ case "thinking":
181
+ ui.label.textContent = "Stop";
182
+ ui.button.setAttribute("aria-label", "Processing");
183
+ updateStatus(ui, "Processing...");
184
+ break;
185
+ case "error":
186
+ ui.label.textContent = "Error";
187
+ ui.button.setAttribute("aria-label", "Error occurred");
188
+ break;
189
+ }
190
+ };
191
+ export const updateTranscript = (ui, text) => {
192
+ ui.transcript.textContent = text;
193
+ };
194
+ export const updateActionIndicator = (ui, actionName, performed, info = {}) => {
195
+ let message = `Action: ${actionName}`;
196
+ if (performed) {
197
+ message += " ✓";
198
+ if (info.newZoom) {
199
+ message += ` (zoom: ${info.newZoom.toFixed(1)}x)`;
200
+ }
201
+ if (info.scrolled && info.direction) {
202
+ message += ` (${info.direction})`;
203
+ }
204
+ }
205
+ else {
206
+ message += " ✗";
207
+ }
208
+ ui.action.textContent = message;
209
+ ui.action.style.color = performed ? "#a5f3fc" : "#fca5a5";
210
+ ui.action.setAttribute("aria-live", "polite");
211
+ };
212
+ export const showError = (ui, error) => {
213
+ ui.action.textContent = `Error: ${error.message}`;
214
+ ui.action.style.color = "#fca5a5";
215
+ };
216
+ export const emitEvent = (type, detail = {}) => {
217
+ if (typeof window === "undefined")
218
+ return;
219
+ const event = new CustomEvent(`navigate:${type}`, {
220
+ detail,
221
+ bubbles: true,
222
+ cancelable: true,
223
+ });
224
+ window.dispatchEvent(event);
225
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@myscheme/voice-navigation-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Voice navigation SDK using Azure Speech and AWS Bedrock",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./server": {
14
+ "types": "./dist/server/index.d.ts",
15
+ "import": "./dist/server/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "dev": "tsc -w -p tsconfig.json",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "npm run clean && npm run build"
27
+ },
28
+ "dependencies": {
29
+ "@opensearch-project/opensearch": "^2.12.0",
30
+ "@aws-sdk/client-bedrock-runtime": "^3.705.0",
31
+ "@azure/core-auth": "^1.10.0",
32
+ "microsoft-cognitiveservices-speech-sdk": "^1.47.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.17.10",
36
+ "typescript": "^5.7.2"
37
+ },
38
+ "peerDependencies": {},
39
+ "keywords": [
40
+ "voice",
41
+ "navigation",
42
+ "accessibility",
43
+ "azure",
44
+ "speech",
45
+ "bedrock",
46
+ "aws",
47
+ "typescript"
48
+ ],
49
+ "author": "",
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": ""
54
+ }
55
+ }