@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/{aom-LJNCLNXL.mjs → aom-SP2LMWQI.mjs} +1 -1
- package/dist/chunk-SXGINP3O.mjs +683 -0
- package/dist/index.js +863 -708
- package/dist/index.mjs +494 -793
- package/package.json +1 -1
- package/dist/chunk-H4LUY7LI.mjs +0 -243
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
696
|
-
const actionsRef =
|
|
697
|
-
const contextsRef =
|
|
698
|
-
const documentationRef =
|
|
699
|
-
const tagsRef =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
483
|
+
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
856
484
|
function useFieldHighlight(stagingFields, executedFields, setExecutedFields) {
|
|
857
|
-
const highlightedRef =
|
|
858
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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] =
|
|
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
|
-
|
|
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
|
-
|
|
770
|
+
useEffect5(() => {
|
|
1143
771
|
if (typeof window === "undefined") return;
|
|
1144
772
|
saveTags(tags);
|
|
1145
773
|
}, [tags]);
|
|
1146
|
-
const getTag =
|
|
774
|
+
const getTag = useCallback2((fingerprint) => {
|
|
1147
775
|
return tags.get(fingerprint);
|
|
1148
776
|
}, [tags]);
|
|
1149
|
-
const setTag =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
844
|
+
const getAllTags = useCallback2(() => {
|
|
1217
845
|
return Array.from(tags.values());
|
|
1218
846
|
}, [tags]);
|
|
1219
|
-
const exportTags =
|
|
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 =
|
|
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
|
|
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] =
|
|
1254
|
-
const [category, setCategory] =
|
|
1255
|
-
const inputRef =
|
|
1256
|
-
|
|
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] =
|
|
1484
|
-
const [positions, setPositions] =
|
|
1485
|
-
const liveElementsRef =
|
|
1486
|
-
const updatePositions =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
2506
|
-
const tagStoreRef =
|
|
2145
|
+
const registeredRef = useRef5(false);
|
|
2146
|
+
const tagStoreRef = useRef5(tagStore);
|
|
2507
2147
|
tagStoreRef.current = tagStore;
|
|
2508
|
-
const serverUrlRef =
|
|
2148
|
+
const serverUrlRef = useRef5(serverUrl);
|
|
2509
2149
|
serverUrlRef.current = serverUrl;
|
|
2510
|
-
const websiteIdRef =
|
|
2150
|
+
const websiteIdRef = useRef5(websiteId);
|
|
2511
2151
|
websiteIdRef.current = websiteId;
|
|
2512
|
-
const toursApiBaseRef =
|
|
2152
|
+
const toursApiBaseRef = useRef5(toursApiBase);
|
|
2513
2153
|
toursApiBaseRef.current = toursApiBase;
|
|
2514
|
-
const userProfileRef =
|
|
2154
|
+
const userProfileRef = useRef5(userProfile);
|
|
2515
2155
|
userProfileRef.current = userProfile;
|
|
2516
|
-
|
|
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
|
|
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
|
|
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
|
|
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] =
|
|
2932
|
-
const setState =
|
|
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
|
|
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 =
|
|
2955
|
-
const [, setTick] =
|
|
2956
|
-
|
|
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
|
-
|
|
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] =
|
|
2982
|
-
|
|
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
|
|
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
|
|
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
|
|
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] =
|
|
3731
|
-
const [currentStepIndex, setCurrentStepIndex] =
|
|
3732
|
-
const [totalSteps, setTotalSteps] =
|
|
3733
|
-
const [activeTour, setActiveTour] =
|
|
3734
|
-
const [playbackState, setPlaybackState] =
|
|
3735
|
-
const [isReviewMode, setIsReviewMode] =
|
|
3736
|
-
const [previewRunId, setPreviewRunId] =
|
|
3737
|
-
const [reviewSubmitting, setReviewSubmitting] =
|
|
3738
|
-
const [reviewStatusMessage, setReviewStatusMessage] =
|
|
3739
|
-
const [pendingTour, setPendingTour] =
|
|
3740
|
-
const [serverState, setServerState] =
|
|
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 =
|
|
3431
|
+
const devModeRef = useRef7(devMode);
|
|
3744
3432
|
devModeRef.current = devMode;
|
|
3745
|
-
const userProfileRef =
|
|
3433
|
+
const userProfileRef = useRef7(userProfile);
|
|
3746
3434
|
userProfileRef.current = userProfile;
|
|
3747
|
-
const experienceTypeRef =
|
|
3435
|
+
const experienceTypeRef = useRef7(experienceType);
|
|
3748
3436
|
experienceTypeRef.current = experienceType;
|
|
3749
|
-
const tourRef =
|
|
3750
|
-
const stepIndexRef =
|
|
3751
|
-
const skipRequestedRef =
|
|
3752
|
-
const advanceRequestedRef =
|
|
3753
|
-
const textInputResolveRef =
|
|
3754
|
-
const voiceInputResolveRef =
|
|
3755
|
-
const askOrFillRef =
|
|
3756
|
-
const pendingManualWaitCleanupRef =
|
|
3757
|
-
const pendingManualInputSyncRef =
|
|
3758
|
-
const llmRespondingRef =
|
|
3759
|
-
const interruptedForQuestionRef =
|
|
3760
|
-
const pendingInputBufRef =
|
|
3761
|
-
const activeExecutionTokenRef =
|
|
3762
|
-
const commandInFlightRef =
|
|
3763
|
-
const reviewModeRef =
|
|
3764
|
-
const previewRunIdRef =
|
|
3765
|
-
const toursApiBaseRef =
|
|
3766
|
-
const pendingTourRef =
|
|
3767
|
-
const pendingTourOptionsRef =
|
|
3768
|
-
const showCaptionsRef =
|
|
3769
|
-
const
|
|
3770
|
-
const
|
|
3771
|
-
const
|
|
3772
|
-
const
|
|
3773
|
-
const
|
|
3774
|
-
const
|
|
3775
|
-
const
|
|
3776
|
-
const
|
|
3777
|
-
const
|
|
3778
|
-
const
|
|
3779
|
-
const
|
|
3780
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
4083
|
-
const
|
|
4084
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
4216
|
+
emitTourInit(s, websiteId, profile);
|
|
4518
4217
|
}, 150);
|
|
4519
4218
|
return () => clearTimeout(timer);
|
|
4520
|
-
}, [disabled, websiteId, userProfile?.userId, userProfile?.type]);
|
|
4521
|
-
|
|
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
|
-
|
|
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 =
|
|
4239
|
+
const syncAOM = useCallback6(async () => {
|
|
4541
4240
|
if (!isActiveRef.current) return;
|
|
4542
|
-
const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4569
|
+
useEffect10(() => {
|
|
4869
4570
|
if (!disabled || !isActiveRef.current) return;
|
|
4870
4571
|
stopTour();
|
|
4871
4572
|
}, [disabled, stopTour]);
|
|
4872
|
-
const startTour =
|
|
4573
|
+
const startTour = useCallback6((tour, options) => {
|
|
4873
4574
|
if (disabled) return;
|
|
4874
4575
|
void runTour(tour, options);
|
|
4875
4576
|
}, [disabled, runTour]);
|
|
4876
|
-
const acceptPendingTour =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4632
|
+
const advanceStep = useCallback6(() => {
|
|
4932
4633
|
advanceRequestedRef.current = true;
|
|
4933
4634
|
}, []);
|
|
4934
|
-
const skipTour =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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] =
|
|
5087
|
-
const [startingExperienceType, setStartingExperienceType] =
|
|
5088
|
-
const [pendingPrompt, setPendingPrompt] =
|
|
5089
|
-
const pendingPromptRef =
|
|
5090
|
-
const queuedStartRef =
|
|
5091
|
-
const bufferedVoiceInputsRef =
|
|
5092
|
-
const previewDiscoveryInFlightRef =
|
|
5093
|
-
const previewSessionRef =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
4881
|
+
useEffect11(() => {
|
|
5181
4882
|
if (playback.isActive && startingExperienceType !== null) {
|
|
5182
4883
|
setStartingExperienceType(null);
|
|
5183
4884
|
}
|
|
5184
4885
|
}, [playback.isActive, startingExperienceType]);
|
|
5185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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] =
|
|
5716
|
-
const [isListening, setIsListening] =
|
|
5717
|
-
const [isMuted, setIsMuted] =
|
|
5718
|
-
const audioRef =
|
|
5719
|
-
const audioBlobUrlRef =
|
|
5720
|
-
const speakResolveRef =
|
|
5721
|
-
const recognitionRef =
|
|
5722
|
-
const isMutedRef =
|
|
5723
|
-
const sttCallbacksRef =
|
|
5724
|
-
const stripIndicesRef =
|
|
5725
|
-
const accumulatedRef =
|
|
5726
|
-
const interimDebounceRef =
|
|
5727
|
-
const lastInterimRef =
|
|
5728
|
-
const lastDeliveredRef =
|
|
5729
|
-
const isSpeakingRef =
|
|
5730
|
-
const speechEndTimeRef =
|
|
5731
|
-
const recentTtsRef =
|
|
5732
|
-
const prefetchedSpeechRef =
|
|
5733
|
-
const nearEndTimeoutRef =
|
|
5734
|
-
const queuePromiseRef =
|
|
5735
|
-
const queueSeqRef =
|
|
5736
|
-
const lastStopSeqRef =
|
|
5737
|
-
const listeningSessionIdRef =
|
|
5738
|
-
const listeningStartedAtRef =
|
|
5739
|
-
const loopbackRef =
|
|
5740
|
-
const aecActiveRef =
|
|
5741
|
-
const mediaRecorderRef =
|
|
5742
|
-
const micStreamRef =
|
|
5743
|
-
const sttSocketRef =
|
|
5744
|
-
const stopLiveSttTransport =
|
|
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
|
-
|
|
5474
|
+
useEffect12(() => {
|
|
5774
5475
|
isMutedRef.current = isMuted;
|
|
5775
5476
|
}, [isMuted]);
|
|
5776
|
-
|
|
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 =
|
|
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 =
|
|
5810
|
-
const loadAudioDurationMs =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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] =
|
|
6474
|
-
const streamRef =
|
|
6475
|
-
const ctxRef =
|
|
6476
|
-
const analyserRef =
|
|
6477
|
-
const rafRef =
|
|
6478
|
-
const timeDataRef =
|
|
6479
|
-
|
|
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
|
|
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 =
|
|
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] =
|
|
6839
|
-
const [steps, setSteps] =
|
|
6840
|
-
const [selectedElement, setSelectedElement] =
|
|
6841
|
-
const [selectedStepType, setSelectedStepType] =
|
|
6842
|
-
const [pendingNarration, setPendingNarration] =
|
|
6843
|
-
const [polishedNarration, setPolishedNarration] =
|
|
6844
|
-
const [captureEvents, setCaptureEvents] =
|
|
6845
|
-
const [capturedTranscript, setCapturedTranscript] =
|
|
6846
|
-
const [isVoiceCaptureActive, setIsVoiceCaptureActive] =
|
|
6847
|
-
const stepsRef =
|
|
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 =
|
|
6850
|
-
const capturedTranscriptRef =
|
|
6550
|
+
const captureEventsRef = useRef11([]);
|
|
6551
|
+
const capturedTranscriptRef = useRef11(capturedTranscript);
|
|
6851
6552
|
capturedTranscriptRef.current = capturedTranscript;
|
|
6852
|
-
const phaseRef =
|
|
6553
|
+
const phaseRef = useRef11(phase);
|
|
6853
6554
|
phaseRef.current = phase;
|
|
6854
|
-
const safeSpeak =
|
|
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 =
|
|
6861
|
-
const shouldKeepVoiceCaptureRef =
|
|
6862
|
-
const resumeVoiceAfterNarrationRef =
|
|
6863
|
-
const lastAutoNoteRef =
|
|
6864
|
-
const lastHoverKeyRef =
|
|
6865
|
-
const lastHoverAtRef =
|
|
6866
|
-
const selectedStepTypeRef =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6685
|
+
const stopBackgroundVoiceCapture = useCallback9(() => {
|
|
6985
6686
|
voice.stopListening();
|
|
6986
6687
|
setIsVoiceCaptureActive(false);
|
|
6987
6688
|
}, [voice]);
|
|
6988
|
-
const startBackgroundVoiceCapture =
|
|
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 =
|
|
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 =
|
|
6738
|
+
const markStep = useCallback9(() => {
|
|
7038
6739
|
if (phase !== "active") return;
|
|
7039
6740
|
setPhase("selecting");
|
|
7040
6741
|
}, [phase]);
|
|
7041
|
-
const selectElement =
|
|
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 =
|
|
6748
|
+
const selectPageLevel = useCallback9(() => {
|
|
7048
6749
|
setSelectedElement(null);
|
|
7049
6750
|
setSelectedStepType("narrate");
|
|
7050
6751
|
setPhase("configuring");
|
|
7051
6752
|
}, []);
|
|
7052
|
-
const cancelSelection =
|
|
6753
|
+
const cancelSelection = useCallback9(() => {
|
|
7053
6754
|
setSelectedElement(null);
|
|
7054
6755
|
setPhase("active");
|
|
7055
6756
|
}, []);
|
|
7056
|
-
const setStepType =
|
|
6757
|
+
const setStepType = useCallback9((type) => {
|
|
7057
6758
|
setSelectedStepType(type);
|
|
7058
6759
|
}, []);
|
|
7059
|
-
const runPolishAndReview =
|
|
6760
|
+
const runPolishAndReview = useCallback9(async (transcript) => {
|
|
7060
6761
|
setPolishedNarration(transcript);
|
|
7061
6762
|
setPhase("reviewing");
|
|
7062
6763
|
}, []);
|
|
7063
|
-
const pendingNarrationRef =
|
|
7064
|
-
const startNarration =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6904
|
+
const editNarration = useCallback9((text) => {
|
|
7204
6905
|
setPendingNarration(text);
|
|
7205
6906
|
setPolishedNarration(text);
|
|
7206
6907
|
}, []);
|
|
7207
|
-
const continueRecording =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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] =
|
|
7568
|
-
|
|
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] =
|
|
7814
|
-
const [showAdvanced, setShowAdvanced] =
|
|
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] =
|
|
7959
|
-
const [showTextInput, setShowTextInput] =
|
|
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] =
|
|
8127
|
-
const [editText, setEditText] =
|
|
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
|
|
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] =
|
|
8422
|
-
const [pendingElement, setPendingElement] =
|
|
8423
|
-
const [showPopover, setShowPopover] =
|
|
8424
|
-
const [hudMinimized, setHudMinimized] =
|
|
8425
|
-
const [voicePanelMinimized, setVoicePanelMinimized] =
|
|
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 =
|
|
8428
|
-
const target = e.target.closest(
|
|
8128
|
+
const handleMouseMove = useCallback10((e) => {
|
|
8129
|
+
const target = e.target.closest(INTERACTIVE_SELECTOR);
|
|
8429
8130
|
setHoveredEl(target);
|
|
8430
8131
|
}, []);
|
|
8431
|
-
const handleClick =
|
|
8132
|
+
const handleClick = useCallback10((e) => {
|
|
8432
8133
|
if (e.target.closest("[data-modelnex-internal]")) return;
|
|
8433
|
-
const target = e.target.closest(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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] =
|
|
9033
|
-
|
|
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] =
|
|
9053
|
-
|
|
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] =
|
|
9126
|
-
const ref =
|
|
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] =
|
|
9221
|
-
const [expandedSteps, setExpandedSteps] =
|
|
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
|
-
|
|
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 =
|
|
9386
|
-
const noopAsync =
|
|
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] =
|
|
9390
|
-
const [expanded, setExpanded] =
|
|
9391
|
-
const [docked, setDocked] =
|
|
9392
|
-
const [input, setInput] =
|
|
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] =
|
|
9397
|
-
const messagesEndRef =
|
|
9398
|
-
const abortControllerRef =
|
|
9399
|
-
const panelRef =
|
|
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] =
|
|
9409
|
-
const [recordingTargetTypes, setRecordingTargetTypes] =
|
|
9410
|
-
const [showStopModal, setShowStopModal] =
|
|
9411
|
-
const [savedDraft, setSavedDraft] =
|
|
9412
|
-
const [reviewDraft, setReviewDraft] =
|
|
9413
|
-
const [tourLiveTranscript, setTourLiveTranscript] =
|
|
9414
|
-
const [activeRecordingExperienceType, setActiveRecordingExperienceType] =
|
|
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
|
-
|
|
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
|
-
|
|
9160
|
+
useEffect16(() => {
|
|
9460
9161
|
return registerExperienceToolBridge({
|
|
9461
9162
|
startExperience: playbackController.startExperience
|
|
9462
9163
|
});
|
|
9463
9164
|
}, [playbackController.startExperience]);
|
|
9464
|
-
const createPlaybackView =
|
|
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 =
|
|
9497
|
-
const handleAutoTag =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
9569
|
-
const [tourListenReady, setTourListenReady] =
|
|
9570
|
-
const [tourSttError, setTourSttError] =
|
|
9571
|
-
const previousTourActiveRef =
|
|
9572
|
-
const tourListenReadyRef =
|
|
9573
|
-
const tourSttErrorRef =
|
|
9574
|
-
const updateTourListenReady =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
9311
|
+
useEffect16(() => () => {
|
|
9611
9312
|
hideFloatingLiveTranscript();
|
|
9612
9313
|
}, []);
|
|
9613
|
-
const setExpandedState =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
9643
|
-
const playbackVoiceRoutingRef =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
9462
|
+
useEffect16(() => {
|
|
9762
9463
|
if (!tourPlayback.isReviewMode && !onboardingPlayback.isReviewMode) {
|
|
9763
9464
|
setReviewDraft("");
|
|
9764
9465
|
}
|
|
9765
9466
|
}, [tourPlayback.isReviewMode, onboardingPlayback.isReviewMode]);
|
|
9766
|
-
|
|
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
|
-
|
|
9476
|
+
useEffect16(() => {
|
|
9776
9477
|
if (recordingMode) {
|
|
9777
9478
|
setExpandedState(false);
|
|
9778
9479
|
}
|
|
9779
9480
|
}, [recordingMode, setExpandedState]);
|
|
9780
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
9832
|
-
const toggleVoiceInput =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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] =
|
|
11225
|
-
const [reviewDraft, setReviewDraft] =
|
|
11226
|
-
const [open, setOpen] =
|
|
11227
|
-
const [currentStepIndex, setCurrentStepIndex] =
|
|
11228
|
-
const [liveTranscript, setLiveTranscript] =
|
|
11229
|
-
const [voiceEnabled, setVoiceEnabled] =
|
|
11230
|
-
const [sttError, setSttError] =
|
|
11231
|
-
const voiceEnabledRef =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
11940
|
-
const [stagingFields, setStagingFields] =
|
|
11941
|
-
const [executedFields, setExecutedFields] =
|
|
11942
|
-
const [highlightActions, setHighlightActions] =
|
|
11943
|
-
const [studioMode, setStudioMode] =
|
|
11944
|
-
const [recordingMode, setRecordingMode] =
|
|
11945
|
-
const [voiceMuted, setVoiceMuted] =
|
|
11946
|
-
const [socketId, setSocketId] =
|
|
11947
|
-
const [actions, setActions] =
|
|
11948
|
-
const [validatedBrowserDevMode, setValidatedBrowserDevMode] =
|
|
11949
|
-
const [resolvedDevModeKey, setResolvedDevModeKey] =
|
|
11950
|
-
const [previewRuntimeMode, setPreviewRuntimeMode] =
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
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 =
|
|
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 =
|
|
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] =
|
|
11989
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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] =
|
|
12041
|
-
|
|
11741
|
+
const [mounted, setMounted] = useState14(false);
|
|
11742
|
+
useEffect18(() => {
|
|
12042
11743
|
setMounted(true);
|
|
12043
11744
|
}, []);
|
|
12044
11745
|
const value = useMemo5(
|