@myscheme/voice-navigation-sdk 0.1.3 → 0.1.6
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 +250 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +397 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/microphone-handler.d.ts +2 -0
- package/dist/microphone-handler.d.ts.map +1 -1
- package/dist/microphone-handler.js +11 -2
- package/dist/navigation-controller.d.ts +2 -0
- package/dist/navigation-controller.d.ts.map +1 -1
- package/dist/navigation-controller.js +89 -10
- package/dist/server/azure-speech-handler.d.ts +12 -0
- package/dist/server/azure-speech-handler.d.ts.map +1 -0
- package/dist/server/azure-speech-handler.js +40 -0
- package/dist/server/bedrock-embedding-handler.d.ts +15 -0
- package/dist/server/bedrock-embedding-handler.d.ts.map +1 -0
- package/dist/server/bedrock-embedding-handler.js +123 -0
- package/dist/server/bedrock-handler.d.ts +14 -0
- package/dist/server/bedrock-handler.d.ts.map +1 -0
- package/dist/server/bedrock-handler.js +114 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/opensearch-env-handler.d.ts +22 -0
- package/dist/server/opensearch-env-handler.d.ts.map +1 -0
- package/dist/server/opensearch-env-handler.js +221 -0
- package/dist/services/azure-speech.d.ts +10 -2
- package/dist/services/azure-speech.d.ts.map +1 -1
- package/dist/services/azure-speech.js +66 -3
- package/dist/services/bedrock.d.ts +13 -4
- package/dist/services/bedrock.d.ts.map +1 -1
- package/dist/services/bedrock.js +171 -17
- package/dist/services/vector-search.d.ts.map +1 -1
- package/dist/services/vector-search.js +15 -2
- package/dist/types.d.ts +24 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +4 -3
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +150 -17
- package/package.json +3 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
function sign(key, msg) {
|
|
3
|
+
return crypto.createHmac("sha256", key).update(msg, "utf8").digest();
|
|
4
|
+
}
|
|
5
|
+
function getSignatureKey(key, dateStamp, region, service) {
|
|
6
|
+
const kDate = sign("AWS4" + key, dateStamp);
|
|
7
|
+
const kRegion = sign(kDate, region);
|
|
8
|
+
const kService = sign(kRegion, service);
|
|
9
|
+
const kSigning = sign(kService, "aws4_request");
|
|
10
|
+
return kSigning;
|
|
11
|
+
}
|
|
12
|
+
export async function bedrockHandler(req, res) {
|
|
13
|
+
if (req.method !== "POST") {
|
|
14
|
+
res.setHeader("Allow", ["POST"]);
|
|
15
|
+
res.status(405).json({ error: "Method not allowed" });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const region = process.env.AWS_REGION || "ap-south-1";
|
|
19
|
+
const accessKey = process.env.AWS_ACCESS_KEY_ID;
|
|
20
|
+
const secretKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
21
|
+
const sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
22
|
+
const modelId = process.env.AWS_MODEL_ID || "anthropic.claude-3-sonnet-20240229-v1:0";
|
|
23
|
+
if (!accessKey || !secretKey) {
|
|
24
|
+
console.error("[VoiceNavigation] Missing AWS credentials. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your .env file");
|
|
25
|
+
res.status(500).json({
|
|
26
|
+
error: "Missing AWS credentials",
|
|
27
|
+
hint: "Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables",
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const requestBody = req.body?.body;
|
|
32
|
+
if (!requestBody) {
|
|
33
|
+
res.status(400).json({ error: "Missing request body" });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const body = JSON.stringify(requestBody);
|
|
37
|
+
const service = "bedrock";
|
|
38
|
+
const host = `bedrock-runtime.${region}.amazonaws.com`;
|
|
39
|
+
const encodedModelId = encodeURIComponent(modelId);
|
|
40
|
+
const path = `/model/${encodedModelId}/invoke`;
|
|
41
|
+
const endpoint = `https://${host}${path}`;
|
|
42
|
+
const canonicalPath = path.replace(/%/g, "%25");
|
|
43
|
+
const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
44
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
45
|
+
const headers = {
|
|
46
|
+
"content-type": "application/json",
|
|
47
|
+
host: host,
|
|
48
|
+
"x-amz-date": amzDate,
|
|
49
|
+
};
|
|
50
|
+
if (sessionToken) {
|
|
51
|
+
headers["x-amz-security-token"] = sessionToken;
|
|
52
|
+
}
|
|
53
|
+
const signedHeaders = Object.keys(headers)
|
|
54
|
+
.map((h) => h.toLowerCase())
|
|
55
|
+
.sort()
|
|
56
|
+
.join(";");
|
|
57
|
+
const canonicalHeaders = Object.keys(headers)
|
|
58
|
+
.map((h) => h.toLowerCase())
|
|
59
|
+
.sort()
|
|
60
|
+
.map((k) => `${k}:${headers[k]}\n`)
|
|
61
|
+
.join("");
|
|
62
|
+
const canonicalRequest = "POST\n" +
|
|
63
|
+
canonicalPath +
|
|
64
|
+
"\n" +
|
|
65
|
+
"\n" +
|
|
66
|
+
canonicalHeaders +
|
|
67
|
+
"\n" +
|
|
68
|
+
signedHeaders +
|
|
69
|
+
"\n" +
|
|
70
|
+
crypto.createHash("sha256").update(body, "utf8").digest("hex");
|
|
71
|
+
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
|
|
72
|
+
const stringToSign = "AWS4-HMAC-SHA256\n" +
|
|
73
|
+
amzDate +
|
|
74
|
+
"\n" +
|
|
75
|
+
credentialScope +
|
|
76
|
+
"\n" +
|
|
77
|
+
crypto.createHash("sha256").update(canonicalRequest, "utf8").digest("hex");
|
|
78
|
+
const signingKey = getSignatureKey(secretKey, dateStamp, region, service);
|
|
79
|
+
const signature = crypto
|
|
80
|
+
.createHmac("sha256", signingKey)
|
|
81
|
+
.update(stringToSign, "utf8")
|
|
82
|
+
.digest("hex");
|
|
83
|
+
const authorizationHeader = `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, ` +
|
|
84
|
+
`SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
85
|
+
const fetchHeaders = {
|
|
86
|
+
...headers,
|
|
87
|
+
Authorization: authorizationHeader,
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(endpoint, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: fetchHeaders,
|
|
93
|
+
body,
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
const errorText = await response.text();
|
|
97
|
+
console.error("[VoiceNavigation] Bedrock API error:", response.status, errorText);
|
|
98
|
+
res.status(response.status).json({
|
|
99
|
+
error: "Bedrock API error",
|
|
100
|
+
details: errorText,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const json = await response.json();
|
|
105
|
+
res.status(200).json(json);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error("[VoiceNavigation] Bedrock action extraction error:", error);
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
error: "Failed to extract action",
|
|
111
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export { azureSpeechTokenHandler } from "./azure-speech-handler.js";
|
|
2
|
+
export { bedrockHandler } from "./bedrock-handler.js";
|
|
3
|
+
export { bedrockEmbeddingHandler } from "./bedrock-embedding-handler.js";
|
|
4
|
+
export { openSearchSearchHandler, openSearchSuggestHandler, } from "./opensearch-env-handler.js";
|
|
1
5
|
export { createOpenSearchProxyHandler, DEFAULT_OPENSEARCH_PROXY_PATH, } from "./opensearch-handler.js";
|
|
2
6
|
export type { VectorSearchProxyRequest, VectorSearchProxyResponse, } from "./opensearch-handler.js";
|
|
3
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
export { azureSpeechTokenHandler } from "./azure-speech-handler.js";
|
|
2
|
+
export { bedrockHandler } from "./bedrock-handler.js";
|
|
3
|
+
export { bedrockEmbeddingHandler } from "./bedrock-embedding-handler.js";
|
|
4
|
+
export { openSearchSearchHandler, openSearchSuggestHandler, } from "./opensearch-env-handler.js";
|
|
1
5
|
export { createOpenSearchProxyHandler, DEFAULT_OPENSEARCH_PROXY_PATH, } from "./opensearch-handler.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type NextApiRequest = {
|
|
2
|
+
method?: string;
|
|
3
|
+
headers?: {
|
|
4
|
+
[key: string]: string | string[] | undefined;
|
|
5
|
+
};
|
|
6
|
+
body?: any;
|
|
7
|
+
query?: {
|
|
8
|
+
[key: string]: string | string[];
|
|
9
|
+
};
|
|
10
|
+
socket?: {
|
|
11
|
+
remoteAddress?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
type NextApiResponse = {
|
|
15
|
+
status: (code: number) => NextApiResponse;
|
|
16
|
+
json: (data: any) => void;
|
|
17
|
+
setHeader: (name: string, value: string | string[]) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function openSearchSearchHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
|
|
20
|
+
export declare function openSearchSuggestHandler(req: NextApiRequest, res: NextApiResponse): Promise<void>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=opensearch-env-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opensearch-env-handler.d.ts","sourceRoot":"","sources":["../../src/server/opensearch-env-handler.ts"],"names":[],"mappings":"AAuBA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3D,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAC;IAC7C,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC1C,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC1B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7D,CAAC;AAmFF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CA6Hf;AAMD,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAgFf"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const rateLimitMap = new Map();
|
|
2
|
+
function checkRateLimit(identifier, maxRequests, windowMs) {
|
|
3
|
+
const now = Date.now();
|
|
4
|
+
const record = rateLimitMap.get(identifier);
|
|
5
|
+
if (!record || now > record.resetTime) {
|
|
6
|
+
rateLimitMap.set(identifier, {
|
|
7
|
+
count: 1,
|
|
8
|
+
resetTime: now + windowMs,
|
|
9
|
+
});
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
if (record.count >= maxRequests) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
record.count++;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function getClientIdentifier(req) {
|
|
19
|
+
const forwardedFor = req.headers?.["x-forwarded-for"];
|
|
20
|
+
if (forwardedFor) {
|
|
21
|
+
const ip = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor;
|
|
22
|
+
return ip.split(",")[0].trim();
|
|
23
|
+
}
|
|
24
|
+
const realIp = req.headers?.["x-real-ip"];
|
|
25
|
+
if (realIp) {
|
|
26
|
+
return Array.isArray(realIp) ? realIp[0] : realIp;
|
|
27
|
+
}
|
|
28
|
+
return req.socket?.remoteAddress || "unknown";
|
|
29
|
+
}
|
|
30
|
+
let opensearchClient = null;
|
|
31
|
+
async function getOpenSearchClient() {
|
|
32
|
+
if (!opensearchClient) {
|
|
33
|
+
const node = process.env.OPENSEARCH_NODE;
|
|
34
|
+
const username = process.env.OPENSEARCH_USERNAME;
|
|
35
|
+
const password = process.env.OPENSEARCH_PASSWORD;
|
|
36
|
+
if (!node || !username || !password) {
|
|
37
|
+
throw new Error("OpenSearch configuration is missing. Set OPENSEARCH_NODE, OPENSEARCH_USERNAME, and OPENSEARCH_PASSWORD in your .env file");
|
|
38
|
+
}
|
|
39
|
+
const { Client } = await import("@opensearch-project/opensearch");
|
|
40
|
+
opensearchClient = new Client({
|
|
41
|
+
node: node,
|
|
42
|
+
auth: {
|
|
43
|
+
username: username,
|
|
44
|
+
password: password,
|
|
45
|
+
},
|
|
46
|
+
ssl: {
|
|
47
|
+
rejectUnauthorized: true,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return opensearchClient;
|
|
52
|
+
}
|
|
53
|
+
export async function openSearchSearchHandler(req, res) {
|
|
54
|
+
if (req.method !== "POST") {
|
|
55
|
+
res.setHeader("Allow", ["POST"]);
|
|
56
|
+
res.status(405).json({ error: "Method not allowed" });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const clientId = getClientIdentifier(req);
|
|
60
|
+
if (!checkRateLimit(clientId, 20, 60 * 1000)) {
|
|
61
|
+
res.status(429).json({
|
|
62
|
+
error: "Too many requests",
|
|
63
|
+
message: "Rate limit exceeded. Please try again later.",
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const { query, vector, size = 10 } = req.body || {};
|
|
68
|
+
if (!query || !vector) {
|
|
69
|
+
res.status(400).json({
|
|
70
|
+
error: "Missing required parameters",
|
|
71
|
+
hint: "Provide both 'query' and 'vector' in request body",
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (typeof query !== "string" || query.length > 500) {
|
|
76
|
+
res.status(400).json({
|
|
77
|
+
error: "Invalid query parameter",
|
|
78
|
+
hint: "Query must be a string with max 500 characters",
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
83
|
+
res.status(400).json({
|
|
84
|
+
error: "Invalid vector parameter",
|
|
85
|
+
hint: "Vector must be a non-empty array of numbers",
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const client = await getOpenSearchClient();
|
|
91
|
+
const indexName = process.env.OPENSEARCH_INDEX;
|
|
92
|
+
const vectorField = process.env.OPENSEARCH_VECTOR_FIELD || "embedding";
|
|
93
|
+
if (!indexName) {
|
|
94
|
+
throw new Error("OPENSEARCH_INDEX environment variable is not set");
|
|
95
|
+
}
|
|
96
|
+
let searchResponse;
|
|
97
|
+
try {
|
|
98
|
+
searchResponse = await client.search({
|
|
99
|
+
index: indexName,
|
|
100
|
+
body: {
|
|
101
|
+
size: Math.min(size, 100),
|
|
102
|
+
query: {
|
|
103
|
+
knn: {
|
|
104
|
+
[vectorField]: {
|
|
105
|
+
vector: vector,
|
|
106
|
+
k: Math.min(size * 2, 200),
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
_source: ["name", "path", "keywords", "description"],
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (knnError) {
|
|
115
|
+
const errorMessage = knnError?.message || "";
|
|
116
|
+
if (errorMessage.includes("is not knn_vector type") ||
|
|
117
|
+
errorMessage.includes("failed to create query") ||
|
|
118
|
+
errorMessage.includes("knn")) {
|
|
119
|
+
console.log(`[VoiceNavigation] Using text-based search (kNN vector search not available for field '${vectorField}')`);
|
|
120
|
+
searchResponse = await client.search({
|
|
121
|
+
index: indexName,
|
|
122
|
+
body: {
|
|
123
|
+
size: Math.min(size, 100),
|
|
124
|
+
query: {
|
|
125
|
+
query_string: {
|
|
126
|
+
query: query,
|
|
127
|
+
default_operator: "OR",
|
|
128
|
+
analyze_wildcard: true,
|
|
129
|
+
lenient: true,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
_source: true,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
throw knnError;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const hits = searchResponse.body.hits.hits.map((hit) => ({
|
|
141
|
+
id: hit._id,
|
|
142
|
+
score: hit._score,
|
|
143
|
+
source: hit._source,
|
|
144
|
+
}));
|
|
145
|
+
res.status(200).json({ hits });
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error("[VoiceNavigation] OpenSearch search error:", error);
|
|
149
|
+
res.status(500).json({
|
|
150
|
+
error: "Search failed",
|
|
151
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export async function openSearchSuggestHandler(req, res) {
|
|
156
|
+
if (req.method !== "POST") {
|
|
157
|
+
res.setHeader("Allow", ["POST"]);
|
|
158
|
+
res.status(405).json({ error: "Method not allowed" });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const clientId = getClientIdentifier(req);
|
|
162
|
+
if (!checkRateLimit(clientId, 30, 60 * 1000)) {
|
|
163
|
+
res.status(429).json({
|
|
164
|
+
error: "Too many requests",
|
|
165
|
+
message: "Rate limit exceeded. Please try again later.",
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const { query } = req.body || {};
|
|
170
|
+
if (!query || typeof query !== "string") {
|
|
171
|
+
res.status(400).json({
|
|
172
|
+
error: "Missing or invalid query parameter",
|
|
173
|
+
hint: "Provide 'query' as a string in request body",
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (query.length > 200) {
|
|
178
|
+
res.status(400).json({
|
|
179
|
+
error: "Query too long",
|
|
180
|
+
hint: "Query must be max 200 characters for suggestions",
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const client = await getOpenSearchClient();
|
|
186
|
+
const indexName = process.env.OPENSEARCH_INDEX;
|
|
187
|
+
if (!indexName) {
|
|
188
|
+
throw new Error("OPENSEARCH_INDEX environment variable is not set");
|
|
189
|
+
}
|
|
190
|
+
const searchResponse = await client.search({
|
|
191
|
+
index: indexName,
|
|
192
|
+
body: {
|
|
193
|
+
size: 5,
|
|
194
|
+
query: {
|
|
195
|
+
multi_match: {
|
|
196
|
+
query: query,
|
|
197
|
+
fields: ["name^3", "keywords^2", "description"],
|
|
198
|
+
type: "bool_prefix",
|
|
199
|
+
fuzziness: "AUTO",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
_source: ["name", "path", "keywords"],
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const suggestions = searchResponse.body.hits.hits.map((hit) => ({
|
|
206
|
+
id: hit._id,
|
|
207
|
+
score: hit._score,
|
|
208
|
+
name: hit._source.name,
|
|
209
|
+
path: hit._source.path,
|
|
210
|
+
keywords: hit._source.keywords || [],
|
|
211
|
+
}));
|
|
212
|
+
res.status(200).json({ suggestions });
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
console.error("[VoiceNavigation] OpenSearch suggest error:", error);
|
|
216
|
+
res.status(500).json({
|
|
217
|
+
error: "Suggestion failed",
|
|
218
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { AzureTokenResponse } from "../types.js";
|
|
2
2
|
export interface AzureSpeechConfig {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
tokenEndpoint?: string;
|
|
4
|
+
subscriptionKey?: string;
|
|
5
|
+
region?: string;
|
|
5
6
|
}
|
|
6
7
|
export declare class AzureSpeechService {
|
|
8
|
+
private tokenEndpoint;
|
|
7
9
|
private subscriptionKey;
|
|
8
10
|
private region;
|
|
11
|
+
private useProxy;
|
|
12
|
+
private cachedToken;
|
|
13
|
+
private tokenExpiration;
|
|
9
14
|
constructor(config: AzureSpeechConfig);
|
|
10
15
|
fetchToken(): Promise<AzureTokenResponse>;
|
|
16
|
+
private fetchTokenViaProxy;
|
|
17
|
+
private fetchTokenDirect;
|
|
18
|
+
clearTokenCache(): void;
|
|
11
19
|
static create(config: AzureSpeechConfig): AzureSpeechService;
|
|
12
20
|
}
|
|
13
21
|
//# sourceMappingURL=azure-speech.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azure-speech.d.ts","sourceRoot":"","sources":["../../src/services/azure-speech.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,iBAAiB;
|
|
1
|
+
{"version":3,"file":"azure-speech.d.ts","sourceRoot":"","sources":["../../src/services/azure-speech.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAEhC,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAAa;gBAExB,MAAM,EAAE,iBAAiB;IAoB/B,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;YAmBjC,kBAAkB;YAoClB,gBAAgB;IAyC9B,eAAe,IAAI,IAAI;IAQvB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,kBAAkB;CAG7D"}
|
|
@@ -1,9 +1,66 @@
|
|
|
1
1
|
export class AzureSpeechService {
|
|
2
2
|
constructor(config) {
|
|
3
|
-
this.
|
|
4
|
-
this.
|
|
3
|
+
this.tokenEndpoint = null;
|
|
4
|
+
this.subscriptionKey = null;
|
|
5
|
+
this.region = null;
|
|
6
|
+
this.useProxy = false;
|
|
7
|
+
this.cachedToken = null;
|
|
8
|
+
this.tokenExpiration = 0;
|
|
9
|
+
this.useProxy = !!config.tokenEndpoint;
|
|
10
|
+
if (this.useProxy) {
|
|
11
|
+
this.tokenEndpoint = config.tokenEndpoint || null;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
if (!config.subscriptionKey || !config.region) {
|
|
15
|
+
throw new Error("Azure subscription key and region required for direct mode");
|
|
16
|
+
}
|
|
17
|
+
this.subscriptionKey = config.subscriptionKey;
|
|
18
|
+
this.region = config.region;
|
|
19
|
+
}
|
|
5
20
|
}
|
|
6
21
|
async fetchToken() {
|
|
22
|
+
if (this.cachedToken && Date.now() < this.tokenExpiration) {
|
|
23
|
+
return {
|
|
24
|
+
token: this.cachedToken,
|
|
25
|
+
region: this.region || "centralindia",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (this.useProxy) {
|
|
29
|
+
return this.fetchTokenViaProxy();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return this.fetchTokenDirect();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async fetchTokenViaProxy() {
|
|
36
|
+
if (!this.tokenEndpoint) {
|
|
37
|
+
throw new Error("Token endpoint not configured");
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(this.tokenEndpoint, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`Failed to fetch token: ${response.status} ${response.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
const data = (await response.json());
|
|
50
|
+
this.cachedToken = data.token;
|
|
51
|
+
this.tokenExpiration = Date.now() + 9 * 60 * 1000;
|
|
52
|
+
this.region = data.region;
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error("Failed to fetch Azure speech token via proxy:", error);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async fetchTokenDirect() {
|
|
61
|
+
if (!this.subscriptionKey || !this.region) {
|
|
62
|
+
throw new Error("Azure subscription key and region not configured");
|
|
63
|
+
}
|
|
7
64
|
const url = `https://${this.region}.api.cognitive.microsoft.com/sts/v1.0/issueToken`;
|
|
8
65
|
try {
|
|
9
66
|
const response = await fetch(url, {
|
|
@@ -17,9 +74,11 @@ export class AzureSpeechService {
|
|
|
17
74
|
throw new Error(`Failed to fetch token: ${response.status} ${response.statusText}`);
|
|
18
75
|
}
|
|
19
76
|
const token = await response.text();
|
|
77
|
+
this.cachedToken = token;
|
|
78
|
+
this.tokenExpiration = Date.now() + 9 * 60 * 1000;
|
|
20
79
|
return {
|
|
21
80
|
token,
|
|
22
|
-
region: this.region,
|
|
81
|
+
region: this.region || "centralindia",
|
|
23
82
|
};
|
|
24
83
|
}
|
|
25
84
|
catch (error) {
|
|
@@ -27,6 +86,10 @@ export class AzureSpeechService {
|
|
|
27
86
|
throw error;
|
|
28
87
|
}
|
|
29
88
|
}
|
|
89
|
+
clearTokenCache() {
|
|
90
|
+
this.cachedToken = null;
|
|
91
|
+
this.tokenExpiration = 0;
|
|
92
|
+
}
|
|
30
93
|
static create(config) {
|
|
31
94
|
return new AzureSpeechService(config);
|
|
32
95
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { AgentActionResponse } from "../types.js";
|
|
2
2
|
import type { PageRegistry } from "./page-registry.js";
|
|
3
3
|
export interface BedrockConfig {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
bedrockEndpoint?: string;
|
|
5
|
+
embeddingEndpoint?: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
accessKeyId?: string;
|
|
8
|
+
secretAccessKey?: string;
|
|
9
|
+
modelId?: string;
|
|
8
10
|
embeddingModelId?: string | null;
|
|
9
11
|
}
|
|
10
12
|
export declare class BedrockService {
|
|
@@ -12,11 +14,18 @@ export declare class BedrockService {
|
|
|
12
14
|
private actionModelId;
|
|
13
15
|
private embeddingModelId;
|
|
14
16
|
private pageRegistry;
|
|
17
|
+
private bedrockEndpoint;
|
|
18
|
+
private embeddingEndpointUrl;
|
|
19
|
+
private useProxy;
|
|
15
20
|
constructor(config: BedrockConfig);
|
|
16
21
|
setPageRegistry(registry: PageRegistry): void;
|
|
17
22
|
private buildSystemPrompt;
|
|
18
23
|
extractAction(userInput: string): Promise<AgentActionResponse>;
|
|
24
|
+
private extractActionViaProxy;
|
|
25
|
+
private extractActionDirect;
|
|
19
26
|
static create(config: BedrockConfig): BedrockService;
|
|
20
27
|
embedText(text: string): Promise<number[]>;
|
|
28
|
+
private embedTextViaProxy;
|
|
29
|
+
private embedTextDirect;
|
|
21
30
|
}
|
|
22
31
|
//# sourceMappingURL=bedrock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bedrock.d.ts","sourceRoot":"","sources":["../../src/services/bedrock.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"bedrock.d.ts","sourceRoot":"","sources":["../../src/services/bedrock.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAE5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAmCD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,QAAQ,CAAkB;gBAEtB,MAAM,EAAE,aAAa;IA6CjC,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAO7C,OAAO,CAAC,iBAAiB;IA+BnB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAWtD,qBAAqB;YA2ErB,mBAAmB;IAyEjC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc;IAO9C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAWlC,iBAAiB;YAkFjB,eAAe;CAoF9B"}
|