@modelnex/sdk 0.5.43 → 0.5.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,25 +1,32 @@
1
1
  import {
2
2
  collectEditableControls,
3
+ emitSdkDebugLog,
4
+ extractInteractiveElements,
3
5
  findEditableControlInShadowRoot,
6
+ generateFingerprint,
4
7
  generateMinifiedAOM,
5
8
  isEditableControlDisabled,
9
+ isSdkDebugEnabled,
6
10
  isValueBearingElement,
7
11
  readEditableControlName,
8
12
  readEditableControlPlaceholder,
9
13
  readEditableControlType,
10
14
  readEditableControlValue,
11
- resolveEditableControlElement
12
- } from "./chunk-H4LUY7LI.mjs";
15
+ resolveEditableControlElement,
16
+ sanitizeAgentDebug,
17
+ sanitizeChatMessages,
18
+ useAutoExtract
19
+ } from "./chunk-SXGINP3O.mjs";
13
20
 
14
21
  // src/index.ts
15
- import React8, { useState as useState15, useCallback as useCallback14, useEffect as useEffect19, useMemo as useMemo5 } from "react";
22
+ import React8, { useState as useState14, useCallback as useCallback13, useEffect as useEffect18, useMemo as useMemo5 } from "react";
16
23
 
17
24
  // src/context.ts
18
25
  import { createContext } from "react";
19
26
  var ModelNexContext = createContext(null);
20
27
 
21
28
  // src/hooks/useModelNexSocket.ts
22
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
29
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
23
30
  import { io } from "socket.io-client";
24
31
 
25
32
  // src/utils/socket-io-transports.ts
@@ -34,390 +41,11 @@ function resolveSocketIoTransports(serverUrl, order) {
34
41
  return ["websocket", "polling"];
35
42
  }
36
43
 
37
- // src/auto-extract.ts
38
- import { useState, useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2 } from "react";
39
-
40
- // src/utils/dev-logging.ts
41
- function isSdkDebugEnabled(devMode) {
42
- if (devMode) return true;
43
- if (typeof window !== "undefined" && Boolean(window.MODELNEX_DEBUG)) {
44
- return true;
45
- }
46
- return process.env.NODE_ENV === "development";
47
- }
48
- function emitSdkDebugLog(message, payload, options) {
49
- if (!isSdkDebugEnabled(options?.devMode)) return;
50
- if (payload && Object.keys(payload).length > 0) {
51
- console.log(message, payload);
52
- } else {
53
- console.log(message);
54
- }
55
- if (options?.dispatchEvent && typeof window !== "undefined") {
56
- window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg: message, data: payload } }));
57
- }
58
- }
59
- function sanitizeActionList(actions) {
60
- if (!Array.isArray(actions) || actions.length === 0) return void 0;
61
- return actions.map(({ actionId }) => ({ actionId }));
62
- }
63
- function sanitizeAgentDebug(debug) {
64
- if (!debug) return void 0;
65
- const actions = sanitizeActionList(debug.actions);
66
- const traces = Array.isArray(debug.traces) && debug.traces.length > 0 ? debug.traces.map((trace) => ({
67
- step: trace.step,
68
- actions: sanitizeActionList(trace.actions) ?? [],
69
- results: Array.isArray(trace.results) && trace.results.length > 0 ? trace.results.map(({ actionId, success }) => ({ actionId, success })) : void 0
70
- })) : void 0;
71
- if ((!actions || actions.length === 0) && (!traces || traces.length === 0)) {
72
- return void 0;
73
- }
74
- return {
75
- ...actions ? { actions } : {},
76
- ...traces ? { traces } : {}
77
- };
78
- }
79
- function sanitizeChatMessages(messages, devMode) {
80
- const includeDebug = isSdkDebugEnabled(devMode);
81
- return messages.map((message) => {
82
- if (message.role !== "assistant") {
83
- return message;
84
- }
85
- return {
86
- ...message,
87
- debug: includeDebug ? sanitizeAgentDebug(message.debug) : void 0
88
- };
89
- });
90
- }
91
-
92
- // src/auto-extract.ts
93
- function simpleHash(str) {
94
- let hash = 0;
95
- for (let i = 0; i < str.length; i++) {
96
- const char = str.charCodeAt(i);
97
- hash = (hash << 5) - hash + char;
98
- hash |= 0;
99
- }
100
- return (hash >>> 0).toString(16).padStart(8, "0");
101
- }
102
- function isStableDomId(id) {
103
- if (!id) return false;
104
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
105
- const REACT_USE_ID_RE = /^(?:radix-)?:[rR][^\s]*$/;
106
- if (REACT_USE_ID_RE.test(id)) return false;
107
- if (/^\d+$/.test(id)) return false;
108
- if (UUID_RE.test(id)) return false;
109
- return true;
110
- }
111
- function getNearestHeading(el) {
112
- const shellContainer = el.closest('nav, aside, header, [role="navigation"], [role="complementary"], [role="banner"]');
113
- const boundary = shellContainer || document.body;
114
- let current = el;
115
- while (current && current !== boundary) {
116
- let sibling = current.previousElementSibling;
117
- while (sibling) {
118
- if (/^H[1-6]$/.test(sibling.tagName)) {
119
- return sibling.textContent?.trim().slice(0, 100) || null;
120
- }
121
- const innerHeading = sibling.querySelector("h1, h2, h3, h4, h5, h6");
122
- if (innerHeading) {
123
- return innerHeading.textContent?.trim().slice(0, 100) || null;
124
- }
125
- sibling = sibling.previousElementSibling;
126
- }
127
- const parentHeading = current.parentElement?.querySelector("h1, h2, h3, h4, h5, h6");
128
- if (parentHeading && !current.contains(parentHeading)) {
129
- return parentHeading.textContent?.trim().slice(0, 100) || null;
130
- }
131
- current = current.parentElement;
132
- }
133
- return null;
134
- }
135
- function getParentContainer(el) {
136
- let current = el.parentElement;
137
- while (current && current !== document.body) {
138
- const role = current.getAttribute("role");
139
- const tag = current.tagName.toLowerCase();
140
- const state = current.getAttribute("data-state");
141
- if (role === "dialog" || role === "alertdialog" || tag === "dialog") {
142
- const title = current.querySelector('h2, h3, [class*="title"]');
143
- const titleText = title?.textContent?.trim().slice(0, 60) || "";
144
- return titleText ? `dialog:${titleText}` : "dialog";
145
- }
146
- if (role === "menu" || role === "listbox") {
147
- return role;
148
- }
149
- if (current.hasAttribute("popover") || state === "open" && current.getAttribute("data-radix-popper-content-wrapper") !== null) {
150
- return "popover";
151
- }
152
- if (current.getAttribute("data-radix-menu-content") !== null || role === "menubar") {
153
- return "dropdown-menu";
154
- }
155
- if (role === "tabpanel") {
156
- const label = current.getAttribute("aria-label") || current.getAttribute("aria-labelledby");
157
- return label ? `tabpanel:${label.slice(0, 40)}` : "tabpanel";
158
- }
159
- if (tag === "nav" || role === "navigation") {
160
- const label = current.getAttribute("aria-label");
161
- return label ? `navigation:${label.slice(0, 40)}` : "navigation";
162
- }
163
- if (tag === "aside") {
164
- return "sidebar";
165
- }
166
- if (tag === "header") {
167
- return "header";
168
- }
169
- current = current.parentElement;
170
- }
171
- return null;
172
- }
173
- function getRowContext(el) {
174
- let current = el.parentElement;
175
- while (current && current !== document.body) {
176
- const tag = current.tagName.toLowerCase();
177
- if (tag === "tr" || tag === "li" || current.getAttribute("role") === "row" || current.getAttribute("role") === "listitem") {
178
- const rowText = current.textContent?.trim().replace(/\s+/g, " ").slice(0, 120) || "";
179
- return rowText;
180
- }
181
- current = current.parentElement;
182
- }
183
- return "";
184
- }
185
- function getNearestAncestorId(el) {
186
- let current = el.parentElement;
187
- while (current && current !== document.body) {
188
- const aid = current.id;
189
- if (isStableDomId(aid)) {
190
- return aid;
191
- }
192
- const ancestorTestId = current.getAttribute("data-testid");
193
- if (ancestorTestId) return `[data-testid="${ancestorTestId}"]`;
194
- current = current.parentElement;
195
- }
196
- return null;
197
- }
198
- function generateFingerprint(el) {
199
- const tag = el.tagName.toLowerCase();
200
- const testId = el.getAttribute("data-testid");
201
- const id = el.id;
202
- const name = el.getAttribute("name");
203
- const ariaLabel = el.getAttribute("aria-label");
204
- const type = el.type || "";
205
- const text = el.textContent?.trim().slice(0, 80) || "";
206
- if (testId) return `tid:${testId}`;
207
- if (isStableDomId(id)) return `id:${id}`;
208
- if (name) return `name:${tag}:${name}`;
209
- if (ariaLabel) {
210
- const rowCtx2 = getRowContext(el);
211
- if (rowCtx2) {
212
- return `aria:${simpleHash(ariaLabel + ":" + rowCtx2)}:${ariaLabel.slice(0, 40)}`;
213
- }
214
- return `aria:${simpleHash(ariaLabel)}:${ariaLabel.slice(0, 40)}`;
215
- }
216
- if (tag === "a") {
217
- const href = el.getAttribute("href");
218
- if (href && href !== "#" && !href.startsWith("javascript:")) {
219
- return `href:${simpleHash(href + ":" + text)}:${text.slice(0, 30) || href.slice(0, 30)}`;
220
- }
221
- }
222
- const ancestorId = getNearestAncestorId(el);
223
- if (ancestorId) {
224
- return `anc:${simpleHash(ancestorId + ":" + tag + ":" + text)}:${text.slice(0, 30) || tag}`;
225
- }
226
- const heading = getNearestHeading(el) || "";
227
- const rowCtx = getRowContext(el);
228
- const raw = `${tag}:${type}:${text}:${heading}:${rowCtx}`;
229
- return `hash:${simpleHash(raw)}:${text.slice(0, 30) || tag}`;
230
- }
231
- function classifyRole(el) {
232
- const tag = el.tagName.toLowerCase();
233
- const role = el.getAttribute("role");
234
- if (tag === "button" || role === "button") return "button";
235
- if (tag === "a") return "link";
236
- if (tag === "input") return "input";
237
- if (tag === "textarea") return "textarea";
238
- if (tag === "select") return "select";
239
- if (tag === "form") return "form";
240
- if (tag === "label") return "label";
241
- if (role === "link") return "link";
242
- if (role === "menuitem") return "menuitem";
243
- if (role === "menuitemcheckbox") return "menuitem";
244
- if (role === "menuitemradio") return "menuitem";
245
- if (role === "tab") return "tab";
246
- if (role === "checkbox") return "checkbox";
247
- if (role === "radio") return "radio";
248
- if (role === "switch") return "switch";
249
- if (role === "slider") return "slider";
250
- if (role === "combobox") return "combobox";
251
- if (role === "option") return "option";
252
- if (role === "treeitem") return "treeitem";
253
- if (role === "gridcell") return "gridcell";
254
- if (el.hasAttribute("tabindex") && el.getAttribute("tabindex") !== "-1") return "interactive";
255
- if (el.hasAttribute("cmdk-item")) return "menuitem";
256
- return null;
257
- }
258
- var INTERACTIVE_SELECTOR = [
259
- "button",
260
- "a",
261
- // All anchor tags (SPA links may not have href)
262
- 'input:not([type="hidden"])',
263
- "textarea",
264
- "select",
265
- "label[for]",
266
- '[role="button"]',
267
- '[role="link"]',
268
- '[role="menuitem"]',
269
- '[role="menuitemcheckbox"]',
270
- '[role="menuitemradio"]',
271
- '[role="tab"]',
272
- '[role="checkbox"]',
273
- '[role="radio"]',
274
- '[role="switch"]',
275
- '[role="slider"]',
276
- '[role="combobox"]',
277
- '[role="option"]',
278
- '[role="treeitem"]',
279
- '[role="gridcell"]',
280
- '[tabindex]:not([tabindex="-1"])',
281
- "[data-discover]",
282
- // React Router links
283
- "[cmdk-item]",
284
- // cmdk menu items
285
- "form"
286
- ].join(", ");
287
- function isVisible(el) {
288
- if (el.offsetParent === null) {
289
- const computed = window.getComputedStyle(el);
290
- if (computed.position !== "fixed" && computed.position !== "sticky") {
291
- if (computed.display === "none" || computed.visibility === "hidden") {
292
- return false;
293
- }
294
- const rect2 = el.getBoundingClientRect();
295
- if (rect2.width === 0 && rect2.height === 0) return false;
296
- }
297
- }
298
- const style = window.getComputedStyle(el);
299
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
300
- return false;
301
- }
302
- const rect = el.getBoundingClientRect();
303
- return rect.width > 0 && rect.height > 0;
304
- }
305
- function extractInteractiveElements() {
306
- const elements = [];
307
- const seen = /* @__PURE__ */ new Set();
308
- try {
309
- const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
310
- nodes.forEach((node) => {
311
- const el = node;
312
- if (!isVisible(el)) return;
313
- if (el.closest(".modelnex-chat-panel, .modelnex-studio-overlay, [data-modelnex-internal]")) return;
314
- const role = classifyRole(el);
315
- if (!role) return;
316
- let fingerprint = generateFingerprint(el);
317
- if (seen.has(fingerprint)) {
318
- let idx = 2;
319
- while (seen.has(`${fingerprint}#${idx}`)) idx++;
320
- fingerprint = `${fingerprint}#${idx}`;
321
- }
322
- seen.add(fingerprint);
323
- const rect = el.getBoundingClientRect();
324
- const text = el.textContent?.trim().slice(0, 200) || "";
325
- elements.push({
326
- fingerprint,
327
- tagName: el.tagName.toLowerCase(),
328
- text,
329
- role,
330
- rect: {
331
- top: rect.top,
332
- left: rect.left,
333
- width: rect.width,
334
- height: rect.height
335
- },
336
- attributes: {
337
- id: el.id || void 0,
338
- name: el.getAttribute("name") || void 0,
339
- type: el.type || void 0,
340
- ariaLabel: el.getAttribute("aria-label") || void 0,
341
- dataTestId: el.getAttribute("data-testid") || void 0,
342
- href: el.href || void 0,
343
- placeholder: el.placeholder || void 0
344
- },
345
- nearestHeading: getNearestHeading(el),
346
- parentContainer: getParentContainer(el),
347
- disabled: el.disabled || el.getAttribute("aria-disabled") === "true",
348
- element: el
349
- });
350
- });
351
- } catch (err) {
352
- console.warn("[ModelNex] Auto-extraction error:", err);
353
- }
354
- return elements;
355
- }
356
- function useAutoExtract(devMode) {
357
- const [elements, setElements] = useState([]);
358
- const timerRef = useRef2(null);
359
- const lastSnapshotRef = useRef2("");
360
- const scan = useCallback2(() => {
361
- if (typeof document === "undefined") return;
362
- const extracted = extractInteractiveElements();
363
- const snapshot = JSON.stringify(extracted.map((e) => ({
364
- fingerprint: e.fingerprint,
365
- role: e.role,
366
- text: e.text,
367
- rect: e.rect,
368
- disabled: e.disabled,
369
- nearestHeading: e.nearestHeading,
370
- parentContainer: e.parentContainer
371
- })));
372
- if (snapshot === lastSnapshotRef.current) return;
373
- lastSnapshotRef.current = snapshot;
374
- emitSdkDebugLog("[ModelNex AutoExtract] Scan complete", {
375
- elementCount: extracted.length
376
- }, { devMode });
377
- setElements(extracted);
378
- }, [devMode]);
379
- useEffect2(() => {
380
- const initialTimer = setTimeout(scan, 300);
381
- const observer = new MutationObserver((mutations) => {
382
- const hasRelevantMutation = mutations.some((mutation) => {
383
- const target = mutation.target;
384
- if (target?.closest?.(".modelnex-chat-panel, .modelnex-studio-overlay, [data-modelnex-internal]")) {
385
- return false;
386
- }
387
- return true;
388
- });
389
- if (!hasRelevantMutation) return;
390
- if (timerRef.current) clearTimeout(timerRef.current);
391
- timerRef.current = setTimeout(scan, 500);
392
- });
393
- observer.observe(document.body, {
394
- childList: true,
395
- subtree: true,
396
- attributes: true,
397
- attributeFilter: ["disabled", "aria-disabled", "hidden", "style", "class"]
398
- });
399
- const handlePositionChange = () => {
400
- if (timerRef.current) clearTimeout(timerRef.current);
401
- timerRef.current = setTimeout(scan, 200);
402
- };
403
- window.addEventListener("scroll", handlePositionChange, { passive: true });
404
- window.addEventListener("resize", handlePositionChange, { passive: true });
405
- return () => {
406
- clearTimeout(initialTimer);
407
- if (timerRef.current) clearTimeout(timerRef.current);
408
- observer.disconnect();
409
- window.removeEventListener("scroll", handlePositionChange);
410
- window.removeEventListener("resize", handlePositionChange);
411
- };
412
- }, [scan]);
413
- return elements;
414
- }
415
-
416
44
  // src/utils/dom-summary.ts
417
45
  function textOf(el) {
418
46
  return el?.textContent?.trim().slice(0, 200) ?? "";
419
47
  }
