@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.
Files changed (41) hide show
  1. package/README.md +250 -1
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +397 -2
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +18 -3
  7. package/dist/microphone-handler.d.ts +2 -0
  8. package/dist/microphone-handler.d.ts.map +1 -1
  9. package/dist/microphone-handler.js +11 -2
  10. package/dist/navigation-controller.d.ts +2 -0
  11. package/dist/navigation-controller.d.ts.map +1 -1
  12. package/dist/navigation-controller.js +89 -10
  13. package/dist/server/azure-speech-handler.d.ts +12 -0
  14. package/dist/server/azure-speech-handler.d.ts.map +1 -0
  15. package/dist/server/azure-speech-handler.js +40 -0
  16. package/dist/server/bedrock-embedding-handler.d.ts +15 -0
  17. package/dist/server/bedrock-embedding-handler.d.ts.map +1 -0
  18. package/dist/server/bedrock-embedding-handler.js +123 -0
  19. package/dist/server/bedrock-handler.d.ts +14 -0
  20. package/dist/server/bedrock-handler.d.ts.map +1 -0
  21. package/dist/server/bedrock-handler.js +114 -0
  22. package/dist/server/index.d.ts +4 -0
  23. package/dist/server/index.d.ts.map +1 -1
  24. package/dist/server/index.js +4 -0
  25. package/dist/server/opensearch-env-handler.d.ts +22 -0
  26. package/dist/server/opensearch-env-handler.d.ts.map +1 -0
  27. package/dist/server/opensearch-env-handler.js +221 -0
  28. package/dist/services/azure-speech.d.ts +10 -2
  29. package/dist/services/azure-speech.d.ts.map +1 -1
  30. package/dist/services/azure-speech.js +66 -3
  31. package/dist/services/bedrock.d.ts +13 -4
  32. package/dist/services/bedrock.d.ts.map +1 -1
  33. package/dist/services/bedrock.js +171 -17
  34. package/dist/services/vector-search.d.ts.map +1 -1
  35. package/dist/services/vector-search.js +15 -2
  36. package/dist/types.d.ts +24 -11
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/ui.d.ts +4 -3
  39. package/dist/ui.d.ts.map +1 -1
  40. package/dist/ui.js +150 -17
  41. package/package.json +3 -3
@@ -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.client = new BedrockRuntimeClient({
34
- region: config.region,
35
- credentials: {
36
- accessKeyId: config.accessKeyId,
37
- secretAccessKey: config.secretAccessKey,
38
- },
39
- });
40
- this.actionModelId = config.modelId;
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
- this.embeddingModelId = DEFAULT_EMBEDDING_MODEL_ID;
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;AAwCD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,MAAM,EAAE,gBAAgB;IA6B9B,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAyF9E,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;IAsB3B,OAAO,CAAC,eAAe;YAkBT,YAAY;CAQ3B"}
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 || DEFAULT_VECTOR_SEARCH_API_PATH,
23
+ apiPath: config.apiPath ||
24
+ config.searchEndpoint ||
25
+ DEFAULT_VECTOR_SEARCH_API_PATH,
26
+ isEndpointOnlyMode,
23
27
  };
24
- this.endpoint = this.resolveEndpoint(this.config.apiPath);
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
- node: string;
35
- username: string;
36
- password: string;
37
- index: string;
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: {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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;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"}
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
- export const injectFloatingControlStyles = () => {
2
- if (document.getElementById("navigate-control-styles")) {
3
- return;
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
- bottom: 24px;
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
- right: 16px;
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: root.querySelector(".navigate-control__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
- ui.label.textContent = "Start";
292
+ updateLabel("Start");
165
293
  ui.button.setAttribute("aria-pressed", "false");
166
294
  ui.button.setAttribute("aria-label", "Start voice control");
167
- updateStatus(ui, "Tap to start voice control");
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
- ui.label.textContent = "Loading...";
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
- ui.label.textContent = "Stop";
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
- ui.label.textContent = "Stop";
311
+ updateLabel("Stop");
182
312
  ui.button.setAttribute("aria-label", "Processing");
183
313
  updateStatus(ui, "Processing...");
184
314
  break;
185
315
  case "error":
186
- ui.label.textContent = "Error";
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;