@navsi.ai/sdk 1.0.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.
@@ -0,0 +1,3752 @@
1
+ import { ChatbotContext, useChatbot, useActionExecution, useWebSocket } from './chunk-6FUUG5WB.js';
2
+ import React2, { useMemo, useReducer, useRef, useState, useEffect, useCallback } from 'react';
3
+ import { createLogger, normalizeWidgetConfig, createMessageId } from '@navsi.ai/shared';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ var MessageQueue = class {
7
+ queue = [];
8
+ maxSize;
9
+ constructor(maxSize = 100) {
10
+ this.maxSize = maxSize;
11
+ }
12
+ enqueue(message) {
13
+ if (this.queue.length >= this.maxSize) {
14
+ this.queue.shift();
15
+ }
16
+ this.queue.push(message);
17
+ }
18
+ dequeueAll() {
19
+ const messages = [...this.queue];
20
+ this.queue = [];
21
+ return messages;
22
+ }
23
+ get length() {
24
+ return this.queue.length;
25
+ }
26
+ clear() {
27
+ this.queue = [];
28
+ }
29
+ };
30
+ var WebSocketClient = class {
31
+ ws = null;
32
+ config;
33
+ state = "disconnected";
34
+ logger = createLogger("SDK.WebSocket");
35
+ reconnectAttempts = 0;
36
+ reconnectTimeout = null;
37
+ heartbeatInterval = null;
38
+ lastPongTime = 0;
39
+ messageQueue = new MessageQueue();
40
+ sessionId = null;
41
+ refreshToken = null;
42
+ tokenExpiresAt = 0;
43
+ tokenRefreshTimeout = null;
44
+ // Event listeners
45
+ listeners = /* @__PURE__ */ new Map();
46
+ constructor(config) {
47
+ this.config = {
48
+ serverUrl: config.serverUrl,
49
+ apiKey: config.apiKey,
50
+ autoReconnect: config.autoReconnect ?? true,
51
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
52
+ reconnectDelay: config.reconnectDelay ?? 1e3,
53
+ heartbeatInterval: config.heartbeatInterval ?? 3e4,
54
+ debug: config.debug ?? false
55
+ };
56
+ this.logger = createLogger("SDK.WebSocket", { enabled: this.config.debug, level: "debug" });
57
+ }
58
+ // ============================================================================
59
+ // Public API
60
+ // ============================================================================
61
+ /**
62
+ * Connect to the WebSocket server
63
+ */
64
+ connect() {
65
+ if (this.state === "connecting" || this.state === "connected") {
66
+ this.log("Already connected or connecting");
67
+ return;
68
+ }
69
+ this.setState("connecting");
70
+ this.createWebSocket();
71
+ }
72
+ /**
73
+ * Disconnect from the server
74
+ */
75
+ disconnect() {
76
+ this.cleanup();
77
+ this.setState("disconnected");
78
+ this.log("Disconnected");
79
+ }
80
+ /**
81
+ * Send a message to the server
82
+ */
83
+ send(message) {
84
+ if (this.state === "connected" && this.ws?.readyState === WebSocket.OPEN) {
85
+ this.ws.send(JSON.stringify(message));
86
+ this.log("Sent message:", message.type, summarizeClientMessage(message));
87
+ } else {
88
+ this.messageQueue.enqueue(message);
89
+ this.log("Queued message (offline):", message.type, summarizeClientMessage(message));
90
+ }
91
+ }
92
+ /**
93
+ * Get current connection state
94
+ */
95
+ getState() {
96
+ return this.state;
97
+ }
98
+ /**
99
+ * Get session ID
100
+ */
101
+ getSessionId() {
102
+ return this.sessionId;
103
+ }
104
+ /**
105
+ * Check if connected
106
+ */
107
+ isConnected() {
108
+ return this.state === "connected";
109
+ }
110
+ // ============================================================================
111
+ // Event Handling
112
+ // ============================================================================
113
+ /**
114
+ * Subscribe to an event
115
+ */
116
+ on(event, callback) {
117
+ if (!this.listeners.has(event)) {
118
+ this.listeners.set(event, /* @__PURE__ */ new Set());
119
+ }
120
+ this.listeners.get(event).add(callback);
121
+ return () => {
122
+ this.listeners.get(event)?.delete(callback);
123
+ };
124
+ }
125
+ /**
126
+ * Emit an event to all listeners
127
+ */
128
+ emit(event, data) {
129
+ this.listeners.get(event)?.forEach((callback) => {
130
+ try {
131
+ callback(data);
132
+ } catch (error) {
133
+ this.logger.error(`Error in ${event} listener`, error);
134
+ }
135
+ });
136
+ }
137
+ // ============================================================================
138
+ // Connection Management
139
+ // ============================================================================
140
+ createWebSocket() {
141
+ try {
142
+ const urlObj = new URL(this.config.serverUrl);
143
+ urlObj.searchParams.set("apiKey", this.config.apiKey);
144
+ if (this.sessionId) {
145
+ urlObj.searchParams.set("sessionId", this.sessionId);
146
+ }
147
+ const url = urlObj.toString();
148
+ this.ws = new WebSocket(url);
149
+ this.ws.onopen = this.handleOpen.bind(this);
150
+ this.ws.onmessage = this.handleMessage.bind(this);
151
+ this.ws.onclose = this.handleClose.bind(this);
152
+ this.ws.onerror = this.handleError.bind(this);
153
+ } catch (error) {
154
+ this.logger.error("Failed to create WebSocket", error);
155
+ this.handleConnectionFailure();
156
+ }
157
+ }
158
+ handleOpen() {
159
+ this.log("WebSocket connected, authenticating...");
160
+ this.ws?.send(JSON.stringify({
161
+ type: "auth",
162
+ apiKey: this.config.apiKey,
163
+ sessionId: this.sessionId
164
+ }));
165
+ }
166
+ handleMessage(event) {
167
+ try {
168
+ const message = JSON.parse(event.data);
169
+ this.log("Received message:", message.type);
170
+ switch (message.type) {
171
+ case "connected":
172
+ this.handleConnected();
173
+ break;
174
+ case "auth_success":
175
+ this.handleAuthSuccess(message);
176
+ break;
177
+ case "auth_error":
178
+ this.handleAuthError(message);
179
+ break;
180
+ case "pong":
181
+ this.handlePong(message);
182
+ break;
183
+ case "token_refreshed":
184
+ this.handleTokenRefreshed(message);
185
+ break;
186
+ default:
187
+ this.emit("message", message);
188
+ }
189
+ } catch (error) {
190
+ this.logger.warn("Failed to parse message", error);
191
+ }
192
+ }
193
+ handleClose(event) {
194
+ this.log("WebSocket closed:", event.code, event.reason);
195
+ this.stopHeartbeat();
196
+ if (this.state !== "disconnected") {
197
+ this.emit("disconnected", { reason: event.reason, code: event.code });
198
+ if (this.config.autoReconnect) {
199
+ this.scheduleReconnect();
200
+ } else {
201
+ this.setState("disconnected");
202
+ }
203
+ }
204
+ }
205
+ handleError(event) {
206
+ this.logger.error("WebSocket error", event);
207
+ this.emit("error", { error: new Error("WebSocket error") });
208
+ }
209
+ // ============================================================================
210
+ // Authentication
211
+ // ============================================================================
212
+ handleConnected() {
213
+ this.log("Server connection acknowledged");
214
+ }
215
+ handleAuthSuccess(message) {
216
+ this.sessionId = message.sessionId;
217
+ this.refreshToken = message.refreshToken;
218
+ this.tokenExpiresAt = message.expiresAt;
219
+ this.reconnectAttempts = 0;
220
+ this.setState("connected");
221
+ this.startHeartbeat();
222
+ this.scheduleTokenRefresh();
223
+ this.flushMessageQueue();
224
+ this.emit("authenticated", {
225
+ sessionId: message.sessionId,
226
+ expiresAt: message.expiresAt
227
+ });
228
+ this.emit("connected", { sessionId: message.sessionId });
229
+ this.log("Authenticated successfully, session:", message.sessionId);
230
+ }
231
+ handleAuthError(message) {
232
+ this.logger.warn("Authentication failed", message.error);
233
+ this.emit("error", { error: new Error(message.error), code: message.code });
234
+ this.disconnect();
235
+ }
236
+ // ============================================================================
237
+ // Token Refresh
238
+ // ============================================================================
239
+ scheduleTokenRefresh() {
240
+ if (this.tokenRefreshTimeout) {
241
+ clearTimeout(this.tokenRefreshTimeout);
242
+ }
243
+ const refreshIn = Math.max(0, this.tokenExpiresAt - Date.now() - 5 * 60 * 1e3);
244
+ this.tokenRefreshTimeout = setTimeout(() => {
245
+ this.refreshAccessToken();
246
+ }, refreshIn);
247
+ this.log("Token refresh scheduled in", Math.round(refreshIn / 1e3), "seconds");
248
+ }
249
+ refreshAccessToken() {
250
+ if (!this.refreshToken || this.state !== "connected") {
251
+ return;
252
+ }
253
+ this.log("Refreshing token...");
254
+ this.ws?.send(JSON.stringify({
255
+ type: "token_refresh",
256
+ refreshToken: this.refreshToken
257
+ }));
258
+ }
259
+ handleTokenRefreshed(message) {
260
+ this.refreshToken = message.refreshToken;
261
+ this.tokenExpiresAt = message.expiresAt;
262
+ this.scheduleTokenRefresh();
263
+ this.emit("token_refreshed", { expiresAt: message.expiresAt });
264
+ this.log("Token refreshed successfully");
265
+ }
266
+ // ============================================================================
267
+ // Heartbeat
268
+ // ============================================================================
269
+ startHeartbeat() {
270
+ this.stopHeartbeat();
271
+ this.lastPongTime = Date.now();
272
+ this.heartbeatInterval = setInterval(() => {
273
+ if (this.state !== "connected") {
274
+ return;
275
+ }
276
+ if (Date.now() - this.lastPongTime > this.config.heartbeatInterval * 2) {
277
+ this.logger.warn("Heartbeat timeout, reconnecting...");
278
+ this.ws?.close();
279
+ return;
280
+ }
281
+ this.ws?.send(JSON.stringify({
282
+ type: "ping",
283
+ timestamp: Date.now()
284
+ }));
285
+ this.log("Sent ping");
286
+ }, this.config.heartbeatInterval);
287
+ }
288
+ stopHeartbeat() {
289
+ if (this.heartbeatInterval) {
290
+ clearInterval(this.heartbeatInterval);
291
+ this.heartbeatInterval = null;
292
+ }
293
+ }
294
+ handlePong(message) {
295
+ this.lastPongTime = Date.now();
296
+ const latency = Date.now() - message.timestamp;
297
+ this.log("Received pong, latency:", latency, "ms");
298
+ }
299
+ // ============================================================================
300
+ // Reconnection
301
+ // ============================================================================
302
+ scheduleReconnect() {
303
+ if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
304
+ this.handleConnectionFailure();
305
+ return;
306
+ }
307
+ this.setState("reconnecting");
308
+ this.reconnectAttempts++;
309
+ const delay = Math.min(
310
+ this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
311
+ 3e4
312
+ );
313
+ this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
314
+ this.emit("reconnecting", {
315
+ attempt: this.reconnectAttempts,
316
+ maxAttempts: this.config.maxReconnectAttempts
317
+ });
318
+ this.reconnectTimeout = setTimeout(() => {
319
+ this.createWebSocket();
320
+ }, delay);
321
+ }
322
+ handleConnectionFailure() {
323
+ this.setState("failed");
324
+ this.emit("reconnect_failed", { reason: "Max reconnection attempts reached" });
325
+ this.logger.error("Connection failed - max reconnection attempts reached");
326
+ }
327
+ // ============================================================================
328
+ // Message Queue
329
+ // ============================================================================
330
+ flushMessageQueue() {
331
+ const messages = this.messageQueue.dequeueAll();
332
+ if (messages.length > 0) {
333
+ this.log(`Flushing ${messages.length} queued messages`);
334
+ messages.forEach((message) => this.send(message));
335
+ }
336
+ }
337
+ // ============================================================================
338
+ // Utilities
339
+ // ============================================================================
340
+ setState(state) {
341
+ if (this.state !== state) {
342
+ this.log("State:", this.state, "->", state);
343
+ this.state = state;
344
+ }
345
+ }
346
+ cleanup() {
347
+ if (this.reconnectTimeout) {
348
+ clearTimeout(this.reconnectTimeout);
349
+ this.reconnectTimeout = null;
350
+ }
351
+ if (this.tokenRefreshTimeout) {
352
+ clearTimeout(this.tokenRefreshTimeout);
353
+ this.tokenRefreshTimeout = null;
354
+ }
355
+ this.stopHeartbeat();
356
+ if (this.ws) {
357
+ const ws = this.ws;
358
+ ws.onopen = null;
359
+ ws.onmessage = null;
360
+ ws.onclose = null;
361
+ ws.onerror = null;
362
+ this.ws = null;
363
+ if (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN) {
364
+ try {
365
+ ws.close(1e3, "Client disconnect");
366
+ } catch {
367
+ }
368
+ }
369
+ }
370
+ }
371
+ log(...args) {
372
+ this.logger.debug(...args);
373
+ }
374
+ };
375
+ function summarizeClientMessage(message) {
376
+ switch (message.type) {
377
+ case "message":
378
+ return {
379
+ messageId: message.messageId,
380
+ mode: message.mode,
381
+ length: message.content.length,
382
+ route: message.context?.route
383
+ };
384
+ case "context":
385
+ return {
386
+ route: message.context?.route,
387
+ actions: message.context?.actions?.length ?? 0,
388
+ headings: message.context?.content?.headings?.length ?? 0
389
+ };
390
+ case "action_result":
391
+ return {
392
+ actionId: message.actionId,
393
+ success: message.success,
394
+ commandId: message.commandId
395
+ };
396
+ case "server_action_request":
397
+ return {
398
+ actionId: message.actionId,
399
+ requestId: message.requestId
400
+ };
401
+ case "auth":
402
+ return {
403
+ hasSessionId: Boolean(message.sessionId)
404
+ };
405
+ case "token_refresh":
406
+ return {};
407
+ case "ping":
408
+ return { timestamp: message.timestamp };
409
+ default:
410
+ return {};
411
+ }
412
+ }
413
+ function createWebSocketClient(config) {
414
+ return new WebSocketClient(config);
415
+ }
416
+
417
+ // src/core/navigation/adapter.ts
418
+ var DEFAULT_ROUTE_TIMEOUT = 1e4;
419
+ function createMemoryAdapter(initialPath = "/") {
420
+ let currentPath = initialPath;
421
+ const listeners = /* @__PURE__ */ new Set();
422
+ return {
423
+ navigate(path) {
424
+ currentPath = path;
425
+ listeners.forEach((callback) => callback(path));
426
+ },
427
+ getCurrentPath() {
428
+ return currentPath;
429
+ },
430
+ onRouteChange(callback) {
431
+ listeners.add(callback);
432
+ return () => listeners.delete(callback);
433
+ },
434
+ async waitForRoute(path, timeout = DEFAULT_ROUTE_TIMEOUT) {
435
+ return new Promise((resolve) => {
436
+ if (currentPath === path) {
437
+ resolve(true);
438
+ return;
439
+ }
440
+ const unsubscribe = this.onRouteChange((newPath) => {
441
+ if (newPath === path) {
442
+ unsubscribe();
443
+ resolve(true);
444
+ }
445
+ });
446
+ setTimeout(() => {
447
+ unsubscribe();
448
+ resolve(currentPath === path);
449
+ }, timeout);
450
+ });
451
+ }
452
+ };
453
+ }
454
+ var NavigationController = class {
455
+ adapter;
456
+ config;
457
+ scanCallback = null;
458
+ logger = createLogger("SDK.Navigation");
459
+ constructor(adapter, config = {}) {
460
+ this.adapter = adapter;
461
+ this.config = {
462
+ domStabilityDelay: config.domStabilityDelay ?? 300,
463
+ domStabilityTimeout: config.domStabilityTimeout ?? 5e3,
464
+ debug: config.debug ?? false,
465
+ onDOMStable: config.onDOMStable
466
+ };
467
+ this.logger = createLogger("SDK.Navigation", { enabled: this.config.debug, level: "debug" });
468
+ }
469
+ /**
470
+ * Set the page scanner callback
471
+ */
472
+ setScanner(scanner) {
473
+ this.scanCallback = scanner;
474
+ }
475
+ /**
476
+ * Get current route
477
+ */
478
+ getCurrentRoute() {
479
+ return this.adapter.getCurrentPath();
480
+ }
481
+ /**
482
+ * Navigate to a route and wait for it to load
483
+ */
484
+ async navigateTo(route, timeout) {
485
+ const currentRoute = this.adapter.getCurrentPath();
486
+ if (currentRoute === route) {
487
+ this.log("Already on route:", route);
488
+ return true;
489
+ }
490
+ this.log("Navigating to:", route);
491
+ this.adapter.navigate(route);
492
+ const success = await this.adapter.waitForRoute(route, timeout);
493
+ if (!success) {
494
+ this.log("Navigation timeout for route:", route);
495
+ }
496
+ return success;
497
+ }
498
+ /**
499
+ * Execute navigation and rescan page context
500
+ */
501
+ async executeNavigation(route) {
502
+ const success = await this.navigateTo(route);
503
+ if (!success) {
504
+ this.log("Navigation failed to:", route);
505
+ return null;
506
+ }
507
+ await this.waitForDOMStable();
508
+ if (this.scanCallback) {
509
+ const context = await this.scanCallback();
510
+ this.log("Rescanned context on route:", route);
511
+ return context;
512
+ }
513
+ return null;
514
+ }
515
+ /**
516
+ * Wait for DOM to become stable after navigation
517
+ * Uses MutationObserver to detect when DOM stops changing
518
+ */
519
+ async waitForDOMStable() {
520
+ this.logger.debug("Waiting for DOM stability", {
521
+ delayMs: this.config.domStabilityDelay,
522
+ timeoutMs: this.config.domStabilityTimeout
523
+ });
524
+ const done = () => {
525
+ this.config.onDOMStable?.();
526
+ };
527
+ return new Promise((resolve) => {
528
+ let resolved = false;
529
+ const resolveOnce = () => {
530
+ if (resolved) return;
531
+ resolved = true;
532
+ done();
533
+ resolve();
534
+ };
535
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") {
536
+ resolveOnce();
537
+ return;
538
+ }
539
+ let mutationTimeout;
540
+ let maxTimeout;
541
+ const startTime = Date.now();
542
+ const cleanup = () => {
543
+ observer.disconnect();
544
+ clearTimeout(mutationTimeout);
545
+ clearTimeout(maxTimeout);
546
+ };
547
+ const observer = new MutationObserver(() => {
548
+ clearTimeout(mutationTimeout);
549
+ if (Date.now() - startTime >= this.config.domStabilityTimeout) {
550
+ cleanup();
551
+ this.log("DOM stability timeout reached");
552
+ resolveOnce();
553
+ return;
554
+ }
555
+ mutationTimeout = setTimeout(() => {
556
+ cleanup();
557
+ this.log("DOM is stable");
558
+ resolveOnce();
559
+ }, this.config.domStabilityDelay);
560
+ });
561
+ observer.observe(document.body, {
562
+ childList: true,
563
+ subtree: true,
564
+ attributes: false
565
+ // Ignore attribute changes for stability
566
+ });
567
+ mutationTimeout = setTimeout(() => {
568
+ cleanup();
569
+ this.log("DOM is stable (no initial mutations)");
570
+ resolveOnce();
571
+ }, this.config.domStabilityDelay);
572
+ maxTimeout = setTimeout(() => {
573
+ cleanup();
574
+ this.log("DOM stability max timeout reached");
575
+ resolveOnce();
576
+ }, this.config.domStabilityTimeout);
577
+ });
578
+ }
579
+ /**
580
+ * Subscribe to route changes
581
+ */
582
+ onRouteChange(callback) {
583
+ return this.adapter.onRouteChange(callback);
584
+ }
585
+ log(...args) {
586
+ this.logger.debug(...args);
587
+ }
588
+ };
589
+ function createNavigationController(adapter, config) {
590
+ return new NavigationController(adapter, config);
591
+ }
592
+ var INTERACTIVE_SELECTORS = [
593
+ "button:not([disabled])",
594
+ "a[href]:not([disabled])",
595
+ 'input:not([type="hidden"]):not([disabled])',
596
+ "select:not([disabled])",
597
+ "textarea:not([disabled])",
598
+ '[contenteditable="true"]',
599
+ '[contenteditable="plaintext-only"]',
600
+ '[role="textbox"]',
601
+ '[role="searchbox"]',
602
+ '[role="combobox"]',
603
+ '[role="checkbox"]',
604
+ '[role="switch"]',
605
+ '[role="radio"]',
606
+ '[role="button"]:not([disabled])',
607
+ '[role="link"]',
608
+ '[role="menuitem"]',
609
+ '[role="tab"]',
610
+ "[onclick]",
611
+ "[data-chatbot-action]"
612
+ ];
613
+ var IGNORE_SELECTORS = [
614
+ "[data-chatbot-ignore]",
615
+ '[aria-hidden="true"]',
616
+ "[hidden]",
617
+ ".chatbot-widget",
618
+ // Legacy class
619
+ ".navsi-chatbot-container",
620
+ // Ignore our own widget
621
+ '[type="password"]',
622
+ // Security: don't interact with password fields
623
+ '[autocomplete*="cc-"]'
624
+ // Security: credit card fields
625
+ ];
626
+ function generateSelector(element) {
627
+ const chatbotAction = element.getAttribute("data-chatbot-action");
628
+ if (chatbotAction) {
629
+ return `[data-chatbot-action="${chatbotAction}"]`;
630
+ }
631
+ if (element.id) {
632
+ return `#${CSS.escape(element.id)}`;
633
+ }
634
+ const testId = element.getAttribute("data-testid");
635
+ if (testId) {
636
+ return `[data-testid="${CSS.escape(testId)}"]`;
637
+ }
638
+ const ariaLabel = element.getAttribute("aria-label");
639
+ if (ariaLabel) {
640
+ const selector = `[aria-label="${CSS.escape(ariaLabel)}"]`;
641
+ const matches = document.querySelectorAll(selector);
642
+ if (matches.length === 1) {
643
+ return selector;
644
+ }
645
+ }
646
+ const name = element.getAttribute?.("name");
647
+ if (name && /^(input|select|textarea|button)$/i.test(element.tagName)) {
648
+ const selector = `${element.tagName.toLowerCase()}[name="${CSS.escape(name)}"]`;
649
+ const matches = document.querySelectorAll(selector);
650
+ if (matches.length === 1) {
651
+ return selector;
652
+ }
653
+ }
654
+ if (element.className && typeof element.className === "string") {
655
+ const classes = element.className.split(/\s+/).filter(Boolean).slice(0, 3);
656
+ if (classes.length > 0) {
657
+ const selector = `${element.tagName.toLowerCase()}.${classes.map((c) => CSS.escape(c)).join(".")}`;
658
+ const matches = document.querySelectorAll(selector);
659
+ if (matches.length === 1) {
660
+ return selector;
661
+ }
662
+ }
663
+ }
664
+ return buildPathSelector(element);
665
+ }
666
+ function buildPathSelector(element) {
667
+ const path = [];
668
+ let current = element;
669
+ while (current && current !== document.body) {
670
+ let selector = current.tagName.toLowerCase();
671
+ if (current.id) {
672
+ selector = `#${CSS.escape(current.id)}`;
673
+ path.unshift(selector);
674
+ break;
675
+ }
676
+ const parent = current.parentElement;
677
+ if (parent) {
678
+ const tagName = current.tagName;
679
+ const children = parent.children;
680
+ const sameTagSiblings = [];
681
+ for (let i = 0; i < children.length; i++) {
682
+ if (children[i].tagName === tagName) {
683
+ sameTagSiblings.push(children[i]);
684
+ }
685
+ }
686
+ if (sameTagSiblings.length > 1) {
687
+ const index = sameTagSiblings.indexOf(current) + 1;
688
+ selector += `:nth-of-type(${index})`;
689
+ }
690
+ }
691
+ path.unshift(selector);
692
+ current = parent;
693
+ }
694
+ return path.join(" > ");
695
+ }
696
+ function isVisible(element) {
697
+ const style = window.getComputedStyle(element);
698
+ if (style.display === "none") return false;
699
+ if (style.visibility === "hidden") return false;
700
+ if (style.opacity === "0") return false;
701
+ const rect = element.getBoundingClientRect();
702
+ if (rect.width === 0 && rect.height === 0) return false;
703
+ if (element.closest('[aria-hidden="true"]')) return false;
704
+ return true;
705
+ }
706
+ function shouldIgnore(element) {
707
+ for (const selector of IGNORE_SELECTORS) {
708
+ if (element.matches(selector)) return true;
709
+ if (element.closest(selector)) return true;
710
+ }
711
+ return false;
712
+ }
713
+ function getActionType(element) {
714
+ const tagName = element.tagName.toLowerCase();
715
+ const role = element.getAttribute("role")?.toLowerCase();
716
+ if (tagName === "input") {
717
+ const type = element.type;
718
+ if (type === "checkbox" || type === "radio") return "click";
719
+ return "type";
720
+ }
721
+ if (tagName === "textarea") return "type";
722
+ if (tagName === "select") return "select";
723
+ if (role === "textbox" || role === "searchbox") return "type";
724
+ if (role === "combobox") return "click";
725
+ if (role === "checkbox" || role === "radio" || role === "switch") return "click";
726
+ return "click";
727
+ }
728
+ function extractLabel(element) {
729
+ const chatbotAction = element.getAttribute("data-chatbot-action");
730
+ if (chatbotAction) {
731
+ return chatbotAction.replace(/-/g, " ");
732
+ }
733
+ const ariaLabel = element.getAttribute("aria-label");
734
+ if (ariaLabel) return ariaLabel;
735
+ const labelledBy = element.getAttribute("aria-labelledby");
736
+ if (labelledBy) {
737
+ const labelText = labelledBy.split(/\s+/).map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean).join(" ");
738
+ if (labelText) return labelText;
739
+ }
740
+ const title = element.getAttribute("title");
741
+ if (title) return title;
742
+ const textContent = element.textContent?.trim();
743
+ if (textContent && textContent.length < 50) {
744
+ return textContent;
745
+ }
746
+ const placeholder = element.getAttribute("placeholder");
747
+ if (placeholder) return placeholder;
748
+ const name = element.getAttribute("name");
749
+ if (name) return name.replace(/[-_]/g, " ");
750
+ if (element.id) {
751
+ const label = document.querySelector(`label[for="${element.id}"]`);
752
+ if (label?.textContent) return label.textContent.trim();
753
+ }
754
+ return element.tagName.toLowerCase();
755
+ }
756
+ var CONTAINER_SELECTORS = [
757
+ "tr",
758
+ '[role="row"]',
759
+ "article",
760
+ '[role="listitem"]',
761
+ "section",
762
+ '[role="group"]',
763
+ "main",
764
+ '[role="region"]'
765
+ ];
766
+ var MAX_CONTAINER_TEXT_LENGTH = 120;
767
+ function getContainerText(element) {
768
+ let current = element;
769
+ let depth = 0;
770
+ const maxDepth = 8;
771
+ while (current && current !== document.body && depth < maxDepth) {
772
+ for (const sel of CONTAINER_SELECTORS) {
773
+ try {
774
+ if (current.matches(sel)) {
775
+ const text = current.textContent?.trim().replace(/\s+/g, " ");
776
+ if (text && text.length > 0) {
777
+ return text.length > MAX_CONTAINER_TEXT_LENGTH ? text.slice(0, MAX_CONTAINER_TEXT_LENGTH) + "\u2026" : text;
778
+ }
779
+ break;
780
+ }
781
+ } catch {
782
+ }
783
+ }
784
+ current = current.parentElement;
785
+ depth++;
786
+ }
787
+ return void 0;
788
+ }
789
+ function getNearbyHeading(element) {
790
+ let current = element.parentElement;
791
+ let depth = 0;
792
+ const maxDepth = 5;
793
+ while (current && depth < maxDepth) {
794
+ const heading = current.querySelector("h1, h2, h3, h4, h5, h6");
795
+ if (heading?.textContent) {
796
+ return heading.textContent.trim();
797
+ }
798
+ let sibling = current.previousElementSibling;
799
+ while (sibling) {
800
+ if (/^H[1-6]$/.test(sibling.tagName)) {
801
+ return sibling.textContent?.trim();
802
+ }
803
+ sibling = sibling.previousElementSibling;
804
+ }
805
+ current = current.parentElement;
806
+ depth++;
807
+ }
808
+ return void 0;
809
+ }
810
+ var DOMScanner = class {
811
+ config;
812
+ mutationObserver = null;
813
+ scanTimeout = null;
814
+ onChangeCallback = null;
815
+ logger = createLogger("SDK.Scanner");
816
+ constructor(config = {}) {
817
+ this.config = {
818
+ root: config.root ?? (typeof document !== "undefined" ? document.body : null),
819
+ debug: config.debug ?? false,
820
+ maxActions: config.maxActions ?? 100,
821
+ debounceMs: config.debounceMs ?? 300
822
+ };
823
+ this.logger = createLogger("SDK.Scanner", { enabled: this.config.debug, level: "debug" });
824
+ }
825
+ /**
826
+ * Scan the DOM for interactive elements
827
+ */
828
+ scan() {
829
+ if (typeof document === "undefined") {
830
+ return [];
831
+ }
832
+ const root = this.config.root ?? document.body;
833
+ const actions = [];
834
+ const currentRoute = typeof window !== "undefined" ? window.location.pathname : "/";
835
+ const selector = INTERACTIVE_SELECTORS.join(", ");
836
+ const elements = this.collectInteractiveElements(root, selector);
837
+ this.log(`Found ${elements.length} potential interactive elements`);
838
+ for (const element of elements) {
839
+ if (actions.length >= this.config.maxActions) break;
840
+ if (shouldIgnore(element)) continue;
841
+ if (!isVisible(element)) continue;
842
+ const action = this.createElement(element, currentRoute);
843
+ if (action) {
844
+ actions.push(action);
845
+ }
846
+ }
847
+ this.log(`Discovered ${actions.length} actions`);
848
+ return actions;
849
+ }
850
+ /**
851
+ * Create action definition from element
852
+ */
853
+ createElement(element, route) {
854
+ try {
855
+ const selector = generateSelector(element);
856
+ const label = extractLabel(element);
857
+ const type = getActionType(element);
858
+ const heading = getNearbyHeading(element);
859
+ const id = `action_${btoa(selector).replace(/[^a-zA-Z0-9]/g, "").slice(0, 16)}`;
860
+ const action = {
861
+ id,
862
+ type,
863
+ selector,
864
+ label,
865
+ route,
866
+ isAutoDiscovered: true,
867
+ priority: element.hasAttribute("data-chatbot-action") ? 10 : 1
868
+ };
869
+ const containerText = getContainerText(element);
870
+ if (heading || containerText) {
871
+ action.metadata = {
872
+ ...heading ? { context: heading } : {},
873
+ ...containerText ? { containerText } : {}
874
+ };
875
+ }
876
+ return action;
877
+ } catch (error) {
878
+ this.log("Error creating action:", error);
879
+ return null;
880
+ }
881
+ }
882
+ /**
883
+ * Collect interactive elements from light DOM and open shadow roots.
884
+ */
885
+ collectInteractiveElements(root, selector) {
886
+ const results = /* @__PURE__ */ new Set();
887
+ const roots = [root];
888
+ while (roots.length > 0) {
889
+ const currentRoot = roots.shift();
890
+ if (!currentRoot) {
891
+ break;
892
+ }
893
+ const scope = currentRoot instanceof ShadowRoot ? currentRoot : currentRoot;
894
+ const matches = scope.querySelectorAll(selector);
895
+ for (const match of matches) {
896
+ results.add(match);
897
+ }
898
+ const hostRoot = currentRoot instanceof ShadowRoot ? currentRoot : currentRoot;
899
+ const hostElements = hostRoot.querySelectorAll("*");
900
+ for (const host of hostElements) {
901
+ const shadowRoot = host.shadowRoot;
902
+ if (shadowRoot) {
903
+ roots.push(shadowRoot);
904
+ }
905
+ }
906
+ }
907
+ return Array.from(results);
908
+ }
909
+ /**
910
+ * Start observing DOM for changes. Disconnects any existing observer first.
911
+ */
912
+ observe(callback) {
913
+ if (typeof MutationObserver === "undefined") return;
914
+ this.disconnect();
915
+ this.onChangeCallback = callback;
916
+ this.mutationObserver = new MutationObserver(() => {
917
+ this.debouncedScan();
918
+ });
919
+ this.mutationObserver.observe(this.config.root ?? document.body, {
920
+ childList: true,
921
+ subtree: true,
922
+ attributes: true,
923
+ attributeFilter: ["disabled", "hidden", "aria-hidden"]
924
+ });
925
+ }
926
+ /**
927
+ * Stop observing DOM
928
+ */
929
+ disconnect() {
930
+ if (this.mutationObserver) {
931
+ this.mutationObserver.disconnect();
932
+ this.mutationObserver = null;
933
+ }
934
+ if (this.scanTimeout) {
935
+ clearTimeout(this.scanTimeout);
936
+ this.scanTimeout = null;
937
+ }
938
+ }
939
+ /**
940
+ * Debounced scan
941
+ */
942
+ debouncedScan() {
943
+ if (this.scanTimeout) {
944
+ clearTimeout(this.scanTimeout);
945
+ }
946
+ this.scanTimeout = setTimeout(() => {
947
+ const actions = this.scan();
948
+ this.onChangeCallback?.(actions);
949
+ }, this.config.debounceMs);
950
+ }
951
+ log(...args) {
952
+ this.logger.debug(...args);
953
+ }
954
+ };
955
+ var ActionRegistry = class {
956
+ actionsByRoute = /* @__PURE__ */ new Map();
957
+ serverActions = /* @__PURE__ */ new Map();
958
+ manualActions = /* @__PURE__ */ new Map();
959
+ debug;
960
+ logger = createLogger("SDK.Registry");
961
+ constructor(config = {}) {
962
+ this.debug = config.debug ?? false;
963
+ this.logger = createLogger("SDK.Registry", { enabled: this.debug, level: "debug" });
964
+ }
965
+ // ============================================================================
966
+ // DOM Actions
967
+ // ============================================================================
968
+ /**
969
+ * Register auto-discovered actions for a route
970
+ */
971
+ registerDiscoveredActions(route, actions) {
972
+ if (!this.actionsByRoute.has(route)) {
973
+ this.actionsByRoute.set(route, /* @__PURE__ */ new Map());
974
+ }
975
+ const routeActions = this.actionsByRoute.get(route);
976
+ for (const [id, action] of routeActions) {
977
+ if (action.isAutoDiscovered) {
978
+ routeActions.delete(id);
979
+ }
980
+ }
981
+ for (const action of actions) {
982
+ const manualAction = this.manualActions.get(action.id);
983
+ if (manualAction) {
984
+ routeActions.set(action.id, { ...action, ...manualAction });
985
+ } else {
986
+ routeActions.set(action.id, action);
987
+ }
988
+ }
989
+ this.log(`Registered ${actions.length} discovered actions for route: ${route}`);
990
+ }
991
+ /**
992
+ * Register a manual action definition (takes priority over auto-discovered)
993
+ */
994
+ registerManualAction(action) {
995
+ this.manualActions.set(action.id, action);
996
+ const routeActions = this.actionsByRoute.get(action.route);
997
+ if (routeActions?.has(action.id)) {
998
+ const existing = routeActions.get(action.id);
999
+ routeActions.set(action.id, { ...existing, ...action, isAutoDiscovered: false });
1000
+ }
1001
+ this.log(`Registered manual action: ${action.id}`);
1002
+ }
1003
+ /**
1004
+ * Get all actions for a route
1005
+ */
1006
+ getActionsForRoute(route) {
1007
+ const routeActions = this.actionsByRoute.get(route);
1008
+ if (!routeActions) return [];
1009
+ return Array.from(routeActions.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
1010
+ }
1011
+ /**
1012
+ * Find action by ID
1013
+ */
1014
+ findActionById(id) {
1015
+ for (const routeActions of this.actionsByRoute.values()) {
1016
+ const action = routeActions.get(id);
1017
+ if (action) return action;
1018
+ }
1019
+ return null;
1020
+ }
1021
+ /**
1022
+ * Find action by selector on a specific route
1023
+ */
1024
+ findActionBySelector(route, selector) {
1025
+ const routeActions = this.actionsByRoute.get(route);
1026
+ if (!routeActions) return null;
1027
+ for (const action of routeActions.values()) {
1028
+ if (action.selector === selector) return action;
1029
+ }
1030
+ return null;
1031
+ }
1032
+ /**
1033
+ * Find action by label (fuzzy match). When context is provided, prefer actions
1034
+ * whose metadata.containerText or metadata.context contains the context string.
1035
+ */
1036
+ findActionByLabel(route, label, context) {
1037
+ const routeActions = this.actionsByRoute.get(route);
1038
+ if (!routeActions) return null;
1039
+ const normalizedLabel = label.toLowerCase().trim();
1040
+ const normalizedContext = context?.toLowerCase().trim();
1041
+ const matchesContext = (action) => {
1042
+ if (!normalizedContext) return true;
1043
+ const containerText = action.metadata?.containerText?.toLowerCase();
1044
+ const metaContext = action.metadata?.context?.toLowerCase();
1045
+ return (containerText?.includes(normalizedContext) ?? false) || (metaContext?.includes(normalizedContext) ?? false);
1046
+ };
1047
+ if (normalizedContext) {
1048
+ for (const action of routeActions.values()) {
1049
+ if (action.label.toLowerCase() === normalizedLabel && matchesContext(action)) {
1050
+ return action;
1051
+ }
1052
+ }
1053
+ for (const action of routeActions.values()) {
1054
+ if (action.label.toLowerCase().includes(normalizedLabel) && matchesContext(action)) {
1055
+ return action;
1056
+ }
1057
+ }
1058
+ }
1059
+ for (const action of routeActions.values()) {
1060
+ if (action.label.toLowerCase() === normalizedLabel) {
1061
+ return action;
1062
+ }
1063
+ }
1064
+ for (const action of routeActions.values()) {
1065
+ if (action.label.toLowerCase().includes(normalizedLabel)) {
1066
+ return action;
1067
+ }
1068
+ }
1069
+ return null;
1070
+ }
1071
+ /**
1072
+ * Clear actions for a route
1073
+ */
1074
+ clearRoute(route) {
1075
+ this.actionsByRoute.delete(route);
1076
+ }
1077
+ /**
1078
+ * Clear all actions
1079
+ */
1080
+ clearAll() {
1081
+ this.actionsByRoute.clear();
1082
+ }
1083
+ // ============================================================================
1084
+ // Server Actions
1085
+ // ============================================================================
1086
+ /**
1087
+ * Register a server action
1088
+ */
1089
+ registerServerAction(action) {
1090
+ this.serverActions.set(action.id, action);
1091
+ this.log(`Registered server action: ${action.id}`);
1092
+ }
1093
+ /**
1094
+ * Get all server actions
1095
+ */
1096
+ getServerActions() {
1097
+ return Array.from(this.serverActions.values());
1098
+ }
1099
+ /**
1100
+ * Find server action by ID
1101
+ */
1102
+ findServerActionById(id) {
1103
+ return this.serverActions.get(id) ?? null;
1104
+ }
1105
+ /**
1106
+ * Find server action by name (fuzzy match)
1107
+ */
1108
+ findServerActionByName(name) {
1109
+ const normalizedName = name.toLowerCase().trim();
1110
+ for (const action of this.serverActions.values()) {
1111
+ if (action.name.toLowerCase() === normalizedName) {
1112
+ return action;
1113
+ }
1114
+ }
1115
+ for (const action of this.serverActions.values()) {
1116
+ if (action.name.toLowerCase().includes(normalizedName)) {
1117
+ return action;
1118
+ }
1119
+ }
1120
+ return null;
1121
+ }
1122
+ /**
1123
+ * Remove a server action
1124
+ */
1125
+ removeServerAction(id) {
1126
+ this.serverActions.delete(id);
1127
+ }
1128
+ // ============================================================================
1129
+ // Utilities
1130
+ // ============================================================================
1131
+ /**
1132
+ * Get statistics
1133
+ */
1134
+ getStats() {
1135
+ let totalActions = 0;
1136
+ for (const routeActions of this.actionsByRoute.values()) {
1137
+ totalActions += routeActions.size;
1138
+ }
1139
+ return {
1140
+ routes: this.actionsByRoute.size,
1141
+ actions: totalActions,
1142
+ serverActions: this.serverActions.size
1143
+ };
1144
+ }
1145
+ log(...args) {
1146
+ this.logger.debug(...args);
1147
+ }
1148
+ };
1149
+ function normalizeWhitespace(s) {
1150
+ return s.replace(/\s+/g, " ").trim();
1151
+ }
1152
+ function extractHeadings() {
1153
+ if (typeof document === "undefined") return [];
1154
+ const chatbotKeywords = ["chatbot", "ai assistant", "ask mode", "navigate mode", "navsi", "intelligent agent"];
1155
+ const headings = [];
1156
+ const elements = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
1157
+ for (const element of elements) {
1158
+ if (element.closest(".chatbot-widget") || element.closest(".navsi-chatbot-container")) continue;
1159
+ const text = element.textContent?.trim();
1160
+ if (text && text.length < 200) {
1161
+ const lower = text.toLowerCase();
1162
+ const isChatbotRelated = chatbotKeywords.some((keyword) => lower.includes(keyword));
1163
+ if (!isChatbotRelated) {
1164
+ headings.push(text);
1165
+ }
1166
+ }
1167
+ }
1168
+ return headings.slice(0, 20);
1169
+ }
1170
+ function extractMainContent(maxLength) {
1171
+ if (typeof document === "undefined") return "";
1172
+ const mainContent = document.querySelector("main") ?? document.querySelector('[role="main"]') ?? document.querySelector("article") ?? document.querySelector(".content") ?? document.body;
1173
+ const walker = document.createTreeWalker(
1174
+ mainContent,
1175
+ NodeFilter.SHOW_TEXT,
1176
+ {
1177
+ acceptNode: (node2) => {
1178
+ const parent = node2.parentElement;
1179
+ if (!parent) return NodeFilter.FILTER_REJECT;
1180
+ const tagName = parent.tagName.toLowerCase();
1181
+ if (["script", "style", "noscript"].includes(tagName)) {
1182
+ return NodeFilter.FILTER_REJECT;
1183
+ }
1184
+ if (parent.closest(".chatbot-widget") || parent.closest(".navsi-chatbot-container")) {
1185
+ return NodeFilter.FILTER_REJECT;
1186
+ }
1187
+ return NodeFilter.FILTER_ACCEPT;
1188
+ }
1189
+ }
1190
+ );
1191
+ let content = "";
1192
+ let node;
1193
+ while ((node = walker.nextNode()) && content.length < maxLength) {
1194
+ const text = node.textContent?.trim();
1195
+ if (text && text.length > 0) {
1196
+ content += text + " ";
1197
+ }
1198
+ }
1199
+ return normalizeWhitespace(content).slice(0, maxLength);
1200
+ }
1201
+ function extractForms() {
1202
+ if (typeof document === "undefined") return [];
1203
+ const forms = [];
1204
+ const formElements = document.querySelectorAll("form");
1205
+ for (const form of formElements) {
1206
+ if (form.closest(".chatbot-widget") || form.closest(".navsi-chatbot-container")) continue;
1207
+ const fields = [];
1208
+ const inputs = form.querySelectorAll("input, select, textarea");
1209
+ for (const input of inputs) {
1210
+ const element = input;
1211
+ if (element.type === "hidden") continue;
1212
+ let label;
1213
+ if (element.id) {
1214
+ const labelElement = document.querySelector(`label[for="${element.id}"]`);
1215
+ label = labelElement?.textContent?.trim();
1216
+ }
1217
+ if (!label && element.getAttribute("aria-label")) {
1218
+ label = element.getAttribute("aria-label") ?? void 0;
1219
+ }
1220
+ fields.push({
1221
+ name: element.name || element.id || "",
1222
+ type: element.type || element.tagName.toLowerCase(),
1223
+ label,
1224
+ placeholder: "placeholder" in element ? element.placeholder : void 0,
1225
+ required: element.required,
1226
+ value: element.value || void 0
1227
+ });
1228
+ }
1229
+ if (fields.length > 0) {
1230
+ forms.push({
1231
+ id: form.id || `form_${forms.length}`,
1232
+ name: form.name || void 0,
1233
+ action: form.action || void 0,
1234
+ method: form.method || void 0,
1235
+ fields
1236
+ });
1237
+ }
1238
+ }
1239
+ return forms.slice(0, 10);
1240
+ }
1241
+ function getMetaDescription() {
1242
+ if (typeof document === "undefined") return void 0;
1243
+ const meta = document.querySelector('meta[name="description"]');
1244
+ return meta?.getAttribute("content") ?? void 0;
1245
+ }
1246
+ var PageContextBuilder = class {
1247
+ config;
1248
+ logger = createLogger("SDK.Context");
1249
+ constructor(config = {}) {
1250
+ this.config = {
1251
+ maxContentLength: config.maxContentLength ?? 2e3,
1252
+ maxActions: config.maxActions ?? 50,
1253
+ debug: config.debug ?? false
1254
+ };
1255
+ this.logger = createLogger("SDK.Context", { enabled: this.config.debug, level: "debug" });
1256
+ }
1257
+ /**
1258
+ * Build page context
1259
+ */
1260
+ build(actions, serverActions, route) {
1261
+ const currentRoute = route ?? (typeof window !== "undefined" ? window.location.pathname : "/");
1262
+ const title = typeof document !== "undefined" ? document.title : "";
1263
+ const content = {
1264
+ headings: extractHeadings(),
1265
+ mainContent: extractMainContent(this.config.maxContentLength),
1266
+ forms: extractForms(),
1267
+ metaDescription: getMetaDescription()
1268
+ };
1269
+ const limitedActions = actions.slice(0, this.config.maxActions);
1270
+ const context = {
1271
+ route: currentRoute,
1272
+ title,
1273
+ actions: limitedActions,
1274
+ serverActions,
1275
+ content,
1276
+ timestamp: Date.now()
1277
+ };
1278
+ this.log("Built page context:", {
1279
+ route: context.route,
1280
+ title: context.title,
1281
+ actions: context.actions.length,
1282
+ serverActions: context.serverActions.length,
1283
+ headings: context.content.headings.length,
1284
+ forms: context.content.forms.length
1285
+ });
1286
+ return context;
1287
+ }
1288
+ /**
1289
+ * Build minimal context (for rapid updates)
1290
+ */
1291
+ buildMinimal(actions, serverActions, route) {
1292
+ const currentRoute = route ?? (typeof window !== "undefined" ? window.location.pathname : "/");
1293
+ return {
1294
+ route: currentRoute,
1295
+ title: typeof document !== "undefined" ? document.title : "",
1296
+ actions: actions.slice(0, this.config.maxActions),
1297
+ serverActions,
1298
+ content: {
1299
+ headings: [],
1300
+ mainContent: "",
1301
+ forms: []
1302
+ },
1303
+ timestamp: Date.now()
1304
+ };
1305
+ }
1306
+ log(...args) {
1307
+ this.logger.debug(...args);
1308
+ }
1309
+ };
1310
+ function querySelectorDeep(selector, options) {
1311
+ const preferVisible = options?.preferVisible ?? false;
1312
+ const roots = [document];
1313
+ let firstMatch = null;
1314
+ while (roots.length > 0) {
1315
+ const root = roots.shift();
1316
+ const match = root.querySelector(selector);
1317
+ if (match) {
1318
+ if (!preferVisible) return match;
1319
+ if (isElementVisible(match)) return match;
1320
+ if (!firstMatch) firstMatch = match;
1321
+ }
1322
+ const hostRoot = root instanceof Document ? root.body ?? root.documentElement : root;
1323
+ if (!hostRoot) continue;
1324
+ const hosts = hostRoot.querySelectorAll("*");
1325
+ for (const host of hosts) {
1326
+ const shadowRoot = host.shadowRoot;
1327
+ if (shadowRoot) {
1328
+ roots.push(shadowRoot);
1329
+ }
1330
+ }
1331
+ }
1332
+ return preferVisible ? firstMatch : null;
1333
+ }
1334
+ async function waitForElement(selector, timeout, requireVisible = true) {
1335
+ const startTime = Date.now();
1336
+ const isReady = (element) => {
1337
+ if (!element) return false;
1338
+ return !requireVisible || isElementVisible(element);
1339
+ };
1340
+ return new Promise((resolve) => {
1341
+ const element = querySelectorDeep(selector, { preferVisible: requireVisible });
1342
+ if (isReady(element)) {
1343
+ resolve(element);
1344
+ return;
1345
+ }
1346
+ const observer = new MutationObserver(() => {
1347
+ const el = querySelectorDeep(selector, { preferVisible: requireVisible });
1348
+ if (isReady(el)) {
1349
+ observer.disconnect();
1350
+ resolve(el);
1351
+ }
1352
+ });
1353
+ observer.observe(document.body, {
1354
+ childList: true,
1355
+ subtree: true,
1356
+ attributes: true
1357
+ });
1358
+ const checkInterval = setInterval(() => {
1359
+ const el = querySelectorDeep(selector, { preferVisible: requireVisible });
1360
+ if (isReady(el)) {
1361
+ clearInterval(checkInterval);
1362
+ observer.disconnect();
1363
+ resolve(el);
1364
+ }
1365
+ if (Date.now() - startTime >= timeout) {
1366
+ clearInterval(checkInterval);
1367
+ observer.disconnect();
1368
+ resolve(null);
1369
+ }
1370
+ }, 100);
1371
+ setTimeout(() => {
1372
+ clearInterval(checkInterval);
1373
+ observer.disconnect();
1374
+ const el = querySelectorDeep(selector, { preferVisible: requireVisible });
1375
+ resolve(isReady(el) ? el : null);
1376
+ }, timeout);
1377
+ });
1378
+ }
1379
+ function isElementVisible(element) {
1380
+ const style = window.getComputedStyle(element);
1381
+ if (style.display === "none") return false;
1382
+ if (style.visibility === "hidden") return false;
1383
+ if (style.opacity === "0") return false;
1384
+ const rect = element.getBoundingClientRect();
1385
+ if (rect.width === 0 && rect.height === 0) return false;
1386
+ const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
1387
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
1388
+ const horizontallyVisible = rect.right > 0 && rect.left < viewportWidth;
1389
+ const verticallyVisible = rect.bottom > 0 && rect.top < viewportHeight;
1390
+ return horizontallyVisible && verticallyVisible;
1391
+ }
1392
+ function getElementCenter(element) {
1393
+ const rect = element.getBoundingClientRect();
1394
+ return {
1395
+ x: rect.left + rect.width / 2,
1396
+ y: rect.top + rect.height / 2
1397
+ };
1398
+ }
1399
+ function setNativeInputValue(element, value) {
1400
+ const prototype = element instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
1401
+ const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
1402
+ if (setter) {
1403
+ setter.call(element, value);
1404
+ } else {
1405
+ element.value = value;
1406
+ }
1407
+ }
1408
+ function scrollToElement(element) {
1409
+ element.scrollIntoView({
1410
+ behavior: "smooth",
1411
+ block: "center",
1412
+ inline: "center"
1413
+ });
1414
+ }
1415
+ function highlightElement(element, duration = 500) {
1416
+ const htmlElement = element;
1417
+ const originalOutline = htmlElement.style.outline;
1418
+ const originalTransition = htmlElement.style.transition;
1419
+ htmlElement.style.transition = "outline 0.2s ease";
1420
+ htmlElement.style.outline = "3px solid #6366f1";
1421
+ setTimeout(() => {
1422
+ htmlElement.style.outline = originalOutline;
1423
+ htmlElement.style.transition = originalTransition;
1424
+ }, duration);
1425
+ }
1426
+ async function simulateTyping(element, text, delay) {
1427
+ element.focus();
1428
+ setNativeInputValue(element, "");
1429
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1430
+ for (const char of text) {
1431
+ try {
1432
+ if (typeof InputEvent !== "undefined") {
1433
+ element.dispatchEvent(
1434
+ new InputEvent("beforeinput", {
1435
+ bubbles: true,
1436
+ cancelable: true,
1437
+ data: char,
1438
+ inputType: "insertText"
1439
+ })
1440
+ );
1441
+ }
1442
+ } catch {
1443
+ }
1444
+ const nextValue = (element.value ?? "") + char;
1445
+ setNativeInputValue(element, nextValue);
1446
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1447
+ element.dispatchEvent(new KeyboardEvent("keydown", { key: char, bubbles: true }));
1448
+ element.dispatchEvent(new KeyboardEvent("keypress", { key: char, bubbles: true }));
1449
+ element.dispatchEvent(new KeyboardEvent("keyup", { key: char, bubbles: true }));
1450
+ if (delay > 0) {
1451
+ await new Promise((resolve) => setTimeout(resolve, delay));
1452
+ }
1453
+ }
1454
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1455
+ }
1456
+ async function simulateContentEditableTyping(element, text, delay) {
1457
+ element.focus();
1458
+ element.textContent = "";
1459
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1460
+ for (const char of text) {
1461
+ try {
1462
+ if (typeof InputEvent !== "undefined") {
1463
+ element.dispatchEvent(
1464
+ new InputEvent("beforeinput", {
1465
+ bubbles: true,
1466
+ cancelable: true,
1467
+ data: char,
1468
+ inputType: "insertText"
1469
+ })
1470
+ );
1471
+ }
1472
+ } catch {
1473
+ }
1474
+ element.textContent = (element.textContent ?? "") + char;
1475
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1476
+ element.dispatchEvent(new KeyboardEvent("keydown", { key: char, bubbles: true }));
1477
+ element.dispatchEvent(new KeyboardEvent("keypress", { key: char, bubbles: true }));
1478
+ element.dispatchEvent(new KeyboardEvent("keyup", { key: char, bubbles: true }));
1479
+ if (delay > 0) {
1480
+ await new Promise((resolve) => setTimeout(resolve, delay));
1481
+ }
1482
+ }
1483
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1484
+ }
1485
+ function collectElementMetadata(element) {
1486
+ const rect = element.getBoundingClientRect();
1487
+ const tagName = element.tagName.toLowerCase();
1488
+ const attrs = {};
1489
+ if (element instanceof HTMLElement) {
1490
+ for (const attr of Array.from(element.attributes)) {
1491
+ if (attr.name.startsWith("data-") || attr.name === "aria-label" || attr.name === "name" || attr.name === "id") {
1492
+ attrs[attr.name] = attr.value;
1493
+ }
1494
+ }
1495
+ }
1496
+ return {
1497
+ tagName,
1498
+ text: (element.textContent ?? "").trim().slice(0, 80),
1499
+ rect: {
1500
+ x: rect.x,
1501
+ y: rect.y,
1502
+ width: rect.width,
1503
+ height: rect.height
1504
+ },
1505
+ attributes: attrs
1506
+ };
1507
+ }
1508
+ var DOMExecutor = class _DOMExecutor {
1509
+ config;
1510
+ logger = createLogger("SDK.Executor");
1511
+ constructor(config = {}) {
1512
+ this.config = {
1513
+ elementTimeout: config.elementTimeout ?? 6e3,
1514
+ typeDelay: config.typeDelay ?? 30,
1515
+ scrollIntoView: config.scrollIntoView ?? true,
1516
+ highlightOnInteract: config.highlightOnInteract ?? true,
1517
+ debug: config.debug ?? false
1518
+ };
1519
+ this.logger = createLogger("SDK.Executor", { enabled: this.config.debug, level: "debug" });
1520
+ }
1521
+ /** Delay before retry on failure (ms) */
1522
+ static RETRY_DELAY_MS = 600;
1523
+ /** Maximum number of retry attempts */
1524
+ static MAX_RETRIES = 2;
1525
+ /**
1526
+ * Click an element (synthetic events + native click fallback; one retry on failure)
1527
+ * Prevents default behavior on links to avoid page reloads
1528
+ */
1529
+ async click(selector) {
1530
+ const startTime = Date.now();
1531
+ const attempt = async () => {
1532
+ const element = await waitForElement(selector, this.config.elementTimeout, true);
1533
+ if (!element) {
1534
+ return this.createResult(
1535
+ selector,
1536
+ "click",
1537
+ startTime,
1538
+ false,
1539
+ "Element not found",
1540
+ "ELEMENT_NOT_FOUND"
1541
+ );
1542
+ }
1543
+ if (this.config.scrollIntoView) {
1544
+ scrollToElement(element);
1545
+ await new Promise((resolve) => setTimeout(resolve, 200));
1546
+ }
1547
+ if (this.config.highlightOnInteract) {
1548
+ highlightElement(element);
1549
+ }
1550
+ if (!isElementVisible(element)) {
1551
+ return this.createResult(
1552
+ selector,
1553
+ "click",
1554
+ startTime,
1555
+ false,
1556
+ "Element is not visible",
1557
+ "NOT_VISIBLE",
1558
+ collectElementMetadata(element)
1559
+ );
1560
+ }
1561
+ const htmlElement = element;
1562
+ const isDisabled = htmlElement.disabled === true || htmlElement.getAttribute("aria-disabled") === "true";
1563
+ if (isDisabled) {
1564
+ return this.createResult(
1565
+ selector,
1566
+ "click",
1567
+ startTime,
1568
+ false,
1569
+ "Element is disabled",
1570
+ "DISABLED",
1571
+ collectElementMetadata(element)
1572
+ );
1573
+ }
1574
+ htmlElement.focus();
1575
+ const { x, y } = getElementCenter(element);
1576
+ try {
1577
+ if (typeof PointerEvent !== "undefined") {
1578
+ element.dispatchEvent(
1579
+ new PointerEvent("pointerover", { bubbles: true, cancelable: true, clientX: x, clientY: y })
1580
+ );
1581
+ element.dispatchEvent(
1582
+ new PointerEvent("pointerenter", { bubbles: true, cancelable: true, clientX: x, clientY: y })
1583
+ );
1584
+ element.dispatchEvent(
1585
+ new PointerEvent("pointermove", { bubbles: true, cancelable: true, clientX: x, clientY: y })
1586
+ );
1587
+ }
1588
+ } catch {
1589
+ }
1590
+ element.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, cancelable: true, clientX: x, clientY: y }));
1591
+ element.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true, cancelable: true, clientX: x, clientY: y }));
1592
+ element.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, cancelable: true, clientX: x, clientY: y }));
1593
+ try {
1594
+ if (typeof PointerEvent !== "undefined") {
1595
+ element.dispatchEvent(
1596
+ new PointerEvent("pointerdown", {
1597
+ bubbles: true,
1598
+ cancelable: true,
1599
+ button: 0,
1600
+ clientX: x,
1601
+ clientY: y
1602
+ })
1603
+ );
1604
+ element.dispatchEvent(
1605
+ new PointerEvent("pointerup", {
1606
+ bubbles: true,
1607
+ cancelable: true,
1608
+ button: 0,
1609
+ clientX: x,
1610
+ clientY: y
1611
+ })
1612
+ );
1613
+ }
1614
+ } catch {
1615
+ }
1616
+ element.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true, clientX: x, clientY: y }));
1617
+ element.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true, clientX: x, clientY: y }));
1618
+ element.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, clientX: x, clientY: y }));
1619
+ htmlElement.click();
1620
+ this.log("Clicked:", selector);
1621
+ return this.createResult(
1622
+ selector,
1623
+ "click",
1624
+ startTime,
1625
+ true,
1626
+ void 0,
1627
+ void 0,
1628
+ collectElementMetadata(element)
1629
+ );
1630
+ };
1631
+ const executeWithRetries = async () => {
1632
+ let lastResult = null;
1633
+ for (let retry = 0; retry <= _DOMExecutor.MAX_RETRIES; retry++) {
1634
+ if (retry > 0) {
1635
+ this.log(`Click retry ${retry}/${_DOMExecutor.MAX_RETRIES} for:`, selector);
1636
+ await new Promise((r) => setTimeout(r, _DOMExecutor.RETRY_DELAY_MS * retry));
1637
+ }
1638
+ try {
1639
+ lastResult = await attempt();
1640
+ if (lastResult.success) {
1641
+ return lastResult;
1642
+ }
1643
+ } catch (error) {
1644
+ lastResult = this.createResult(
1645
+ selector,
1646
+ "click",
1647
+ startTime,
1648
+ false,
1649
+ String(error),
1650
+ "RUNTIME_ERROR"
1651
+ );
1652
+ }
1653
+ }
1654
+ return lastResult;
1655
+ };
1656
+ return executeWithRetries();
1657
+ }
1658
+ /**
1659
+ * Type text into an element (one retry on failure)
1660
+ */
1661
+ async type(selector, text, clearFirst = true) {
1662
+ const startTime = Date.now();
1663
+ const attempt = async () => {
1664
+ const element = await waitForElement(selector, this.config.elementTimeout, true);
1665
+ if (!element) {
1666
+ return this.createResult(
1667
+ selector,
1668
+ "type",
1669
+ startTime,
1670
+ false,
1671
+ "Element not found",
1672
+ "ELEMENT_NOT_FOUND"
1673
+ );
1674
+ }
1675
+ if (!this.isTypeable(element)) {
1676
+ return this.createResult(
1677
+ selector,
1678
+ "type",
1679
+ startTime,
1680
+ false,
1681
+ "Element is not typeable",
1682
+ "TYPE_MISMATCH",
1683
+ collectElementMetadata(element)
1684
+ );
1685
+ }
1686
+ if (this.config.scrollIntoView) {
1687
+ scrollToElement(element);
1688
+ await new Promise((resolve) => setTimeout(resolve, 200));
1689
+ }
1690
+ if (this.config.highlightOnInteract) {
1691
+ highlightElement(element);
1692
+ }
1693
+ const metadata = collectElementMetadata(element);
1694
+ if (!isElementVisible(element)) {
1695
+ return this.createResult(
1696
+ selector,
1697
+ "type",
1698
+ startTime,
1699
+ false,
1700
+ "Element is not visible",
1701
+ "NOT_VISIBLE",
1702
+ metadata
1703
+ );
1704
+ }
1705
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
1706
+ const inputElement = element;
1707
+ if (inputElement.disabled || inputElement.getAttribute("aria-disabled") === "true") {
1708
+ return this.createResult(
1709
+ selector,
1710
+ "type",
1711
+ startTime,
1712
+ false,
1713
+ "Element is disabled",
1714
+ "DISABLED",
1715
+ metadata
1716
+ );
1717
+ }
1718
+ if (clearFirst) {
1719
+ await simulateTyping(inputElement, text, this.config.typeDelay);
1720
+ } else {
1721
+ inputElement.focus();
1722
+ setNativeInputValue(inputElement, (inputElement.value ?? "") + text);
1723
+ inputElement.dispatchEvent(new Event("input", { bubbles: true }));
1724
+ inputElement.dispatchEvent(new Event("change", { bubbles: true }));
1725
+ }
1726
+ } else if (element instanceof HTMLElement && element.isContentEditable) {
1727
+ if (element.getAttribute("aria-disabled") === "true") {
1728
+ return this.createResult(
1729
+ selector,
1730
+ "type",
1731
+ startTime,
1732
+ false,
1733
+ "Element is disabled",
1734
+ "DISABLED",
1735
+ metadata
1736
+ );
1737
+ }
1738
+ await simulateContentEditableTyping(element, text, this.config.typeDelay);
1739
+ } else {
1740
+ return this.createResult(
1741
+ selector,
1742
+ "type",
1743
+ startTime,
1744
+ false,
1745
+ "Element is not a supported type target",
1746
+ "TYPE_MISMATCH",
1747
+ metadata
1748
+ );
1749
+ }
1750
+ this.log("Typed into:", selector, "text:", text.slice(0, 20) + (text.length > 20 ? "..." : ""));
1751
+ return this.createResult(
1752
+ selector,
1753
+ "type",
1754
+ startTime,
1755
+ true,
1756
+ void 0,
1757
+ void 0,
1758
+ {
1759
+ ...metadata,
1760
+ textLength: text.length
1761
+ }
1762
+ );
1763
+ };
1764
+ const executeWithRetries = async () => {
1765
+ let lastResult = null;
1766
+ for (let retry = 0; retry <= _DOMExecutor.MAX_RETRIES; retry++) {
1767
+ if (retry > 0) {
1768
+ this.log(`Type retry ${retry}/${_DOMExecutor.MAX_RETRIES} for:`, selector);
1769
+ await new Promise((r) => setTimeout(r, _DOMExecutor.RETRY_DELAY_MS * retry));
1770
+ }
1771
+ try {
1772
+ lastResult = await attempt();
1773
+ if (lastResult.success) {
1774
+ return lastResult;
1775
+ }
1776
+ } catch (error) {
1777
+ lastResult = this.createResult(
1778
+ selector,
1779
+ "type",
1780
+ startTime,
1781
+ false,
1782
+ String(error),
1783
+ "RUNTIME_ERROR"
1784
+ );
1785
+ }
1786
+ }
1787
+ return lastResult;
1788
+ };
1789
+ return executeWithRetries();
1790
+ }
1791
+ /**
1792
+ * Select an option from a dropdown (one retry on failure)
1793
+ */
1794
+ async select(selector, value) {
1795
+ const startTime = Date.now();
1796
+ const attempt = async () => {
1797
+ const element = await waitForElement(selector, this.config.elementTimeout, true);
1798
+ if (!element) {
1799
+ return this.createResult(
1800
+ selector,
1801
+ "select",
1802
+ startTime,
1803
+ false,
1804
+ "Element not found",
1805
+ "ELEMENT_NOT_FOUND"
1806
+ );
1807
+ }
1808
+ if (element.tagName.toLowerCase() !== "select") {
1809
+ return this.createResult(
1810
+ selector,
1811
+ "select",
1812
+ startTime,
1813
+ false,
1814
+ "Element is not a select",
1815
+ "TYPE_MISMATCH",
1816
+ collectElementMetadata(element)
1817
+ );
1818
+ }
1819
+ if (this.config.scrollIntoView) {
1820
+ scrollToElement(element);
1821
+ await new Promise((resolve) => setTimeout(resolve, 200));
1822
+ }
1823
+ if (this.config.highlightOnInteract) {
1824
+ highlightElement(element);
1825
+ }
1826
+ if (!isElementVisible(element)) {
1827
+ return this.createResult(
1828
+ selector,
1829
+ "select",
1830
+ startTime,
1831
+ false,
1832
+ "Element is not visible",
1833
+ "NOT_VISIBLE",
1834
+ collectElementMetadata(element)
1835
+ );
1836
+ }
1837
+ const selectElement = element;
1838
+ if (selectElement.disabled || selectElement.getAttribute("aria-disabled") === "true") {
1839
+ return this.createResult(
1840
+ selector,
1841
+ "select",
1842
+ startTime,
1843
+ false,
1844
+ "Element is disabled",
1845
+ "DISABLED",
1846
+ collectElementMetadata(element)
1847
+ );
1848
+ }
1849
+ let found = false;
1850
+ for (const option of selectElement.options) {
1851
+ if (option.value === value || option.textContent?.toLowerCase().includes(value.toLowerCase())) {
1852
+ selectElement.value = option.value;
1853
+ found = true;
1854
+ break;
1855
+ }
1856
+ }
1857
+ if (!found) {
1858
+ return this.createResult(
1859
+ selector,
1860
+ "select",
1861
+ startTime,
1862
+ false,
1863
+ `Option not found: ${value}`,
1864
+ "OPTION_NOT_FOUND",
1865
+ {
1866
+ ...collectElementMetadata(element),
1867
+ attemptedValue: value
1868
+ }
1869
+ );
1870
+ }
1871
+ selectElement.dispatchEvent(new Event("input", { bubbles: true }));
1872
+ selectElement.dispatchEvent(new Event("change", { bubbles: true }));
1873
+ this.log("Selected:", selector, "value:", value);
1874
+ return this.createResult(
1875
+ selector,
1876
+ "select",
1877
+ startTime,
1878
+ true,
1879
+ void 0,
1880
+ void 0,
1881
+ {
1882
+ ...collectElementMetadata(element),
1883
+ selectedValue: value
1884
+ }
1885
+ );
1886
+ };
1887
+ const executeWithRetries = async () => {
1888
+ let lastResult = null;
1889
+ for (let retry = 0; retry <= _DOMExecutor.MAX_RETRIES; retry++) {
1890
+ if (retry > 0) {
1891
+ this.log(`Select retry ${retry}/${_DOMExecutor.MAX_RETRIES} for:`, selector);
1892
+ await new Promise((r) => setTimeout(r, _DOMExecutor.RETRY_DELAY_MS * retry));
1893
+ }
1894
+ try {
1895
+ lastResult = await attempt();
1896
+ if (lastResult.success) {
1897
+ return lastResult;
1898
+ }
1899
+ } catch (error) {
1900
+ lastResult = this.createResult(
1901
+ selector,
1902
+ "select",
1903
+ startTime,
1904
+ false,
1905
+ String(error),
1906
+ "RUNTIME_ERROR"
1907
+ );
1908
+ }
1909
+ }
1910
+ return lastResult;
1911
+ };
1912
+ return executeWithRetries();
1913
+ }
1914
+ /**
1915
+ * Scroll to an element or position
1916
+ */
1917
+ async scroll(target) {
1918
+ const startTime = Date.now();
1919
+ const selectorStr = typeof target === "string" ? target : `${target.x},${target.y}`;
1920
+ try {
1921
+ if (typeof target === "string") {
1922
+ const element = await waitForElement(target, this.config.elementTimeout, false);
1923
+ if (!element) {
1924
+ return this.createResult(
1925
+ selectorStr,
1926
+ "scroll",
1927
+ startTime,
1928
+ false,
1929
+ "Element not found",
1930
+ "ELEMENT_NOT_FOUND"
1931
+ );
1932
+ }
1933
+ if (this.config.highlightOnInteract) {
1934
+ highlightElement(element);
1935
+ }
1936
+ scrollToElement(element);
1937
+ } else {
1938
+ window.scrollTo({
1939
+ left: target.x,
1940
+ top: target.y,
1941
+ behavior: "smooth"
1942
+ });
1943
+ }
1944
+ this.log("Scrolled to:", target);
1945
+ return this.createResult(
1946
+ selectorStr,
1947
+ "scroll",
1948
+ startTime,
1949
+ true,
1950
+ void 0,
1951
+ void 0,
1952
+ typeof target === "string" ? (() => {
1953
+ const element = querySelectorDeep(target);
1954
+ return element ? collectElementMetadata(element) : { selector: target };
1955
+ })() : { position: target }
1956
+ );
1957
+ } catch (error) {
1958
+ return this.createResult(
1959
+ selectorStr,
1960
+ "scroll",
1961
+ startTime,
1962
+ false,
1963
+ String(error),
1964
+ "RUNTIME_ERROR"
1965
+ );
1966
+ }
1967
+ }
1968
+ /**
1969
+ * Submit a form
1970
+ */
1971
+ async submit(selector) {
1972
+ const startTime = Date.now();
1973
+ try {
1974
+ const element = await waitForElement(selector, this.config.elementTimeout, true);
1975
+ if (!element) {
1976
+ return this.createResult(
1977
+ selector,
1978
+ "submit",
1979
+ startTime,
1980
+ false,
1981
+ "Element not found",
1982
+ "ELEMENT_NOT_FOUND"
1983
+ );
1984
+ }
1985
+ const form = element.tagName.toLowerCase() === "form" ? element : element.closest("form");
1986
+ if (!form) {
1987
+ const submitButton = element.querySelector('button[type="submit"], input[type="submit"]');
1988
+ if (submitButton) {
1989
+ return this.click(this.generateSelector(submitButton));
1990
+ }
1991
+ return this.createResult(
1992
+ selector,
1993
+ "submit",
1994
+ startTime,
1995
+ false,
1996
+ "No form found",
1997
+ "NO_FORM",
1998
+ collectElementMetadata(element)
1999
+ );
2000
+ }
2001
+ if (this.config.highlightOnInteract) {
2002
+ highlightElement(form);
2003
+ }
2004
+ const submitter = form.querySelector('button[type="submit"], input[type="submit"]');
2005
+ if (typeof form.requestSubmit === "function") {
2006
+ form.requestSubmit(submitter ?? void 0);
2007
+ } else {
2008
+ const submitEvent = new Event("submit", { bubbles: true, cancelable: true });
2009
+ const shouldSubmit = form.dispatchEvent(submitEvent);
2010
+ if (shouldSubmit) {
2011
+ form.submit();
2012
+ }
2013
+ }
2014
+ this.log("Submitted form:", selector);
2015
+ return this.createResult(
2016
+ selector,
2017
+ "submit",
2018
+ startTime,
2019
+ true,
2020
+ void 0,
2021
+ void 0,
2022
+ collectElementMetadata(form)
2023
+ );
2024
+ } catch (error) {
2025
+ return this.createResult(
2026
+ selector,
2027
+ "submit",
2028
+ startTime,
2029
+ false,
2030
+ String(error),
2031
+ "RUNTIME_ERROR"
2032
+ );
2033
+ }
2034
+ }
2035
+ /**
2036
+ * Check if element is typeable
2037
+ */
2038
+ isTypeable(element) {
2039
+ const tagName = element.tagName.toLowerCase();
2040
+ if (tagName === "input") {
2041
+ const type = element.type;
2042
+ return ["text", "email", "password", "search", "tel", "url", "number"].includes(type);
2043
+ }
2044
+ if (tagName === "textarea") return true;
2045
+ if (element.hasAttribute("contenteditable")) return true;
2046
+ return false;
2047
+ }
2048
+ /**
2049
+ * Generate a selector for an element
2050
+ */
2051
+ generateSelector(element) {
2052
+ if (element.id) return `#${element.id}`;
2053
+ const dataTestId = element.getAttribute("data-testid");
2054
+ if (dataTestId) return `[data-testid="${dataTestId}"]`;
2055
+ return element.tagName.toLowerCase();
2056
+ }
2057
+ /**
2058
+ * Create execution result
2059
+ */
2060
+ createResult(selector, action, startTime, success, error, errorCode, metadata) {
2061
+ if (!success) {
2062
+ this.logger.warn("Action failed", {
2063
+ action,
2064
+ selector,
2065
+ error,
2066
+ errorCode,
2067
+ metadata
2068
+ });
2069
+ }
2070
+ return {
2071
+ success,
2072
+ selector,
2073
+ action,
2074
+ duration: Date.now() - startTime,
2075
+ errorCode,
2076
+ error,
2077
+ metadata
2078
+ };
2079
+ }
2080
+ log(...args) {
2081
+ this.logger.debug(...args);
2082
+ }
2083
+ };
2084
+ var CommandProcessor = class {
2085
+ config;
2086
+ executor;
2087
+ navController;
2088
+ registry;
2089
+ logger = createLogger("SDK.Processor");
2090
+ isExecuting = false;
2091
+ shouldAbort = false;
2092
+ listeners = /* @__PURE__ */ new Map();
2093
+ // Server action handler
2094
+ serverActionHandler = null;
2095
+ constructor(executor, navController, registry, config = {}) {
2096
+ this.executor = executor;
2097
+ this.navController = navController;
2098
+ this.registry = registry;
2099
+ this.config = {
2100
+ commandDelay: config.commandDelay ?? 650,
2101
+ commandTimeout: config.commandTimeout ?? 3e4,
2102
+ debug: config.debug ?? false
2103
+ };
2104
+ this.logger = createLogger("SDK.Processor", { enabled: this.config.debug, level: "debug" });
2105
+ }
2106
+ /**
2107
+ * Set server action handler
2108
+ */
2109
+ setServerActionHandler(handler) {
2110
+ this.serverActionHandler = handler;
2111
+ }
2112
+ /**
2113
+ * Execute a list of commands
2114
+ */
2115
+ async execute(commands) {
2116
+ if (this.isExecuting) {
2117
+ throw new Error("Already executing commands");
2118
+ }
2119
+ this.isExecuting = true;
2120
+ this.shouldAbort = false;
2121
+ const results = [];
2122
+ let previousCommandType = null;
2123
+ try {
2124
+ for (let i = 0; i < commands.length; i++) {
2125
+ if (this.shouldAbort) {
2126
+ this.log("Execution aborted");
2127
+ break;
2128
+ }
2129
+ const command = commands[i];
2130
+ const needsStability = ["click", "type", "select", "scroll"].includes(command.type);
2131
+ const previousWasNavigation = previousCommandType && ["navigate", "wait_for_route"].includes(previousCommandType);
2132
+ if (needsStability && previousWasNavigation) {
2133
+ this.log("Auto-waiting for DOM stability before:", command.type);
2134
+ await this.navController.waitForDOMStable();
2135
+ await new Promise((resolve) => setTimeout(resolve, 200));
2136
+ }
2137
+ this.emit("stateChange", {
2138
+ isExecuting: true,
2139
+ currentCommand: command,
2140
+ currentIndex: i,
2141
+ totalCommands: commands.length,
2142
+ description: this.describeCommand(command)
2143
+ });
2144
+ this.emit("commandStart", command, i);
2145
+ const result = await this.executeCommand(command);
2146
+ results.push(result);
2147
+ this.emit("commandComplete", result);
2148
+ previousCommandType = command.type;
2149
+ if (!result.success) {
2150
+ this.logger.warn("Command failed, stopping execution", {
2151
+ type: command.type,
2152
+ error: result.error,
2153
+ result: result.result
2154
+ });
2155
+ break;
2156
+ }
2157
+ if (i < commands.length - 1) {
2158
+ await new Promise((resolve) => setTimeout(resolve, this.config.commandDelay));
2159
+ }
2160
+ }
2161
+ this.emit("complete", results);
2162
+ return results;
2163
+ } catch (error) {
2164
+ const err = error instanceof Error ? error : new Error(String(error));
2165
+ this.logger.error("Execution error", err);
2166
+ this.emit("error", err);
2167
+ throw err;
2168
+ } finally {
2169
+ this.isExecuting = false;
2170
+ this.emit("stateChange", {
2171
+ isExecuting: false,
2172
+ currentCommand: void 0,
2173
+ currentIndex: 0,
2174
+ totalCommands: commands.length,
2175
+ description: void 0
2176
+ });
2177
+ }
2178
+ }
2179
+ /**
2180
+ * Abort current execution
2181
+ */
2182
+ abort() {
2183
+ this.shouldAbort = true;
2184
+ }
2185
+ /**
2186
+ * Get execution status
2187
+ */
2188
+ getIsExecuting() {
2189
+ return this.isExecuting;
2190
+ }
2191
+ /**
2192
+ * Execute a single command
2193
+ */
2194
+ async executeCommand(command) {
2195
+ const startTime = Date.now();
2196
+ try {
2197
+ switch (command.type) {
2198
+ case "navigate":
2199
+ return await this.executeNavigate(command, startTime);
2200
+ case "wait_for_route":
2201
+ return await this.executeWaitForRoute(command, startTime);
2202
+ case "wait_for_dom_stable":
2203
+ return await this.executeWaitForDomStable(command, startTime);
2204
+ case "scan_context":
2205
+ return await this.executeScanContext(command, startTime);
2206
+ case "click":
2207
+ return await this.executeClick(command, startTime);
2208
+ case "type":
2209
+ return await this.executeType(command, startTime);
2210
+ case "select":
2211
+ return await this.executeSelect(command, startTime);
2212
+ case "scroll":
2213
+ return await this.executeScroll(command, startTime);
2214
+ case "server_action":
2215
+ return await this.executeServerAction(command, startTime);
2216
+ case "report_result":
2217
+ return await this.executeReportResult(command, startTime);
2218
+ default:
2219
+ return this.createResult(command, startTime, false, `Unknown command type`);
2220
+ }
2221
+ } catch (error) {
2222
+ this.logger.error("Command execution error", {
2223
+ type: command.type,
2224
+ error: String(error)
2225
+ });
2226
+ return this.createResult(command, startTime, false, String(error));
2227
+ }
2228
+ }
2229
+ // ============================================================================
2230
+ // Command Executors
2231
+ // ============================================================================
2232
+ async executeNavigate(command, startTime) {
2233
+ this.log("Navigating to:", command.target);
2234
+ const context = await this.navController.executeNavigation(command.target);
2235
+ if (!context) {
2236
+ return this.createResult(command, startTime, false, "Navigation failed");
2237
+ }
2238
+ return this.createResult(command, startTime, true);
2239
+ }
2240
+ async executeWaitForRoute(command, startTime) {
2241
+ this.log("Waiting for route:", command.target);
2242
+ const success = await this.navController.navigateTo(command.target, command.timeout);
2243
+ return this.createResult(command, startTime, success, success ? void 0 : "Route timeout");
2244
+ }
2245
+ async executeWaitForDomStable(command, startTime) {
2246
+ this.log("Waiting for DOM to stabilize");
2247
+ await this.navController.waitForDOMStable();
2248
+ return this.createResult(command, startTime, true);
2249
+ }
2250
+ async executeScanContext(command, startTime) {
2251
+ this.log("Scanning page context");
2252
+ return this.createResult(command, startTime, true);
2253
+ }
2254
+ async executeClick(command, startTime) {
2255
+ let selector = command.selector;
2256
+ const route = this.navController.getCurrentRoute();
2257
+ if (selector) {
2258
+ this.log("Clicking with selector:", selector);
2259
+ const result = await this.executor.click(selector);
2260
+ if (result.success) {
2261
+ return this.createResult(command, startTime, true);
2262
+ }
2263
+ this.log("Selector failed, trying text fallback:", command.text || selector);
2264
+ }
2265
+ if (command.text) {
2266
+ const action = this.registry.findActionByLabel(route, command.text, command.context);
2267
+ if (action && action.selector !== selector) {
2268
+ this.log("Found action by label, clicking:", action.selector);
2269
+ const result = await this.executor.click(action.selector);
2270
+ if (result.success) {
2271
+ return this.createResult(command, startTime, true);
2272
+ }
2273
+ }
2274
+ }
2275
+ if (command.text) {
2276
+ const actionAttr = command.text.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2277
+ const chatbotSelector = `[data-chatbot-action*="${actionAttr}"]`;
2278
+ this.log("Trying data-chatbot-action selector:", chatbotSelector);
2279
+ const result = await this.executor.click(chatbotSelector);
2280
+ if (result.success) {
2281
+ return this.createResult(command, startTime, true);
2282
+ }
2283
+ }
2284
+ if (command.text) {
2285
+ const ariaSelector = `[aria-label="${command.text}"]`;
2286
+ this.log("Trying aria-label selector:", ariaSelector);
2287
+ const result = await this.executor.click(ariaSelector);
2288
+ if (result.success) {
2289
+ return this.createResult(command, startTime, true);
2290
+ }
2291
+ }
2292
+ if (command.text) {
2293
+ const textToFind = command.text.toLowerCase().trim();
2294
+ const clickables = document.querySelectorAll('button, a, [role="button"], [data-chatbot-action]');
2295
+ for (const el of clickables) {
2296
+ const elText = el.textContent?.toLowerCase().trim() || "";
2297
+ const ariaLabel = el.getAttribute("aria-label")?.toLowerCase().trim() || "";
2298
+ const chatbotAction = el.getAttribute("data-chatbot-action")?.toLowerCase().replace(/-/g, " ") || "";
2299
+ if (elText === textToFind || ariaLabel === textToFind || elText.includes(textToFind) || chatbotAction === textToFind || chatbotAction.includes(textToFind.replace(/\s+/g, "-"))) {
2300
+ let foundSelector = "";
2301
+ if (el.id) {
2302
+ foundSelector = `#${el.id}`;
2303
+ } else if (el.getAttribute("data-chatbot-action")) {
2304
+ foundSelector = `[data-chatbot-action="${el.getAttribute("data-chatbot-action")}"]`;
2305
+ } else if (el.getAttribute("aria-label")) {
2306
+ foundSelector = `[aria-label="${el.getAttribute("aria-label")}"]`;
2307
+ }
2308
+ if (foundSelector) {
2309
+ this.log("Found element by text content, clicking:", foundSelector);
2310
+ const result = await this.executor.click(foundSelector);
2311
+ if (result.success) {
2312
+ return this.createResult(command, startTime, true);
2313
+ }
2314
+ }
2315
+ }
2316
+ }
2317
+ }
2318
+ const errorMsg = selector ? `Element not found or click failed: ${selector}` : `No selector found for: ${command.text || "unknown"}`;
2319
+ return this.createResult(command, startTime, false, errorMsg);
2320
+ }
2321
+ async executeType(command, startTime) {
2322
+ let selector = command.selector;
2323
+ if (!selector && command.text) {
2324
+ const action = this.registry.findActionByLabel(
2325
+ this.navController.getCurrentRoute(),
2326
+ command.text
2327
+ );
2328
+ if (action) selector = action.selector;
2329
+ }
2330
+ if (!selector) {
2331
+ return this.createResult(command, startTime, false, "No selector provided");
2332
+ }
2333
+ this.log("Typing into:", selector, "value:", command.value);
2334
+ let result = await this.executor.type(selector, command.value, command.clear ?? true);
2335
+ if (!result.success && command.text && result.error?.includes("not found")) {
2336
+ const action = this.registry.findActionByLabel(
2337
+ this.navController.getCurrentRoute(),
2338
+ command.text
2339
+ );
2340
+ if (action) {
2341
+ result = await this.executor.type(action.selector, command.value, command.clear ?? true);
2342
+ }
2343
+ }
2344
+ return this.createResult(command, startTime, result.success, result.error);
2345
+ }
2346
+ async executeSelect(command, startTime) {
2347
+ let selector = command.selector;
2348
+ if (!selector && command.text) {
2349
+ const action = this.registry.findActionByLabel(
2350
+ this.navController.getCurrentRoute(),
2351
+ command.text
2352
+ );
2353
+ if (action) selector = action.selector;
2354
+ }
2355
+ if (!selector) {
2356
+ return this.createResult(command, startTime, false, "No selector provided");
2357
+ }
2358
+ this.log("Selecting:", selector, "value:", command.value);
2359
+ let result = await this.executor.select(selector, command.value);
2360
+ if (!result.success && command.text && result.error?.includes("not found")) {
2361
+ const action = this.registry.findActionByLabel(
2362
+ this.navController.getCurrentRoute(),
2363
+ command.text
2364
+ );
2365
+ if (action) {
2366
+ result = await this.executor.select(action.selector, command.value);
2367
+ }
2368
+ }
2369
+ return this.createResult(command, startTime, result.success, result.error);
2370
+ }
2371
+ async executeScroll(command, startTime) {
2372
+ const target = command.selector ?? command.position ?? { x: 0, y: 0 };
2373
+ this.log("Scrolling to:", target);
2374
+ const result = await this.executor.scroll(target);
2375
+ return this.createResult(command, startTime, result.success, result.error);
2376
+ }
2377
+ async executeServerAction(command, startTime) {
2378
+ if (!this.serverActionHandler) {
2379
+ return this.createResult(command, startTime, false, "No server action handler set");
2380
+ }
2381
+ const action = this.registry.findServerActionById(command.actionId);
2382
+ if (!action) {
2383
+ return this.createResult(command, startTime, false, `Server action not found: ${command.actionId}`);
2384
+ }
2385
+ this.log("Executing server action:", command.actionId);
2386
+ try {
2387
+ const result = await this.serverActionHandler(command.actionId, command.params);
2388
+ return this.createResult(command, startTime, true, void 0, result);
2389
+ } catch (error) {
2390
+ return this.createResult(command, startTime, false, String(error));
2391
+ }
2392
+ }
2393
+ async executeReportResult(command, startTime) {
2394
+ this.log("Report result command executed");
2395
+ return this.createResult(command, startTime, true);
2396
+ }
2397
+ // ============================================================================
2398
+ // Utilities
2399
+ // ============================================================================
2400
+ createResult(command, startTime, success, error, result) {
2401
+ return {
2402
+ command,
2403
+ success,
2404
+ result,
2405
+ error,
2406
+ duration: Date.now() - startTime
2407
+ };
2408
+ }
2409
+ /**
2410
+ * Build a simple, human-readable description for the current command.
2411
+ * This is used by the widget to surface clear progress messages.
2412
+ */
2413
+ describeCommand(command) {
2414
+ switch (command.type) {
2415
+ case "click":
2416
+ return command.text ? `Click "${command.text}"` : `Click element${command.selector ? ` (${command.selector})` : ""}`;
2417
+ case "type":
2418
+ return command.text ? `Type into "${command.text}"` : `Type into element${command.selector ? ` (${command.selector})` : ""}`;
2419
+ case "select":
2420
+ return command.text ? `Select "${command.value}" in "${command.text}"` : `Select "${command.value}"${command.selector ? ` (${command.selector})` : ""}`;
2421
+ case "scroll":
2422
+ if (command.selector) return `Scroll to element (${command.selector})`;
2423
+ if (command.position) return `Scroll to (${command.position.x}, ${command.position.y})`;
2424
+ return "Scroll";
2425
+ case "navigate":
2426
+ return `Navigate to "${command.target}"`;
2427
+ case "wait_for_route":
2428
+ return `Wait for route "${command.target}"`;
2429
+ case "wait_for_dom_stable":
2430
+ return "Wait for page to finish loading";
2431
+ case "scan_context":
2432
+ return "Scan page for available actions";
2433
+ case "server_action":
2434
+ return `Execute server action "${command.actionId}"`;
2435
+ case "report_result":
2436
+ return "Report result to server";
2437
+ default:
2438
+ return "Execute action";
2439
+ }
2440
+ }
2441
+ /**
2442
+ * Subscribe to events
2443
+ */
2444
+ on(event, callback) {
2445
+ if (!this.listeners.has(event)) {
2446
+ this.listeners.set(event, /* @__PURE__ */ new Set());
2447
+ }
2448
+ this.listeners.get(event).add(callback);
2449
+ return () => {
2450
+ this.listeners.get(event)?.delete(callback);
2451
+ };
2452
+ }
2453
+ emit(event, ...args) {
2454
+ this.listeners.get(event)?.forEach((callback) => {
2455
+ try {
2456
+ callback(...args);
2457
+ } catch (error) {
2458
+ this.logger.error(`Error in ${event} listener`, error);
2459
+ }
2460
+ });
2461
+ }
2462
+ log(...args) {
2463
+ this.logger.debug(...args);
2464
+ }
2465
+ };
2466
+ var ServerActionBridge = class {
2467
+ config;
2468
+ actions = /* @__PURE__ */ new Map();
2469
+ context = null;
2470
+ logger = createLogger("SDK.ServerAction");
2471
+ constructor(config = {}) {
2472
+ this.config = {
2473
+ defaultTimeout: config.defaultTimeout ?? 3e4,
2474
+ debug: config.debug ?? false,
2475
+ webhookHeaders: config.webhookHeaders ?? {},
2476
+ baseUrl: config.baseUrl ?? ""
2477
+ };
2478
+ this.logger = createLogger("SDK.ServerAction", { enabled: this.config.debug, level: "debug" });
2479
+ }
2480
+ /**
2481
+ * Set the execution context
2482
+ */
2483
+ setContext(context) {
2484
+ this.context = context;
2485
+ }
2486
+ /**
2487
+ * Register a server action
2488
+ */
2489
+ register(action) {
2490
+ this.actions.set(action.id, action);
2491
+ this.log(`Registered server action: ${action.id}`);
2492
+ }
2493
+ /**
2494
+ * Register multiple server actions
2495
+ */
2496
+ registerMany(actions) {
2497
+ for (const action of actions) {
2498
+ this.register(action);
2499
+ }
2500
+ }
2501
+ /**
2502
+ * Unregister a server action
2503
+ */
2504
+ unregister(actionId) {
2505
+ this.actions.delete(actionId);
2506
+ }
2507
+ /**
2508
+ * Get all registered actions
2509
+ */
2510
+ getActions() {
2511
+ return Array.from(this.actions.values());
2512
+ }
2513
+ /**
2514
+ * Get action by ID
2515
+ */
2516
+ getAction(actionId) {
2517
+ return this.actions.get(actionId);
2518
+ }
2519
+ /**
2520
+ * Execute a server action
2521
+ */
2522
+ async execute(actionId, params) {
2523
+ const action = this.actions.get(actionId);
2524
+ if (!action) {
2525
+ return {
2526
+ success: false,
2527
+ error: `Server action not found: ${actionId}`
2528
+ };
2529
+ }
2530
+ if (!this.context) {
2531
+ return {
2532
+ success: false,
2533
+ error: "No execution context set"
2534
+ };
2535
+ }
2536
+ const missingParams = action.parameters.filter((p) => p.required && !(p.name in params)).map((p) => p.name);
2537
+ if (missingParams.length > 0) {
2538
+ return {
2539
+ success: false,
2540
+ error: `Missing required parameters: ${missingParams.join(", ")}`
2541
+ };
2542
+ }
2543
+ const finalParams = { ...params };
2544
+ for (const param of action.parameters) {
2545
+ if (!(param.name in finalParams) && param.defaultValue !== void 0) {
2546
+ finalParams[param.name] = param.defaultValue;
2547
+ }
2548
+ }
2549
+ this.log(`Executing server action: ${actionId}`, finalParams);
2550
+ try {
2551
+ if (action.handler) {
2552
+ return await this.executeHandler(action, finalParams);
2553
+ } else if (action.webhookUrl) {
2554
+ return await this.executeWebhook(action, finalParams);
2555
+ } else {
2556
+ return {
2557
+ success: false,
2558
+ error: "Server action has no handler or webhookUrl"
2559
+ };
2560
+ }
2561
+ } catch (error) {
2562
+ const errorMessage = error instanceof Error ? error.message : String(error);
2563
+ this.log(`Server action error: ${errorMessage}`);
2564
+ return {
2565
+ success: false,
2566
+ error: errorMessage
2567
+ };
2568
+ }
2569
+ }
2570
+ /**
2571
+ * Execute handler mode action
2572
+ */
2573
+ async executeHandler(action, params) {
2574
+ if (!action.handler) {
2575
+ return { success: false, error: "No handler defined" };
2576
+ }
2577
+ const timeout = action.timeout ?? this.config.defaultTimeout;
2578
+ const result = await Promise.race([
2579
+ action.handler(params, this.context),
2580
+ new Promise(
2581
+ (_, reject) => setTimeout(() => reject(new Error("Timeout")), timeout)
2582
+ )
2583
+ ]);
2584
+ return result;
2585
+ }
2586
+ /**
2587
+ * Execute webhook mode action
2588
+ */
2589
+ async executeWebhook(action, params) {
2590
+ if (!action.webhookUrl) {
2591
+ return { success: false, error: "No webhookUrl defined" };
2592
+ }
2593
+ const timeout = action.timeout ?? this.config.defaultTimeout;
2594
+ const url = action.webhookUrl.startsWith("http") ? action.webhookUrl : `${this.config.baseUrl}${action.webhookUrl}`;
2595
+ const headers = {
2596
+ "Content-Type": "application/json",
2597
+ ...this.config.webhookHeaders
2598
+ };
2599
+ if (action.webhookSecret) {
2600
+ const signature = await this.generateSignature(
2601
+ JSON.stringify(params),
2602
+ action.webhookSecret
2603
+ );
2604
+ headers["X-Signature"] = signature;
2605
+ }
2606
+ if (this.context) {
2607
+ headers["X-Session-Id"] = this.context.sessionId;
2608
+ if (this.context.authToken) {
2609
+ headers["Authorization"] = `Bearer ${this.context.authToken}`;
2610
+ }
2611
+ }
2612
+ const controller = new AbortController();
2613
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
2614
+ try {
2615
+ const response = await fetch(url, {
2616
+ method: "POST",
2617
+ headers,
2618
+ body: JSON.stringify({
2619
+ actionId: action.id,
2620
+ params,
2621
+ context: {
2622
+ sessionId: this.context?.sessionId,
2623
+ userId: this.context?.userId,
2624
+ currentRoute: this.context?.currentRoute
2625
+ }
2626
+ }),
2627
+ signal: controller.signal
2628
+ });
2629
+ clearTimeout(timeoutId);
2630
+ if (!response.ok) {
2631
+ this.logger.warn("Webhook failed", { actionId: action.id, status: response.status });
2632
+ return {
2633
+ success: false,
2634
+ error: `Webhook failed with status ${response.status}`
2635
+ };
2636
+ }
2637
+ const data = await response.json();
2638
+ this.logger.debug("Webhook success", { actionId: action.id });
2639
+ return {
2640
+ success: true,
2641
+ data
2642
+ };
2643
+ } catch (error) {
2644
+ clearTimeout(timeoutId);
2645
+ if (error instanceof Error && error.name === "AbortError") {
2646
+ return { success: false, error: "Request timeout" };
2647
+ }
2648
+ throw error;
2649
+ }
2650
+ }
2651
+ /**
2652
+ * Generate HMAC signature for webhook
2653
+ */
2654
+ async generateSignature(payload, secret) {
2655
+ if (typeof crypto !== "undefined" && crypto.subtle) {
2656
+ const encoder = new TextEncoder();
2657
+ const key = await crypto.subtle.importKey(
2658
+ "raw",
2659
+ encoder.encode(secret),
2660
+ { name: "HMAC", hash: "SHA-256" },
2661
+ false,
2662
+ ["sign"]
2663
+ );
2664
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
2665
+ return btoa(String.fromCharCode(...new Uint8Array(signature)));
2666
+ }
2667
+ return "";
2668
+ }
2669
+ log(...args) {
2670
+ this.logger.debug(...args);
2671
+ }
2672
+ };
2673
+ var storageLogger = createLogger("SDK.Storage", { enabled: true, level: "warn" });
2674
+ var persistDebounceTimer = null;
2675
+ function persistMessages(messages, storagePrefix = "chatbot") {
2676
+ if (typeof window !== "undefined") {
2677
+ if (persistDebounceTimer) clearTimeout(persistDebounceTimer);
2678
+ persistDebounceTimer = setTimeout(() => {
2679
+ try {
2680
+ const messagesToSave = messages.slice(-100);
2681
+ localStorage.setItem(`${storagePrefix}-messages`, JSON.stringify(messagesToSave));
2682
+ } catch (e) {
2683
+ storageLogger.warn("Failed to save messages to localStorage", e);
2684
+ try {
2685
+ const messagesToSave = messages.slice(-50);
2686
+ localStorage.setItem(`${storagePrefix}-messages`, JSON.stringify(messagesToSave));
2687
+ } catch (e2) {
2688
+ storageLogger.warn("Failed to save even reduced messages", e2);
2689
+ }
2690
+ }
2691
+ }, 500);
2692
+ }
2693
+ }
2694
+ function chatbotReducer(state, action) {
2695
+ let newState;
2696
+ switch (action.type) {
2697
+ case "ADD_MESSAGE": {
2698
+ newState = { ...state, messages: [...state.messages, action.message] };
2699
+ persistMessages(newState.messages);
2700
+ return newState;
2701
+ }
2702
+ case "UPDATE_MESSAGE": {
2703
+ newState = {
2704
+ ...state,
2705
+ messages: state.messages.map(
2706
+ (m) => m.id === action.id ? { ...m, ...action.updates } : m
2707
+ )
2708
+ };
2709
+ persistMessages(newState.messages);
2710
+ return newState;
2711
+ }
2712
+ case "SET_MESSAGES": {
2713
+ newState = { ...state, messages: action.messages };
2714
+ persistMessages(newState.messages);
2715
+ return newState;
2716
+ }
2717
+ case "CLEAR_MESSAGES": {
2718
+ newState = { ...state, messages: [] };
2719
+ persistMessages([]);
2720
+ return newState;
2721
+ }
2722
+ case "SET_MODE":
2723
+ return { ...state, mode: action.mode };
2724
+ case "SET_CONNECTION_STATE":
2725
+ return { ...state, connectionState: action.state };
2726
+ case "SET_EXECUTING":
2727
+ return { ...state, isExecuting: action.isExecuting };
2728
+ case "SET_EXECUTION_STATE":
2729
+ return { ...state, executionState: action.state };
2730
+ case "SET_ERROR":
2731
+ return { ...state, error: action.error };
2732
+ case "CLEAR_ERROR":
2733
+ return { ...state, error: null };
2734
+ default:
2735
+ return state;
2736
+ }
2737
+ }
2738
+ function getInitialState(storagePrefix = "chatbot") {
2739
+ let messages = [];
2740
+ let mode = "ask";
2741
+ if (typeof window !== "undefined") {
2742
+ try {
2743
+ const saved = localStorage.getItem(`${storagePrefix}-messages`);
2744
+ if (saved) {
2745
+ messages = JSON.parse(saved);
2746
+ }
2747
+ } catch (e) {
2748
+ storageLogger.warn("Failed to load messages from localStorage", e);
2749
+ }
2750
+ const savedMode = localStorage.getItem(`${storagePrefix}-mode`);
2751
+ if (savedMode === "ask" || savedMode === "navigate") {
2752
+ mode = savedMode;
2753
+ }
2754
+ }
2755
+ return {
2756
+ messages,
2757
+ mode,
2758
+ connectionState: "disconnected",
2759
+ isExecuting: false,
2760
+ executionState: null,
2761
+ error: null
2762
+ };
2763
+ }
2764
+ function ChatbotProvider({
2765
+ apiKey,
2766
+ serverUrl,
2767
+ navigationAdapter,
2768
+ serverActions = [],
2769
+ config: configFromApi,
2770
+ options,
2771
+ routes,
2772
+ debug = false,
2773
+ autoConnect = true,
2774
+ children
2775
+ }) {
2776
+ const storagePrefix = useMemo(() => `chatbot-${apiKey.slice(0, 8)}`, [apiKey]);
2777
+ const logger = useMemo(() => createLogger("SDK", { enabled: debug, level: "debug" }), [debug]);
2778
+ const normalizedFromApi = useMemo(() => normalizeWidgetConfig(configFromApi ?? void 0), [configFromApi]);
2779
+ const widgetConfig = useMemo(() => {
2780
+ const base = { ...normalizedFromApi };
2781
+ if (options?.theme) {
2782
+ base.theme = { ...base.theme, ...options.theme };
2783
+ }
2784
+ return {
2785
+ ...base,
2786
+ ...options,
2787
+ theme: base.theme
2788
+ };
2789
+ }, [normalizedFromApi, options]);
2790
+ const [state, dispatch] = useReducer(chatbotReducer, storagePrefix, getInitialState);
2791
+ const messagesRef = useRef(state.messages);
2792
+ const [isWidgetOpen, setWidgetOpen] = useState(() => {
2793
+ if (typeof window !== "undefined") {
2794
+ const saved = localStorage.getItem(`${storagePrefix}-widget-open`);
2795
+ if (saved !== null) {
2796
+ return saved === "true";
2797
+ }
2798
+ }
2799
+ return options?.defaultOpen ?? false;
2800
+ });
2801
+ const [selectedVoiceLanguage, setSelectedVoiceLanguage] = useState(null);
2802
+ const effectiveVoiceLanguage = selectedVoiceLanguage ?? widgetConfig?.voiceLanguage ?? void 0;
2803
+ const defaultModeApplied = useRef(false);
2804
+ useEffect(() => {
2805
+ if (defaultModeApplied.current) return;
2806
+ const mode = widgetConfig.defaultMode;
2807
+ if (mode === "ask" || mode === "navigate") {
2808
+ dispatch({ type: "SET_MODE", mode });
2809
+ defaultModeApplied.current = true;
2810
+ }
2811
+ }, [widgetConfig.defaultMode]);
2812
+ useEffect(() => {
2813
+ if (typeof window !== "undefined") {
2814
+ localStorage.setItem(`${storagePrefix}-widget-open`, String(isWidgetOpen));
2815
+ }
2816
+ }, [isWidgetOpen, storagePrefix]);
2817
+ useEffect(() => {
2818
+ messagesRef.current = state.messages;
2819
+ }, [state.messages]);
2820
+ useEffect(() => {
2821
+ if (typeof window !== "undefined") {
2822
+ localStorage.setItem(`${storagePrefix}-mode`, state.mode);
2823
+ }
2824
+ }, [state.mode, storagePrefix]);
2825
+ const wsClientRef = useRef(null);
2826
+ const navControllerRef = useRef(null);
2827
+ const serverActionsRef = useRef(/* @__PURE__ */ new Map());
2828
+ const pageContextRef = useRef(null);
2829
+ const scannerRef = useRef(null);
2830
+ const registryRef = useRef(null);
2831
+ const contextBuilderRef = useRef(null);
2832
+ const executorRef = useRef(null);
2833
+ const commandProcessorRef = useRef(null);
2834
+ const serverActionBridgeRef = useRef(null);
2835
+ const routesRef = useRef(void 0);
2836
+ useEffect(() => {
2837
+ routesRef.current = routes;
2838
+ }, [routes]);
2839
+ const adapter = useMemo(() => {
2840
+ return navigationAdapter ?? createMemoryAdapter();
2841
+ }, [navigationAdapter]);
2842
+ useEffect(() => {
2843
+ const client = new WebSocketClient({
2844
+ serverUrl,
2845
+ apiKey,
2846
+ debug
2847
+ });
2848
+ wsClientRef.current = client;
2849
+ client.on("connected", ({ sessionId }) => {
2850
+ log("Connected, session:", sessionId);
2851
+ dispatch({ type: "SET_CONNECTION_STATE", state: "connected" });
2852
+ });
2853
+ client.on("authenticated", ({ sessionId }) => {
2854
+ const bridge = serverActionBridgeRef.current;
2855
+ const currentRoute = adapter.getCurrentPath();
2856
+ if (bridge) {
2857
+ bridge.setContext({
2858
+ sessionId,
2859
+ currentRoute
2860
+ });
2861
+ }
2862
+ });
2863
+ client.on("disconnected", () => {
2864
+ dispatch({ type: "SET_CONNECTION_STATE", state: "disconnected" });
2865
+ });
2866
+ client.on("reconnecting", ({ attempt, maxAttempts }) => {
2867
+ log(`Reconnecting ${attempt}/${maxAttempts}`);
2868
+ dispatch({ type: "SET_CONNECTION_STATE", state: "reconnecting" });
2869
+ });
2870
+ client.on("reconnect_failed", () => {
2871
+ dispatch({ type: "SET_CONNECTION_STATE", state: "failed" });
2872
+ dispatch({ type: "SET_ERROR", error: new Error("Connection failed") });
2873
+ });
2874
+ client.on("error", ({ error }) => {
2875
+ dispatch({ type: "SET_ERROR", error });
2876
+ });
2877
+ client.on("message", (message) => {
2878
+ handleServerMessage(message);
2879
+ });
2880
+ if (autoConnect) {
2881
+ client.connect();
2882
+ dispatch({ type: "SET_CONNECTION_STATE", state: "connecting" });
2883
+ }
2884
+ return () => {
2885
+ client.disconnect();
2886
+ wsClientRef.current = null;
2887
+ };
2888
+ }, [serverUrl, apiKey, debug, autoConnect]);
2889
+ useEffect(() => {
2890
+ const registry = new ActionRegistry({ debug });
2891
+ const scanner = new DOMScanner({ debug });
2892
+ const builder = new PageContextBuilder({ debug });
2893
+ const executor = new DOMExecutor({ debug });
2894
+ const serverBridge = new ServerActionBridge({ debug });
2895
+ registryRef.current = registry;
2896
+ scannerRef.current = scanner;
2897
+ contextBuilderRef.current = builder;
2898
+ executorRef.current = executor;
2899
+ serverActionBridgeRef.current = serverBridge;
2900
+ const sendContext = (context) => {
2901
+ pageContextRef.current = context;
2902
+ wsClientRef.current?.send({ type: "context", context });
2903
+ };
2904
+ const updateBridgeContext = (route) => {
2905
+ serverBridge.setContext({
2906
+ sessionId: wsClientRef.current?.getSessionId() ?? "",
2907
+ currentRoute: route
2908
+ });
2909
+ };
2910
+ const runFullScan = () => {
2911
+ const nav2 = navControllerRef.current;
2912
+ const route = nav2 ? nav2.getCurrentRoute() : typeof window !== "undefined" ? window.location.pathname : "/";
2913
+ const actions = scanner.scan();
2914
+ registry.registerDiscoveredActions(route, actions);
2915
+ const context = {
2916
+ ...builder.build(actions, registry.getServerActions(), route),
2917
+ ...routes?.length ? { routes } : {}
2918
+ };
2919
+ updateBridgeContext(context.route);
2920
+ sendContext(context);
2921
+ return context;
2922
+ };
2923
+ const nav = new NavigationController(adapter, {
2924
+ debug,
2925
+ onDOMStable: () => runFullScan()
2926
+ });
2927
+ navControllerRef.current = nav;
2928
+ nav.setScanner(async () => runFullScan());
2929
+ const processor = new CommandProcessor(executor, nav, registry, { debug });
2930
+ commandProcessorRef.current = processor;
2931
+ processor.setServerActionHandler(async (actionId, params) => {
2932
+ return serverBridge.execute(actionId, params);
2933
+ });
2934
+ const processorUnsubscribes = [];
2935
+ if (debug) {
2936
+ processorUnsubscribes.push(
2937
+ processor.on("commandStart", (command, index) => {
2938
+ log("Command start:", { index, type: command.type, command });
2939
+ })
2940
+ );
2941
+ processorUnsubscribes.push(
2942
+ processor.on("commandComplete", (result) => {
2943
+ log("Command complete:", {
2944
+ type: result.command.type,
2945
+ success: result.success,
2946
+ duration: result.duration,
2947
+ error: result.error
2948
+ });
2949
+ })
2950
+ );
2951
+ processorUnsubscribes.push(
2952
+ processor.on("complete", (results) => {
2953
+ log("Command batch complete:", {
2954
+ total: results.length,
2955
+ failed: results.filter((r) => !r.success).length
2956
+ });
2957
+ })
2958
+ );
2959
+ processorUnsubscribes.push(
2960
+ processor.on("error", (err) => {
2961
+ log("Command processor error:", err);
2962
+ })
2963
+ );
2964
+ }
2965
+ serverActions.forEach((action) => {
2966
+ serverActionsRef.current.set(action.id, action);
2967
+ registry.registerServerAction(action);
2968
+ serverBridge.register(action);
2969
+ });
2970
+ scanner.observe((actions) => {
2971
+ registry.registerDiscoveredActions(nav.getCurrentRoute(), actions);
2972
+ const context = {
2973
+ ...builder.build(actions, registry.getServerActions(), nav.getCurrentRoute()),
2974
+ ...routes?.length ? { routes } : {}
2975
+ };
2976
+ updateBridgeContext(context.route);
2977
+ sendContext(context);
2978
+ });
2979
+ const unsubscribeRoute = nav.onRouteChange(() => {
2980
+ updateBridgeContext(nav.getCurrentRoute());
2981
+ runFullScan();
2982
+ });
2983
+ if (typeof window !== "undefined") {
2984
+ if (document.readyState === "complete") {
2985
+ updateBridgeContext(nav.getCurrentRoute());
2986
+ runFullScan();
2987
+ } else {
2988
+ window.addEventListener("load", () => {
2989
+ updateBridgeContext(nav.getCurrentRoute());
2990
+ runFullScan();
2991
+ }, { once: true });
2992
+ }
2993
+ }
2994
+ return () => {
2995
+ scanner.disconnect();
2996
+ processor.abort();
2997
+ processorUnsubscribes.forEach((fn) => fn());
2998
+ unsubscribeRoute();
2999
+ };
3000
+ }, [adapter, debug, routes]);
3001
+ useEffect(() => {
3002
+ const registry = registryRef.current;
3003
+ const bridge = serverActionBridgeRef.current;
3004
+ if (!registry || !bridge) return;
3005
+ serverActions.forEach((action) => {
3006
+ serverActionsRef.current.set(action.id, action);
3007
+ registry.registerServerAction(action);
3008
+ bridge.register(action);
3009
+ });
3010
+ }, [serverActions]);
3011
+ const executeCommandBatch = useCallback(async (commands, batchId) => {
3012
+ const processor = commandProcessorRef.current;
3013
+ if (!processor) {
3014
+ return;
3015
+ }
3016
+ dispatch({ type: "SET_EXECUTING", isExecuting: true });
3017
+ const unsubscribes = [];
3018
+ unsubscribes.push(
3019
+ processor.on("stateChange", (execState) => {
3020
+ dispatch({ type: "SET_EXECUTION_STATE", state: execState });
3021
+ dispatch({ type: "SET_EXECUTING", isExecuting: execState.isExecuting });
3022
+ })
3023
+ );
3024
+ unsubscribes.push(
3025
+ processor.on("error", (err) => {
3026
+ dispatch({ type: "SET_ERROR", error: err });
3027
+ })
3028
+ );
3029
+ try {
3030
+ const results = await processor.execute(commands);
3031
+ const ws = wsClientRef.current;
3032
+ results.forEach((result) => {
3033
+ if (!ws) return;
3034
+ if (result.command.type === "server_action") {
3035
+ ws.send({
3036
+ type: "action_result",
3037
+ actionId: result.command.actionId,
3038
+ success: result.success,
3039
+ result: result.result,
3040
+ error: result.error,
3041
+ commandId: batchId
3042
+ });
3043
+ } else {
3044
+ ws.send({
3045
+ type: "action_result",
3046
+ actionId: result.command.type,
3047
+ success: result.success,
3048
+ error: result.error,
3049
+ commandId: batchId
3050
+ });
3051
+ }
3052
+ });
3053
+ if (scannerRef.current && registryRef.current && contextBuilderRef.current && navControllerRef.current) {
3054
+ const actions = scannerRef.current.scan();
3055
+ const route = navControllerRef.current.getCurrentRoute();
3056
+ registryRef.current.registerDiscoveredActions(route, actions);
3057
+ const built = contextBuilderRef.current.build(actions, registryRef.current.getServerActions(), route);
3058
+ const context = {
3059
+ ...built,
3060
+ ...routesRef.current?.length ? { routes: routesRef.current } : {}
3061
+ };
3062
+ pageContextRef.current = context;
3063
+ if (wsClientRef.current) {
3064
+ wsClientRef.current.send({ type: "context", context });
3065
+ }
3066
+ }
3067
+ } catch (error) {
3068
+ const err = error instanceof Error ? error : new Error(String(error));
3069
+ dispatch({ type: "SET_ERROR", error: err });
3070
+ const ws = wsClientRef.current;
3071
+ const errorMessage = err.message || "Execution failed";
3072
+ commands.forEach((cmd) => {
3073
+ if (!ws) return;
3074
+ const actionId = cmd.type === "server_action" ? cmd.actionId : cmd.type;
3075
+ ws.send({
3076
+ type: "action_result",
3077
+ actionId,
3078
+ success: false,
3079
+ error: errorMessage,
3080
+ commandId: batchId
3081
+ });
3082
+ });
3083
+ } finally {
3084
+ unsubscribes.forEach((fn) => fn());
3085
+ dispatch({ type: "SET_EXECUTING", isExecuting: false });
3086
+ dispatch({ type: "SET_EXECUTION_STATE", state: null });
3087
+ }
3088
+ }, []);
3089
+ const handleServerMessage = useCallback((message) => {
3090
+ log("Server message received:", message.type, message);
3091
+ switch (message.type) {
3092
+ case "response":
3093
+ {
3094
+ const existing = messagesRef.current.find((m) => m.id === message.messageId);
3095
+ if (existing && existing.role === "assistant") {
3096
+ dispatch({
3097
+ type: "UPDATE_MESSAGE",
3098
+ id: existing.id,
3099
+ updates: {
3100
+ content: message.message,
3101
+ timestamp: Date.now(),
3102
+ status: "sent"
3103
+ }
3104
+ });
3105
+ } else {
3106
+ dispatch({
3107
+ type: "ADD_MESSAGE",
3108
+ message: {
3109
+ id: existing ? createMessageId() : message.messageId,
3110
+ role: "assistant",
3111
+ content: message.message,
3112
+ timestamp: Date.now(),
3113
+ status: "sent"
3114
+ }
3115
+ });
3116
+ }
3117
+ }
3118
+ break;
3119
+ case "command": {
3120
+ log("Received commands:", message.commands);
3121
+ const commands = Array.isArray(message.commands) ? message.commands : [];
3122
+ const batchId = message.batchId ?? createMessageId();
3123
+ if (message.message) {
3124
+ dispatch({
3125
+ type: "ADD_MESSAGE",
3126
+ message: {
3127
+ id: createMessageId(),
3128
+ role: "assistant",
3129
+ content: message.message,
3130
+ timestamp: Date.now(),
3131
+ commands: commands.length > 0 ? commands : void 0
3132
+ }
3133
+ });
3134
+ }
3135
+ if (commands.length === 0) {
3136
+ break;
3137
+ }
3138
+ const processor = commandProcessorRef.current;
3139
+ if (!processor) {
3140
+ dispatch({ type: "SET_ERROR", error: new Error("Action runner not ready") });
3141
+ commands.forEach((cmd) => {
3142
+ const actionId = cmd.type === "server_action" ? cmd.actionId : cmd.type;
3143
+ if (wsClientRef.current) {
3144
+ wsClientRef.current.send({
3145
+ type: "action_result",
3146
+ actionId,
3147
+ success: false,
3148
+ error: "Action runner not ready",
3149
+ commandId: batchId
3150
+ });
3151
+ }
3152
+ });
3153
+ break;
3154
+ }
3155
+ executeCommandBatch(commands, batchId);
3156
+ break;
3157
+ }
3158
+ case "typing":
3159
+ break;
3160
+ case "server_action_result":
3161
+ log("Server action result:", message.actionId, message.result);
3162
+ break;
3163
+ case "error":
3164
+ log("Server error:", message.code, message.error, message.details);
3165
+ dispatch({
3166
+ type: "SET_ERROR",
3167
+ error: new Error(message.error)
3168
+ });
3169
+ break;
3170
+ case "request_context":
3171
+ if (pageContextRef.current && wsClientRef.current) {
3172
+ wsClientRef.current.send({
3173
+ type: "context",
3174
+ context: pageContextRef.current
3175
+ });
3176
+ }
3177
+ break;
3178
+ }
3179
+ }, [executeCommandBatch]);
3180
+ const sendMessage = useCallback((content) => {
3181
+ const messageId = createMessageId();
3182
+ log("Sending message:", {
3183
+ messageId,
3184
+ mode: state.mode,
3185
+ length: content.length,
3186
+ connectionState: state.connectionState,
3187
+ route: pageContextRef.current?.route ?? adapter.getCurrentPath()
3188
+ });
3189
+ dispatch({
3190
+ type: "ADD_MESSAGE",
3191
+ message: {
3192
+ id: messageId,
3193
+ role: "user",
3194
+ content,
3195
+ timestamp: Date.now(),
3196
+ status: "sending"
3197
+ }
3198
+ });
3199
+ const context = pageContextRef.current ?? {
3200
+ route: adapter.getCurrentPath(),
3201
+ title: typeof document !== "undefined" ? document.title : "",
3202
+ actions: [],
3203
+ serverActions: Array.from(serverActionsRef.current.values()),
3204
+ content: { headings: [], mainContent: "", forms: [] },
3205
+ timestamp: Date.now(),
3206
+ ...routesRef.current?.length ? { routes: routesRef.current } : {}
3207
+ };
3208
+ const locale = selectedVoiceLanguage ?? widgetConfig?.voiceLanguage ?? void 0;
3209
+ wsClientRef.current?.send({
3210
+ type: "message",
3211
+ content,
3212
+ context,
3213
+ mode: state.mode,
3214
+ messageId,
3215
+ ...locale != null ? { locale } : {}
3216
+ });
3217
+ dispatch({
3218
+ type: "UPDATE_MESSAGE",
3219
+ id: messageId,
3220
+ updates: { status: "sent" }
3221
+ });
3222
+ }, [state.mode, adapter, selectedVoiceLanguage, widgetConfig?.voiceLanguage]);
3223
+ const setVoiceLanguage = useCallback((lang) => {
3224
+ setSelectedVoiceLanguage(lang ?? null);
3225
+ }, []);
3226
+ const setMode = useCallback((mode) => {
3227
+ dispatch({ type: "SET_MODE", mode });
3228
+ }, []);
3229
+ const executeAction = useCallback(async (actionId, params) => {
3230
+ const bridge = serverActionBridgeRef.current;
3231
+ if (!bridge) {
3232
+ throw new Error("Server action bridge not ready");
3233
+ }
3234
+ if (!bridge.getAction(actionId)) {
3235
+ const action = serverActionsRef.current.get(actionId);
3236
+ if (action) {
3237
+ bridge.register(action);
3238
+ }
3239
+ }
3240
+ const result = await bridge.execute(actionId, params ?? {});
3241
+ if (wsClientRef.current) {
3242
+ wsClientRef.current.send({
3243
+ type: "action_result",
3244
+ actionId,
3245
+ success: result.success,
3246
+ result: result.data,
3247
+ error: result.error
3248
+ });
3249
+ }
3250
+ return result;
3251
+ }, []);
3252
+ const registerServerAction = useCallback((action) => {
3253
+ serverActionsRef.current.set(action.id, action);
3254
+ registryRef.current?.registerServerAction(action);
3255
+ serverActionBridgeRef.current?.register(action);
3256
+ logger.debug("Registered server action:", action.id);
3257
+ }, [logger]);
3258
+ const clearMessages = useCallback(() => {
3259
+ dispatch({ type: "CLEAR_MESSAGES" });
3260
+ }, []);
3261
+ const clearError = useCallback(() => {
3262
+ dispatch({ type: "CLEAR_ERROR" });
3263
+ }, []);
3264
+ const stopExecution = useCallback(() => {
3265
+ commandProcessorRef.current?.abort();
3266
+ }, []);
3267
+ const connect = useCallback(() => {
3268
+ dispatch({ type: "SET_CONNECTION_STATE", state: "connecting" });
3269
+ wsClientRef.current?.connect();
3270
+ }, []);
3271
+ const disconnect = useCallback(() => {
3272
+ wsClientRef.current?.disconnect();
3273
+ dispatch({ type: "SET_CONNECTION_STATE", state: "disconnected" });
3274
+ }, []);
3275
+ function log(...args) {
3276
+ logger.debug(...args);
3277
+ }
3278
+ const contextValue = useMemo(() => ({
3279
+ widgetConfig,
3280
+ // State
3281
+ messages: state.messages,
3282
+ mode: state.mode,
3283
+ connectionState: state.connectionState,
3284
+ isExecuting: state.isExecuting,
3285
+ executionState: state.executionState,
3286
+ error: state.error,
3287
+ // Widget visibility (persists across navigation)
3288
+ isWidgetOpen,
3289
+ setWidgetOpen,
3290
+ voiceLanguage: effectiveVoiceLanguage,
3291
+ setVoiceLanguage,
3292
+ // Actions
3293
+ sendMessage,
3294
+ setMode,
3295
+ executeAction,
3296
+ registerServerAction,
3297
+ clearMessages,
3298
+ clearError,
3299
+ stopExecution,
3300
+ // Connection
3301
+ connect,
3302
+ disconnect,
3303
+ isConnected: state.connectionState === "connected"
3304
+ }), [
3305
+ widgetConfig,
3306
+ state,
3307
+ isWidgetOpen,
3308
+ effectiveVoiceLanguage,
3309
+ setVoiceLanguage,
3310
+ sendMessage,
3311
+ setMode,
3312
+ executeAction,
3313
+ registerServerAction,
3314
+ clearMessages,
3315
+ clearError,
3316
+ stopExecution,
3317
+ connect,
3318
+ disconnect
3319
+ ]);
3320
+ return /* @__PURE__ */ jsx(ChatbotContext.Provider, { value: contextValue, children });
3321
+ }
3322
+ function renderInlineWithBold(text) {
3323
+ const parts = [];
3324
+ let key = 0;
3325
+ let remaining = text;
3326
+ while (remaining.length > 0) {
3327
+ const i = remaining.indexOf("**");
3328
+ if (i === -1) {
3329
+ parts.push(/* @__PURE__ */ jsx(React2.Fragment, { children: remaining }, key++));
3330
+ break;
3331
+ }
3332
+ const before = remaining.slice(0, i);
3333
+ const after = remaining.slice(i + 2);
3334
+ const j = after.indexOf("**");
3335
+ if (j === -1) {
3336
+ parts.push(/* @__PURE__ */ jsx(React2.Fragment, { children: remaining }, key++));
3337
+ break;
3338
+ }
3339
+ if (before) parts.push(/* @__PURE__ */ jsx(React2.Fragment, { children: before }, key++));
3340
+ parts.push(/* @__PURE__ */ jsx("strong", { children: after.slice(0, j) }, key++));
3341
+ remaining = after.slice(j + 2);
3342
+ }
3343
+ return parts.length === 1 ? parts[0] : /* @__PURE__ */ jsx(Fragment, { children: parts });
3344
+ }
3345
+ function MessageContent({ content, isUser }) {
3346
+ if (isUser) return /* @__PURE__ */ jsx(Fragment, { children: content });
3347
+ const raw = typeof content === "string" ? content : "";
3348
+ if (!raw.trim()) return /* @__PURE__ */ jsx(Fragment, { children: "\xA0" });
3349
+ const lines = raw.split(/\n/);
3350
+ const nodes = [];
3351
+ let listItems = [];
3352
+ const flushList = () => {
3353
+ if (listItems.length > 0) {
3354
+ nodes.push(
3355
+ /* @__PURE__ */ jsx("ul", { className: "navsi-chatbot-message-list", children: listItems.map((item, i) => /* @__PURE__ */ jsx("li", { className: "navsi-chatbot-message-list-item", children: renderInlineWithBold(item) }, i)) }, nodes.length)
3356
+ );
3357
+ listItems = [];
3358
+ }
3359
+ };
3360
+ for (let i = 0; i < lines.length; i++) {
3361
+ const line = lines[i];
3362
+ const trimmed = line.trim();
3363
+ const bulletMatch = trimmed.match(/^[*-]\s+(.*)$/);
3364
+ if (bulletMatch) {
3365
+ flushList();
3366
+ listItems.push(bulletMatch[1].trim());
3367
+ } else {
3368
+ flushList();
3369
+ if (trimmed) {
3370
+ nodes.push(
3371
+ /* @__PURE__ */ jsx("p", { className: "navsi-chatbot-message-paragraph", children: renderInlineWithBold(trimmed) }, nodes.length)
3372
+ );
3373
+ }
3374
+ }
3375
+ }
3376
+ flushList();
3377
+ if (nodes.length === 0) return /* @__PURE__ */ jsx(Fragment, { children: raw });
3378
+ return /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-message-content", children: nodes });
3379
+ }
3380
+ function getSpeechRecognitionConstructor() {
3381
+ if (typeof window === "undefined") return null;
3382
+ const win = window;
3383
+ const recognition = win.SpeechRecognition ?? win.webkitSpeechRecognition;
3384
+ return recognition ?? null;
3385
+ }
3386
+ function isBraveBrowser() {
3387
+ return typeof navigator !== "undefined" && "brave" in navigator;
3388
+ }
3389
+ function describeSpeechError(error) {
3390
+ if (error === "network") {
3391
+ const online = typeof navigator !== "undefined" ? navigator.onLine : true;
3392
+ if (!online) {
3393
+ return "Voice input error: network. You appear to be offline.";
3394
+ }
3395
+ if (isBraveBrowser()) {
3396
+ return "Voice input error: network. Brave may block speech services; disable Shields or try Chrome/Edge.";
3397
+ }
3398
+ const isLocalhost = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
3399
+ const isSecure = typeof window !== "undefined" && window.location.protocol === "https:";
3400
+ if (!isSecure && !isLocalhost) {
3401
+ return "Voice input error: network. Microphone access requires HTTPS.";
3402
+ }
3403
+ return "Voice input error: network. Check microphone permission and reload the page.";
3404
+ }
3405
+ if (error === "not-allowed" || error === "service-not-allowed") {
3406
+ return "Voice input error: microphone permission blocked.";
3407
+ }
3408
+ if (error === "no-speech") {
3409
+ return "Voice input error: no speech detected.";
3410
+ }
3411
+ if (error === "audio-capture") {
3412
+ return "Voice input error: no microphone found.";
3413
+ }
3414
+ return `Voice input error: ${error}`;
3415
+ }
3416
+ function ChatbotWidget({ className, windowClassName, buttonClassName }) {
3417
+ const [input, setInput] = useState("");
3418
+ const [isListening, setIsListening] = useState(false);
3419
+ const [voiceTranscript, setVoiceTranscript] = useState("");
3420
+ const [voiceError, setVoiceError] = useState(null);
3421
+ const messagesEndRef = useRef(null);
3422
+ const messagesContainerRef = useRef(null);
3423
+ const recognitionRef = useRef(null);
3424
+ const finalTranscriptRef = useRef("");
3425
+ const lastSpokenIdRef = useRef(null);
3426
+ const hasInitializedSpeechRef = useRef(false);
3427
+ const shouldSpeakNextAssistantRef = useRef(false);
3428
+ const { messages, sendMessage, isConnected, mode, setMode, isExecuting, error, clearError, clearMessages, isWidgetOpen, setWidgetOpen, stopExecution, widgetConfig, voiceLanguage, setVoiceLanguage } = useChatbot();
3429
+ const effectiveVoiceLang = voiceLanguage ?? widgetConfig?.voiceLanguage ?? (typeof navigator !== "undefined" ? navigator.language : void 0) ?? "en-US";
3430
+ const { progress } = useActionExecution();
3431
+ const { isReconnecting } = useWebSocket();
3432
+ const speechRecognitionConstructor = useMemo(getSpeechRecognitionConstructor, []);
3433
+ const isVoiceSupported = !!speechRecognitionConstructor;
3434
+ useEffect(() => {
3435
+ if (messages.length > 0 && messagesContainerRef.current && isWidgetOpen) {
3436
+ requestAnimationFrame(() => {
3437
+ if (messagesContainerRef.current) {
3438
+ messagesContainerRef.current.scrollTo({
3439
+ top: messagesContainerRef.current.scrollHeight,
3440
+ behavior: "smooth"
3441
+ });
3442
+ }
3443
+ });
3444
+ }
3445
+ }, [messages, isWidgetOpen]);
3446
+ useEffect(() => {
3447
+ if (!hasInitializedSpeechRef.current) {
3448
+ const lastAssistant = [...messages].reverse().find((msg) => msg.role === "assistant");
3449
+ lastSpokenIdRef.current = lastAssistant?.id ?? null;
3450
+ hasInitializedSpeechRef.current = true;
3451
+ }
3452
+ }, []);
3453
+ useEffect(() => {
3454
+ const lastAssistant = [...messages].reverse().find((msg) => msg.role === "assistant");
3455
+ if (!lastAssistant) return;
3456
+ if (!hasInitializedSpeechRef.current) return;
3457
+ if (lastAssistant.id === lastSpokenIdRef.current) return;
3458
+ if (isListening) return;
3459
+ if (!shouldSpeakNextAssistantRef.current) return;
3460
+ if (typeof window === "undefined" || !("speechSynthesis" in window)) return;
3461
+ const content = lastAssistant.content?.trim();
3462
+ if (!content) return;
3463
+ const ttsContent = content.replace(/\*\*(.*?)\*\*/g, "$1").replace(/[*_~`]/g, "");
3464
+ const ttsLang = effectiveVoiceLang;
3465
+ window.speechSynthesis.cancel();
3466
+ const utterance = new SpeechSynthesisUtterance(ttsContent);
3467
+ utterance.lang = ttsLang;
3468
+ window.speechSynthesis.speak(utterance);
3469
+ lastSpokenIdRef.current = lastAssistant.id;
3470
+ shouldSpeakNextAssistantRef.current = false;
3471
+ }, [messages, isListening, effectiveVoiceLang]);
3472
+ const handleSend = () => {
3473
+ if (input.trim()) {
3474
+ shouldSpeakNextAssistantRef.current = false;
3475
+ sendMessage(input.trim());
3476
+ setInput("");
3477
+ }
3478
+ };
3479
+ const handleKeyDown = (e) => {
3480
+ if (e.key === "Enter" && !e.shiftKey) {
3481
+ e.preventDefault();
3482
+ handleSend();
3483
+ }
3484
+ };
3485
+ const startVoiceInput = useCallback(() => {
3486
+ if (isListening) return;
3487
+ if (!speechRecognitionConstructor) {
3488
+ setVoiceError("Voice input is not supported in this browser.");
3489
+ return;
3490
+ }
3491
+ setVoiceError(null);
3492
+ finalTranscriptRef.current = "";
3493
+ const recognition = new speechRecognitionConstructor();
3494
+ recognitionRef.current = recognition;
3495
+ recognition.continuous = false;
3496
+ recognition.interimResults = true;
3497
+ recognition.maxAlternatives = 1;
3498
+ recognition.lang = effectiveVoiceLang;
3499
+ recognition.onstart = () => {
3500
+ setIsListening(true);
3501
+ setVoiceTranscript("");
3502
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
3503
+ window.speechSynthesis.cancel();
3504
+ }
3505
+ };
3506
+ recognition.onresult = (event) => {
3507
+ let interimTranscript = "";
3508
+ let finalTranscript = "";
3509
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
3510
+ const result = event.results[i];
3511
+ const text = result[0]?.transcript ?? "";
3512
+ if (result.isFinal) {
3513
+ finalTranscript += text;
3514
+ } else {
3515
+ interimTranscript += text;
3516
+ }
3517
+ }
3518
+ if (finalTranscript) {
3519
+ finalTranscriptRef.current = `${finalTranscriptRef.current} ${finalTranscript}`.trim();
3520
+ setVoiceTranscript(finalTranscriptRef.current);
3521
+ } else if (interimTranscript) {
3522
+ setVoiceTranscript(interimTranscript.trim());
3523
+ }
3524
+ };
3525
+ recognition.onerror = (event) => {
3526
+ setVoiceError(event.error ? describeSpeechError(event.error) : "Voice input error");
3527
+ };
3528
+ recognition.onend = () => {
3529
+ setIsListening(false);
3530
+ const finalText = finalTranscriptRef.current.trim();
3531
+ finalTranscriptRef.current = "";
3532
+ setVoiceTranscript("");
3533
+ if (finalText) {
3534
+ shouldSpeakNextAssistantRef.current = true;
3535
+ sendMessage(finalText);
3536
+ setInput("");
3537
+ }
3538
+ };
3539
+ recognition.start();
3540
+ }, [isListening, speechRecognitionConstructor, sendMessage, effectiveVoiceLang]);
3541
+ const stopVoiceInput = useCallback(() => {
3542
+ recognitionRef.current?.stop();
3543
+ }, []);
3544
+ useEffect(() => {
3545
+ return () => {
3546
+ recognitionRef.current?.abort();
3547
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
3548
+ window.speechSynthesis.cancel();
3549
+ }
3550
+ };
3551
+ }, []);
3552
+ const containerStyle = {
3553
+ position: "fixed",
3554
+ zIndex: 9999
3555
+ };
3556
+ const title = widgetConfig?.headerTitle ?? "AI Assistant";
3557
+ const welcomeMessage = widgetConfig?.welcomeMessage ?? "How can I help you today?";
3558
+ const welcomeHint = widgetConfig?.subtitle ?? "Type a message to get started";
3559
+ const askPlaceholder = widgetConfig?.askPlaceholder ?? "Ask a question...";
3560
+ const navigatePlaceholder = widgetConfig?.navigatePlaceholder ?? "What should I do? Try: Go to cart, Add product 1 to cart";
3561
+ const supportedLanguages = widgetConfig?.supportedLanguages ?? [
3562
+ { code: "en-US", label: "EN" },
3563
+ { code: "hi-IN", label: "\u0939\u093F\u0902\u0926\u0940" }
3564
+ ];
3565
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, className: `navsi-chatbot-container ${className || ""}`, children: [
3566
+ isWidgetOpen && /* @__PURE__ */ jsxs("div", { className: `navsi-chatbot-window ${windowClassName || ""}`, children: [
3567
+ /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-header", children: [
3568
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-header-left", children: /* @__PURE__ */ jsxs("div", { children: [
3569
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-title", children: title }),
3570
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-status", children: isConnected ? "Connected" : isReconnecting ? "Reconnecting" : "Disconnected" }),
3571
+ isListening && /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-voice-indicator", "aria-live": "polite", children: [
3572
+ /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3573
+ /* @__PURE__ */ jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
3574
+ /* @__PURE__ */ jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
3575
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
3576
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
3577
+ ] }),
3578
+ "Listening\u2026"
3579
+ ] })
3580
+ ] }) }),
3581
+ /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-mode-toggle", children: [
3582
+ /* @__PURE__ */ jsx(
3583
+ "button",
3584
+ {
3585
+ className: `navsi-chatbot-mode-button ${mode === "ask" ? "navsi-chatbot-mode-active" : ""}`,
3586
+ onClick: () => setMode("ask"),
3587
+ children: "Ask"
3588
+ }
3589
+ ),
3590
+ /* @__PURE__ */ jsx(
3591
+ "button",
3592
+ {
3593
+ className: `navsi-chatbot-mode-button ${mode === "navigate" ? "navsi-chatbot-mode-active" : ""}`,
3594
+ onClick: () => setMode("navigate"),
3595
+ children: "Action"
3596
+ }
3597
+ )
3598
+ ] }),
3599
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-lang-toggle", role: "group", "aria-label": "Voice and response language", children: supportedLanguages.map((lang) => /* @__PURE__ */ jsx(
3600
+ "button",
3601
+ {
3602
+ type: "button",
3603
+ className: `navsi-chatbot-lang-button ${effectiveVoiceLang.startsWith(lang.code.split("-")[0]) ? "navsi-chatbot-lang-active" : ""}`,
3604
+ onClick: () => setVoiceLanguage(lang.code),
3605
+ title: lang.label,
3606
+ "aria-pressed": effectiveVoiceLang.startsWith(lang.code.split("-")[0]),
3607
+ children: lang.label
3608
+ },
3609
+ lang.code
3610
+ )) }),
3611
+ messages.length > 0 && /* @__PURE__ */ jsx(
3612
+ "button",
3613
+ {
3614
+ type: "button",
3615
+ onClick: () => clearMessages(),
3616
+ className: "navsi-chatbot-clear",
3617
+ title: "Clear chat",
3618
+ "aria-label": "Clear chat",
3619
+ children: "Clear"
3620
+ }
3621
+ )
3622
+ ] }),
3623
+ isExecuting && /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-banner", children: [
3624
+ /* @__PURE__ */ jsx("span", { children: "\u26A1 Executing actions\u2026" }),
3625
+ progress && /* @__PURE__ */ jsxs(Fragment, { children: [
3626
+ /* @__PURE__ */ jsxs("span", { className: "navsi-chatbot-pill", children: [
3627
+ progress.current + 1,
3628
+ "/",
3629
+ progress.total
3630
+ ] }),
3631
+ progress.description && /* @__PURE__ */ jsx("span", { className: "navsi-chatbot-step", children: progress.description })
3632
+ ] })
3633
+ ] }),
3634
+ progress && /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-progress-container", children: /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-progress-bar", children: /* @__PURE__ */ jsx(
3635
+ "div",
3636
+ {
3637
+ className: "navsi-chatbot-progress-fill",
3638
+ style: { width: `${progress.percentage ?? 0}%` }
3639
+ }
3640
+ ) }) }),
3641
+ error && /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-error", children: [
3642
+ /* @__PURE__ */ jsx("span", { children: error.message }),
3643
+ /* @__PURE__ */ jsx(
3644
+ "button",
3645
+ {
3646
+ className: "navsi-chatbot-error-close",
3647
+ onClick: clearError,
3648
+ children: "\u2715"
3649
+ }
3650
+ )
3651
+ ] }),
3652
+ voiceError && /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-voice-error", role: "alert", children: voiceError }),
3653
+ /* @__PURE__ */ jsxs(
3654
+ "div",
3655
+ {
3656
+ ref: messagesContainerRef,
3657
+ className: "navsi-chatbot-messages",
3658
+ role: "log",
3659
+ "aria-live": "polite",
3660
+ "aria-label": "Chat messages",
3661
+ children: [
3662
+ messages.length === 0 && /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-welcome", children: [
3663
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-welcome-icon", children: "\u{1F44B}" }),
3664
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-welcome-text", children: welcomeMessage }),
3665
+ /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-welcome-hint", children: welcomeHint })
3666
+ ] }),
3667
+ messages.map((msg) => /* @__PURE__ */ jsx(
3668
+ "div",
3669
+ {
3670
+ className: `navsi-chatbot-message ${msg.role === "user" ? "navsi-chatbot-message-user" : "navsi-chatbot-message-assistant"}`,
3671
+ children: /* @__PURE__ */ jsx(MessageContent, { content: msg.content, isUser: msg.role === "user" })
3672
+ },
3673
+ msg.id
3674
+ )),
3675
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
3676
+ ]
3677
+ }
3678
+ ),
3679
+ /* @__PURE__ */ jsxs("div", { className: "navsi-chatbot-input-area", children: [
3680
+ /* @__PURE__ */ jsx(
3681
+ "input",
3682
+ {
3683
+ type: "text",
3684
+ value: input,
3685
+ onChange: (e) => setInput(e.target.value),
3686
+ onKeyDown: handleKeyDown,
3687
+ placeholder: mode === "ask" ? askPlaceholder : navigatePlaceholder,
3688
+ className: "navsi-chatbot-input",
3689
+ maxLength: 8e3,
3690
+ "aria-label": mode === "ask" ? askPlaceholder : navigatePlaceholder
3691
+ }
3692
+ ),
3693
+ /* @__PURE__ */ jsx(
3694
+ "button",
3695
+ {
3696
+ className: `navsi-chatbot-voice-button ${isListening ? "navsi-chatbot-voice-button-active" : ""}`,
3697
+ onClick: () => {
3698
+ if (isListening) {
3699
+ stopVoiceInput();
3700
+ } else {
3701
+ startVoiceInput();
3702
+ }
3703
+ },
3704
+ disabled: !isConnected || !isVoiceSupported,
3705
+ "aria-label": !isVoiceSupported ? "Voice input not supported in this browser" : isListening ? "Stop voice input" : "Start voice input",
3706
+ "aria-pressed": isListening,
3707
+ title: !isVoiceSupported ? "Voice input is not available in this browser" : isListening ? "Click to stop and send" : "Click to start listening",
3708
+ type: "button",
3709
+ children: isListening ? /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "8" }) }) : /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3710
+ /* @__PURE__ */ jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
3711
+ /* @__PURE__ */ jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
3712
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
3713
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
3714
+ ] })
3715
+ }
3716
+ ),
3717
+ isExecuting ? /* @__PURE__ */ jsx(
3718
+ "button",
3719
+ {
3720
+ className: "navsi-chatbot-stop-button",
3721
+ onClick: stopExecution,
3722
+ "aria-label": "Stop",
3723
+ title: "Stop current action",
3724
+ children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1" }) })
3725
+ }
3726
+ ) : /* @__PURE__ */ jsx(
3727
+ "button",
3728
+ {
3729
+ className: "navsi-chatbot-send-button",
3730
+ onClick: handleSend,
3731
+ disabled: !isConnected,
3732
+ children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
3733
+ }
3734
+ )
3735
+ ] }),
3736
+ voiceTranscript && /* @__PURE__ */ jsx("div", { className: "navsi-chatbot-voice-transcript", "aria-live": "polite", children: voiceTranscript })
3737
+ ] }),
3738
+ /* @__PURE__ */ jsx(
3739
+ "button",
3740
+ {
3741
+ className: `navsi-chatbot-toggle ${buttonClassName || ""}`,
3742
+ onClick: () => setWidgetOpen(!isWidgetOpen),
3743
+ "aria-label": isWidgetOpen ? "Close chat" : "Open chat",
3744
+ children: isWidgetOpen ? /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }) : /* @__PURE__ */ jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" }) })
3745
+ }
3746
+ )
3747
+ ] });
3748
+ }
3749
+
3750
+ export { ChatbotProvider, ChatbotWidget, DEFAULT_ROUTE_TIMEOUT, NavigationController, WebSocketClient, createMemoryAdapter, createNavigationController, createWebSocketClient };
3751
+ //# sourceMappingURL=chunk-EHZXIZIP.js.map
3752
+ //# sourceMappingURL=chunk-EHZXIZIP.js.map