420
- function isVisible2(el) {
48
+ function isVisible(el) {
421
49
  const htmlEl = el;
422
50
  const style = window.getComputedStyle(htmlEl);
423
51
  if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
@@ -488,7 +116,7 @@ function captureDomSummary(tags) {
488
116
  const alerts = safeQueryAll('[role="alert"], [role="status"]').map(textOf).filter(Boolean);
489
117
  const allButtons = safeQueryAll('button, [role="button"], a[class*="btn"], a[class*="Button"], a');
490
118
  const disabledButtons = allButtons.filter((b) => b.disabled || b.getAttribute("aria-disabled") === "true").map((b) => ({ text: textOf(b), fingerprint: generateFingerprint(b) })).filter((o) => !!o.text);
491
- const enabledButtons = allButtons.filter((b) => !b.disabled && b.getAttribute("aria-disabled") !== "true").filter((b) => isVisible2(b)).map((b) => ({ text: textOf(b), fingerprint: generateFingerprint(b) })).filter((o) => !!o.text).slice(0, 40);
119
+ const enabledButtons = allButtons.filter((b) => !b.disabled && b.getAttribute("aria-disabled") !== "true").filter((b) => isVisible(b)).map((b) => ({ text: textOf(b), fingerprint: generateFingerprint(b) })).filter((o) => !!o.text).slice(0, 40);
492
120
  const visibleDialogs = safeQueryAll('[role="dialog"], [data-state="open"], dialog[open]').map((d) => {
493
121
  const title = d.querySelector('h2, h3, [class*="title"]');
494
122
  return title ? textOf(title) : textOf(d).slice(0, 100);
@@ -692,16 +320,16 @@ function useModelNexSocket({
692
320
  websiteId,
693
321
  devMode
694
322
  }) {
695
- const socketRef = useRef3(null);
696
- const actionsRef = useRef3(actions);
697
- const contextsRef = useRef3(contexts);
698
- const documentationRef = useRef3(documentation);
699
- const tagsRef = useRef3(tags);
323
+ const socketRef = useRef2(null);
324
+ const actionsRef = useRef2(actions);
325
+ const contextsRef = useRef2(contexts);
326
+ const documentationRef = useRef2(documentation);
327
+ const tagsRef = useRef2(tags);
700
328
  actionsRef.current = actions;
701
329
  contextsRef.current = contexts;
702
330
  documentationRef.current = documentation;
703
331
  tagsRef.current = tags;
704
- useEffect3(() => {
332
+ useEffect2(() => {
705
333
  if (disabled) {
706
334
  socketRef.current = null;
707
335
  onSocketId?.(null);
@@ -834,7 +462,7 @@ function useModelNexSocket({
834
462
  socket.disconnect();
835
463
  };
836
464
  }, [disabled, onSocketId, serverUrl]);
837
- useEffect3(() => {
465
+ useEffect2(() => {
838
466
  if (disabled) {
839
467
  return;
840
468
  }
@@ -852,10 +480,10 @@ function useModelNexSocket({
852
480
  }
853
481
 
854
482
  // src/hooks/useFieldHighlight.ts
855
- import { useEffect as useEffect4, useRef as useRef4 } from "react";
483
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
856
484
  function useFieldHighlight(stagingFields, executedFields, setExecutedFields) {
857
- const highlightedRef = useRef4(/* @__PURE__ */ new Map());
858
- useEffect4(() => {
485
+ const highlightedRef = useRef3(/* @__PURE__ */ new Map());
486
+ useEffect3(() => {
859
487
  const prev = highlightedRef.current;
860
488
  prev.forEach((_, fieldId) => {
861
489
  if (!stagingFields.has(fieldId)) {
@@ -874,7 +502,7 @@ function useFieldHighlight(stagingFields, executedFields, setExecutedFields) {
874
502
  prev.clear();
875
503
  };
876
504
  }, [stagingFields]);
877
- useEffect4(() => {
505
+ useEffect3(() => {
878
506
  const timeouts = [];
879
507
  executedFields.forEach((fieldId) => {
880
508
  const els = document.querySelectorAll(`[data-modelnex-field-id="${fieldId}"]`);
@@ -895,7 +523,7 @@ function useFieldHighlight(stagingFields, executedFields, setExecutedFields) {
895
523
  }
896
524
 
897
525
  // src/overlay.tsx
898
- import React2, { useState as useState2, useEffect as useEffect5 } from "react";
526
+ import React2, { useState, useEffect as useEffect4 } from "react";
899
527
  var AIActivityOverlay = ({ activeActions }) => {
900
528
  if (activeActions.size === 0) return null;
901
529
  return React2.createElement(
@@ -1092,7 +720,7 @@ body.modelnex-highlight-actions [data-modelnex-action-id]::before {
1092
720
  `;
1093
721
 
1094
722
  // src/tag-store.ts
1095
- import { useState as useState3, useCallback as useCallback3, useEffect as useEffect6 } from "react";
723
+ import { useState as useState2, useCallback as useCallback2, useEffect as useEffect5 } from "react";
1096
724
  var STORAGE_KEY = "modelnex-tags";
1097
725
  function loadTags() {
1098
726
  try {
@@ -1116,10 +744,10 @@ function saveTags(tags) {
1116
744
  }
1117
745
  }
1118
746
  function useTagStore(options) {
1119
- const [tags, setTags] = useState3(/* @__PURE__ */ new Map());
747
+ const [tags, setTags] = useState2(/* @__PURE__ */ new Map());
1120
748
  const apiUrl = options?.serverUrl ? `${options.serverUrl.replace(/\/$/, "")}/api/tags` : null;
1121
749
  const websiteId = options?.websiteId;
1122
- useEffect6(() => {
750
+ useEffect5(() => {
1123
751
  const local = loadTags();
1124
752
  setTags(local);
1125
753
  if (apiUrl) {
@@ -1139,14 +767,14 @@ function useTagStore(options) {
1139
767
  }).catch((err) => console.warn("[ModelNex] Failed to fetch remote tags:", err));
1140
768
  }
1141
769
  }, [apiUrl]);
1142
- useEffect6(() => {
770
+ useEffect5(() => {
1143
771
  if (typeof window === "undefined") return;
1144
772
  saveTags(tags);
1145
773
  }, [tags]);
1146
- const getTag = useCallback3((fingerprint) => {
774
+ const getTag = useCallback2((fingerprint) => {
1147
775
  return tags.get(fingerprint);
1148
776
  }, [tags]);
1149
- const setTag = useCallback3((fingerprint, description, category, metadata, selector, patternId, behavior, sourcePage, displayContext, skipRemoteSync) => {
777
+ const setTag = useCallback2((fingerprint, description, category, metadata, selector, patternId, behavior, sourcePage, displayContext, skipRemoteSync) => {
1150
778
  setTags((prev) => {
1151
779
  const next = new Map(prev);
1152
780
  const key = selector ? `selector:${selector}` : fingerprint;
@@ -1178,7 +806,7 @@ function useTagStore(options) {
1178
806
  return next;
1179
807
  });
1180
808
  }, [apiUrl, websiteId]);
1181
- const setTagsBatch = useCallback3((newTags, skipRemoteSync) => {
809
+ const setTagsBatch = useCallback2((newTags, skipRemoteSync) => {
1182
810
  setTags((prev) => {
1183
811
  const next = new Map(prev);
1184
812
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1206,24 +834,24 @@ function useTagStore(options) {
1206
834
  return next;
1207
835
  });
1208
836
  }, [apiUrl, websiteId]);
1209
- const deleteTag = useCallback3((fingerprint) => {
837
+ const deleteTag = useCallback2((fingerprint) => {
1210
838
  setTags((prev) => {
1211
839
  const next = new Map(prev);
1212
840
  next.delete(fingerprint);
1213
841
  return next;
1214
842
  });
1215
843
  }, []);
1216
- const getAllTags = useCallback3(() => {
844
+ const getAllTags = useCallback2(() => {
1217
845
  return Array.from(tags.values());
1218
846
  }, [tags]);
1219
- const exportTags = useCallback3(() => {
847
+ const exportTags = useCallback2(() => {
1220
848
  const obj = {};
1221
849
  tags.forEach((v, k) => {
1222
850
  obj[k] = v;
1223
851
  });
1224
852
  return JSON.stringify(obj, null, 2);
1225
853
  }, [tags]);
1226
- const importTags = useCallback3((json) => {
854
+ const importTags = useCallback2((json) => {
1227
855
  try {
1228
856
  const parsed = JSON.parse(json);
1229
857
  setTags((prev) => {
@@ -1239,7 +867,7 @@ function useTagStore(options) {
1239
867
  }
1240
868
 
1241
869
  // src/studio-mode.tsx
1242
- import { useState as useState4, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback4 } from "react";
870
+ import { useState as useState3, useEffect as useEffect6, useRef as useRef4, useCallback as useCallback3 } from "react";
1243
871
  import { jsx, jsxs } from "react/jsx-runtime";
1244
872
  var CATEGORIES = [
1245
873
  { value: "button", label: "Button" },
@@ -1250,10 +878,10 @@ var CATEGORIES = [
1250
878
  { value: "other", label: "Other" }
1251
879
  ];
1252
880
  function TaggingPanel({ element, existingTag, onSave, onDelete, onClose }) {
1253
- const [description, setDescription] = useState4(existingTag?.description ?? "");
1254
- const [category, setCategory] = useState4(existingTag?.category ?? "other");
1255
- const inputRef = useRef5(null);
1256
- useEffect7(() => {
881
+ const [description, setDescription] = useState3(existingTag?.description ?? "");
882
+ const [category, setCategory] = useState3(existingTag?.category ?? "other");
883
+ const inputRef = useRef4(null);
884
+ useEffect6(() => {
1257
885
  inputRef.current?.focus();
1258
886
  }, []);
1259
887
  const handleSave = () => {
@@ -1480,10 +1108,10 @@ function findTagForElement(el, tagStore) {
1480
1108
  return void 0;
1481
1109
  }
1482
1110
  function StudioOverlay({ elements, tagStore }) {
1483
- const [selectedFingerprint, setSelectedFingerprint] = useState4(null);
1484
- const [positions, setPositions] = useState4(/* @__PURE__ */ new Map());
1485
- const liveElementsRef = useRef5(/* @__PURE__ */ new Map());
1486
- const updatePositions = useCallback4(() => {
1111
+ const [selectedFingerprint, setSelectedFingerprint] = useState3(null);
1112
+ const [positions, setPositions] = useState3(/* @__PURE__ */ new Map());
1113
+ const liveElementsRef = useRef4(/* @__PURE__ */ new Map());
1114
+ const updatePositions = useCallback3(() => {
1487
1115
  const newPositions = /* @__PURE__ */ new Map();
1488
1116
  const newLiveElements = /* @__PURE__ */ new Map();
1489
1117
  const knownFingerprints = new Set(elements.map((e) => e.fingerprint));
@@ -1518,7 +1146,7 @@ function StudioOverlay({ elements, tagStore }) {
1518
1146
  liveElementsRef.current = newLiveElements;
1519
1147
  setPositions(newPositions);
1520
1148
  }, [elements]);
1521
- useEffect7(() => {
1149
+ useEffect6(() => {
1522
1150
  updatePositions();
1523
1151
  const interval = setInterval(updatePositions, 1e3);
1524
1152
  window.addEventListener("scroll", updatePositions, { passive: true });
@@ -1662,12 +1290,23 @@ function StudioOverlay({ elements, tagStore }) {
1662
1290
  }
1663
1291
 
1664
1292
  // src/hooks/useBuiltinActions.ts
1665
- import { useEffect as useEffect8, useRef as useRef6 } from "react";
1293
+ import { useEffect as useEffect7, useRef as useRef5 } from "react";
1666
1294
  import { z as z2 } from "zod";
1667
1295
 
1668
1296
  // src/utils/screenshot.ts
1669
1297
  import html2canvas from "html2canvas";
1670
- async function captureScreenshot(selector) {
1298
+ var SCREENSHOT_SCALE_BY_RESOLUTION = {
1299
+ low: 0.75,
1300
+ medium: 1.25,
1301
+ high: 2
1302
+ };
1303
+ function normalizeScreenshotResolution(resolution) {
1304
+ return resolution === "low" || resolution === "high" || resolution === "medium" ? resolution : "medium";
1305
+ }
1306
+ function resolveScreenshotScale(resolution) {
1307
+ return SCREENSHOT_SCALE_BY_RESOLUTION[normalizeScreenshotResolution(resolution)];
1308
+ }
1309
+ async function captureScreenshot(selector, resolution) {
1671
1310
  const target = selector ? document.querySelector(selector) : document.documentElement;
1672
1311
  if (!target || !(target instanceof HTMLElement)) {
1673
1312
  throw new Error(`Screenshot target not found: ${selector ?? "document"}`);
@@ -1675,7 +1314,7 @@ async function captureScreenshot(selector) {
1675
1314
  const canvas = await html2canvas(target, {
1676
1315
  useCORS: true,
1677
1316
  allowTaint: true,
1678
- scale: Math.min(window.devicePixelRatio, 2),
1317
+ scale: resolveScreenshotScale(resolution),
1679
1318
  width: window.innerWidth,
1680
1319
  height: window.innerHeight,
1681
1320
  x: window.scrollX,
@@ -2138,14 +1777,15 @@ async function resolveWorkflowFromInput(getters, params) {
2138
1777
  return { workflow: ranked[0].workflow };
2139
1778
  }
2140
1779
  var screenshotSchema = z2.object({
2141
- selector: z2.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport.")
1780
+ selector: z2.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport."),
1781
+ resolution: z2.enum(["low", "medium", "high"]).optional().describe("Optional screenshot resolution. Use low for smaller/faster captures, medium for the default balance, or high for extra detail.")
2142
1782
  });
2143
1783
  var BUILTIN_SCREENSHOT_ACTION = {
2144
1784
  id: "take_screenshot",
2145
1785
  description: "Take a screenshot of the current page (or a specific element via CSS selector). Returns a base64 PNG image. Use when you need to see the current UI state, verify a visual change, or when the user asks you to look at the screen.",
2146
1786
  schema: screenshotSchema,
2147
1787
  execute: async (params) => {
2148
- return await captureScreenshot(params.selector);
1788
+ return await captureScreenshot(params.selector, params.resolution);
2149
1789
  }
2150
1790
  };
2151
1791
  var clickElementSchema = z2.object({
@@ -2502,18 +2142,18 @@ var BUILTIN_ACTION_IDS = {
2502
2142
  startWorkflow: "start_workflow"
2503
2143
  };
2504
2144
  function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile) {
2505
- const registeredRef = useRef6(false);
2506
- const tagStoreRef = useRef6(tagStore);
2145
+ const registeredRef = useRef5(false);
2146
+ const tagStoreRef = useRef5(tagStore);
2507
2147
  tagStoreRef.current = tagStore;
2508
- const serverUrlRef = useRef6(serverUrl);
2148
+ const serverUrlRef = useRef5(serverUrl);
2509
2149
  serverUrlRef.current = serverUrl;
2510
- const websiteIdRef = useRef6(websiteId);
2150
+ const websiteIdRef = useRef5(websiteId);
2511
2151
  websiteIdRef.current = websiteId;
2512
- const toursApiBaseRef = useRef6(toursApiBase);
2152
+ const toursApiBaseRef = useRef5(toursApiBase);
2513
2153
  toursApiBaseRef.current = toursApiBase;
2514
- const userProfileRef = useRef6(userProfile);
2154
+ const userProfileRef = useRef5(userProfile);
2515
2155
  userProfileRef.current = userProfile;
2516
- useEffect8(() => {
2156
+ useEffect7(() => {
2517
2157
  if (registeredRef.current) return;
2518
2158
  registeredRef.current = true;
2519
2159
  const getTagStore = () => tagStoreRef.current;
@@ -2868,7 +2508,7 @@ function readPreviewSessionSuppression() {
2868
2508
  }
2869
2509
 
2870
2510
  // src/hooks/useRunCommand.ts
2871
- import { useCallback as useCallback5, useContext as useContext2 } from "react";
2511
+ import { useCallback as useCallback4, useContext as useContext2 } from "react";
2872
2512
  function searchTaggedElementsForQuery(store, query, limit = 8) {
2873
2513
  const allTags = store.getAllTags();
2874
2514
  if (allTags.length === 0) return [];
@@ -2891,7 +2531,7 @@ function useRunCommand(serverUrlOverride) {
2891
2531
  const context = useContext2(ModelNexContext);
2892
2532
  const baseUrl = serverUrlOverride ?? context?.commandUrl ?? context?.serverUrl ?? DEFAULT_MODELNEX_SERVER_URL;
2893
2533
  const tagStore = context?.tagStore;
2894
- return useCallback5(
2534
+ return useCallback4(
2895
2535
  async (command, signal) => {
2896
2536
  const url = baseUrl.startsWith("/") ? `${window.location.origin}${baseUrl}/agent/command` : `${baseUrl}/agent/command`;
2897
2537
  const relevantTaggedElements = tagStore ? searchTaggedElementsForQuery(tagStore, command) : void 0;
@@ -2919,7 +2559,7 @@ function useRunCommand(serverUrlOverride) {
2919
2559
  }
2920
2560
 
2921
2561
  // src/ui-state.tsx
2922
- import { createContext as createContext2, useContext as useContext3, useState as useState5, useCallback as useCallback6, useMemo as useMemo2 } from "react";
2562
+ import { createContext as createContext2, useContext as useContext3, useState as useState4, useCallback as useCallback5, useMemo as useMemo2 } from "react";
2923
2563
  import { jsx as jsx2 } from "react/jsx-runtime";
2924
2564
  var UIStateContext = createContext2(null);
2925
2565
  function UIStateProvider({
@@ -2928,8 +2568,8 @@ function UIStateProvider({
2928
2568
  type = "ui",
2929
2569
  initialState = {}
2930
2570
  }) {
2931
- const [state, setStateInternal] = useState5(initialState);
2932
- const setState = useCallback6((update) => {
2571
+ const [state, setStateInternal] = useState4(initialState);
2572
+ const setState = useCallback5((update) => {
2933
2573
  setStateInternal((prev) => ({ ...prev, ...update }));
2934
2574
  }, []);
2935
2575
  const value = useMemo2(() => ({ state, setState }), [state, setState]);
@@ -2942,7 +2582,7 @@ function useUIState() {
2942
2582
  }
2943
2583
 
2944
2584
  // src/viewport.ts
2945
- import { useEffect as useEffect10, useRef as useRef7, useState as useState6 } from "react";
2585
+ import { useEffect as useEffect9, useRef as useRef6, useState as useState5 } from "react";
2946
2586
  var visibleIds = /* @__PURE__ */ new Set();
2947
2587
  var listeners = /* @__PURE__ */ new Set();
2948
2588
  function updateVisibility(id, visible) {
@@ -2951,9 +2591,9 @@ function updateVisibility(id, visible) {
2951
2591
  listeners.forEach((fn) => fn());
2952
2592
  }
2953
2593
  function useViewportTrack(id) {
2954
- const ref = useRef7(null);
2955
- const [, setTick] = useState6(0);
2956
- useEffect10(() => {
2594
+ const ref = useRef6(null);
2595
+ const [, setTick] = useState5(0);
2596
+ useEffect9(() => {
2957
2597
  const el = ref.current;
2958
2598
  if (!el) return;
2959
2599
  const obs = new IntersectionObserver(
@@ -2968,7 +2608,7 @@ function useViewportTrack(id) {
2968
2608
  updateVisibility(id, false);
2969
2609
  };
2970
2610
  }, [id]);
2971
- useEffect10(() => {
2611
+ useEffect9(() => {
2972
2612
  const fn = () => setTick((t) => t + 1);
2973
2613
  listeners.add(fn);
2974
2614
  return () => {
@@ -2978,8 +2618,8 @@ function useViewportTrack(id) {
2978
2618
  return ref;
2979
2619
  }
2980
2620
  function useVisibleIds() {
2981
- const [ids, setIds] = useState6([]);
2982
- useEffect10(() => {
2621
+ const [ids, setIds] = useState5([]);
2622
+ useEffect9(() => {
2983
2623
  const fn = () => setIds(Array.from(visibleIds));
2984
2624
  fn();
2985
2625
  listeners.add(fn);
@@ -2996,11 +2636,11 @@ function useAgentViewport(options = {}) {
2996
2636
  }
2997
2637
 
2998
2638
  // src/chat-bubble.tsx
2999
- import { useState as useState13, useRef as useRef13, useEffect as useEffect17, useContext as useContext5, useCallback as useCallback12, useMemo as useMemo3 } from "react";
2639
+ import { useState as useState12, useRef as useRef12, useEffect as useEffect16, useContext as useContext5, useCallback as useCallback11, useMemo as useMemo3 } from "react";
3000
2640
  import { createPortal, flushSync } from "react-dom";
3001
2641
 
3002
2642
  // src/hooks/useExperiencePlaybackController.ts
3003
- import { useCallback as useCallback8, useEffect as useEffect12, useRef as useRef9, useState as useState8 } from "react";
2643
+ import { useCallback as useCallback7, useEffect as useEffect11, useRef as useRef8, useState as useState7 } from "react";
3004
2644
 
3005
2645
  // src/utils/locationSignature.ts
3006
2646
  function getLocationSignature(locationLike) {
@@ -3009,7 +2649,7 @@ function getLocationSignature(locationLike) {
3009
2649
  }
3010
2650
 
3011
2651
  // src/hooks/useTourPlayback.ts
3012
- import { useState as useState7, useRef as useRef8, useCallback as useCallback7, useEffect as useEffect11, useContext as useContext4 } from "react";
2652
+ import { useState as useState6, useRef as useRef7, useCallback as useCallback6, useEffect as useEffect10, useContext as useContext4 } from "react";
3013
2653
 
3014
2654
  // src/utils/retryLookup.ts
3015
2655
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -3155,6 +2795,54 @@ function compactTourForTransport(tour) {
3155
2795
  };
3156
2796
  }
3157
2797
 
2798
+ // src/utils/tourSessionPersistence.ts
2799
+ var ACTIVE_TOUR_SESSION_STORAGE_KEY = "modelnex-active-tour-session";
2800
+ var ACTIVE_TOUR_SESSION_VERSION = 1;
2801
+ var ACTIVE_TOUR_SESSION_MAX_AGE_MS = 10 * 60 * 1e3;
2802
+ function canUseSessionStorage2() {
2803
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
2804
+ }
2805
+ function persistActiveTourSession(runId) {
2806
+ if (!canUseSessionStorage2() || !Number.isFinite(runId)) return;
2807
+ const session = {
2808
+ version: ACTIVE_TOUR_SESSION_VERSION,
2809
+ runId,
2810
+ savedAt: Date.now()
2811
+ };
2812
+ try {
2813
+ window.sessionStorage.setItem(ACTIVE_TOUR_SESSION_STORAGE_KEY, JSON.stringify(session));
2814
+ } catch {
2815
+ }
2816
+ }
2817
+ function clearPersistedActiveTourSession() {
2818
+ if (!canUseSessionStorage2()) return;
2819
+ try {
2820
+ window.sessionStorage.removeItem(ACTIVE_TOUR_SESSION_STORAGE_KEY);
2821
+ } catch {
2822
+ }
2823
+ }
2824
+ function readPersistedActiveTourSession() {
2825
+ if (!canUseSessionStorage2()) return null;
2826
+ try {
2827
+ const raw = window.sessionStorage.getItem(ACTIVE_TOUR_SESSION_STORAGE_KEY);
2828
+ if (!raw) return null;
2829
+ const parsed = JSON.parse(raw);
2830
+ const isValid = parsed && parsed.version === ACTIVE_TOUR_SESSION_VERSION && typeof parsed.runId === "number" && Number.isFinite(parsed.runId) && typeof parsed.savedAt === "number" && Number.isFinite(parsed.savedAt);
2831
+ if (!isValid) {
2832
+ clearPersistedActiveTourSession();
2833
+ return null;
2834
+ }
2835
+ if (Date.now() - parsed.savedAt > ACTIVE_TOUR_SESSION_MAX_AGE_MS) {
2836
+ clearPersistedActiveTourSession();
2837
+ return null;
2838
+ }
2839
+ return parsed;
2840
+ } catch {
2841
+ clearPersistedActiveTourSession();
2842
+ return null;
2843
+ }
2844
+ }
2845
+
3158
2846
  // src/utils/tour-playback-guards.ts
3159
2847
  var playbackOwners = /* @__PURE__ */ new Map();
3160
2848
  function shouldExecuteTourCommandBatch(isPlaybackActive) {
@@ -3727,57 +3415,58 @@ function useTourPlayback({
3727
3415
  enableAutoDiscovery = true
3728
3416
  }) {
3729
3417
  const locationSignature = typeof window === "undefined" ? "" : getLocationSignature(window.location);
3730
- const [isActive, setIsActive] = useState7(false);
3731
- const [currentStepIndex, setCurrentStepIndex] = useState7(0);
3732
- const [totalSteps, setTotalSteps] = useState7(0);
3733
- const [activeTour, setActiveTour] = useState7(null);
3734
- const [playbackState, setPlaybackState] = useState7("idle");
3735
- const [isReviewMode, setIsReviewMode] = useState7(false);
3736
- const [previewRunId, setPreviewRunId] = useState7(null);
3737
- const [reviewSubmitting, setReviewSubmitting] = useState7(false);
3738
- const [reviewStatusMessage, setReviewStatusMessage] = useState7(null);
3739
- const [pendingTour, setPendingTour] = useState7(null);
3740
- const [serverState, setServerState] = useState7(null);
3418
+ const [isActive, setIsActive] = useState6(false);
3419
+ const [currentStepIndex, setCurrentStepIndex] = useState6(0);
3420
+ const [totalSteps, setTotalSteps] = useState6(0);
3421
+ const [activeTour, setActiveTour] = useState6(null);
3422
+ const [playbackState, setPlaybackState] = useState6("idle");
3423
+ const [isReviewMode, setIsReviewMode] = useState6(false);
3424
+ const [previewRunId, setPreviewRunId] = useState6(null);
3425
+ const [reviewSubmitting, setReviewSubmitting] = useState6(false);
3426
+ const [reviewStatusMessage, setReviewStatusMessage] = useState6(null);
3427
+ const [pendingTour, setPendingTour] = useState6(null);
3428
+ const [serverState, setServerState] = useState6(null);
3741
3429
  const ctx = useContext4(ModelNexContext);
3742
3430
  const devMode = ctx?.devMode;
3743
- const devModeRef = useRef8(devMode);
3431
+ const devModeRef = useRef7(devMode);
3744
3432
  devModeRef.current = devMode;
3745
- const userProfileRef = useRef8(userProfile);
3433
+ const userProfileRef = useRef7(userProfile);
3746
3434
  userProfileRef.current = userProfile;
3747
- const experienceTypeRef = useRef8(experienceType);
3435
+ const experienceTypeRef = useRef7(experienceType);
3748
3436
  experienceTypeRef.current = experienceType;
3749
- const tourRef = useRef8(null);
3750
- const stepIndexRef = useRef8(0);
3751
- const skipRequestedRef = useRef8(false);
3752
- const advanceRequestedRef = useRef8(false);
3753
- const textInputResolveRef = useRef8(null);
3754
- const voiceInputResolveRef = useRef8(null);
3755
- const askOrFillRef = useRef8(null);
3756
- const pendingManualWaitCleanupRef = useRef8(null);
3757
- const pendingManualInputSyncRef = useRef8(null);
3758
- const llmRespondingRef = useRef8(false);
3759
- const interruptedForQuestionRef = useRef8(false);
3760
- const pendingInputBufRef = useRef8(null);
3761
- const activeExecutionTokenRef = useRef8(0);
3762
- const commandInFlightRef = useRef8(false);
3763
- const reviewModeRef = useRef8(false);
3764
- const previewRunIdRef = useRef8(null);
3765
- const toursApiBaseRef = useRef8(toursApiBase);
3766
- const pendingTourRef = useRef8(null);
3767
- const pendingTourOptionsRef = useRef8(null);
3768
- const showCaptionsRef = useRef8(showCaptions);
3769
- const runIdRef = useRef8(null);
3770
- const turnIdRef = useRef8(null);
3771
- const startRequestedRef = useRef8(false);
3772
- const playbackOwnerIdRef = useRef8(`playback-owner-${Math.random().toString(36).slice(2, 10)}`);
3773
- const claimedPlaybackOwnerKeyRef = useRef8(null);
3774
- const socketRef = useRef8(null);
3775
- const socketIdRef = useRef8(socketId);
3776
- const commandUrlRef = useRef8(commandUrl);
3777
- const websiteIdRef = useRef8(websiteId);
3778
- const onStepChangeRef = useRef8(onStepChange);
3779
- const isActiveRef = useRef8(false);
3780
- const activeCommandBatchIdRef = useRef8(null);
3437
+ const tourRef = useRef7(null);
3438
+ const stepIndexRef = useRef7(0);
3439
+ const skipRequestedRef = useRef7(false);
3440
+ const advanceRequestedRef = useRef7(false);
3441
+ const textInputResolveRef = useRef7(null);
3442
+ const voiceInputResolveRef = useRef7(null);
3443
+ const askOrFillRef = useRef7(null);
3444
+ const pendingManualWaitCleanupRef = useRef7(null);
3445
+ const pendingManualInputSyncRef = useRef7(null);
3446
+ const llmRespondingRef = useRef7(false);
3447
+ const interruptedForQuestionRef = useRef7(false);
3448
+ const pendingInputBufRef = useRef7(null);
3449
+ const activeExecutionTokenRef = useRef7(0);
3450
+ const commandInFlightRef = useRef7(false);
3451
+ const reviewModeRef = useRef7(false);
3452
+ const previewRunIdRef = useRef7(null);
3453
+ const toursApiBaseRef = useRef7(toursApiBase);
3454
+ const pendingTourRef = useRef7(null);
3455
+ const pendingTourOptionsRef = useRef7(null);
3456
+ const showCaptionsRef = useRef7(showCaptions);
3457
+ const initialPersistedTourSessionRef = useRef7(readPersistedActiveTourSession());
3458
+ const runIdRef = useRef7(initialPersistedTourSessionRef.current?.runId ?? null);
3459
+ const turnIdRef = useRef7(null);
3460
+ const startRequestedRef = useRef7(false);
3461
+ const playbackOwnerIdRef = useRef7(`playback-owner-${Math.random().toString(36).slice(2, 10)}`);
3462
+ const claimedPlaybackOwnerKeyRef = useRef7(null);
3463
+ const socketRef = useRef7(null);
3464
+ const socketIdRef = useRef7(socketId);
3465
+ const commandUrlRef = useRef7(commandUrl);
3466
+ const websiteIdRef = useRef7(websiteId);
3467
+ const onStepChangeRef = useRef7(onStepChange);
3468
+ const isActiveRef = useRef7(false);
3469
+ const activeCommandBatchIdRef = useRef7(null);
3781
3470
  socketIdRef.current = socketId;
3782
3471
  commandUrlRef.current = commandUrl;
3783
3472
  websiteIdRef.current = websiteId;
@@ -3788,13 +3477,26 @@ function useTourPlayback({
3788
3477
  toursApiBaseRef.current = toursApiBase;
3789
3478
  pendingTourRef.current = pendingTour;
3790
3479
  showCaptionsRef.current = showCaptions;
3791
- const releasePlaybackOwnership = useCallback7(() => {
3480
+ const releasePlaybackOwnership = useCallback6(() => {
3792
3481
  const claimedKey = claimedPlaybackOwnerKeyRef.current;
3793
3482
  if (!claimedKey) return;
3794
3483
  releaseTourPlaybackOwnership(claimedKey, playbackOwnerIdRef.current);
3795
3484
  claimedPlaybackOwnerKeyRef.current = null;
3796
3485
  }, []);
3797
- useEffect11(() => {
3486
+ const emitTourInit = useCallback6((socket, currentWebsiteId, profile) => {
3487
+ if (!currentWebsiteId || !profile?.type) return;
3488
+ const persistedRunId = runIdRef.current ?? readPersistedActiveTourSession()?.runId ?? null;
3489
+ const payload = {
3490
+ websiteId: currentWebsiteId,
3491
+ userId: profile.userId,
3492
+ userType: profile.type
3493
+ };
3494
+ if (typeof persistedRunId === "number") {
3495
+ payload.resumeRunId = persistedRunId;
3496
+ }
3497
+ emitSocketEvent(socket, "tour:init", payload);
3498
+ }, []);
3499
+ useEffect10(() => {
3798
3500
  if (disabled) return;
3799
3501
  if (typeof window === "undefined") return;
3800
3502
  const socket = tourSocketPool.acquire(serverUrl);
@@ -3805,9 +3507,7 @@ function useTourPlayback({
3805
3507
  }, { devMode: devModeRef.current });
3806
3508
  const profile = userProfileRef.current;
3807
3509
  const currentWebsiteId = websiteIdRef.current;
3808
- if (currentWebsiteId && profile?.type) {
3809
- emitSocketEvent(socket, "tour:init", { websiteId: currentWebsiteId, userId: profile.userId, userType: profile.type });
3810
- }
3510
+ emitTourInit(socket, currentWebsiteId, profile);
3811
3511
  };
3812
3512
  const handleServerState = (payload) => {
3813
3513
  if (typeof payload?.runId === "number") {
@@ -3816,6 +3516,11 @@ function useTourPlayback({
3816
3516
  if (typeof payload?.turnId === "string" || payload?.turnId === null) {
3817
3517
  turnIdRef.current = payload.turnId ?? null;
3818
3518
  }
3519
+ if (payload?.isActive && typeof payload?.runId === "number") {
3520
+ persistActiveTourSession(payload.runId);
3521
+ } else if (payload?.isActive === false) {
3522
+ clearPersistedActiveTourSession();
3523
+ }
3819
3524
  setServerState(payload);
3820
3525
  };
3821
3526
  const handleCommandCancel = (payload) => {
@@ -3940,7 +3645,7 @@ function useTourPlayback({
3940
3645
  resolve: async () => {
3941
3646
  let targetEl = null;
3942
3647
  if (params.uid) {
3943
- const { getElementByUid } = await import("./aom-LJNCLNXL.mjs");
3648
+ const { getElementByUid } = await import("./aom-SP2LMWQI.mjs");
3944
3649
  targetEl = getElementByUid(params.uid);
3945
3650
  }
3946
3651
  if (!targetEl) {
@@ -4079,19 +3784,9 @@ function useTourPlayback({
4079
3784
  return { result: value };
4080
3785
  }
4081
3786
  if (action.type === "take_screenshot") {
4082
- const html2canvasModule = await import("html2canvas");
4083
- const html2canvas2 = html2canvasModule.default;
4084
- const canvas = await html2canvas2(document.body, {
4085
- useCORS: true,
4086
- allowTaint: true,
4087
- scale: Math.min(window.devicePixelRatio, 2),
4088
- width: window.innerWidth,
4089
- height: window.innerHeight,
4090
- x: window.scrollX,
4091
- y: window.scrollY,
4092
- logging: false
4093
- });
4094
- return { result: canvas.toDataURL("image/png") };
3787
+ const selector = typeof action.params?.selector === "string" ? action.params.selector : void 0;
3788
+ const resolution = typeof action.params?.resolution === "string" ? action.params.resolution : void 0;
3789
+ return { result: await captureScreenshot(selector, resolution) };
4095
3790
  }
4096
3791
  if (action.type === "navigate_to_url") {
4097
3792
  const nextUrl = typeof action.params?.url === "string" ? action.params.url : "";
@@ -4383,6 +4078,9 @@ function useTourPlayback({
4383
4078
  const handleTourStart = async (tourData) => {
4384
4079
  if (isActiveRef.current) return;
4385
4080
  runIdRef.current = typeof tourData.runId === "number" ? tourData.runId : runIdRef.current;
4081
+ if (typeof runIdRef.current === "number") {
4082
+ persistActiveTourSession(runIdRef.current);
4083
+ }
4386
4084
  const tour = tourData.tourContext ?? tourRef.current;
4387
4085
  const expType = experienceTypeRef.current;
4388
4086
  if (tour?.type && tour.type !== expType) {
@@ -4425,7 +4123,7 @@ function useTourPlayback({
4425
4123
  void recordTourEvent(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, "started", websiteId);
4426
4124
  }
4427
4125
  try {
4428
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4126
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-SP2LMWQI.mjs");
4429
4127
  const aom = generateMinifiedAOM2();
4430
4128
  if (socketRef.current === socket) {
4431
4129
  emitSocketEvent(socket, "tour:sync_dom", {
@@ -4458,6 +4156,7 @@ function useTourPlayback({
4458
4156
  }
4459
4157
  };
4460
4158
  const handleTourEndEvent = () => {
4159
+ clearPersistedActiveTourSession();
4461
4160
  setServerState((prev) => prev ? { ...prev, isActive: false, phase: "completed" } : prev);
4462
4161
  handleTourEnd();
4463
4162
  };
@@ -4507,23 +4206,23 @@ function useTourPlayback({
4507
4206
  releasePlaybackOwnership();
4508
4207
  tourSocketPool.release(serverUrl, toClose);
4509
4208
  };
4510
- }, [serverUrl, disabled, releasePlaybackOwnership]);
4511
- useEffect11(() => {
4209
+ }, [serverUrl, disabled, emitTourInit, releasePlaybackOwnership]);
4210
+ useEffect10(() => {
4512
4211
  if (disabled) return;
4513
4212
  const s = socketRef.current;
4514
4213
  const profile = userProfile;
4515
4214
  if (!websiteId || !profile?.type) return;
4516
4215
  const timer = setTimeout(() => {
4517
- emitSocketEvent(s, "tour:init", { websiteId, userId: profile.userId, userType: profile.type });
4216
+ emitTourInit(s, websiteId, profile);
4518
4217
  }, 150);
4519
4218
  return () => clearTimeout(timer);
4520
- }, [disabled, websiteId, userProfile?.userId, userProfile?.type]);
4521
- useEffect11(() => {
4219
+ }, [disabled, emitTourInit, websiteId, userProfile?.userId, userProfile?.type]);
4220
+ useEffect10(() => {
4522
4221
  if (!showCaptions || !isReviewMode) {
4523
4222
  removeCaption();
4524
4223
  }
4525
4224
  }, [showCaptions, isReviewMode]);
4526
- useEffect11(() => {
4225
+ useEffect10(() => {
4527
4226
  if (!isActiveRef.current) return;
4528
4227
  emitSocketEvent(socketRef.current, "tour:client_state", {
4529
4228
  runId: runIdRef.current,
@@ -4537,9 +4236,9 @@ function useTourPlayback({
4537
4236
  awaitingVoiceResolve: Boolean(voiceInputResolveRef.current)
4538
4237
  });
4539
4238
  }, [isActive, playbackState, voice.isListening, voice.isSpeaking]);
4540
- const syncAOM = useCallback7(async () => {
4239
+ const syncAOM = useCallback6(async () => {
4541
4240
  if (!isActiveRef.current) return;
4542
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4241
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-SP2LMWQI.mjs");
4543
4242
  const aom = generateMinifiedAOM2();
4544
4243
  emitSocketEvent(socketRef.current, "tour:sync_dom", {
4545
4244
  url: window.location.pathname + window.location.search + window.location.hash,
@@ -4547,7 +4246,7 @@ function useTourPlayback({
4547
4246
  domSummary: captureDomSummary()
4548
4247
  });
4549
4248
  }, []);
4550
- const scheduleManualInputSync = useCallback7(() => {
4249
+ const scheduleManualInputSync = useCallback6(() => {
4551
4250
  if (pendingManualInputSyncRef.current) {
4552
4251
  clearTimeout(pendingManualInputSyncRef.current);
4553
4252
  }
@@ -4559,13 +4258,13 @@ function useTourPlayback({
4559
4258
  await syncAOM();
4560
4259
  }, 150);
4561
4260
  }, [syncAOM]);
4562
- const clearPendingManualInputSync = useCallback7(() => {
4261
+ const clearPendingManualInputSync = useCallback6(() => {
4563
4262
  if (pendingManualInputSyncRef.current) {
4564
4263
  clearTimeout(pendingManualInputSyncRef.current);
4565
4264
  pendingManualInputSyncRef.current = null;
4566
4265
  }
4567
4266
  }, []);
4568
- const interruptExecution = useCallback7((transcript) => {
4267
+ const interruptExecution = useCallback6((transcript) => {
4569
4268
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4570
4269
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
4571
4270
  interruptedForQuestionRef.current = true;
@@ -4593,7 +4292,7 @@ function useTourPlayback({
4593
4292
  setPlaybackState("thinking");
4594
4293
  return true;
4595
4294
  }, [voice]);
4596
- const stopTour = useCallback7(() => {
4295
+ const stopTour = useCallback6(() => {
4597
4296
  skipRequestedRef.current = true;
4598
4297
  isActiveRef.current = false;
4599
4298
  startRequestedRef.current = false;
@@ -4636,6 +4335,7 @@ function useTourPlayback({
4636
4335
  tourRef.current = null;
4637
4336
  setPlaybackState("idle");
4638
4337
  setServerState(null);
4338
+ clearPersistedActiveTourSession();
4639
4339
  runIdRef.current = null;
4640
4340
  turnIdRef.current = null;
4641
4341
  setPreviewRunId(null);
@@ -4643,7 +4343,7 @@ function useTourPlayback({
4643
4343
  pendingInputBufRef.current = null;
4644
4344
  onTourEnd?.();
4645
4345
  }, [voice, onTourEnd, serverUrl, websiteId, releasePlaybackOwnership]);
4646
- const handleTourEnd = useCallback7(() => {
4346
+ const handleTourEnd = useCallback6(() => {
4647
4347
  const endingTourId = tourRef.current?.id;
4648
4348
  const endingPreviewRunId = previewRunIdRef.current;
4649
4349
  const endingStepOrder = stepIndexRef.current;
@@ -4665,6 +4365,7 @@ function useTourPlayback({
4665
4365
  tourRef.current = null;
4666
4366
  setPlaybackState("idle");
4667
4367
  setServerState(null);
4368
+ clearPersistedActiveTourSession();
4668
4369
  runIdRef.current = null;
4669
4370
  turnIdRef.current = null;
4670
4371
  setPreviewRunId(null);
@@ -4689,7 +4390,7 @@ function useTourPlayback({
4689
4390
  }
4690
4391
  onTourEnd?.();
4691
4392
  }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId, releasePlaybackOwnership]);
4692
- const runTour = useCallback7(async (tour, options) => {
4393
+ const runTour = useCallback6(async (tour, options) => {
4693
4394
  if (!shouldAcceptTourStart({
4694
4395
  isPlaybackActive: isActiveRef.current,
4695
4396
  startRequested: startRequestedRef.current
@@ -4754,7 +4455,7 @@ function useTourPlayback({
4754
4455
  tourContext: compactTourForTransport(tour)
4755
4456
  });
4756
4457
  }, [serverUrl, websiteId]);
4757
- useEffect11(() => {
4458
+ useEffect10(() => {
4758
4459
  if (!shouldRunTourAutoDiscovery({
4759
4460
  enableAutoDiscovery,
4760
4461
  disabled,
@@ -4818,7 +4519,7 @@ function useTourPlayback({
4818
4519
  cancelled = true;
4819
4520
  };
4820
4521
  }, [serverUrl, toursApiBase, disabled, websiteId, experienceType, enableAutoDiscovery, locationSignature]);
4821
- useEffect11(() => {
4522
+ useEffect10(() => {
4822
4523
  if (!shouldRunTourAutoDiscovery({
4823
4524
  enableAutoDiscovery,
4824
4525
  disabled,
@@ -4865,22 +4566,22 @@ function useTourPlayback({
4865
4566
  clearTimeout(timer);
4866
4567
  };
4867
4568
  }, [websiteId, serverUrl, toursApiBase, disabled, experienceType, userProfile?.userId, userProfile?.type, userProfile?.isNewUser, enableAutoDiscovery, locationSignature]);
4868
- useEffect11(() => {
4569
+ useEffect10(() => {
4869
4570
  if (!disabled || !isActiveRef.current) return;
4870
4571
  stopTour();
4871
4572
  }, [disabled, stopTour]);
4872
- const startTour = useCallback7((tour, options) => {
4573
+ const startTour = useCallback6((tour, options) => {
4873
4574
  if (disabled) return;
4874
4575
  void runTour(tour, options);
4875
4576
  }, [disabled, runTour]);
4876
- const acceptPendingTour = useCallback7(() => {
4577
+ const acceptPendingTour = useCallback6(() => {
4877
4578
  const tour = pendingTourRef.current;
4878
4579
  if (!tour || disabled) return;
4879
4580
  const opts = pendingTourOptionsRef.current;
4880
4581
  pendingTourOptionsRef.current = null;
4881
4582
  void runTour(tour, opts ?? void 0);
4882
4583
  }, [disabled, runTour]);
4883
- const dismissPendingTour = useCallback7(() => {
4584
+ const dismissPendingTour = useCallback6(() => {
4884
4585
  const tour = pendingTourRef.current;
4885
4586
  setPendingTour(null);
4886
4587
  pendingTourRef.current = null;
@@ -4888,7 +4589,7 @@ function useTourPlayback({
4888
4589
  if (!tour || !userProfile?.userId) return;
4889
4590
  void markTourDismissed(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, experienceType, websiteId);
4890
4591
  }, [experienceType, serverUrl, userProfile, websiteId]);
4891
- const submitReviewFeedbackAction = useCallback7(async (utterance, apply = false) => {
4592
+ const submitReviewFeedbackAction = useCallback6(async (utterance, apply = false) => {
4892
4593
  const trimmed = utterance.trim();
4893
4594
  const currentTour = tourRef.current;
4894
4595
  const currentPreviewRunId = previewRunIdRef.current;
@@ -4928,10 +4629,10 @@ function useTourPlayback({
4928
4629
  setReviewSubmitting(false);
4929
4630
  }
4930
4631
  }, [playbackState, serverUrl, websiteId]);
4931
- const advanceStep = useCallback7(() => {
4632
+ const advanceStep = useCallback6(() => {
4932
4633
  advanceRequestedRef.current = true;
4933
4634
  }, []);
4934
- const skipTour = useCallback7(() => {
4635
+ const skipTour = useCallback6(() => {
4935
4636
  skipRequestedRef.current = true;
4936
4637
  voiceInputResolveRef.current?.("");
4937
4638
  voiceInputResolveRef.current = null;
@@ -4941,19 +4642,19 @@ function useTourPlayback({
4941
4642
  askOrFillRef.current = null;
4942
4643
  stopTour();
4943
4644
  }, [stopTour]);
4944
- const pauseTour = useCallback7(() => {
4645
+ const pauseTour = useCallback6(() => {
4945
4646
  if (isSocketWritable(socketRef.current) && isActiveRef.current) {
4946
4647
  emitSocketEvent(socketRef.current, "tour:pause");
4947
4648
  setPlaybackState("paused");
4948
4649
  }
4949
4650
  }, []);
4950
- const resumeTour = useCallback7(() => {
4651
+ const resumeTour = useCallback6(() => {
4951
4652
  if (isSocketWritable(socketRef.current) && isActiveRef.current) {
4952
4653
  emitSocketEvent(socketRef.current, "tour:resume");
4953
4654
  setPlaybackState("executing");
4954
4655
  }
4955
4656
  }, []);
4956
- const repeatStep = useCallback7(() => {
4657
+ const repeatStep = useCallback6(() => {
4957
4658
  const tour = tourRef.current;
4958
4659
  const step = tour?.steps[stepIndexRef.current];
4959
4660
  if (step) {
@@ -4961,7 +4662,7 @@ function useTourPlayback({
4961
4662
  voice.speak(text, tour?.voice.ttsVoice);
4962
4663
  }
4963
4664
  }, [voice]);
4964
- const handleVoiceInput = useCallback7((transcript) => {
4665
+ const handleVoiceInput = useCallback6((transcript) => {
4965
4666
  const text = transcript.trim();
4966
4667
  emitSdkDebugLog("[TourAgent] Voice input received", {
4967
4668
  textLength: text.length
@@ -4989,7 +4690,7 @@ function useTourPlayback({
4989
4690
  pendingInputBufRef.current = text;
4990
4691
  }
4991
4692
  }, [interruptExecution]);
4992
- const handleTextInput = useCallback7((text) => {
4693
+ const handleTextInput = useCallback6((text) => {
4993
4694
  if (voiceInputResolveRef.current) {
4994
4695
  const resolve = voiceInputResolveRef.current;
4995
4696
  voiceInputResolveRef.current = null;
@@ -5002,7 +4703,7 @@ function useTourPlayback({
5002
4703
  handleVoiceInput(text.trim());
5003
4704
  }
5004
4705
  }, [handleVoiceInput]);
5005
- useEffect11(() => {
4706
+ useEffect10(() => {
5006
4707
  return () => {
5007
4708
  removeHighlight();
5008
4709
  removeCaption();
@@ -5083,14 +4784,14 @@ function useExperiencePlaybackController({
5083
4784
  initialExperienceType = "tour"
5084
4785
  }) {
5085
4786
  const locationSignature = typeof window === "undefined" ? "" : getLocationSignature(window.location);
5086
- const [activeExperienceType, setActiveExperienceType] = useState8(initialExperienceType);
5087
- const [startingExperienceType, setStartingExperienceType] = useState8(null);
5088
- const [pendingPrompt, setPendingPrompt] = useState8(null);
5089
- const pendingPromptRef = useRef9(null);
5090
- const queuedStartRef = useRef9(null);
5091
- const bufferedVoiceInputsRef = useRef9([]);
5092
- const previewDiscoveryInFlightRef = useRef9(false);
5093
- const previewSessionRef = useRef9(readPreviewSessionSuppression());
4787
+ const [activeExperienceType, setActiveExperienceType] = useState7(initialExperienceType);
4788
+ const [startingExperienceType, setStartingExperienceType] = useState7(null);
4789
+ const [pendingPrompt, setPendingPrompt] = useState7(null);
4790
+ const pendingPromptRef = useRef8(null);
4791
+ const queuedStartRef = useRef8(null);
4792
+ const bufferedVoiceInputsRef = useRef8([]);
4793
+ const previewDiscoveryInFlightRef = useRef8(false);
4794
+ const previewSessionRef = useRef8(readPreviewSessionSuppression());
5094
4795
  pendingPromptRef.current = pendingPrompt;
5095
4796
  const playback = useTourPlayback({
5096
4797
  serverUrl,
@@ -5109,7 +4810,7 @@ function useExperiencePlaybackController({
5109
4810
  showCaptions,
5110
4811
  enableAutoDiscovery: false
5111
4812
  });
5112
- const queueExperienceStart = useCallback8((tour, experienceType, options) => {
4813
+ const queueExperienceStart = useCallback7((tour, experienceType, options) => {
5113
4814
  setPendingPrompt(null);
5114
4815
  pendingPromptRef.current = null;
5115
4816
  setStartingExperienceType(experienceType);
@@ -5124,17 +4825,17 @@ function useExperiencePlaybackController({
5124
4825
  }
5125
4826
  playback.startTour(tour, options);
5126
4827
  }, [activeExperienceType, playback]);
5127
- const startExperience = useCallback8((tour, experienceType, options) => {
4828
+ const startExperience = useCallback7((tour, experienceType, options) => {
5128
4829
  const resolvedExperienceType = experienceType ?? tour.type ?? activeExperienceType;
5129
4830
  queueExperienceStart(tour, resolvedExperienceType, options);
5130
4831
  }, [activeExperienceType, queueExperienceStart]);
5131
- const acceptPendingPrompt = useCallback8((experienceType) => {
4832
+ const acceptPendingPrompt = useCallback7((experienceType) => {
5132
4833
  const prompt = pendingPromptRef.current;
5133
4834
  if (!prompt) return;
5134
4835
  if (experienceType && prompt.experienceType !== experienceType) return;
5135
4836
  queueExperienceStart(prompt.tour, prompt.experienceType, prompt.options);
5136
4837
  }, [queueExperienceStart]);
5137
- const dismissPendingPrompt = useCallback8((experienceType) => {
4838
+ const dismissPendingPrompt = useCallback7((experienceType) => {
5138
4839
  const prompt = pendingPromptRef.current;
5139
4840
  if (!prompt) return;
5140
4841
  if (experienceType && prompt.experienceType !== experienceType) return;
@@ -5157,7 +4858,7 @@ function useExperiencePlaybackController({
5157
4858
  websiteId
5158
4859
  );
5159
4860
  }, [serverUrl, toursApiBase, userProfile, websiteId]);
5160
- const handleVoiceInput = useCallback8((transcript, experienceType) => {
4861
+ const handleVoiceInput = useCallback7((transcript, experienceType) => {
5161
4862
  const trimmed = transcript.trim();
5162
4863
  if (!trimmed) return;
5163
4864
  const targetExperienceType = experienceType ?? activeExperienceType;
@@ -5177,12 +4878,12 @@ function useExperiencePlaybackController({
5177
4878
  }
5178
4879
  playback.handleVoiceInput(trimmed);
5179
4880
  }, [activeExperienceType, playback]);
5180
- useEffect12(() => {
4881
+ useEffect11(() => {
5181
4882
  if (playback.isActive && startingExperienceType !== null) {
5182
4883
  setStartingExperienceType(null);
5183
4884
  }
5184
4885
  }, [playback.isActive, startingExperienceType]);
5185
- useEffect12(() => {
4886
+ useEffect11(() => {
5186
4887
  const queuedStart = queuedStartRef.current;
5187
4888
  if (!queuedStart) return;
5188
4889
  if (playback.isActive) return;
@@ -5190,14 +4891,14 @@ function useExperiencePlaybackController({
5190
4891
  queuedStartRef.current = null;
5191
4892
  playback.startTour(queuedStart.tour, queuedStart.options);
5192
4893
  }, [activeExperienceType, playback]);
5193
- useEffect12(() => {
4894
+ useEffect11(() => {
5194
4895
  if (!playback.isActive) return;
5195
4896
  const readyInputs = bufferedVoiceInputsRef.current.filter((item) => item.experienceType === activeExperienceType);
5196
4897
  if (readyInputs.length === 0) return;
5197
4898
  bufferedVoiceInputsRef.current = bufferedVoiceInputsRef.current.filter((item) => item.experienceType !== activeExperienceType);
5198
4899
  readyInputs.forEach((item) => playback.handleVoiceInput(item.transcript));
5199
4900
  }, [activeExperienceType, playback]);
5200
- useEffect12(() => {
4901
+ useEffect11(() => {
5201
4902
  if (!shouldDiscoverDraftPreview({
5202
4903
  disabled,
5203
4904
  hasPendingPrompt: Boolean(pendingPromptRef.current),
@@ -5279,7 +4980,7 @@ function useExperiencePlaybackController({
5279
4980
  previewDiscoveryInFlightRef.current = false;
5280
4981
  };
5281
4982
  }, [disabled, playback.isActive, queueExperienceStart, serverUrl, startingExperienceType, toursApiBase, websiteId, locationSignature]);
5282
- useEffect12(() => {
4983
+ useEffect11(() => {
5283
4984
  if (!shouldDiscoverEligibleTours({
5284
4985
  disabled,
5285
4986
  hasPendingPrompt: Boolean(pendingPromptRef.current),
@@ -5343,7 +5044,7 @@ function useExperiencePlaybackController({
5343
5044
  }
5344
5045
 
5345
5046
  // src/hooks/useVoice.ts
5346
- import { useState as useState9, useRef as useRef10, useCallback as useCallback9, useEffect as useEffect13 } from "react";
5047
+ import { useState as useState8, useRef as useRef9, useCallback as useCallback8, useEffect as useEffect12 } from "react";
5347
5048
 
5348
5049
  // src/utils/webrtc-aec.ts
5349
5050
  function isWebRtcAecSupported() {
@@ -5712,36 +5413,36 @@ function isFatalSpeechError(error) {
5712
5413
  return error === "not-allowed" || error === "service-not-allowed" || error === "audio-capture";
5713
5414
  }
5714
5415
  function useVoice(serverUrl) {
5715
- const [isSpeaking, setIsSpeaking] = useState9(false);
5716
- const [isListening, setIsListening] = useState9(false);
5717
- const [isMuted, setIsMuted] = useState9(false);
5718
- const audioRef = useRef10(null);
5719
- const audioBlobUrlRef = useRef10(null);
5720
- const speakResolveRef = useRef10(null);
5721
- const recognitionRef = useRef10(null);
5722
- const isMutedRef = useRef10(false);
5723
- const sttCallbacksRef = useRef10(null);
5724
- const stripIndicesRef = useRef10({});
5725
- const accumulatedRef = useRef10([]);
5726
- const interimDebounceRef = useRef10(null);
5727
- const lastInterimRef = useRef10("");
5728
- const lastDeliveredRef = useRef10("");
5729
- const isSpeakingRef = useRef10(false);
5730
- const speechEndTimeRef = useRef10(0);
5731
- const recentTtsRef = useRef10([]);
5732
- const prefetchedSpeechRef = useRef10(/* @__PURE__ */ new Map());
5733
- const nearEndTimeoutRef = useRef10(null);
5734
- const queuePromiseRef = useRef10(Promise.resolve());
5735
- const queueSeqRef = useRef10(0);
5736
- const lastStopSeqRef = useRef10(0);
5737
- const listeningSessionIdRef = useRef10(null);
5738
- const listeningStartedAtRef = useRef10(0);
5739
- const loopbackRef = useRef10(null);
5740
- const aecActiveRef = useRef10(false);
5741
- const mediaRecorderRef = useRef10(null);
5742
- const micStreamRef = useRef10(null);
5743
- const sttSocketRef = useRef10(null);
5744
- const stopLiveSttTransport = useCallback9(() => {
5416
+ const [isSpeaking, setIsSpeaking] = useState8(false);
5417
+ const [isListening, setIsListening] = useState8(false);
5418
+ const [isMuted, setIsMuted] = useState8(false);
5419
+ const audioRef = useRef9(null);
5420
+ const audioBlobUrlRef = useRef9(null);
5421
+ const speakResolveRef = useRef9(null);
5422
+ const recognitionRef = useRef9(null);
5423
+ const isMutedRef = useRef9(false);
5424
+ const sttCallbacksRef = useRef9(null);
5425
+ const stripIndicesRef = useRef9({});
5426
+ const accumulatedRef = useRef9([]);
5427
+ const interimDebounceRef = useRef9(null);
5428
+ const lastInterimRef = useRef9("");
5429
+ const lastDeliveredRef = useRef9("");
5430
+ const isSpeakingRef = useRef9(false);
5431
+ const speechEndTimeRef = useRef9(0);
5432
+ const recentTtsRef = useRef9([]);
5433
+ const prefetchedSpeechRef = useRef9(/* @__PURE__ */ new Map());
5434
+ const nearEndTimeoutRef = useRef9(null);
5435
+ const queuePromiseRef = useRef9(Promise.resolve());
5436
+ const queueSeqRef = useRef9(0);
5437
+ const lastStopSeqRef = useRef9(0);
5438
+ const listeningSessionIdRef = useRef9(null);
5439
+ const listeningStartedAtRef = useRef9(0);
5440
+ const loopbackRef = useRef9(null);
5441
+ const aecActiveRef = useRef9(false);
5442
+ const mediaRecorderRef = useRef9(null);
5443
+ const micStreamRef = useRef9(null);
5444
+ const sttSocketRef = useRef9(null);
5445
+ const stopLiveSttTransport = useCallback8(() => {
5745
5446
  const socket = sttSocketRef.current;
5746
5447
  sttSocketRef.current = null;
5747
5448
  if (socket) {
@@ -5770,16 +5471,16 @@ function useVoice(serverUrl) {
5770
5471
  }
5771
5472
  mediaRecorderRef.current = null;
5772
5473
  }, []);
5773
- useEffect13(() => {
5474
+ useEffect12(() => {
5774
5475
  isMutedRef.current = isMuted;
5775
5476
  }, [isMuted]);
5776
- useEffect13(() => {
5477
+ useEffect12(() => {
5777
5478
  isSpeakingRef.current = isSpeaking;
5778
5479
  }, [isSpeaking]);
5779
5480
  const webSpeechSupported = typeof window !== "undefined" && !!(window.SpeechRecognition || window.webkitSpeechRecognition);
5780
5481
  const mediaCaptureSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined";
5781
5482
  const sttSupported = typeof window !== "undefined" && (mediaCaptureSupported || webSpeechSupported);
5782
- const stopSpeaking = useCallback9(() => {
5483
+ const stopSpeaking = useCallback8(() => {
5783
5484
  emitVoiceDebug("tts_stop", {
5784
5485
  queueSeq: queueSeqRef.current,
5785
5486
  wasSpeaking: isSpeakingRef.current
@@ -5806,8 +5507,8 @@ function useVoice(serverUrl) {
5806
5507
  speakResolveRef.current?.();
5807
5508
  speakResolveRef.current = null;
5808
5509
  }, []);
5809
- const getSpeechCacheKey = useCallback9((text, voiceId) => `${voiceId}::${text}`, []);
5810
- const loadAudioDurationMs = useCallback9(async (url, fallbackText) => {
5510
+ const getSpeechCacheKey = useCallback8((text, voiceId) => `${voiceId}::${text}`, []);
5511
+ const loadAudioDurationMs = useCallback8(async (url, fallbackText) => {
5811
5512
  try {
5812
5513
  const probe = new Audio();
5813
5514
  probe.preload = "metadata";
@@ -5829,7 +5530,7 @@ function useVoice(serverUrl) {
5829
5530
  return estimateSpeechDurationMs(fallbackText);
5830
5531
  }
5831
5532
  }, []);
5832
- const fetchSpeechClip = useCallback9(async (text, voiceId) => {
5533
+ const fetchSpeechClip = useCallback8(async (text, voiceId) => {
5833
5534
  const baseUrl = serverUrl.replace(/\/$/, "");
5834
5535
  const debugEnabled = isVoiceDebugEnabled();
5835
5536
  const requestId = createVoiceDebugId("tts");
@@ -5891,7 +5592,7 @@ function useVoice(serverUrl) {
5891
5592
  });
5892
5593
  return { url, durationMs, requestId, buffered: true };
5893
5594
  }, [loadAudioDurationMs, serverUrl]);
5894
- const createStreamingSpeechClipForPlayback = useCallback9((text, voiceId) => {
5595
+ const createStreamingSpeechClipForPlayback = useCallback8((text, voiceId) => {
5895
5596
  const requestId = createVoiceDebugId("tts");
5896
5597
  const clip = createStreamingSpeechClip(serverUrl, text, voiceId, {
5897
5598
  requestId,
@@ -5906,7 +5607,7 @@ function useVoice(serverUrl) {
5906
5607
  });
5907
5608
  return clip;
5908
5609
  }, [serverUrl]);
5909
- const prefetchSpeech = useCallback9(async (text, voiceId = DEFAULT_VOICE_ID) => {
5610
+ const prefetchSpeech = useCallback8(async (text, voiceId = DEFAULT_VOICE_ID) => {
5910
5611
  if (isMutedRef.current || !text.trim()) return;
5911
5612
  const key = getSpeechCacheKey(text, voiceId);
5912
5613
  if (!prefetchedSpeechRef.current.has(key)) {
@@ -5920,7 +5621,7 @@ function useVoice(serverUrl) {
5920
5621
  }
5921
5622
  await prefetchedSpeechRef.current.get(key);
5922
5623
  }, [fetchSpeechClip, getSpeechCacheKey]);
5923
- const playClipDirect = useCallback9(async (clip, timeoutMs) => {
5624
+ const playClipDirect = useCallback8(async (clip, timeoutMs) => {
5924
5625
  const { url } = clip;
5925
5626
  const audio = new Audio();
5926
5627
  audio.crossOrigin = "anonymous";
@@ -6016,7 +5717,7 @@ function useVoice(serverUrl) {
6016
5717
  });
6017
5718
  });
6018
5719
  }, []);
6019
- const speak = useCallback9(async (text, voiceId = DEFAULT_VOICE_ID, options = {}) => {
5720
+ const speak = useCallback8(async (text, voiceId = DEFAULT_VOICE_ID, options = {}) => {
6020
5721
  if (isMutedRef.current) return;
6021
5722
  const mySeq = ++queueSeqRef.current;
6022
5723
  const interrupt = options.interrupt === true;
@@ -6108,7 +5809,7 @@ function useVoice(serverUrl) {
6108
5809
  }
6109
5810
  return taskPromise;
6110
5811
  }, [createStreamingSpeechClipForPlayback, fetchSpeechClip, getSpeechCacheKey, playClipDirect, stopSpeaking]);
6111
- const stopListening = useCallback9(() => {
5812
+ const stopListening = useCallback8(() => {
6112
5813
  emitVoiceDebug("stt_stop_listening", {
6113
5814
  listeningSessionId: listeningSessionIdRef.current,
6114
5815
  listeningMs: listeningStartedAtRef.current ? Math.round(performance.now() - listeningStartedAtRef.current) : null
@@ -6140,7 +5841,7 @@ function useVoice(serverUrl) {
6140
5841
  accumulatedRef.current = [];
6141
5842
  setIsListening(false);
6142
5843
  }, [stopLiveSttTransport]);
6143
- const startListening = useCallback9((onResult, onInterruption, onError, options = {}) => {
5844
+ const startListening = useCallback8((onResult, onInterruption, onError, options = {}) => {
6144
5845
  stopListening();
6145
5846
  listeningSessionIdRef.current = createVoiceDebugId("stt");
6146
5847
  listeningStartedAtRef.current = performance.now();
@@ -6424,7 +6125,7 @@ function useVoice(serverUrl) {
6424
6125
  }
6425
6126
  fallbackToWebSpeech();
6426
6127
  }, [serverUrl, stopListening, stopSpeaking, stopLiveSttTransport]);
6427
- const toggleMute = useCallback9(() => {
6128
+ const toggleMute = useCallback8(() => {
6428
6129
  setIsMuted((prev) => {
6429
6130
  const next = !prev;
6430
6131
  if (next) {
@@ -6436,7 +6137,7 @@ function useVoice(serverUrl) {
6436
6137
  return next;
6437
6138
  });
6438
6139
  }, []);
6439
- useEffect13(() => {
6140
+ useEffect12(() => {
6440
6141
  return () => {
6441
6142
  stopSpeaking();
6442
6143
  stopListening();
@@ -6465,18 +6166,18 @@ function useVoice(serverUrl) {
6465
6166
  }
6466
6167
 
6467
6168
  // src/hooks/useAudioLevel.ts
6468
- import { useState as useState10, useEffect as useEffect14, useRef as useRef11 } from "react";
6169
+ import { useState as useState9, useEffect as useEffect13, useRef as useRef10 } from "react";
6469
6170
  var BAR_COUNT = 9;
6470
6171
  var SENSITIVITY = 2.8;
6471
6172
  var FLOOR = 0.08;
6472
6173
  function useAudioLevel(active) {
6473
- const [levels, setLevels] = useState10(() => Array(BAR_COUNT).fill(FLOOR));
6474
- const streamRef = useRef11(null);
6475
- const ctxRef = useRef11(null);
6476
- const analyserRef = useRef11(null);
6477
- const rafRef = useRef11(0);
6478
- const timeDataRef = useRef11(null);
6479
- useEffect14(() => {
6174
+ const [levels, setLevels] = useState9(() => Array(BAR_COUNT).fill(FLOOR));
6175
+ const streamRef = useRef10(null);
6176
+ const ctxRef = useRef10(null);
6177
+ const analyserRef = useRef10(null);
6178
+ const rafRef = useRef10(0);
6179
+ const timeDataRef = useRef10(null);
6180
+ useEffect13(() => {
6480
6181
  if (!active || typeof window === "undefined") {
6481
6182
  setLevels(Array(BAR_COUNT).fill(FLOOR));
6482
6183
  return;
@@ -6548,7 +6249,7 @@ function useAudioLevel(active) {
6548
6249
  }
6549
6250
 
6550
6251
  // src/hooks/useRecordingMode.ts
6551
- import { useState as useState11, useRef as useRef12, useCallback as useCallback10, useEffect as useEffect15 } from "react";
6252
+ import { useState as useState10, useRef as useRef11, useCallback as useCallback9, useEffect as useEffect14 } from "react";
6552
6253
 
6553
6254
  // src/utils/tourStepTypes.ts
6554
6255
  function isAskDrivenInputStepType(stepType) {
@@ -6829,46 +6530,46 @@ function useRecordingMode({
6829
6530
  onPreview,
6830
6531
  experienceType = "tour"
6831
6532
  }) {
6832
- const restoredSessionRef = useRef12(void 0);
6533
+ const restoredSessionRef = useRef11(void 0);
6833
6534
  if (restoredSessionRef.current === void 0) {
6834
6535
  restoredSessionRef.current = readPersistedRecordingSession();
6835
6536
  }
6836
6537
  const restoredSession = restoredSessionRef.current;
6837
6538
  const restoredPhase = restoredSession ? "active" : "idle";
6838
- const [phase, setPhase] = useState11(restoredPhase);
6839
- const [steps, setSteps] = useState11(() => restoredSession?.steps ?? []);
6840
- const [selectedElement, setSelectedElement] = useState11(null);
6841
- const [selectedStepType, setSelectedStepType] = useState11(() => restoredSession?.selectedStepType ?? "ask_or_fill");
6842
- const [pendingNarration, setPendingNarration] = useState11("");
6843
- const [polishedNarration, setPolishedNarration] = useState11("");
6844
- const [captureEvents, setCaptureEvents] = useState11(() => restoredSession?.captureEvents ?? []);
6845
- const [capturedTranscript, setCapturedTranscript] = useState11(() => restoredSession?.capturedTranscript ?? "");
6846
- const [isVoiceCaptureActive, setIsVoiceCaptureActive] = useState11(false);
6847
- const stepsRef = useRef12([]);
6539
+ const [phase, setPhase] = useState10(restoredPhase);
6540
+ const [steps, setSteps] = useState10(() => restoredSession?.steps ?? []);
6541
+ const [selectedElement, setSelectedElement] = useState10(null);
6542
+ const [selectedStepType, setSelectedStepType] = useState10(() => restoredSession?.selectedStepType ?? "ask_or_fill");
6543
+ const [pendingNarration, setPendingNarration] = useState10("");
6544
+ const [polishedNarration, setPolishedNarration] = useState10("");
6545
+ const [captureEvents, setCaptureEvents] = useState10(() => restoredSession?.captureEvents ?? []);
6546
+ const [capturedTranscript, setCapturedTranscript] = useState10(() => restoredSession?.capturedTranscript ?? "");
6547
+ const [isVoiceCaptureActive, setIsVoiceCaptureActive] = useState10(false);
6548
+ const stepsRef = useRef11([]);
6848
6549
  stepsRef.current = steps;
6849
- const captureEventsRef = useRef12([]);
6850
- const capturedTranscriptRef = useRef12(capturedTranscript);
6550
+ const captureEventsRef = useRef11([]);
6551
+ const capturedTranscriptRef = useRef11(capturedTranscript);
6851
6552
  capturedTranscriptRef.current = capturedTranscript;
6852
- const phaseRef = useRef12(phase);
6553
+ const phaseRef = useRef11(phase);
6853
6554
  phaseRef.current = phase;
6854
- const safeSpeak = useCallback10((text) => {
6555
+ const safeSpeak = useCallback9((text) => {
6855
6556
  void voice.speak(text).catch((err) => {
6856
6557
  console.warn("[Recording] Voice playback unavailable:", err);
6857
6558
  });
6858
6559
  }, [voice]);
6859
6560
  captureEventsRef.current = captureEvents;
6860
- const pendingClicksRef = useRef12(restoredSession?.pendingClicks ?? []);
6861
- const shouldKeepVoiceCaptureRef = useRef12(restoredSession?.voiceCaptureEnabled === true);
6862
- const resumeVoiceAfterNarrationRef = useRef12(false);
6863
- const lastAutoNoteRef = useRef12("");
6864
- const lastHoverKeyRef = useRef12("");
6865
- const lastHoverAtRef = useRef12(0);
6866
- const selectedStepTypeRef = useRef12("ask_or_fill");
6561
+ const pendingClicksRef = useRef11(restoredSession?.pendingClicks ?? []);
6562
+ const shouldKeepVoiceCaptureRef = useRef11(restoredSession?.voiceCaptureEnabled === true);
6563
+ const resumeVoiceAfterNarrationRef = useRef11(false);
6564
+ const lastAutoNoteRef = useRef11("");
6565
+ const lastHoverKeyRef = useRef11("");
6566
+ const lastHoverAtRef = useRef11(0);
6567
+ const selectedStepTypeRef = useRef11("ask_or_fill");
6867
6568
  selectedStepTypeRef.current = selectedStepType;
6868
6569
  const isRecording = phase !== "idle";
6869
6570
  const stepCount = steps.length;
6870
6571
  const captureEventCount = captureEvents.length;
6871
- const persistSnapshot = useCallback10((overrides) => {
6572
+ const persistSnapshot = useCallback9((overrides) => {
6872
6573
  const nextPhase = overrides?.phase ?? phaseRef.current;
6873
6574
  const nextSteps = overrides?.steps ?? stepsRef.current;
6874
6575
  const nextCaptureEvents = overrides?.captureEvents ?? captureEventsRef.current;
@@ -6891,7 +6592,7 @@ function useRecordingMode({
6891
6592
  voiceCaptureEnabled: nextVoiceCaptureEnabled
6892
6593
  });
6893
6594
  }, [experienceType]);
6894
- const appendCaptureEvent = useCallback10((event) => {
6595
+ const appendCaptureEvent = useCallback9((event) => {
6895
6596
  const nextEvent = {
6896
6597
  id: event.id || newCaptureId(event.type),
6897
6598
  order: captureEventsRef.current.length,
@@ -6903,7 +6604,7 @@ function useRecordingMode({
6903
6604
  setCaptureEvents(nextCaptureEvents);
6904
6605
  persistSnapshot({ captureEvents: nextCaptureEvents });
6905
6606
  }, [persistSnapshot]);
6906
- const updateCaptureEvent = useCallback10((id, metadataPatch) => {
6607
+ const updateCaptureEvent = useCallback9((id, metadataPatch) => {
6907
6608
  const nextCaptureEvents = captureEventsRef.current.map((event) => {
6908
6609
  if (event.id === id) {
6909
6610
  return {
@@ -6920,7 +6621,7 @@ function useRecordingMode({
6920
6621
  setCaptureEvents(nextCaptureEvents);
6921
6622
  persistSnapshot({ captureEvents: nextCaptureEvents });
6922
6623
  }, [persistSnapshot]);
6923
- const appendVoiceNote = useCallback10((transcript) => {
6624
+ const appendVoiceNote = useCallback9((transcript) => {
6924
6625
  const text = transcript.trim();
6925
6626
  if (!text) return;
6926
6627
  if (lastAutoNoteRef.current === text) return;
@@ -6981,11 +6682,11 @@ function useRecordingMode({
6981
6682
  }
6982
6683
  });
6983
6684
  }, [appendCaptureEvent, persistSnapshot]);
6984
- const stopBackgroundVoiceCapture = useCallback10(() => {
6685
+ const stopBackgroundVoiceCapture = useCallback9(() => {
6985
6686
  voice.stopListening();
6986
6687
  setIsVoiceCaptureActive(false);
6987
6688
  }, [voice]);
6988
- const startBackgroundVoiceCapture = useCallback10(() => {
6689
+ const startBackgroundVoiceCapture = useCallback9(() => {
6989
6690
  if (!shouldKeepVoiceCaptureRef.current) return;
6990
6691
  if (!isRecording) return;
6991
6692
  if (phase === "narrating" || phase === "reviewing") return;
@@ -7002,7 +6703,7 @@ function useRecordingMode({
7002
6703
  );
7003
6704
  setIsVoiceCaptureActive(true);
7004
6705
  }, [appendVoiceNote, isRecording, phase, voice]);
7005
- const startRecording = useCallback10(() => {
6706
+ const startRecording = useCallback9(() => {
7006
6707
  const nextPhase = "active";
7007
6708
  const nextCaptureEvents = [{
7008
6709
  id: newCaptureId("session_start"),
@@ -7034,34 +6735,34 @@ function useRecordingMode({
7034
6735
  voiceCaptureEnabled: false
7035
6736
  });
7036
6737
  }, [persistSnapshot]);
7037
- const markStep = useCallback10(() => {
6738
+ const markStep = useCallback9(() => {
7038
6739
  if (phase !== "active") return;
7039
6740
  setPhase("selecting");
7040
6741
  }, [phase]);
7041
- const selectElement = useCallback10((recorded) => {
6742
+ const selectElement = useCallback9((recorded) => {
7042
6743
  setSelectedElement(recorded);
7043
6744
  setPhase("configuring");
7044
6745
  const isFormField = ["INPUT", "SELECT", "TEXTAREA"].includes(recorded.el.tagName);
7045
6746
  setSelectedStepType(isFormField ? "ask_or_fill" : "narrate");
7046
6747
  }, []);
7047
- const selectPageLevel = useCallback10(() => {
6748
+ const selectPageLevel = useCallback9(() => {
7048
6749
  setSelectedElement(null);
7049
6750
  setSelectedStepType("narrate");
7050
6751
  setPhase("configuring");
7051
6752
  }, []);
7052
- const cancelSelection = useCallback10(() => {
6753
+ const cancelSelection = useCallback9(() => {
7053
6754
  setSelectedElement(null);
7054
6755
  setPhase("active");
7055
6756
  }, []);
7056
- const setStepType = useCallback10((type) => {
6757
+ const setStepType = useCallback9((type) => {
7057
6758
  setSelectedStepType(type);
7058
6759
  }, []);
7059
- const runPolishAndReview = useCallback10(async (transcript) => {
6760
+ const runPolishAndReview = useCallback9(async (transcript) => {
7060
6761
  setPolishedNarration(transcript);
7061
6762
  setPhase("reviewing");
7062
6763
  }, []);
7063
- const pendingNarrationRef = useRef12("");
7064
- const startNarration = useCallback10(() => {
6764
+ const pendingNarrationRef = useRef11("");
6765
+ const startNarration = useCallback9(() => {
7065
6766
  resumeVoiceAfterNarrationRef.current = shouldKeepVoiceCaptureRef.current && isVoiceCaptureActive;
7066
6767
  if (isVoiceCaptureActive) {
7067
6768
  stopBackgroundVoiceCapture();
@@ -7083,7 +6784,7 @@ function useRecordingMode({
7083
6784
  { continuous: true }
7084
6785
  );
7085
6786
  }, [isVoiceCaptureActive, stopBackgroundVoiceCapture, voice]);
7086
- const finishNarration = useCallback10(async () => {
6787
+ const finishNarration = useCallback9(async () => {
7087
6788
  if (phase !== "narrating") return;
7088
6789
  voice.stopListening();
7089
6790
  const text = pendingNarrationRef.current;
@@ -7098,13 +6799,13 @@ function useRecordingMode({
7098
6799
  }
7099
6800
  }
7100
6801
  }, [phase, runPolishAndReview, startBackgroundVoiceCapture, voice]);
7101
- const submitTextNarration = useCallback10(async (text) => {
6802
+ const submitTextNarration = useCallback9(async (text) => {
7102
6803
  if (phase !== "narrating" || !text.trim()) return;
7103
6804
  voice.stopListening();
7104
6805
  setPendingNarration(text);
7105
6806
  await runPolishAndReview(text);
7106
6807
  }, [phase, voice, runPolishAndReview]);
7107
- const approveNarration = useCallback10(() => {
6808
+ const approveNarration = useCallback9(() => {
7108
6809
  if (phase !== "reviewing") return;
7109
6810
  const el = selectedElement;
7110
6811
  const narration = polishedNarration || pendingNarration;
@@ -7194,17 +6895,17 @@ function useRecordingMode({
7194
6895
  }, 0);
7195
6896
  }
7196
6897
  }, [appendCaptureEvent, phase, polishedNarration, pendingNarration, persistSnapshot, selectedElement, selectedStepType, startBackgroundVoiceCapture, voice]);
7197
- const redoNarration = useCallback10(() => {
6898
+ const redoNarration = useCallback9(() => {
7198
6899
  if (phase !== "reviewing") return;
7199
6900
  setPendingNarration("");
7200
6901
  setPolishedNarration("");
7201
6902
  startNarration();
7202
6903
  }, [phase, startNarration]);
7203
- const editNarration = useCallback10((text) => {
6904
+ const editNarration = useCallback9((text) => {
7204
6905
  setPendingNarration(text);
7205
6906
  setPolishedNarration(text);
7206
6907
  }, []);
7207
- const continueRecording = useCallback10(() => {
6908
+ const continueRecording = useCallback9(() => {
7208
6909
  phaseRef.current = "active";
7209
6910
  setPhase("active");
7210
6911
  persistSnapshot({ phase: "active" });
@@ -7214,7 +6915,7 @@ function useRecordingMode({
7214
6915
  }, 0);
7215
6916
  }
7216
6917
  }, [persistSnapshot, startBackgroundVoiceCapture]);
7217
- const undoLastStep = useCallback10(() => {
6918
+ const undoLastStep = useCallback9(() => {
7218
6919
  const previous = stepsRef.current;
7219
6920
  const nextSteps = previous.slice(0, -1);
7220
6921
  stepsRef.current = nextSteps;
@@ -7226,7 +6927,7 @@ function useRecordingMode({
7226
6927
  setPhase("active");
7227
6928
  persistSnapshot({ steps: nextSteps, phase: "active" });
7228
6929
  }, [persistSnapshot, voice]);
7229
- const previewSteps = useCallback10(() => {
6930
+ const previewSteps = useCallback9(() => {
7230
6931
  if (steps.length === 0) {
7231
6932
  safeSpeak("No steps recorded yet.");
7232
6933
  return;
@@ -7234,13 +6935,13 @@ function useRecordingMode({
7234
6935
  onPreview?.(steps);
7235
6936
  safeSpeak(`Previewing ${steps.length} step${steps.length !== 1 ? "s" : ""}.`);
7236
6937
  }, [onPreview, safeSpeak, steps]);
7237
- const prepareToStopRecording = useCallback10(() => {
6938
+ const prepareToStopRecording = useCallback9(() => {
7238
6939
  shouldKeepVoiceCaptureRef.current = false;
7239
6940
  resumeVoiceAfterNarrationRef.current = false;
7240
6941
  stopBackgroundVoiceCapture();
7241
6942
  persistSnapshot({ voiceCaptureEnabled: false });
7242
6943
  }, [persistSnapshot, stopBackgroundVoiceCapture]);
7243
- const cancelRecording = useCallback10(() => {
6944
+ const cancelRecording = useCallback9(() => {
7244
6945
  shouldKeepVoiceCaptureRef.current = false;
7245
6946
  stopBackgroundVoiceCapture();
7246
6947
  phaseRef.current = "idle";
@@ -7258,7 +6959,7 @@ function useRecordingMode({
7258
6959
  clearPersistedRecordingSession();
7259
6960
  safeSpeak("Recording cancelled.");
7260
6961
  }, [safeSpeak, stopBackgroundVoiceCapture]);
7261
- useEffect15(() => {
6962
+ useEffect14(() => {
7262
6963
  if (!isRecording || typeof document === "undefined") return;
7263
6964
  const handleMouseMove = (event) => {
7264
6965
  const target = event.target;
@@ -7368,7 +7069,7 @@ function useRecordingMode({
7368
7069
  window.clearInterval(routePoll);
7369
7070
  };
7370
7071
  }, [appendCaptureEvent, isRecording]);
7371
- useEffect15(() => {
7072
+ useEffect14(() => {
7372
7073
  if (!isRecording || typeof window === "undefined") return;
7373
7074
  const flushRecordingSession = () => {
7374
7075
  persistSnapshot();
@@ -7380,7 +7081,7 @@ function useRecordingMode({
7380
7081
  window.removeEventListener("beforeunload", flushRecordingSession);
7381
7082
  };
7382
7083
  }, [isRecording, persistSnapshot]);
7383
- const stopRecording = useCallback10(async (tourName, targetUserTypes) => {
7084
+ const stopRecording = useCallback9(async (tourName, targetUserTypes) => {
7384
7085
  phaseRef.current = "finishing";
7385
7086
  setPhase("finishing");
7386
7087
  shouldKeepVoiceCaptureRef.current = false;
@@ -7454,7 +7155,7 @@ function useRecordingMode({
7454
7155
  return null;
7455
7156
  }
7456
7157
  }, [capturedTranscript, experienceType, persistSnapshot, safeSpeak, serverUrl, stopBackgroundVoiceCapture, steps, toursApiBase, websiteId]);
7457
- const toggleVoiceCapture = useCallback10(() => {
7158
+ const toggleVoiceCapture = useCallback9(() => {
7458
7159
  if (isVoiceCaptureActive || shouldKeepVoiceCaptureRef.current) {
7459
7160
  shouldKeepVoiceCaptureRef.current = false;
7460
7161
  stopBackgroundVoiceCapture();
@@ -7465,7 +7166,7 @@ function useRecordingMode({
7465
7166
  persistSnapshot({ voiceCaptureEnabled: true });
7466
7167
  startBackgroundVoiceCapture();
7467
7168
  }, [isVoiceCaptureActive, persistSnapshot, startBackgroundVoiceCapture, stopBackgroundVoiceCapture]);
7468
- useEffect15(() => {
7169
+ useEffect14(() => {
7469
7170
  if (!isRecording) return;
7470
7171
  if (phase === "active" && shouldKeepVoiceCaptureRef.current && !isVoiceCaptureActive) {
7471
7172
  startBackgroundVoiceCapture();
@@ -7474,7 +7175,7 @@ function useRecordingMode({
7474
7175
  stopBackgroundVoiceCapture();
7475
7176
  }
7476
7177
  }, [isRecording, isVoiceCaptureActive, phase, startBackgroundVoiceCapture, stopBackgroundVoiceCapture]);
7477
- const handleVoiceCommand = useCallback10((transcript) => {
7178
+ const handleVoiceCommand = useCallback9((transcript) => {
7478
7179
  const lower = transcript.toLowerCase().trim();
7479
7180
  if (!isRecording) return;
7480
7181
  if (lower.includes("mark step") && phase === "active") {
@@ -7536,7 +7237,7 @@ function useRecordingMode({
7536
7237
  }
7537
7238
 
7538
7239
  // src/components/RecordingOverlay.tsx
7539
- import { useEffect as useEffect16, useState as useState12, useCallback as useCallback11 } from "react";
7240
+ import { useEffect as useEffect15, useState as useState11, useCallback as useCallback10 } from "react";
7540
7241
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
7541
7242
  function buildFingerprint(el) {
7542
7243
  const tag = el.tagName.toLowerCase();
@@ -7564,8 +7265,8 @@ var panelBase = {
7564
7265
  color: "#18181b"
7565
7266
  };
7566
7267
  function ActiveHUD({ stepCount, captureEventCount, isVoiceCaptureActive, minimized, onToggleVoiceCapture, onToggleMinimized, onStop, onCancel }) {
7567
- const [hasActivatedVoiceCapture, setHasActivatedVoiceCapture] = useState12(isVoiceCaptureActive);
7568
- useEffect16(() => {
7268
+ const [hasActivatedVoiceCapture, setHasActivatedVoiceCapture] = useState11(isVoiceCaptureActive);
7269
+ useEffect15(() => {
7569
7270
  if (isVoiceCaptureActive) {
7570
7271
  setHasActivatedVoiceCapture(true);
7571
7272
  }
@@ -7810,8 +7511,8 @@ var STEP_TYPES_ADVANCED = [
7810
7511
  ];
7811
7512
  function StepPopover({ element, stepIndex, onConfirm, onCancel }) {
7812
7513
  const defaultType = element && ["INPUT", "SELECT", "TEXTAREA"].includes(element.el.tagName) ? "ask_or_fill" : "narrate";
7813
- const [selected, setSelected] = useState12(defaultType);
7814
- const [showAdvanced, setShowAdvanced] = useState12(false);
7514
+ const [selected, setSelected] = useState11(defaultType);
7515
+ const [showAdvanced, setShowAdvanced] = useState11(false);
7815
7516
  const hasTestId = !!element?.testId;
7816
7517
  const allTypes = showAdvanced ? [...STEP_TYPES_PRIMARY, ...STEP_TYPES_ADVANCED] : STEP_TYPES_PRIMARY;
7817
7518
  const popoverStyle = {
@@ -7955,8 +7656,8 @@ function StepPopover({ element, stepIndex, onConfirm, onCancel }) {
7955
7656
  ] });
7956
7657
  }
7957
7658
  function NarrationPanel({ stepIndex, stepCount, pendingNarration, isListening, onSubmitText, onCancel, onStopRecording }) {
7958
- const [textInput, setTextInput] = useState12("");
7959
- const [showTextInput, setShowTextInput] = useState12(false);
7659
+ const [textInput, setTextInput] = useState11("");
7660
+ const [showTextInput, setShowTextInput] = useState11(false);
7960
7661
  const audioLevels = useAudioLevel(isListening);
7961
7662
  return /* @__PURE__ */ jsxs2(
7962
7663
  "div",
@@ -8123,8 +7824,8 @@ function NarrationPanel({ stepIndex, stepCount, pendingNarration, isListening, o
8123
7824
  );
8124
7825
  }
8125
7826
  function ReviewPanel({ stepIndex, stepCount, polishedNarration, rawNarration, onApprove, onRedo, onEdit }) {
8126
- const [editMode, setEditMode] = useState12(false);
8127
- const [editText, setEditText] = useState12(polishedNarration || rawNarration);
7827
+ const [editMode, setEditMode] = useState11(false);
7828
+ const [editText, setEditText] = useState11(polishedNarration || rawNarration);
8128
7829
  const displayText = polishedNarration || rawNarration;
8129
7830
  return /* @__PURE__ */ jsxs2(
8130
7831
  "div",
@@ -8380,7 +8081,7 @@ function FinishingPanel() {
8380
8081
  }
8381
8082
  );
8382
8083
  }
8383
- var INTERACTIVE_SELECTOR2 = [
8084
+ var INTERACTIVE_SELECTOR = [
8384
8085
  "button",
8385
8086
  "a[href]",
8386
8087
  'input:not([type="hidden"])',
@@ -8418,19 +8119,19 @@ function RecordingOverlay({
8418
8119
  onCancelRecording,
8419
8120
  onStopRecording
8420
8121
  }) {
8421
- const [hoveredEl, setHoveredEl] = useState12(null);
8422
- const [pendingElement, setPendingElement] = useState12(null);
8423
- const [showPopover, setShowPopover] = useState12(false);
8424
- const [hudMinimized, setHudMinimized] = useState12(false);
8425
- const [voicePanelMinimized, setVoicePanelMinimized] = useState12(false);
8122
+ const [hoveredEl, setHoveredEl] = useState11(null);
8123
+ const [pendingElement, setPendingElement] = useState11(null);
8124
+ const [showPopover, setShowPopover] = useState11(false);
8125
+ const [hudMinimized, setHudMinimized] = useState11(false);
8126
+ const [voicePanelMinimized, setVoicePanelMinimized] = useState11(false);
8426
8127
  const isSelecting = phase === "selecting";
8427
- const handleMouseMove = useCallback11((e) => {
8428
- const target = e.target.closest(INTERACTIVE_SELECTOR2);
8128
+ const handleMouseMove = useCallback10((e) => {
8129
+ const target = e.target.closest(INTERACTIVE_SELECTOR);
8429
8130
  setHoveredEl(target);
8430
8131
  }, []);
8431
- const handleClick = useCallback11((e) => {
8132
+ const handleClick = useCallback10((e) => {
8432
8133
  if (e.target.closest("[data-modelnex-internal]")) return;
8433
- const target = e.target.closest(INTERACTIVE_SELECTOR2);
8134
+ const target = e.target.closest(INTERACTIVE_SELECTOR);
8434
8135
  if (target) {
8435
8136
  e.preventDefault();
8436
8137
  e.stopPropagation();
@@ -8453,7 +8154,7 @@ function RecordingOverlay({
8453
8154
  setShowPopover(true);
8454
8155
  }
8455
8156
  }, []);
8456
- useEffect16(() => {
8157
+ useEffect15(() => {
8457
8158
  if (!isSelecting) return;
8458
8159
  document.addEventListener("mousemove", handleMouseMove, true);
8459
8160
  document.addEventListener("click", handleClick, true);
@@ -8465,14 +8166,14 @@ function RecordingOverlay({
8465
8166
  document.body.style.cursor = prev;
8466
8167
  };
8467
8168
  }, [isSelecting, handleMouseMove, handleClick]);
8468
- useEffect16(() => {
8169
+ useEffect15(() => {
8469
8170
  if (isSelecting) {
8470
8171
  setShowPopover(false);
8471
8172
  setPendingElement(null);
8472
8173
  setHoveredEl(null);
8473
8174
  }
8474
8175
  }, [isSelecting]);
8475
- const handlePopoverConfirm = useCallback11((type) => {
8176
+ const handlePopoverConfirm = useCallback10((type) => {
8476
8177
  setShowPopover(false);
8477
8178
  if (pendingElement) {
8478
8179
  onElementSelected(pendingElement);
@@ -8482,7 +8183,7 @@ function RecordingOverlay({
8482
8183
  onStepTypeConfirmed(type);
8483
8184
  onStartNarration();
8484
8185
  }, [pendingElement, onElementSelected, onPageLevelStep, onStepTypeConfirmed, onStartNarration]);
8485
- const handlePopoverCancel = useCallback11(() => {
8186
+ const handlePopoverCancel = useCallback10(() => {
8486
8187
  setShowPopover(false);
8487
8188
  setPendingElement(null);
8488
8189
  }, []);
@@ -9029,8 +8730,8 @@ function getViewportHeight() {
9029
8730
  return layoutViewportHeight;
9030
8731
  }
9031
8732
  function useViewportHeight() {
9032
- const [viewportHeight, setViewportHeight] = useState13(() => getViewportHeight());
9033
- useEffect17(() => {
8733
+ const [viewportHeight, setViewportHeight] = useState12(() => getViewportHeight());
8734
+ useEffect16(() => {
9034
8735
  if (typeof window === "undefined") return;
9035
8736
  const updateViewportHeight = () => {
9036
8737
  setViewportHeight(getViewportHeight());
@@ -9049,8 +8750,8 @@ function useViewportHeight() {
9049
8750
  return viewportHeight;
9050
8751
  }
9051
8752
  function useMediaQuery(query) {
9052
- const [matches, setMatches] = useState13(false);
9053
- useEffect17(() => {
8753
+ const [matches, setMatches] = useState12(false);
8754
+ useEffect16(() => {
9054
8755
  const media = window.matchMedia(query);
9055
8756
  if (media.matches !== matches) setMatches(media.matches);
9056
8757
  const listener = () => setMatches(media.matches);
@@ -9122,8 +8823,8 @@ var GLOBAL_STYLES = `
9122
8823
  }
9123
8824
  `;
9124
8825
  function Tooltip({ children, title }) {
9125
- const [show, setShow] = useState13(false);
9126
- const ref = useRef13(null);
8826
+ const [show, setShow] = useState12(false);
8827
+ const ref = useRef12(null);
9127
8828
  const handleShow = () => setShow(true);
9128
8829
  const handleHide = () => setShow(false);
9129
8830
  return /* @__PURE__ */ jsxs3(
@@ -9217,11 +8918,11 @@ var TrashIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16",
9217
8918
  var StopIcon = () => /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
9218
8919
  var ChevronDown = ({ open }) => /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { transform: open ? "rotate(180deg)" : "none", transition: "transform 0.2s" }, children: /* @__PURE__ */ jsx4("path", { d: "m6 9 6 6 6-6" }) });
9219
8920
  function AgentTraces({ debug, command, defaultExpanded = true }) {
9220
- const [expanded, setExpanded] = useState13(defaultExpanded);
9221
- const [expandedSteps, setExpandedSteps] = useState13(/* @__PURE__ */ new Set());
8921
+ const [expanded, setExpanded] = useState12(defaultExpanded);
8922
+ const [expandedSteps, setExpandedSteps] = useState12(/* @__PURE__ */ new Set());
9222
8923
  const traces = debug.traces ?? [];
9223
8924
  const hasTraceContent = traces.length > 0 || debug.llmInput || (debug.llmOutput?.length ?? 0) > 0 || (debug.actions?.length ?? 0) > 0;
9224
- useEffect17(() => {
8925
+ useEffect16(() => {
9225
8926
  setExpandedSteps(new Set(traces.map((t) => t.step)));
9226
8927
  }, [debug]);
9227
8928
  const toggleStep = (step) => {
@@ -9382,21 +9083,21 @@ function ModelNexChatBubble({
9382
9083
  }) {
9383
9084
  const onCommand = void 0;
9384
9085
  const recordingExperienceType = "tour";
9385
- const noop = useCallback12(() => void 0, []);
9386
- const noopAsync = useCallback12(async () => void 0, []);
9086
+ const noop = useCallback11(() => void 0, []);
9087
+ const noopAsync = useCallback11(async () => void 0, []);
9387
9088
  const runCommand = useRunCommand();
9388
9089
  const ctx = useContext5(ModelNexContext);
9389
- const [hydrated, setHydrated] = useState13(false);
9390
- const [expanded, setExpanded] = useState13(false);
9391
- const [docked, setDocked] = useState13(false);
9392
- const [input, setInput] = useState13("");
9090
+ const [hydrated, setHydrated] = useState12(false);
9091
+ const [expanded, setExpanded] = useState12(false);
9092
+ const [docked, setDocked] = useState12(false);
9093
+ const [input, setInput] = useState12("");
9393
9094
  const messages = ctx?.chatMessages ?? [];
9394
9095
  const setMessages = ctx?.setChatMessages ?? (() => {
9395
9096
  });
9396
- const [loading, setLoading] = useState13(false);
9397
- const messagesEndRef = useRef13(null);
9398
- const abortControllerRef = useRef13(null);
9399
- const panelRef = useRef13(null);
9097
+ const [loading, setLoading] = useState12(false);
9098
+ const messagesEndRef = useRef12(null);
9099
+ const abortControllerRef = useRef12(null);
9100
+ const panelRef = useRef12(null);
9400
9101
  const serverUrl = ctx?.serverUrl ?? DEFAULT_MODELNEX_SERVER_URL;
9401
9102
  const voice = useVoice(serverUrl);
9402
9103
  const audioLevels = useAudioLevel(voice.isListening);
@@ -9405,13 +9106,13 @@ function ModelNexChatBubble({
9405
9106
  });
9406
9107
  const devMode = ctx?.devMode ?? false;
9407
9108
  const authoringMode = ctx?.authoringMode ?? devMode;
9408
- const [recordingTourName, setRecordingTourName] = useState13("");
9409
- const [recordingTargetTypes, setRecordingTargetTypes] = useState13("admin");
9410
- const [showStopModal, setShowStopModal] = useState13(false);
9411
- const [savedDraft, setSavedDraft] = useState13(null);
9412
- const [reviewDraft, setReviewDraft] = useState13("");
9413
- const [tourLiveTranscript, setTourLiveTranscript] = useState13("");
9414
- const [activeRecordingExperienceType, setActiveRecordingExperienceType] = useState13(
9109
+ const [recordingTourName, setRecordingTourName] = useState12("");
9110
+ const [recordingTargetTypes, setRecordingTargetTypes] = useState12("admin");
9111
+ const [showStopModal, setShowStopModal] = useState12(false);
9112
+ const [savedDraft, setSavedDraft] = useState12(null);
9113
+ const [reviewDraft, setReviewDraft] = useState12("");
9114
+ const [tourLiveTranscript, setTourLiveTranscript] = useState12("");
9115
+ const [activeRecordingExperienceType, setActiveRecordingExperienceType] = useState12(
9415
9116
  () => readPersistedRecordingExperienceType() ?? recordingExperienceType
9416
9117
  );
9417
9118
  const recording = useRecordingMode({
@@ -9423,7 +9124,7 @@ function ModelNexChatBubble({
9423
9124
  });
9424
9125
  const isGeneratingDraft = isRecordingDraftGenerating(recording.phase);
9425
9126
  const showRecordingOverlay = recordingMode && shouldShowRecordingOverlay(recording.phase);
9426
- useEffect17(() => {
9127
+ useEffect16(() => {
9427
9128
  const shouldBeRecording = recording.phase !== "idle";
9428
9129
  if (shouldBeRecording && !recordingMode) {
9429
9130
  setRecordingMode(true);
@@ -9456,12 +9157,12 @@ function ModelNexChatBubble({
9456
9157
  const activePlayback = playbackController.playback;
9457
9158
  const activeExperienceType = playbackController.activeExperienceType;
9458
9159
  const startingExperienceType = playbackController.startingExperienceType;
9459
- useEffect17(() => {
9160
+ useEffect16(() => {
9460
9161
  return registerExperienceToolBridge({
9461
9162
  startExperience: playbackController.startExperience
9462
9163
  });
9463
9164
  }, [playbackController.startExperience]);
9464
- const createPlaybackView = useCallback12((experienceType) => {
9165
+ const createPlaybackView = useCallback11((experienceType) => {
9465
9166
  const isActiveExperience = activePlayback.isActive && activeExperienceType === experienceType;
9466
9167
  const pendingTour = playbackController.pendingPrompt?.experienceType === experienceType ? playbackController.pendingPrompt.tour : null;
9467
9168
  return {
@@ -9493,8 +9194,8 @@ function ModelNexChatBubble({
9493
9194
  const onboardingPlayback = useMemo3(() => createPlaybackView("onboarding"), [createPlaybackView]);
9494
9195
  const tourReviewToggle = getReviewModeToggleConfig(tourPlayback.playbackState);
9495
9196
  const tourReviewModeEnabled = isReviewModeEnabled(devMode, tourPlayback.isReviewMode);
9496
- const lastAutoTaggedUrlRef = useRef13(null);
9497
- const handleAutoTag = useCallback12(async () => {
9197
+ const lastAutoTaggedUrlRef = useRef12(null);
9198
+ const handleAutoTag = useCallback11(async () => {
9498
9199
  if (!ctx) return;
9499
9200
  const { extractedElements, tagStore, commandUrl, serverUrl: serverUrl2, websiteId } = ctx;
9500
9201
  if (extractedElements.length === 0) return;
@@ -9533,7 +9234,7 @@ function ModelNexChatBubble({
9533
9234
  console.warn("[ModelNex] Auto-tag error:", err);
9534
9235
  }
9535
9236
  }, [ctx]);
9536
- useEffect17(() => {
9237
+ useEffect16(() => {
9537
9238
  if (authoringMode && ctx?.extractedElements.length) {
9538
9239
  const timer = setTimeout(handleAutoTag, 1e3);
9539
9240
  return () => clearTimeout(timer);
@@ -9555,7 +9256,7 @@ function ModelNexChatBubble({
9555
9256
  recordingMode,
9556
9257
  pendingNotificationType
9557
9258
  });
9558
- useEffect17(() => {
9259
+ useEffect16(() => {
9559
9260
  setHydrated(true);
9560
9261
  try {
9561
9262
  setExpanded(sessionStorage.getItem(BUBBLE_EXPANDED_STORAGE_KEY) === "true");
@@ -9565,18 +9266,18 @@ function ModelNexChatBubble({
9565
9266
  setDocked(false);
9566
9267
  }
9567
9268
  }, []);
9568
- const sttActiveRef = useRef13(false);
9569
- const [tourListenReady, setTourListenReady] = useState13(false);
9570
- const [tourSttError, setTourSttError] = useState13(null);
9571
- const previousTourActiveRef = useRef13(false);
9572
- const tourListenReadyRef = useRef13(false);
9573
- const tourSttErrorRef = useRef13(null);
9574
- const updateTourListenReady = useCallback12((next) => {
9269
+ const sttActiveRef = useRef12(false);
9270
+ const [tourListenReady, setTourListenReady] = useState12(false);
9271
+ const [tourSttError, setTourSttError] = useState12(null);
9272
+ const previousTourActiveRef = useRef12(false);
9273
+ const tourListenReadyRef = useRef12(false);
9274
+ const tourSttErrorRef = useRef12(null);
9275
+ const updateTourListenReady = useCallback11((next) => {
9575
9276
  if (tourListenReadyRef.current === next) return;
9576
9277
  tourListenReadyRef.current = next;
9577
9278
  setTourListenReady(next);
9578
9279
  }, []);
9579
- const updateTourSttError = useCallback12((next) => {
9280
+ const updateTourSttError = useCallback11((next) => {
9580
9281
  if (tourSttErrorRef.current === next) return;
9581
9282
  tourSttErrorRef.current = next;
9582
9283
  setTourSttError(next);
@@ -9585,7 +9286,7 @@ function ModelNexChatBubble({
9585
9286
  () => buildTranscriptPreviewLines(tourLiveTranscript, { maxCharsPerLine: 36, maxLines: 2 }),
9586
9287
  [tourLiveTranscript]
9587
9288
  );
9588
- useEffect17(() => {
9289
+ useEffect16(() => {
9589
9290
  const shouldShowFloatingTranscript = (tourPlayback.isActive || onboardingPlayback.isActive) && tourListenReady && voice.isListening;
9590
9291
  if (shouldShowFloatingTranscript) {
9591
9292
  showFloatingLiveTranscript({
@@ -9607,10 +9308,10 @@ function ModelNexChatBubble({
9607
9308
  tourPlayback.isActive,
9608
9309
  voice.isListening
9609
9310
  ]);
9610
- useEffect17(() => () => {
9311
+ useEffect16(() => () => {
9611
9312
  hideFloatingLiveTranscript();
9612
9313
  }, []);
9613
- const setExpandedState = useCallback12((next, opts) => {
9314
+ const setExpandedState = useCallback11((next, opts) => {
9614
9315
  setExpanded(next);
9615
9316
  try {
9616
9317
  sessionStorage.setItem(BUBBLE_EXPANDED_STORAGE_KEY, String(next));
@@ -9622,14 +9323,14 @@ function ModelNexChatBubble({
9622
9323
  } catch {
9623
9324
  }
9624
9325
  }, [tourPlayback.isActive, onboardingPlayback.isActive]);
9625
- const setDockedState = useCallback12((next) => {
9326
+ const setDockedState = useCallback11((next) => {
9626
9327
  setDocked(next);
9627
9328
  try {
9628
9329
  sessionStorage.setItem(BUBBLE_DOCKED_STORAGE_KEY, String(next));
9629
9330
  } catch {
9630
9331
  }
9631
9332
  }, []);
9632
- useEffect17(() => {
9333
+ useEffect16(() => {
9633
9334
  if (shouldAutoExpandForPendingPrompt({
9634
9335
  pendingPrompt,
9635
9336
  isPlaybackActive,
@@ -9639,8 +9340,8 @@ function ModelNexChatBubble({
9639
9340
  setExpandedState(true);
9640
9341
  }
9641
9342
  }, [isPlaybackActive, pendingNotificationType, pendingPrompt, recordingMode, setExpandedState]);
9642
- const preferredListeningExperienceRef = useRef13(null);
9643
- const playbackVoiceRoutingRef = useRef13({
9343
+ const preferredListeningExperienceRef = useRef12(null);
9344
+ const playbackVoiceRoutingRef = useRef12({
9644
9345
  activeExperienceType,
9645
9346
  isActive: activePlayback.isActive,
9646
9347
  isReviewMode: activePlayback.isReviewMode,
@@ -9648,7 +9349,7 @@ function ModelNexChatBubble({
9648
9349
  playbackState: activePlayback.playbackState,
9649
9350
  handleVoiceInput: playbackController.handleVoiceInput
9650
9351
  });
9651
- useEffect17(() => {
9352
+ useEffect16(() => {
9652
9353
  playbackVoiceRoutingRef.current = {
9653
9354
  activeExperienceType,
9654
9355
  isActive: activePlayback.isActive,
@@ -9665,7 +9366,7 @@ function ModelNexChatBubble({
9665
9366
  playbackController.handleVoiceInput,
9666
9367
  playbackController.pendingPrompt?.experienceType
9667
9368
  ]);
9668
- const handleVoiceTourInput = useCallback12(createSinglePlaybackTranscriptRouter(
9369
+ const handleVoiceTourInput = useCallback11(createSinglePlaybackTranscriptRouter(
9669
9370
  () => ({
9670
9371
  isReviewMode: playbackVoiceRoutingRef.current.isReviewMode,
9671
9372
  playbackState: playbackVoiceRoutingRef.current.playbackState
@@ -9680,7 +9381,7 @@ function ModelNexChatBubble({
9680
9381
  }
9681
9382
  }
9682
9383
  ), []);
9683
- const startTourListening = useCallback12((preferredExperience) => {
9384
+ const startTourListening = useCallback11((preferredExperience) => {
9684
9385
  const listeningState = {
9685
9386
  isTourActive: tourPlayback.isActive,
9686
9387
  isOnboardingActive: onboardingPlayback.isActive,
@@ -9735,7 +9436,7 @@ function ModelNexChatBubble({
9735
9436
  updateTourListenReady,
9736
9437
  updateTourSttError
9737
9438
  ]);
9738
- useEffect17(() => {
9439
+ useEffect16(() => {
9739
9440
  const isPlaybackActive2 = isTourListeningSessionActive({
9740
9441
  isTourActive: tourPlayback.isActive,
9741
9442
  isOnboardingActive: onboardingPlayback.isActive,
@@ -9758,12 +9459,12 @@ function ModelNexChatBubble({
9758
9459
  }
9759
9460
  }
9760
9461
  }, [tourPlayback.isActive, onboardingPlayback.isActive, setExpandedState, startingExperienceType, updateTourSttError]);
9761
- useEffect17(() => {
9462
+ useEffect16(() => {
9762
9463
  if (!tourPlayback.isReviewMode && !onboardingPlayback.isReviewMode) {
9763
9464
  setReviewDraft("");
9764
9465
  }
9765
9466
  }, [tourPlayback.isReviewMode, onboardingPlayback.isReviewMode]);
9766
- useEffect17(() => {
9467
+ useEffect16(() => {
9767
9468
  if (!isTourListeningSessionActive({
9768
9469
  isTourActive: tourPlayback.isActive,
9769
9470
  isOnboardingActive: onboardingPlayback.isActive,
@@ -9772,12 +9473,12 @@ function ModelNexChatBubble({
9772
9473
  preferredListeningExperienceRef.current = null;
9773
9474
  }
9774
9475
  }, [tourPlayback.isActive, onboardingPlayback.isActive, startingExperienceType]);
9775
- useEffect17(() => {
9476
+ useEffect16(() => {
9776
9477
  if (recordingMode) {
9777
9478
  setExpandedState(false);
9778
9479
  }
9779
9480
  }, [recordingMode, setExpandedState]);
9780
- useEffect17(() => {
9481
+ useEffect16(() => {
9781
9482
  const isPlaybackActive2 = isTourListeningSessionActive({
9782
9483
  isTourActive: tourPlayback.isActive,
9783
9484
  isOnboardingActive: onboardingPlayback.isActive,
@@ -9791,7 +9492,7 @@ function ModelNexChatBubble({
9791
9492
  }
9792
9493
  updateTourListenReady(Boolean(voice.isListening && sttActiveRef.current));
9793
9494
  }, [tourPlayback.isActive, onboardingPlayback.isActive, voice.isListening, startingExperienceType, updateTourListenReady]);
9794
- useEffect17(() => {
9495
+ useEffect16(() => {
9795
9496
  const isPlaybackActive2 = isTourListeningSessionActive({
9796
9497
  isTourActive: tourPlayback.isActive,
9797
9498
  isOnboardingActive: onboardingPlayback.isActive,
@@ -9806,7 +9507,7 @@ function ModelNexChatBubble({
9806
9507
  voice.stopListening();
9807
9508
  }
9808
9509
  }, [tourPlayback.isActive, onboardingPlayback.isActive, voice, startingExperienceType, updateTourListenReady, updateTourSttError]);
9809
- useEffect17(() => {
9510
+ useEffect16(() => {
9810
9511
  const isPlaybackActive2 = isTourListeningSessionActive({
9811
9512
  isTourActive: tourPlayback.isActive,
9812
9513
  isOnboardingActive: onboardingPlayback.isActive,
@@ -9828,8 +9529,8 @@ function ModelNexChatBubble({
9828
9529
  window.removeEventListener("keydown", enableTourListeningFromGesture, true);
9829
9530
  };
9830
9531
  }, [tourPlayback.isActive, onboardingPlayback.isActive, tourListenReady, tourSttError, voice.sttSupported, startTourListening, startingExperienceType]);
9831
- const [voiceInputMode, setVoiceInputMode] = useState13(false);
9832
- const toggleVoiceInput = useCallback12(() => {
9532
+ const [voiceInputMode, setVoiceInputMode] = useState12(false);
9533
+ const toggleVoiceInput = useCallback11(() => {
9833
9534
  if (voiceInputMode) {
9834
9535
  voice.stopListening();
9835
9536
  setVoiceInputMode(false);
@@ -9852,7 +9553,7 @@ function ModelNexChatBubble({
9852
9553
  );
9853
9554
  }
9854
9555
  }, [voiceInputMode, voice, recordingMode, recording]);
9855
- useEffect17(() => {
9556
+ useEffect16(() => {
9856
9557
  const panel = panelRef.current;
9857
9558
  if (!panel) return;
9858
9559
  const stopKeyPropagation = (e) => {
@@ -9876,7 +9577,7 @@ function ModelNexChatBubble({
9876
9577
  const tourCompletionRatio = tourPlayback.totalSteps > 0 ? Math.min(tourPlayback.currentStepIndex / tourPlayback.totalSteps, 1) : 0;
9877
9578
  const tourCurrentStep = tourPlayback.activeTour?.steps?.[tourPlayback.currentStepIndex] || null;
9878
9579
  const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
9879
- useEffect17(() => {
9580
+ useEffect16(() => {
9880
9581
  if (messages.length > 0) scrollToBottom();
9881
9582
  }, [messages, loading]);
9882
9583
  const runAgent = async () => {
@@ -9933,7 +9634,7 @@ function ModelNexChatBubble({
9933
9634
  const clearChat = () => {
9934
9635
  setMessages([]);
9935
9636
  };
9936
- const startRecordingSession = useCallback12((experienceType) => {
9637
+ const startRecordingSession = useCallback11((experienceType) => {
9937
9638
  abortControllerRef.current?.abort();
9938
9639
  abortControllerRef.current = null;
9939
9640
  setLoading(false);
@@ -11186,7 +10887,7 @@ function ModelNexChatBubble({
11186
10887
  }
11187
10888
 
11188
10889
  // src/onboarding-panel.tsx
11189
- import { useCallback as useCallback13, useContext as useContext6, useEffect as useEffect18, useMemo as useMemo4, useRef as useRef14, useState as useState14 } from "react";
10890
+ import { useCallback as useCallback12, useContext as useContext6, useEffect as useEffect17, useMemo as useMemo4, useRef as useRef13, useState as useState13 } from "react";
11190
10891
  import { createPortal as createPortal2 } from "react-dom";
11191
10892
 
11192
10893
  // src/hooks/useOnboardingPlayback.ts
@@ -11221,14 +10922,14 @@ function ModelNexOnboardingPanel({
11221
10922
  const devMode = ctx?.devMode ?? false;
11222
10923
  const voice = useVoice(serverUrl);
11223
10924
  const audioLevels = useAudioLevel(voice.isListening);
11224
- const [input, setInput] = useState14("");
11225
- const [reviewDraft, setReviewDraft] = useState14("");
11226
- const [open, setOpen] = useState14(true);
11227
- const [currentStepIndex, setCurrentStepIndex] = useState14(0);
11228
- const [liveTranscript, setLiveTranscript] = useState14("");
11229
- const [voiceEnabled, setVoiceEnabled] = useState14(false);
11230
- const [sttError, setSttError] = useState14(null);
11231
- const voiceEnabledRef = useRef14(false);
10925
+ const [input, setInput] = useState13("");
10926
+ const [reviewDraft, setReviewDraft] = useState13("");
10927
+ const [open, setOpen] = useState13(true);
10928
+ const [currentStepIndex, setCurrentStepIndex] = useState13(0);
10929
+ const [liveTranscript, setLiveTranscript] = useState13("");
10930
+ const [voiceEnabled, setVoiceEnabled] = useState13(false);
10931
+ const [sttError, setSttError] = useState13(null);
10932
+ const voiceEnabledRef = useRef13(false);
11232
10933
  const playback = useOnboardingPlayback({
11233
10934
  serverUrl,
11234
10935
  commandUrl: ctx?.commandUrl,
@@ -11248,26 +10949,26 @@ function ModelNexOnboardingPanel({
11248
10949
  voiceEnabledRef.current = false;
11249
10950
  }
11250
10951
  });
11251
- const playbackVoiceRoutingRef = useRef14({
10952
+ const playbackVoiceRoutingRef = useRef13({
11252
10953
  isReviewMode: false,
11253
10954
  playbackState: playback.playbackState,
11254
10955
  handleVoiceInput: playback.handleVoiceInput
11255
10956
  });
11256
10957
  const reviewToggle = getReviewModeToggleConfig(playback.playbackState);
11257
10958
  const reviewModeEnabled = isReviewModeEnabled(devMode, playback.isReviewMode);
11258
- useEffect18(() => {
10959
+ useEffect17(() => {
11259
10960
  playbackVoiceRoutingRef.current = {
11260
10961
  isReviewMode: playback.isReviewMode,
11261
10962
  playbackState: playback.playbackState,
11262
10963
  handleVoiceInput: playback.handleVoiceInput
11263
10964
  };
11264
10965
  }, [playback.isReviewMode, playback.playbackState, playback.handleVoiceInput]);
11265
- useEffect18(() => {
10966
+ useEffect17(() => {
11266
10967
  if (playback.isActive && !playback.isReviewMode) {
11267
10968
  setOpen(true);
11268
10969
  }
11269
10970
  }, [playback.activeTour?.id, playback.isActive, playback.isReviewMode]);
11270
- const startVoiceListening = useCallback13(() => {
10971
+ const startVoiceListening = useCallback12(() => {
11271
10972
  if (voiceEnabledRef.current || !voice.sttSupported) return;
11272
10973
  voiceEnabledRef.current = true;
11273
10974
  setVoiceEnabled(true);
@@ -11314,7 +11015,7 @@ function ModelNexOnboardingPanel({
11314
11015
  }
11315
11016
  );
11316
11017
  }, [playback.isActive, voice]);
11317
- useEffect18(() => {
11018
+ useEffect17(() => {
11318
11019
  if (!playback.isActive || voiceEnabledRef.current || !voice.sttSupported) return;
11319
11020
  const startOnGesture = (event) => {
11320
11021
  if (shouldIgnorePanelGestureStart(event.target)) {
@@ -11329,7 +11030,7 @@ function ModelNexOnboardingPanel({
11329
11030
  window.removeEventListener("keydown", startOnGesture, true);
11330
11031
  };
11331
11032
  }, [playback.isActive, voice.sttSupported, startVoiceListening]);
11332
- useEffect18(() => {
11033
+ useEffect17(() => {
11333
11034
  if (!playback.isActive && voiceEnabledRef.current) {
11334
11035
  voiceEnabledRef.current = false;
11335
11036
  setVoiceEnabled(false);
@@ -11349,7 +11050,7 @@ function ModelNexOnboardingPanel({
11349
11050
  () => buildTranscriptPreviewLines(liveTranscript, { maxCharsPerLine: 34, maxLines: 2 }),
11350
11051
  [liveTranscript]
11351
11052
  );
11352
- useEffect18(() => {
11053
+ useEffect17(() => {
11353
11054
  if (voiceEnabled && voice.isListening) {
11354
11055
  showFloatingLiveTranscript({
11355
11056
  levels: audioLevels,
@@ -11363,7 +11064,7 @@ function ModelNexOnboardingPanel({
11363
11064
  }
11364
11065
  hideFloatingLiveTranscript();
11365
11066
  }, [audioLevels, liveTranscriptLines, voiceEnabled, voice.isListening]);
11366
- useEffect18(() => () => {
11067
+ useEffect17(() => () => {
11367
11068
  hideFloatingLiveTranscript();
11368
11069
  }, []);
11369
11070
  const actionButtonStyle = {
@@ -11928,7 +11629,7 @@ var ModelNexProvider = ({
11928
11629
  const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
11929
11630
  const commandUrl = void 0;
11930
11631
  const disableSocket = false;
11931
- useEffect19(() => {
11632
+ useEffect18(() => {
11932
11633
  if (process.env.NODE_ENV !== "production" && !serverUrlProp) {
11933
11634
  console.warn(
11934
11635
  `[ModelNex SDK] ModelNexProvider is using the default server URL (${DEFAULT_MODELNEX_SERVER_URL}). Pass \`serverUrl\` explicitly in local development to avoid accidentally targeting the hosted backend.`
@@ -11936,21 +11637,21 @@ var ModelNexProvider = ({
11936
11637
  }
11937
11638
  }, [serverUrlProp]);
11938
11639
  const renderedChildren = children;
11939
- const [activeAgentActions, setActiveAgentActions] = useState15(/* @__PURE__ */ new Set());
11940
- const [stagingFields, setStagingFields] = useState15(/* @__PURE__ */ new Set());
11941
- const [executedFields, setExecutedFields] = useState15(/* @__PURE__ */ new Set());
11942
- const [highlightActions, setHighlightActions] = useState15(false);
11943
- const [studioMode, setStudioMode] = useState15(false);
11944
- const [recordingMode, setRecordingMode] = useState15(() => hasPersistedRecordingSession());
11945
- const [voiceMuted, setVoiceMuted] = useState15(false);
11946
- const [socketId, setSocketId] = useState15(null);
11947
- const [actions, setActions] = useState15(/* @__PURE__ */ new Map());
11948
- const [validatedBrowserDevMode, setValidatedBrowserDevMode] = useState15(false);
11949
- const [resolvedDevModeKey, setResolvedDevModeKey] = useState15(() => resolveInjectedDevModeKey());
11950
- const [previewRuntimeMode, setPreviewRuntimeMode] = useState15(() => hasDraftPreviewModeSignal());
11951
- useEffect19(() => observeInjectedDevModeKey(setResolvedDevModeKey), []);
11952
- useEffect19(() => observeDraftPreviewModeSignal(setPreviewRuntimeMode), []);
11953
- useEffect19(() => {
11640
+ const [activeAgentActions, setActiveAgentActions] = useState14(/* @__PURE__ */ new Set());
11641
+ const [stagingFields, setStagingFields] = useState14(/* @__PURE__ */ new Set());
11642
+ const [executedFields, setExecutedFields] = useState14(/* @__PURE__ */ new Set());
11643
+ const [highlightActions, setHighlightActions] = useState14(false);
11644
+ const [studioMode, setStudioMode] = useState14(false);
11645
+ const [recordingMode, setRecordingMode] = useState14(() => hasPersistedRecordingSession());
11646
+ const [voiceMuted, setVoiceMuted] = useState14(false);
11647
+ const [socketId, setSocketId] = useState14(null);
11648
+ const [actions, setActions] = useState14(/* @__PURE__ */ new Map());
11649
+ const [validatedBrowserDevMode, setValidatedBrowserDevMode] = useState14(false);
11650
+ const [resolvedDevModeKey, setResolvedDevModeKey] = useState14(() => resolveInjectedDevModeKey());
11651
+ const [previewRuntimeMode, setPreviewRuntimeMode] = useState14(() => hasDraftPreviewModeSignal());
11652
+ useEffect18(() => observeInjectedDevModeKey(setResolvedDevModeKey), []);
11653
+ useEffect18(() => observeDraftPreviewModeSignal(setPreviewRuntimeMode), []);
11654
+ useEffect18(() => {
11954
11655
  let cancelled = false;
11955
11656
  if (!websiteId || !resolvedDevModeKey) {
11956
11657
  setValidatedBrowserDevMode(false);
@@ -11967,14 +11668,14 @@ var ModelNexProvider = ({
11967
11668
  };
11968
11669
  }, [resolvedDevModeKey, serverUrl, websiteId]);
11969
11670
  const effectiveDevMode = validatedBrowserDevMode || previewRuntimeMode;
11970
- const registerAction = useCallback14((action) => {
11671
+ const registerAction = useCallback13((action) => {
11971
11672
  setActions((prev) => {
11972
11673
  const next = new Map(prev);
11973
11674
  next.set(action.id, action);
11974
11675
  return next;
11975
11676
  });
11976
11677
  }, []);
11977
- const unregisterAction = useCallback14((id) => {
11678
+ const unregisterAction = useCallback13((id) => {
11978
11679
  setActions((prev) => {
11979
11680
  const next = new Map(prev);
11980
11681
  next.delete(id);
@@ -11985,8 +11686,8 @@ var ModelNexProvider = ({
11985
11686
  const tagStore = useTagStore({ serverUrl, websiteId });
11986
11687
  useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile);
11987
11688
  const CHAT_STORAGE_KEY = "modelnex-chat-messages";
11988
- const [chatMessages, setChatMessagesRaw] = useState15([]);
11989
- useEffect19(() => {
11689
+ const [chatMessages, setChatMessagesRaw] = useState14([]);
11690
+ useEffect18(() => {
11990
11691
  try {
11991
11692
  const stored = sessionStorage.getItem(CHAT_STORAGE_KEY);
11992
11693
  if (stored) {
@@ -11995,7 +11696,7 @@ var ModelNexProvider = ({
11995
11696
  } catch {
11996
11697
  }
11997
11698
  }, [effectiveDevMode]);
11998
- useEffect19(() => {
11699
+ useEffect18(() => {
11999
11700
  setChatMessagesRaw((prev) => {
12000
11701
  const next = sanitizeChatMessages(prev, effectiveDevMode);
12001
11702
  try {
@@ -12005,7 +11706,7 @@ var ModelNexProvider = ({
12005
11706
  return next;
12006
11707
  });
12007
11708
  }, [effectiveDevMode]);
12008
- const setChatMessages = useCallback14((action) => {
11709
+ const setChatMessages = useCallback13((action) => {
12009
11710
  setChatMessagesRaw((prev) => {
12010
11711
  const resolved = typeof action === "function" ? action(prev) : action;
12011
11712
  const next = sanitizeChatMessages(resolved, effectiveDevMode);
@@ -12031,14 +11732,14 @@ var ModelNexProvider = ({
12031
11732
  devMode: effectiveDevMode
12032
11733
  });
12033
11734
  useFieldHighlight(stagingFields, executedFields, setExecutedFields);
12034
- useEffect19(() => {
11735
+ useEffect18(() => {
12035
11736
  document.body.classList.toggle("modelnex-highlight-actions", highlightActions);
12036
11737
  return () => {
12037
11738
  document.body.classList.remove("modelnex-highlight-actions");
12038
11739
  };
12039
11740
  }, [highlightActions]);
12040
- const [mounted, setMounted] = useState15(false);
12041
- useEffect19(() => {
11741
+ const [mounted, setMounted] = useState14(false);
11742
+ useEffect18(() => {
12042
11743
  setMounted(true);
12043
11744
  }, []);
12044
11745
  const value = useMemo5(