@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
package/dist/services/bedrock.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BedrockRuntimeClient, InvokeModelCommand, } from "@aws-sdk/client-bedrock-runtime";
|
|
2
|
-
const CORE_ACTIONS_LIST = `"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", "search_content", "stop"`;
|
|
2
|
+
const CORE_ACTIONS_LIST = `"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", "search_content", "stop", "quit", "tab_next", "tab_back", "click", "enter", "submit", "move_left", "move_right", "move_up", "move_down", "move_beginning", "move_end", "select_all", "select_line", "type_text", "cut", "copy", "paste", "clear"`;
|
|
3
3
|
const BASE_SYSTEM_PROMPT = `You are an action extraction assistant.
|
|
4
4
|
|
|
5
5
|
CRITICAL RULES:
|
|
@@ -14,6 +14,7 @@ CRITICAL RULES:
|
|
|
14
14
|
RESPONSE FORMAT:
|
|
15
15
|
- Core actions: {"action": "action_name"}
|
|
16
16
|
- Search: {"action": "search_content", "query": "keywords"}
|
|
17
|
+
- Type text: {"action": "type_text", "text": "text to type"}
|
|
17
18
|
- Page navigation: {"action": "navigate_<page_id>"}
|
|
18
19
|
|
|
19
20
|
EXAMPLES:
|
|
@@ -21,6 +22,7 @@ EXAMPLES:
|
|
|
21
22
|
- User says "show me the team page" → {"action": "navigate_team"}
|
|
22
23
|
- User says "scroll down" → {"action": "scroll_down"}
|
|
23
24
|
- User says "find schemes" → {"action": "search_content", "query": "schemes"}
|
|
25
|
+
- User says "type hello world" → {"action": "type_text", "text": "hello world"}
|
|
24
26
|
|
|
25
27
|
MATCHING RULES FOR PAGE NAVIGATION:
|
|
26
28
|
- Match user's words to page names and keywords
|
|
@@ -29,25 +31,45 @@ MATCHING RULES FOR PAGE NAVIGATION:
|
|
|
29
31
|
const DEFAULT_EMBEDDING_MODEL_ID = "cohere.embed-multilingual-v3";
|
|
30
32
|
export class BedrockService {
|
|
31
33
|
constructor(config) {
|
|
34
|
+
this.client = null;
|
|
35
|
+
this.actionModelId = null;
|
|
32
36
|
this.pageRegistry = null;
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (config.embeddingModelId === null) {
|
|
37
|
+
this.bedrockEndpoint = null;
|
|
38
|
+
this.embeddingEndpointUrl = null;
|
|
39
|
+
this.useProxy = false;
|
|
40
|
+
this.useProxy = !!(config.bedrockEndpoint || config.embeddingEndpoint);
|
|
41
|
+
if (this.useProxy) {
|
|
42
|
+
this.bedrockEndpoint = config.bedrockEndpoint || null;
|
|
43
|
+
this.embeddingEndpointUrl = config.embeddingEndpoint || null;
|
|
44
|
+
this.actionModelId = null;
|
|
42
45
|
this.embeddingModelId = null;
|
|
43
46
|
}
|
|
44
|
-
else if (typeof config.embeddingModelId === "string") {
|
|
45
|
-
const trimmed = config.embeddingModelId.trim();
|
|
46
|
-
this.embeddingModelId =
|
|
47
|
-
trimmed.length > 0 ? trimmed : DEFAULT_EMBEDDING_MODEL_ID;
|
|
48
|
-
}
|
|
49
47
|
else {
|
|
50
|
-
|
|
48
|
+
if (!config.region ||
|
|
49
|
+
!config.accessKeyId ||
|
|
50
|
+
!config.secretAccessKey ||
|
|
51
|
+
!config.modelId) {
|
|
52
|
+
throw new Error("AWS credentials required for direct mode");
|
|
53
|
+
}
|
|
54
|
+
this.client = new BedrockRuntimeClient({
|
|
55
|
+
region: config.region,
|
|
56
|
+
credentials: {
|
|
57
|
+
accessKeyId: config.accessKeyId,
|
|
58
|
+
secretAccessKey: config.secretAccessKey,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
this.actionModelId = config.modelId;
|
|
62
|
+
if (config.embeddingModelId === null) {
|
|
63
|
+
this.embeddingModelId = null;
|
|
64
|
+
}
|
|
65
|
+
else if (typeof config.embeddingModelId === "string") {
|
|
66
|
+
const trimmed = config.embeddingModelId.trim();
|
|
67
|
+
this.embeddingModelId =
|
|
68
|
+
trimmed.length > 0 ? trimmed : DEFAULT_EMBEDDING_MODEL_ID;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.embeddingModelId = DEFAULT_EMBEDDING_MODEL_ID;
|
|
72
|
+
}
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
75
|
setPageRegistry(registry) {
|
|
@@ -71,6 +93,73 @@ export class BedrockService {
|
|
|
71
93
|
return prompt;
|
|
72
94
|
}
|
|
73
95
|
async extractAction(userInput) {
|
|
96
|
+
if (this.useProxy) {
|
|
97
|
+
return this.extractActionViaProxy(userInput);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return this.extractActionDirect(userInput);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async extractActionViaProxy(userInput) {
|
|
104
|
+
if (!this.bedrockEndpoint) {
|
|
105
|
+
throw new Error("Bedrock endpoint not configured");
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const systemPrompt = this.buildSystemPrompt();
|
|
109
|
+
const requestBody = {
|
|
110
|
+
anthropic_version: "bedrock-2023-05-31",
|
|
111
|
+
max_tokens: 70,
|
|
112
|
+
system: systemPrompt,
|
|
113
|
+
messages: [
|
|
114
|
+
{
|
|
115
|
+
role: "user",
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: `User input: "${userInput}". Identify the intended action from the allowed list and respond ONLY with a JSON object. If the user input is unclear, respond with {"action": "unknown"}. Include no additional text.`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
const response = await fetch(this.bedrockEndpoint, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
"Content-Type": "application/json",
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
body: requestBody,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new Error(`Bedrock proxy error: ${response.status} ${response.statusText}`);
|
|
136
|
+
}
|
|
137
|
+
const data = (await response.json());
|
|
138
|
+
let rawText = "";
|
|
139
|
+
if (data.content && data.content.length > 0) {
|
|
140
|
+
rawText = data.content[0].text || "";
|
|
141
|
+
}
|
|
142
|
+
if (!rawText) {
|
|
143
|
+
return { action: "unknown" };
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(rawText);
|
|
147
|
+
return parsed;
|
|
148
|
+
}
|
|
149
|
+
catch (parseError) {
|
|
150
|
+
console.error("Failed to parse Bedrock response:", rawText, parseError);
|
|
151
|
+
return { action: "unknown" };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error("Bedrock proxy API error:", error);
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async extractActionDirect(userInput) {
|
|
160
|
+
if (!this.client || !this.actionModelId) {
|
|
161
|
+
throw new Error("AWS Bedrock client not initialized");
|
|
162
|
+
}
|
|
74
163
|
const systemPrompt = this.buildSystemPrompt();
|
|
75
164
|
const body = {
|
|
76
165
|
anthropic_version: "bedrock-2023-05-31",
|
|
@@ -126,11 +215,76 @@ export class BedrockService {
|
|
|
126
215
|
return new BedrockService(config);
|
|
127
216
|
}
|
|
128
217
|
async embedText(text) {
|
|
218
|
+
if (this.useProxy) {
|
|
219
|
+
return this.embedTextViaProxy(text);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
return this.embedTextDirect(text);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async embedTextViaProxy(text) {
|
|
226
|
+
const prompt = text.trim();
|
|
227
|
+
if (!prompt) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
if (!this.embeddingEndpointUrl) {
|
|
231
|
+
throw new Error("Embedding endpoint not configured");
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch(this.embeddingEndpointUrl, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
headers: {
|
|
237
|
+
"Content-Type": "application/json",
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
text: prompt,
|
|
241
|
+
inputType: "search_query",
|
|
242
|
+
}),
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw new Error(`Embedding proxy error: ${response.status} ${response.statusText}`);
|
|
246
|
+
}
|
|
247
|
+
const decoded = (await response.json());
|
|
248
|
+
let embedding;
|
|
249
|
+
if (decoded.embeddings) {
|
|
250
|
+
if (Array.isArray(decoded.embeddings)) {
|
|
251
|
+
if (decoded.embeddings.length > 0 &&
|
|
252
|
+
Array.isArray(decoded.embeddings[0])) {
|
|
253
|
+
embedding = decoded.embeddings[0];
|
|
254
|
+
}
|
|
255
|
+
else if (decoded.embeddings.length > 0 &&
|
|
256
|
+
typeof decoded.embeddings[0] === "number") {
|
|
257
|
+
embedding = decoded.embeddings;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (typeof decoded.embeddings === "object" &&
|
|
261
|
+
"values" in decoded.embeddings) {
|
|
262
|
+
embedding = decoded.embeddings.values;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!embedding) {
|
|
266
|
+
embedding =
|
|
267
|
+
decoded.embedding ||
|
|
268
|
+
decoded.vector ||
|
|
269
|
+
decoded.results?.find((item) => Array.isArray(item.embedding))
|
|
270
|
+
?.embedding;
|
|
271
|
+
}
|
|
272
|
+
if (!Array.isArray(embedding)) {
|
|
273
|
+
throw new Error("Unexpected embedding response structure");
|
|
274
|
+
}
|
|
275
|
+
return embedding.map((value) => Number(value));
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
console.error("Embedding proxy error:", error);
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async embedTextDirect(text) {
|
|
129
283
|
const prompt = text.trim();
|
|
130
284
|
if (!prompt) {
|
|
131
285
|
return [];
|
|
132
286
|
}
|
|
133
|
-
if (!this.embeddingModelId) {
|
|
287
|
+
if (!this.client || !this.embeddingModelId) {
|
|
134
288
|
throw new Error("Bedrock embedding model is not configured");
|
|
135
289
|
}
|
|
136
290
|
const payload = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../src/services/vector-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;
|
|
1
|
+
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../src/services/vector-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAyCD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,MAAM,EAAE,gBAAgB;IAuC9B,MAAM,CACV,MAAM,EAAE,MAAM,EAAE,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC;IA6F7B,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI;IAoCjD,oBAAoB,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG;QAC7C,GAAG,EAAE,eAAe,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI;IAaR,OAAO,CAAC,mBAAmB;IAqC3B,OAAO,CAAC,eAAe;YAqBT,YAAY;CAQ3B"}
|
|
@@ -11,6 +11,7 @@ export class VectorSearchService {
|
|
|
11
11
|
const requestTimeoutMs = typeof config.requestTimeoutMs === "number" && config.requestTimeoutMs > 0
|
|
12
12
|
? Math.floor(config.requestTimeoutMs)
|
|
13
13
|
: 15000;
|
|
14
|
+
const isEndpointOnlyMode = !!(config.searchEndpoint && !config.node);
|
|
14
15
|
this.config = {
|
|
15
16
|
...config,
|
|
16
17
|
vectorField: config.vectorField || "embedding",
|
|
@@ -19,9 +20,12 @@ export class VectorSearchService {
|
|
|
19
20
|
numCandidates,
|
|
20
21
|
requestTimeoutMs,
|
|
21
22
|
sourceFields: config.sourceFields,
|
|
22
|
-
apiPath: config.apiPath ||
|
|
23
|
+
apiPath: config.apiPath ||
|
|
24
|
+
config.searchEndpoint ||
|
|
25
|
+
DEFAULT_VECTOR_SEARCH_API_PATH,
|
|
26
|
+
isEndpointOnlyMode,
|
|
23
27
|
};
|
|
24
|
-
this.endpoint = this.resolveEndpoint(
|
|
28
|
+
this.endpoint = this.resolveEndpoint(config.searchEndpoint || config.apiPath || DEFAULT_VECTOR_SEARCH_API_PATH);
|
|
25
29
|
}
|
|
26
30
|
async search(vector, queryText) {
|
|
27
31
|
if (!Array.isArray(vector) || vector.length === 0) {
|
|
@@ -133,6 +137,15 @@ export class VectorSearchService {
|
|
|
133
137
|
return null;
|
|
134
138
|
}
|
|
135
139
|
buildRequestPayload(vector, queryText) {
|
|
140
|
+
if (this.config.isEndpointOnlyMode) {
|
|
141
|
+
return {
|
|
142
|
+
vector,
|
|
143
|
+
query: queryText,
|
|
144
|
+
size: this.config.size,
|
|
145
|
+
minScore: this.config.minScore,
|
|
146
|
+
sourceFields: this.config.sourceFields,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
136
149
|
return {
|
|
137
150
|
vector,
|
|
138
151
|
queryText,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
export type CoreNavigationAction = "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" | "search_content" | "stop" | "unknown";
|
|
1
|
+
export type CoreNavigationAction = "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" | "search_content" | "stop" | "quit" | "tab_next" | "tab_back" | "click" | "enter" | "submit" | "move_left" | "move_right" | "move_up" | "move_down" | "move_beginning" | "move_end" | "select_all" | "select_line" | "type_text" | "cut" | "copy" | "paste" | "clear" | "unknown";
|
|
2
2
|
export type NavigationAction = CoreNavigationAction | `navigate_${string}`;
|
|
3
|
+
export type ButtonPlacement = "bottom-right" | "bottom-left" | "top-right" | "top-left" | "center-right" | "center-left";
|
|
4
|
+
export interface UIConfig {
|
|
5
|
+
showDefaultButton?: boolean;
|
|
6
|
+
customButtonSelector?: string;
|
|
7
|
+
buttonPlacement?: ButtonPlacement;
|
|
8
|
+
}
|
|
3
9
|
export interface NavigationConfig {
|
|
4
10
|
aws?: {
|
|
11
|
+
bedrockEndpoint?: string;
|
|
12
|
+
embeddingEndpoint?: string;
|
|
5
13
|
region?: string;
|
|
6
14
|
accessKeyId?: string;
|
|
7
15
|
secretAccessKey?: string;
|
|
@@ -9,6 +17,7 @@ export interface NavigationConfig {
|
|
|
9
17
|
embeddingModelId?: string | null;
|
|
10
18
|
};
|
|
11
19
|
azure?: {
|
|
20
|
+
tokenEndpoint?: string;
|
|
12
21
|
subscriptionKey?: string;
|
|
13
22
|
region?: string;
|
|
14
23
|
};
|
|
@@ -18,6 +27,7 @@ export interface NavigationConfig {
|
|
|
18
27
|
opensearch?: OpenSearchConfig;
|
|
19
28
|
openSearch?: OpenSearchConfig;
|
|
20
29
|
pages?: NavigationPagesConfig;
|
|
30
|
+
ui?: UIConfig;
|
|
21
31
|
}
|
|
22
32
|
export interface NavigationPagesConfig {
|
|
23
33
|
xml?: string;
|
|
@@ -31,20 +41,22 @@ export interface NavigationPagesConfig {
|
|
|
31
41
|
}>;
|
|
32
42
|
}
|
|
33
43
|
export interface OpenSearchConfig {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
searchEndpoint?: string;
|
|
45
|
+
suggestEndpoint?: string;
|
|
46
|
+
node?: string;
|
|
47
|
+
username?: string;
|
|
48
|
+
password?: string;
|
|
49
|
+
index?: string;
|
|
50
|
+
ssl?: {
|
|
51
|
+
rejectUnauthorized?: boolean;
|
|
52
|
+
};
|
|
53
|
+
apiPath?: string;
|
|
38
54
|
vectorField?: string;
|
|
39
55
|
size?: number;
|
|
40
56
|
minScore?: number;
|
|
41
57
|
numCandidates?: number;
|
|
42
58
|
requestTimeoutMs?: number;
|
|
43
59
|
sourceFields?: string[];
|
|
44
|
-
ssl?: {
|
|
45
|
-
rejectUnauthorized?: boolean;
|
|
46
|
-
};
|
|
47
|
-
apiPath?: string;
|
|
48
60
|
}
|
|
49
61
|
export interface ActionContext {
|
|
50
62
|
controller?: any;
|
|
@@ -102,11 +114,12 @@ export interface NavigationCallbacks {
|
|
|
102
114
|
export type NavigationState = "idle" | "loading" | "listening" | "thinking" | "error";
|
|
103
115
|
export interface UIElements {
|
|
104
116
|
root: HTMLElement;
|
|
105
|
-
button: HTMLButtonElement;
|
|
106
|
-
label: HTMLElement;
|
|
117
|
+
button: HTMLButtonElement | HTMLElement;
|
|
118
|
+
label: HTMLElement | null;
|
|
107
119
|
status: HTMLElement;
|
|
108
120
|
transcript: HTMLElement;
|
|
109
121
|
action: HTMLElement;
|
|
122
|
+
isCustomButton?: boolean;
|
|
110
123
|
}
|
|
111
124
|
export interface NavigationEvent extends CustomEvent {
|
|
112
125
|
detail: {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,UAAU,GACV,WAAW,GACX,aAAa,GACb,aAAa,GACb,cAAc,GACd,SAAS,GACT,WAAW,GACX,YAAY,GACZ,eAAe,GACf,SAAS,GACT,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,UAAU,GACV,WAAW,GACX,YAAY,GACZ,cAAc,GACd,mBAAmB,GACnB,iBAAiB,GACjB,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,cAAc,GACd,gBAAgB,GAChB,MAAM,GACN,SAAS,CAAC;AAKd,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,YAAY,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { UIElements, NavigationState } from "./types.js";
|
|
2
|
-
export declare const injectFloatingControlStyles: () => void;
|
|
3
|
-
export declare const createFloatingControl: () => UIElements;
|
|
1
|
+
import type { UIElements, NavigationState, ButtonPlacement } from "./types.js";
|
|
2
|
+
export declare const injectFloatingControlStyles: (placement?: ButtonPlacement) => void;
|
|
3
|
+
export declare const createFloatingControl: (placement?: ButtonPlacement) => UIElements;
|
|
4
|
+
export declare const attachToCustomButton: (buttonSelector: string) => Promise<UIElements>;
|
|
4
5
|
export declare const updateStatus: (ui: UIElements, text: string) => void;
|
|
5
6
|
export declare const setState: (ui: UIElements, state: NavigationState) => void;
|
|
6
7
|
export declare const updateTranscript: (ui: UIElements, text: string) => void;
|
package/dist/ui.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAuC/E,eAAO,MAAM,2BAA2B,GACtC,YAAW,eAAgC,KAC1C,IAqHF,CAAC;AAKF,eAAO,MAAM,qBAAqB,GAChC,YAAW,eAAgC,KAC1C,UAgEF,CAAC;AA+EF,eAAO,MAAM,oBAAoB,GAC/B,gBAAgB,MAAM,KACrB,OAAO,CAAC,UAAU,CAuEpB,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,IAgDjE,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
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const getPlacementStyles = (placement) => {
|
|
2
|
+
const positions = {
|
|
3
|
+
"bottom-right": "bottom: 24px; right: 24px; left: auto; top: auto;",
|
|
4
|
+
"bottom-left": "bottom: 24px; left: 24px; right: auto; top: auto;",
|
|
5
|
+
"top-right": "top: 24px; right: 24px; left: auto; bottom: auto;",
|
|
6
|
+
"top-left": "top: 24px; left: 24px; right: auto; bottom: auto;",
|
|
7
|
+
"center-right": "top: 50%; right: 24px; left: auto; bottom: auto; transform: translateY(-50%);",
|
|
8
|
+
"center-left": "top: 50%; left: 24px; right: auto; bottom: auto; transform: translateY(-50%);",
|
|
9
|
+
};
|
|
10
|
+
return positions[placement] || positions["bottom-right"];
|
|
11
|
+
};
|
|
12
|
+
const getMobilePlacementStyles = (placement) => {
|
|
13
|
+
const positions = {
|
|
14
|
+
"bottom-right": "bottom: 16px; right: 16px; left: auto; top: auto;",
|
|
15
|
+
"bottom-left": "bottom: 16px; left: 16px; right: auto; top: auto;",
|
|
16
|
+
"top-right": "top: 16px; right: 16px; left: auto; bottom: auto;",
|
|
17
|
+
"top-left": "top: 16px; left: 16px; right: auto; bottom: auto;",
|
|
18
|
+
"center-right": "top: 50%; right: 16px; left: auto; bottom: auto; transform: translateY(-50%);",
|
|
19
|
+
"center-left": "top: 50%; left: 16px; right: auto; bottom: auto; transform: translateY(-50%);",
|
|
20
|
+
};
|
|
21
|
+
return positions[placement] || positions["bottom-right"];
|
|
22
|
+
};
|
|
23
|
+
export const injectFloatingControlStyles = (placement = "bottom-right") => {
|
|
24
|
+
const existingStyle = document.getElementById("navigate-control-styles");
|
|
25
|
+
if (existingStyle) {
|
|
26
|
+
existingStyle.remove();
|
|
4
27
|
}
|
|
5
28
|
const style = document.createElement("style");
|
|
6
29
|
style.id = "navigate-control-styles";
|
|
30
|
+
const placementStyles = getPlacementStyles(placement);
|
|
31
|
+
const mobilePlacementStyles = getMobilePlacementStyles(placement);
|
|
7
32
|
style.textContent = `
|
|
8
33
|
.navigate-control {
|
|
9
34
|
position: fixed;
|
|
10
|
-
|
|
11
|
-
right: 24px;
|
|
35
|
+
${placementStyles}
|
|
12
36
|
display: flex;
|
|
13
37
|
gap: 12px;
|
|
14
|
-
align-items: flex-end;
|
|
38
|
+
align-items: ${placement.startsWith("top") ? "flex-start" : "flex-end"};
|
|
15
39
|
z-index: 2147483647;
|
|
16
40
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
17
41
|
color: #0f172a;
|
|
@@ -101,8 +125,7 @@ export const injectFloatingControlStyles = () => {
|
|
|
101
125
|
|
|
102
126
|
@media (max-width: 640px) {
|
|
103
127
|
.navigate-control {
|
|
104
|
-
|
|
105
|
-
bottom: 16px;
|
|
128
|
+
${mobilePlacementStyles}
|
|
106
129
|
}
|
|
107
130
|
|
|
108
131
|
.navigate-control__panel {
|
|
@@ -112,8 +135,8 @@ export const injectFloatingControlStyles = () => {
|
|
|
112
135
|
`;
|
|
113
136
|
document.head.appendChild(style);
|
|
114
137
|
};
|
|
115
|
-
export const createFloatingControl = () => {
|
|
116
|
-
injectFloatingControlStyles();
|
|
138
|
+
export const createFloatingControl = (placement = "bottom-right") => {
|
|
139
|
+
injectFloatingControlStyles(placement);
|
|
117
140
|
let root = document.getElementById("navigate-control");
|
|
118
141
|
if (!root) {
|
|
119
142
|
root = document.createElement("div");
|
|
@@ -133,10 +156,12 @@ export const createFloatingControl = () => {
|
|
|
133
156
|
`;
|
|
134
157
|
const appendToBody = () => {
|
|
135
158
|
if (!document.body) {
|
|
159
|
+
console.warn("Voice navigation: document.body not available yet");
|
|
136
160
|
return;
|
|
137
161
|
}
|
|
138
162
|
if (!document.body.contains(root)) {
|
|
139
163
|
document.body.appendChild(root);
|
|
164
|
+
console.log(`Voice navigation: Button created with placement "${placement}"`);
|
|
140
165
|
}
|
|
141
166
|
};
|
|
142
167
|
appendToBody();
|
|
@@ -145,13 +170,111 @@ export const createFloatingControl = () => {
|
|
|
145
170
|
once: true,
|
|
146
171
|
});
|
|
147
172
|
}
|
|
173
|
+
const button = root.querySelector(".navigate-control__button");
|
|
174
|
+
if (!button) {
|
|
175
|
+
console.error("Voice navigation: Failed to create button element");
|
|
176
|
+
}
|
|
148
177
|
return {
|
|
149
178
|
root,
|
|
150
|
-
button
|
|
179
|
+
button,
|
|
151
180
|
label: root.querySelector(".navigate-control__label"),
|
|
152
181
|
status: root.querySelector(".navigate-control__status"),
|
|
153
182
|
transcript: root.querySelector(".navigate-control__transcript"),
|
|
154
183
|
action: root.querySelector(".navigate-control__action"),
|
|
184
|
+
isCustomButton: false,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
const waitForElement = (selector, timeout = 30000) => {
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
const element = document.querySelector(selector);
|
|
190
|
+
if (element) {
|
|
191
|
+
console.log(`[Voice Navigation] Element "${selector}" found immediately`);
|
|
192
|
+
resolve(element);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
console.log(`[Voice Navigation] Element "${selector}" not found, waiting up to ${timeout}ms...`);
|
|
196
|
+
let observer = null;
|
|
197
|
+
const waitForBody = () => {
|
|
198
|
+
if (!document.body) {
|
|
199
|
+
console.log("[Voice Navigation] Waiting for document.body to be available...");
|
|
200
|
+
setTimeout(waitForBody, 100);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
observer = new MutationObserver(() => {
|
|
204
|
+
const element = document.querySelector(selector);
|
|
205
|
+
if (element) {
|
|
206
|
+
observer?.disconnect();
|
|
207
|
+
clearTimeout(timeoutId);
|
|
208
|
+
console.log(`[Voice Navigation] Element "${selector}" found after waiting`);
|
|
209
|
+
resolve(element);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
observer.observe(document.body, {
|
|
213
|
+
childList: true,
|
|
214
|
+
subtree: true,
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
waitForBody();
|
|
218
|
+
const timeoutId = setTimeout(() => {
|
|
219
|
+
observer?.disconnect();
|
|
220
|
+
console.error(`[Voice Navigation] Timeout: Element "${selector}" not found after ${timeout}ms`);
|
|
221
|
+
const allButtons = document.querySelectorAll('[id^="btn"]');
|
|
222
|
+
if (allButtons.length > 0) {
|
|
223
|
+
console.log("[Voice Navigation] Available buttons with id starting with 'btn':", Array.from(allButtons).map((btn) => `#${btn.id}`));
|
|
224
|
+
}
|
|
225
|
+
reject(new Error(`Custom button not found with selector: "${selector}" after ${timeout}ms. Make sure the button exists in the DOM.`));
|
|
226
|
+
}, timeout);
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
export const attachToCustomButton = async (buttonSelector) => {
|
|
230
|
+
injectFloatingControlStyles("bottom-right");
|
|
231
|
+
console.log(`Voice navigation: Waiting for custom button "${buttonSelector}"...`);
|
|
232
|
+
const customButton = await waitForElement(buttonSelector).catch((error) => {
|
|
233
|
+
console.error(error.message);
|
|
234
|
+
throw error;
|
|
235
|
+
});
|
|
236
|
+
console.log(`Voice navigation: Custom button found "${buttonSelector}"`);
|
|
237
|
+
let root = document.getElementById("navigate-control-panel");
|
|
238
|
+
if (!root) {
|
|
239
|
+
root = document.createElement("div");
|
|
240
|
+
root.id = "navigate-control-panel";
|
|
241
|
+
root.className = "navigate-control";
|
|
242
|
+
root.style.display = "none";
|
|
243
|
+
}
|
|
244
|
+
root.dataset.state = root.dataset.state || "idle";
|
|
245
|
+
root.innerHTML = `
|
|
246
|
+
<div class="navigate-control__panel">
|
|
247
|
+
<div class="navigate-control__status">Tap your button to start voice control</div>
|
|
248
|
+
<div class="navigate-control__transcript" role="status" aria-live="polite"></div>
|
|
249
|
+
<div class="navigate-control__action" aria-live="polite"></div>
|
|
250
|
+
</div>
|
|
251
|
+
`;
|
|
252
|
+
const appendToBody = () => {
|
|
253
|
+
if (!document.body) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!document.body.contains(root)) {
|
|
257
|
+
document.body.appendChild(root);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
appendToBody();
|
|
261
|
+
if (document.readyState === "loading") {
|
|
262
|
+
document.addEventListener("DOMContentLoaded", appendToBody, {
|
|
263
|
+
once: true,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
customButton.setAttribute("aria-pressed", "false");
|
|
267
|
+
if (!customButton.getAttribute("aria-label")) {
|
|
268
|
+
customButton.setAttribute("aria-label", "Start voice control");
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
root,
|
|
272
|
+
button: customButton,
|
|
273
|
+
label: null,
|
|
274
|
+
status: root.querySelector(".navigate-control__status"),
|
|
275
|
+
transcript: root.querySelector(".navigate-control__transcript"),
|
|
276
|
+
action: root.querySelector(".navigate-control__action"),
|
|
277
|
+
isCustomButton: true,
|
|
155
278
|
};
|
|
156
279
|
};
|
|
157
280
|
export const updateStatus = (ui, text) => {
|
|
@@ -159,34 +282,44 @@ export const updateStatus = (ui, text) => {
|
|
|
159
282
|
};
|
|
160
283
|
export const setState = (ui, state) => {
|
|
161
284
|
ui.root.dataset.state = state;
|
|
285
|
+
const updateLabel = (text) => {
|
|
286
|
+
if (ui.label) {
|
|
287
|
+
ui.label.textContent = text;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
162
290
|
switch (state) {
|
|
163
291
|
case "idle":
|
|
164
|
-
|
|
292
|
+
updateLabel("Start");
|
|
165
293
|
ui.button.setAttribute("aria-pressed", "false");
|
|
166
294
|
ui.button.setAttribute("aria-label", "Start voice control");
|
|
167
|
-
updateStatus(ui,
|
|
295
|
+
updateStatus(ui, ui.isCustomButton
|
|
296
|
+
? "Tap your button to start voice control"
|
|
297
|
+
: "Tap to start voice control");
|
|
168
298
|
break;
|
|
169
299
|
case "loading":
|
|
170
|
-
|
|
300
|
+
updateLabel("Loading...");
|
|
171
301
|
ui.button.setAttribute("aria-label", "Loading voice control");
|
|
172
302
|
updateStatus(ui, "Loading...");
|
|
173
303
|
break;
|
|
174
304
|
case "listening":
|
|
175
|
-
|
|
305
|
+
updateLabel("Stop");
|
|
176
306
|
ui.button.setAttribute("aria-pressed", "true");
|
|
177
307
|
ui.button.setAttribute("aria-label", "Stop voice control");
|
|
178
308
|
updateStatus(ui, "Listening...");
|
|
179
309
|
break;
|
|
180
310
|
case "thinking":
|
|
181
|
-
|
|
311
|
+
updateLabel("Stop");
|
|
182
312
|
ui.button.setAttribute("aria-label", "Processing");
|
|
183
313
|
updateStatus(ui, "Processing...");
|
|
184
314
|
break;
|
|
185
315
|
case "error":
|
|
186
|
-
|
|
316
|
+
updateLabel("Error");
|
|
187
317
|
ui.button.setAttribute("aria-label", "Error occurred");
|
|
188
318
|
break;
|
|
189
319
|
}
|
|
320
|
+
if (ui.isCustomButton) {
|
|
321
|
+
ui.root.style.display = state === "idle" ? "none" : "flex";
|
|
322
|
+
}
|
|
190
323
|
};
|
|
191
324
|
export const updateTranscript = (ui, text) => {
|
|
192
325
|
ui.transcript.textContent = text;
|