@modelnex/sdk 0.5.27 → 0.5.29
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/{dom-sync-L5KIP45X.mjs → dom-sync-GABDEODR.mjs} +0 -1
- package/dist/index.d.mts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +701 -257
- package/dist/index.mjs +705 -260
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -27,6 +27,60 @@ function resolveSocketIoTransports(serverUrl, order) {
|
|
|
27
27
|
|
|
28
28
|
// src/auto-extract.ts
|
|
29
29
|
import { useState, useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
30
|
+
|
|
31
|
+
// src/utils/dev-logging.ts
|
|
32
|
+
function isSdkDebugEnabled(devMode) {
|
|
33
|
+
if (devMode) return true;
|
|
34
|
+
if (typeof window !== "undefined" && Boolean(window.MODELNEX_DEBUG)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return process.env.NODE_ENV === "development";
|
|
38
|
+
}
|
|
39
|
+
function emitSdkDebugLog(message, payload, options) {
|
|
40
|
+
if (!isSdkDebugEnabled(options?.devMode)) return;
|
|
41
|
+
if (payload && Object.keys(payload).length > 0) {
|
|
42
|
+
console.log(message, payload);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(message);
|
|
45
|
+
}
|
|
46
|
+
if (options?.dispatchEvent && typeof window !== "undefined") {
|
|
47
|
+
window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg: message, data: payload } }));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function sanitizeActionList(actions) {
|
|
51
|
+
if (!Array.isArray(actions) || actions.length === 0) return void 0;
|
|
52
|
+
return actions.map(({ actionId }) => ({ actionId }));
|
|
53
|
+
}
|
|
54
|
+
function sanitizeAgentDebug(debug) {
|
|
55
|
+
if (!debug) return void 0;
|
|
56
|
+
const actions = sanitizeActionList(debug.actions);
|
|
57
|
+
const traces = Array.isArray(debug.traces) && debug.traces.length > 0 ? debug.traces.map((trace) => ({
|
|
58
|
+
step: trace.step,
|
|
59
|
+
actions: sanitizeActionList(trace.actions) ?? [],
|
|
60
|
+
results: Array.isArray(trace.results) && trace.results.length > 0 ? trace.results.map(({ actionId, success }) => ({ actionId, success })) : void 0
|
|
61
|
+
})) : void 0;
|
|
62
|
+
if ((!actions || actions.length === 0) && (!traces || traces.length === 0)) {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
...actions ? { actions } : {},
|
|
67
|
+
...traces ? { traces } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function sanitizeChatMessages(messages, devMode) {
|
|
71
|
+
const includeDebug = isSdkDebugEnabled(devMode);
|
|
72
|
+
return messages.map((message) => {
|
|
73
|
+
if (message.role !== "assistant") {
|
|
74
|
+
return message;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
...message,
|
|
78
|
+
debug: includeDebug ? sanitizeAgentDebug(message.debug) : void 0
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/auto-extract.ts
|
|
30
84
|
function simpleHash(str) {
|
|
31
85
|
let hash = 0;
|
|
32
86
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -290,7 +344,7 @@ function extractInteractiveElements() {
|
|
|
290
344
|
}
|
|
291
345
|
return elements;
|
|
292
346
|
}
|
|
293
|
-
function useAutoExtract() {
|
|
347
|
+
function useAutoExtract(devMode) {
|
|
294
348
|
const [elements, setElements] = useState([]);
|
|
295
349
|
const timerRef = useRef2(null);
|
|
296
350
|
const lastSnapshotRef = useRef2("");
|
|
@@ -308,9 +362,11 @@ function useAutoExtract() {
|
|
|
308
362
|
})));
|
|
309
363
|
if (snapshot === lastSnapshotRef.current) return;
|
|
310
364
|
lastSnapshotRef.current = snapshot;
|
|
311
|
-
|
|
365
|
+
emitSdkDebugLog("[ModelNex AutoExtract] Scan complete", {
|
|
366
|
+
elementCount: extracted.length
|
|
367
|
+
}, { devMode });
|
|
312
368
|
setElements(extracted);
|
|
313
|
-
}, []);
|
|
369
|
+
}, [devMode]);
|
|
314
370
|
useEffect2(() => {
|
|
315
371
|
const initialTimer = setTimeout(scan, 300);
|
|
316
372
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -625,7 +681,8 @@ function useModelNexSocket({
|
|
|
625
681
|
setStagingFields,
|
|
626
682
|
setExecutedFields,
|
|
627
683
|
onSocketId,
|
|
628
|
-
websiteId
|
|
684
|
+
websiteId,
|
|
685
|
+
devMode
|
|
629
686
|
}) {
|
|
630
687
|
const socketRef = useRef3(null);
|
|
631
688
|
const actionsRef = useRef3(actions);
|
|
@@ -658,12 +715,14 @@ function useModelNexSocket({
|
|
|
658
715
|
allTaggedElements: tagsRef.current ? Array.from(tagsRef.current.values()) : void 0
|
|
659
716
|
});
|
|
660
717
|
socket.on("connect", () => {
|
|
661
|
-
|
|
718
|
+
emitSdkDebugLog("[ModelNex SDK] Connected to agent server", {
|
|
719
|
+
socketId: socket.id ?? null
|
|
720
|
+
}, { devMode });
|
|
662
721
|
onSocketId?.(socket.id || null);
|
|
663
722
|
socket.emit("client:sync", buildSyncPayload());
|
|
664
723
|
});
|
|
665
724
|
socket.on("disconnect", () => {
|
|
666
|
-
|
|
725
|
+
emitSdkDebugLog("[ModelNex SDK] Disconnected from agent server", void 0, { devMode });
|
|
667
726
|
onSocketId?.(null);
|
|
668
727
|
});
|
|
669
728
|
socket.on("agent:request_context", () => {
|
|
@@ -689,12 +748,11 @@ function useModelNexSocket({
|
|
|
689
748
|
reasoning
|
|
690
749
|
}) => {
|
|
691
750
|
const log = (msg, data) => {
|
|
692
|
-
|
|
693
|
-
window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg, data } }));
|
|
751
|
+
emitSdkDebugLog(msg, data, { devMode, dispatchEvent: true });
|
|
694
752
|
};
|
|
695
753
|
if (reasoning || params?.fingerprint) {
|
|
696
754
|
window.dispatchEvent(new CustomEvent("modelnex-action-start", {
|
|
697
|
-
detail: { actionId, fingerprint: params?.fingerprint ?? null
|
|
755
|
+
detail: { actionId, fingerprint: params?.fingerprint ?? null }
|
|
698
756
|
}));
|
|
699
757
|
}
|
|
700
758
|
const sendResult = (success, result, error) => {
|
|
@@ -705,17 +763,18 @@ function useModelNexSocket({
|
|
|
705
763
|
const currentActions = actionsRef.current;
|
|
706
764
|
log("[SDK] agent:execute received", {
|
|
707
765
|
actionId,
|
|
708
|
-
|
|
709
|
-
|
|
766
|
+
executionId: executionId ?? null,
|
|
767
|
+
fieldId: fieldId ?? null,
|
|
768
|
+
hasParams: params != null
|
|
710
769
|
});
|
|
711
770
|
const NAV_ACTION_IDS = ["navigate_to_documents", "navigate_to_templates", "navigate_to_inbox", "navigate_to_settings", "navigate_to_template", "navigate_to_document", "navigate_to_folder", "navigate_editor_step"];
|
|
712
771
|
const action = currentActions.get(actionId);
|
|
713
772
|
if (action) {
|
|
714
773
|
try {
|
|
715
774
|
const validatedParams = action.schema.parse(params);
|
|
716
|
-
log("[SDK] Executing", { actionId
|
|
775
|
+
log("[SDK] Executing action", { actionId });
|
|
717
776
|
const execResult = await action.execute(validatedParams);
|
|
718
|
-
log("[SDK]
|
|
777
|
+
log("[SDK] Action completed", { actionId });
|
|
719
778
|
sendResult(true, execResult);
|
|
720
779
|
if (NAV_ACTION_IDS.includes(actionId)) {
|
|
721
780
|
requestAnimationFrame(() => {
|
|
@@ -739,25 +798,29 @@ function useModelNexSocket({
|
|
|
739
798
|
}
|
|
740
799
|
} catch (err) {
|
|
741
800
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
742
|
-
console.error(`[ModelNex SDK] Execution failed for ${actionId}
|
|
801
|
+
console.error(`[ModelNex SDK] Execution failed for ${actionId}: ${errMsg}`);
|
|
802
|
+
if (isSdkDebugEnabled(devMode)) {
|
|
803
|
+
window.dispatchEvent(
|
|
804
|
+
new CustomEvent("modelnex-debug", {
|
|
805
|
+
detail: { msg: "[SDK] Execution failed", data: { actionId, error: errMsg } }
|
|
806
|
+
})
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
sendResult(false, void 0, errMsg);
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
const errMsg = `Action not found: ${actionId}`;
|
|
813
|
+
console.warn(`[ModelNex SDK] ${errMsg}`);
|
|
814
|
+
if (isSdkDebugEnabled(devMode)) {
|
|
743
815
|
window.dispatchEvent(
|
|
744
816
|
new CustomEvent("modelnex-debug", {
|
|
745
|
-
detail: {
|
|
817
|
+
detail: {
|
|
818
|
+
msg: "[SDK] Action not found",
|
|
819
|
+
data: { actionId }
|
|
820
|
+
}
|
|
746
821
|
})
|
|
747
822
|
);
|
|
748
|
-
sendResult(false, void 0, errMsg);
|
|
749
823
|
}
|
|
750
|
-
} else {
|
|
751
|
-
const errMsg = `Action not found: ${actionId}`;
|
|
752
|
-
console.warn("[ModelNex SDK]", errMsg, "Available:", Array.from(currentActions.keys()));
|
|
753
|
-
window.dispatchEvent(
|
|
754
|
-
new CustomEvent("modelnex-debug", {
|
|
755
|
-
detail: {
|
|
756
|
-
msg: "[SDK] Action not found",
|
|
757
|
-
data: { actionId, available: Array.from(actions.keys()) }
|
|
758
|
-
}
|
|
759
|
-
})
|
|
760
|
-
);
|
|
761
824
|
sendResult(false, void 0, errMsg);
|
|
762
825
|
}
|
|
763
826
|
}
|
|
@@ -1617,8 +1680,153 @@ async function captureScreenshot(selector) {
|
|
|
1617
1680
|
return canvas.toDataURL("image/png");
|
|
1618
1681
|
}
|
|
1619
1682
|
|
|
1683
|
+
// src/utils/experience-tool-bridge.ts
|
|
1684
|
+
var activeBridge = null;
|
|
1685
|
+
function registerExperienceToolBridge(bridge) {
|
|
1686
|
+
activeBridge = bridge;
|
|
1687
|
+
return () => {
|
|
1688
|
+
if (activeBridge === bridge) {
|
|
1689
|
+
activeBridge = null;
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
function getExperienceToolBridge() {
|
|
1694
|
+
return activeBridge;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/utils/tourDiscovery.ts
|
|
1698
|
+
async function fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, experienceType = "tour") {
|
|
1699
|
+
try {
|
|
1700
|
+
const params = new URLSearchParams({
|
|
1701
|
+
websiteId,
|
|
1702
|
+
userType
|
|
1703
|
+
});
|
|
1704
|
+
if (userId) params.set("userId", userId);
|
|
1705
|
+
if (experienceType !== "tour") params.set("type", experienceType);
|
|
1706
|
+
const res = await fetch(getTourApiUrl(serverUrl, toursApiBase, `/tours?${params.toString()}`));
|
|
1707
|
+
if (!res.ok) return [];
|
|
1708
|
+
const data = await res.json();
|
|
1709
|
+
return data.tours ?? [];
|
|
1710
|
+
} catch {
|
|
1711
|
+
return [];
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
async function fetchTourById(serverUrl, toursApiBase, tourId, experienceType = "tour", websiteId) {
|
|
1715
|
+
try {
|
|
1716
|
+
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}`), websiteId));
|
|
1717
|
+
if (experienceType !== "tour") {
|
|
1718
|
+
url.searchParams.set("type", experienceType);
|
|
1719
|
+
}
|
|
1720
|
+
const res = await fetch(url.toString());
|
|
1721
|
+
if (!res.ok) {
|
|
1722
|
+
return null;
|
|
1723
|
+
}
|
|
1724
|
+
const data = await res.json();
|
|
1725
|
+
return data.tour ?? null;
|
|
1726
|
+
} catch {
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
async function markTourComplete(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
|
|
1731
|
+
try {
|
|
1732
|
+
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/complete`), websiteId));
|
|
1733
|
+
if (experienceType !== "tour") {
|
|
1734
|
+
url.searchParams.set("type", experienceType);
|
|
1735
|
+
}
|
|
1736
|
+
await fetch(url.toString(), {
|
|
1737
|
+
method: "POST",
|
|
1738
|
+
headers: { "Content-Type": "application/json" },
|
|
1739
|
+
body: JSON.stringify({ userId })
|
|
1740
|
+
});
|
|
1741
|
+
} catch {
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async function markTourDismissed(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
|
|
1745
|
+
try {
|
|
1746
|
+
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/dismiss`), websiteId));
|
|
1747
|
+
if (experienceType !== "tour") {
|
|
1748
|
+
url.searchParams.set("type", experienceType);
|
|
1749
|
+
}
|
|
1750
|
+
await fetch(url.toString(), {
|
|
1751
|
+
method: "POST",
|
|
1752
|
+
headers: { "Content-Type": "application/json" },
|
|
1753
|
+
body: JSON.stringify({ userId })
|
|
1754
|
+
});
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
async function recordTourEvent(serverUrl, toursApiBase, tourId, userId, eventType, websiteId) {
|
|
1759
|
+
try {
|
|
1760
|
+
const url = withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/events`), websiteId);
|
|
1761
|
+
await fetch(url, {
|
|
1762
|
+
method: "POST",
|
|
1763
|
+
headers: { "Content-Type": "application/json" },
|
|
1764
|
+
body: JSON.stringify({ userId, eventType })
|
|
1765
|
+
});
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
function getToursBaseUrl(serverUrl, toursApiBase) {
|
|
1770
|
+
if (toursApiBase?.startsWith("/")) {
|
|
1771
|
+
return `${typeof window !== "undefined" ? window.location.origin : ""}${toursApiBase.replace(/\/$/, "")}`;
|
|
1772
|
+
}
|
|
1773
|
+
return serverUrl.replace(/\/$/, "");
|
|
1774
|
+
}
|
|
1775
|
+
function getTourApiUrl(serverUrl, toursApiBase, path) {
|
|
1776
|
+
const base = getToursBaseUrl(serverUrl, toursApiBase);
|
|
1777
|
+
return toursApiBase ? `${base}${path}` : `${base}/api${path}`;
|
|
1778
|
+
}
|
|
1779
|
+
function withWebsiteId(url, websiteId) {
|
|
1780
|
+
if (!websiteId) {
|
|
1781
|
+
return url;
|
|
1782
|
+
}
|
|
1783
|
+
const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
1784
|
+
const resolved = new URL(url, baseOrigin);
|
|
1785
|
+
resolved.searchParams.set("websiteId", websiteId);
|
|
1786
|
+
return resolved.toString();
|
|
1787
|
+
}
|
|
1788
|
+
function normalizeTrigger(trigger) {
|
|
1789
|
+
if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
|
|
1790
|
+
return "feature_launch";
|
|
1791
|
+
}
|
|
1792
|
+
if (trigger === "return_visit") {
|
|
1793
|
+
return "return_visit";
|
|
1794
|
+
}
|
|
1795
|
+
return trigger ?? "first_visit";
|
|
1796
|
+
}
|
|
1797
|
+
function getStartPolicy(tour) {
|
|
1798
|
+
return tour.startPolicy ?? (normalizeTrigger(tour.trigger) === "manual" ? "immediate_start" : "prompt_only");
|
|
1799
|
+
}
|
|
1800
|
+
function getNotificationType(tour) {
|
|
1801
|
+
return tour.notificationType ?? "bubble_card";
|
|
1802
|
+
}
|
|
1803
|
+
function getUserProfileFeatures(userProfile) {
|
|
1804
|
+
const directFeatures = Array.isArray(userProfile.features) ? userProfile.features : [];
|
|
1805
|
+
const nestedFeatures = Array.isArray(userProfile.tourFacts?.features) ? userProfile.tourFacts.features : [];
|
|
1806
|
+
return [...new Set([...directFeatures, ...nestedFeatures].filter((value) => Boolean(value)))];
|
|
1807
|
+
}
|
|
1808
|
+
function isTourEligible(tour, userProfile) {
|
|
1809
|
+
switch (normalizeTrigger(tour.trigger)) {
|
|
1810
|
+
case "first_visit":
|
|
1811
|
+
return !!userProfile.isNewUser;
|
|
1812
|
+
case "return_visit":
|
|
1813
|
+
return userProfile.isNewUser === false;
|
|
1814
|
+
case "feature_launch": {
|
|
1815
|
+
const featureKey = tour.featureKey?.trim();
|
|
1816
|
+
if (!featureKey) return false;
|
|
1817
|
+
return getUserProfileFeatures(userProfile).includes(featureKey);
|
|
1818
|
+
}
|
|
1819
|
+
case "manual":
|
|
1820
|
+
return false;
|
|
1821
|
+
default:
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1620
1826
|
// src/hooks/useBuiltinActions.ts
|
|
1621
1827
|
var resolutionCache = /* @__PURE__ */ new Map();
|
|
1828
|
+
var DEFAULT_WORKFLOW_SEARCH_LIMIT = 5;
|
|
1829
|
+
var WORKFLOW_TYPES = ["onboarding", "tour"];
|
|
1622
1830
|
function safeQueryAll2(selector) {
|
|
1623
1831
|
try {
|
|
1624
1832
|
return Array.from(document.querySelectorAll(selector)).filter(
|
|
@@ -1793,6 +2001,137 @@ function lastResortScan(fingerprint, options, elements) {
|
|
|
1793
2001
|
function cacheResolution(originalFp, selector, resolvedFp) {
|
|
1794
2002
|
resolutionCache.set(originalFp, { selector, resolvedFingerprint: resolvedFp });
|
|
1795
2003
|
}
|
|
2004
|
+
function normalizeWorkflowQuery(value) {
|
|
2005
|
+
return value.trim().toLowerCase();
|
|
2006
|
+
}
|
|
2007
|
+
function getWorkflowSearchText(tour) {
|
|
2008
|
+
const firstSteps = (tour.steps || []).slice(0, 6).flatMap((step) => [step.goal, step.narration, step.ask]).filter(Boolean).join(" ");
|
|
2009
|
+
return [
|
|
2010
|
+
tour.name,
|
|
2011
|
+
tour.type,
|
|
2012
|
+
tour.trigger,
|
|
2013
|
+
tour.startPolicy,
|
|
2014
|
+
tour.featureKey,
|
|
2015
|
+
tour.goal?.primaryAction,
|
|
2016
|
+
tour.goal?.successMetric,
|
|
2017
|
+
...tour.targetUserTypes,
|
|
2018
|
+
firstSteps
|
|
2019
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
2020
|
+
}
|
|
2021
|
+
function scoreWorkflowMatch(tour, query) {
|
|
2022
|
+
const normalizedQuery = normalizeWorkflowQuery(query);
|
|
2023
|
+
if (!normalizedQuery) return 1;
|
|
2024
|
+
const name = tour.name.toLowerCase();
|
|
2025
|
+
const featureKey = (tour.featureKey || "").toLowerCase();
|
|
2026
|
+
const haystack = getWorkflowSearchText(tour);
|
|
2027
|
+
if (name === normalizedQuery) return 140;
|
|
2028
|
+
if (featureKey && featureKey === normalizedQuery) return 125;
|
|
2029
|
+
if (name.includes(normalizedQuery)) return 115;
|
|
2030
|
+
if (featureKey && featureKey.includes(normalizedQuery)) return 100;
|
|
2031
|
+
if (haystack.includes(normalizedQuery)) return 85;
|
|
2032
|
+
const words = normalizedQuery.split(/\s+/).filter((word) => word.length > 1);
|
|
2033
|
+
if (words.length === 0) return 0;
|
|
2034
|
+
const matched = words.filter((word) => haystack.includes(word));
|
|
2035
|
+
if (matched.length === 0) return 0;
|
|
2036
|
+
const ratio = matched.length / words.length;
|
|
2037
|
+
return Math.round(ratio * 70);
|
|
2038
|
+
}
|
|
2039
|
+
function summarizeWorkflowStep(step, index) {
|
|
2040
|
+
const detail = step.goal || step.ask || step.narration || step.rawNarration || "No description";
|
|
2041
|
+
return `${index + 1}. [${step.type}] ${detail}`;
|
|
2042
|
+
}
|
|
2043
|
+
function formatWorkflowSummary(tour, options) {
|
|
2044
|
+
const stepLimit = options?.stepLimit ?? 4;
|
|
2045
|
+
const parts = [
|
|
2046
|
+
`**${tour.name}**`,
|
|
2047
|
+
`ID: ${tour.id}`,
|
|
2048
|
+
`Type: ${tour.type || "tour"}`,
|
|
2049
|
+
`Trigger: ${tour.trigger}`,
|
|
2050
|
+
`Start policy: ${tour.startPolicy || "prompt_only"}`,
|
|
2051
|
+
tour.featureKey ? `Feature key: ${tour.featureKey}` : null,
|
|
2052
|
+
tour.targetUserTypes?.length ? `Target users: ${tour.targetUserTypes.join(", ")}` : null,
|
|
2053
|
+
`Steps: ${tour.steps?.length ?? 0}`,
|
|
2054
|
+
tour.goal?.primaryAction ? `Primary action: ${tour.goal.primaryAction}` : null
|
|
2055
|
+
].filter(Boolean);
|
|
2056
|
+
if (options?.includeSteps) {
|
|
2057
|
+
const stepPreview = (tour.steps || []).slice(0, stepLimit).map((step, index) => summarizeWorkflowStep(step, index)).join("\n");
|
|
2058
|
+
if (stepPreview) {
|
|
2059
|
+
parts.push(`Steps preview:
|
|
2060
|
+
${stepPreview}`);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return parts.join("\n");
|
|
2064
|
+
}
|
|
2065
|
+
function getRequestedWorkflowTypes(experienceType) {
|
|
2066
|
+
if (!experienceType || experienceType === "all") {
|
|
2067
|
+
return WORKFLOW_TYPES;
|
|
2068
|
+
}
|
|
2069
|
+
return [experienceType];
|
|
2070
|
+
}
|
|
2071
|
+
async function fetchAvailableWorkflows(getters, experienceType) {
|
|
2072
|
+
const serverUrl = getters.serverUrl();
|
|
2073
|
+
const websiteId = getters.websiteId();
|
|
2074
|
+
if (!serverUrl) {
|
|
2075
|
+
throw new Error("Server URL is not configured.");
|
|
2076
|
+
}
|
|
2077
|
+
if (!websiteId) {
|
|
2078
|
+
throw new Error("websiteId is required to search workflows.");
|
|
2079
|
+
}
|
|
2080
|
+
const userProfile = getters.userProfile();
|
|
2081
|
+
const userType = userProfile?.type || "";
|
|
2082
|
+
const userId = userProfile?.userId;
|
|
2083
|
+
const toursApiBase = getters.toursApiBase();
|
|
2084
|
+
const requestedTypes = getRequestedWorkflowTypes(experienceType);
|
|
2085
|
+
const lists = await Promise.all(
|
|
2086
|
+
requestedTypes.map(
|
|
2087
|
+
(type) => fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, type)
|
|
2088
|
+
)
|
|
2089
|
+
);
|
|
2090
|
+
return lists.flatMap(
|
|
2091
|
+
(list, index) => list.map((tour) => ({
|
|
2092
|
+
...tour,
|
|
2093
|
+
type: tour.type || requestedTypes[index]
|
|
2094
|
+
}))
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
async function resolveWorkflowFromInput(getters, params) {
|
|
2098
|
+
const serverUrl = getters.serverUrl();
|
|
2099
|
+
const websiteId = getters.websiteId();
|
|
2100
|
+
const toursApiBase = getters.toursApiBase();
|
|
2101
|
+
if (!serverUrl) {
|
|
2102
|
+
throw new Error("Server URL is not configured.");
|
|
2103
|
+
}
|
|
2104
|
+
if (params.workflowId) {
|
|
2105
|
+
const requestedTypes = getRequestedWorkflowTypes(params.experienceType);
|
|
2106
|
+
for (const type of requestedTypes) {
|
|
2107
|
+
const workflow = await fetchTourById(serverUrl, toursApiBase, params.workflowId, type, websiteId);
|
|
2108
|
+
if (workflow) {
|
|
2109
|
+
return { workflow: { ...workflow, type: workflow.type || type } };
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
const query = params.name?.trim();
|
|
2114
|
+
if (!query) {
|
|
2115
|
+
return { workflow: null, reason: "Provide either workflowId or name." };
|
|
2116
|
+
}
|
|
2117
|
+
const workflows = await fetchAvailableWorkflows(getters, params.experienceType);
|
|
2118
|
+
const ranked = workflows.map((workflow) => ({ workflow, score: scoreWorkflowMatch(workflow, query) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
|
|
2119
|
+
if (ranked.length === 0) {
|
|
2120
|
+
return {
|
|
2121
|
+
workflow: null,
|
|
2122
|
+
reason: `No workflow matched "${query}". Try search_workflows first for available options.`
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
if (ranked.length > 1 && ranked[0].score === ranked[1].score) {
|
|
2126
|
+
return {
|
|
2127
|
+
workflow: null,
|
|
2128
|
+
reason: `Multiple workflows matched "${query}". Try view_workflow with an exact workflowId.
|
|
2129
|
+
|
|
2130
|
+
` + ranked.slice(0, 3).map(({ workflow }) => formatWorkflowSummary(workflow)).join("\n\n---\n\n")
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
return { workflow: ranked[0].workflow };
|
|
2134
|
+
}
|
|
1796
2135
|
var screenshotSchema = z2.object({
|
|
1797
2136
|
selector: z2.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport.")
|
|
1798
2137
|
});
|
|
@@ -2073,15 +2412,91 @@ function createSearchTaggedElementsAction(getTagStore) {
|
|
|
2073
2412
|
}
|
|
2074
2413
|
};
|
|
2075
2414
|
}
|
|
2415
|
+
var workflowExperienceTypeSchema = z2.enum(["onboarding", "tour", "all"]);
|
|
2416
|
+
var searchWorkflowsSchema = z2.object({
|
|
2417
|
+
query: z2.string().optional().describe("Optional workflow name, feature, or task phrase to search for. Omit to list available workflows."),
|
|
2418
|
+
experienceType: workflowExperienceTypeSchema.optional().describe("Which workflow type to search: onboarding, tour, or all. Default: onboarding."),
|
|
2419
|
+
limit: z2.number().optional().describe(`Maximum number of workflows to return (default: ${DEFAULT_WORKFLOW_SEARCH_LIMIT}).`)
|
|
2420
|
+
});
|
|
2421
|
+
function createSearchWorkflowsAction(getters) {
|
|
2422
|
+
return {
|
|
2423
|
+
id: "search_workflows",
|
|
2424
|
+
description: "Search available workflows and tours that can be launched for the current website/user. Use this when the user asks for onboarding, a walkthrough, a tutorial, a guided setup, or a named workflow/tour.",
|
|
2425
|
+
schema: searchWorkflowsSchema,
|
|
2426
|
+
execute: async (params) => {
|
|
2427
|
+
const workflows = await fetchAvailableWorkflows(getters, params.experienceType ?? "onboarding");
|
|
2428
|
+
if (workflows.length === 0) {
|
|
2429
|
+
return "No workflows are currently available for this website/user.";
|
|
2430
|
+
}
|
|
2431
|
+
const query = params.query?.trim();
|
|
2432
|
+
const limit = params.limit ?? DEFAULT_WORKFLOW_SEARCH_LIMIT;
|
|
2433
|
+
const ranked = workflows.map((workflow) => ({
|
|
2434
|
+
workflow,
|
|
2435
|
+
score: query ? scoreWorkflowMatch(workflow, query) : 1
|
|
2436
|
+
})).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
2437
|
+
if (ranked.length === 0) {
|
|
2438
|
+
return `No workflows matched "${query}".`;
|
|
2439
|
+
}
|
|
2440
|
+
return ranked.map(({ workflow }) => formatWorkflowSummary(workflow)).join("\n\n---\n\n");
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
var resolveWorkflowSchema = z2.object({
|
|
2445
|
+
workflowId: z2.string().optional().describe("Exact workflow id when known."),
|
|
2446
|
+
name: z2.string().optional().describe("Workflow name or a close name match when the id is unknown."),
|
|
2447
|
+
experienceType: workflowExperienceTypeSchema.optional().describe("Which workflow type to search: onboarding, tour, or all. Default: onboarding.")
|
|
2448
|
+
});
|
|
2449
|
+
function createViewWorkflowAction(getters) {
|
|
2450
|
+
return {
|
|
2451
|
+
id: "view_workflow",
|
|
2452
|
+
description: "View a specific workflow or tour in more detail, including its id, trigger, start policy, and the first few steps. Use after search_workflows when you need to inspect a candidate before starting it.",
|
|
2453
|
+
schema: resolveWorkflowSchema,
|
|
2454
|
+
execute: async (params) => {
|
|
2455
|
+
const { workflow, reason } = await resolveWorkflowFromInput(getters, params);
|
|
2456
|
+
if (!workflow) {
|
|
2457
|
+
return reason || "Workflow not found.";
|
|
2458
|
+
}
|
|
2459
|
+
return formatWorkflowSummary(workflow, { includeSteps: true, stepLimit: 5 });
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
var startWorkflowSchema = resolveWorkflowSchema.extend({
|
|
2464
|
+
reviewMode: z2.boolean().optional().describe("Optional. Start in review mode instead of normal playback. Default: false.")
|
|
2465
|
+
});
|
|
2466
|
+
function createStartWorkflowAction(getters) {
|
|
2467
|
+
return {
|
|
2468
|
+
id: "start_workflow",
|
|
2469
|
+
description: "Start a specific workflow or tour in the chat experience. Use this after search_workflows or view_workflow when the user wants to begin a named guided workflow.",
|
|
2470
|
+
schema: startWorkflowSchema,
|
|
2471
|
+
execute: async (params) => {
|
|
2472
|
+
const { workflow, reason } = await resolveWorkflowFromInput(getters, params);
|
|
2473
|
+
if (!workflow) {
|
|
2474
|
+
return reason || "Workflow not found.";
|
|
2475
|
+
}
|
|
2476
|
+
const bridge = getExperienceToolBridge();
|
|
2477
|
+
if (!bridge) {
|
|
2478
|
+
return "Workflow launch is unavailable because no chat playback controller is mounted.";
|
|
2479
|
+
}
|
|
2480
|
+
bridge.startExperience(workflow, workflow.type, params.reviewMode ? {
|
|
2481
|
+
reviewMode: true,
|
|
2482
|
+
reviewMetadata: { trigger: "agent_tool" }
|
|
2483
|
+
} : void 0);
|
|
2484
|
+
return `Started workflow "${workflow.name}" (${workflow.id}).`;
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2076
2488
|
var BUILTIN_ACTION_IDS = {
|
|
2077
2489
|
screenshot: "take_screenshot",
|
|
2078
2490
|
click: "click_element",
|
|
2079
2491
|
fill: "fill_input",
|
|
2080
2492
|
wait: "request_user_action",
|
|
2081
2493
|
searchDocs: "search_docs",
|
|
2082
|
-
searchTaggedElements: "search_tagged_elements"
|
|
2494
|
+
searchTaggedElements: "search_tagged_elements",
|
|
2495
|
+
searchWorkflows: "search_workflows",
|
|
2496
|
+
viewWorkflow: "view_workflow",
|
|
2497
|
+
startWorkflow: "start_workflow"
|
|
2083
2498
|
};
|
|
2084
|
-
function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId) {
|
|
2499
|
+
function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile) {
|
|
2085
2500
|
const registeredRef = useRef6(false);
|
|
2086
2501
|
const tagStoreRef = useRef6(tagStore);
|
|
2087
2502
|
tagStoreRef.current = tagStore;
|
|
@@ -2089,18 +2504,33 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
|
|
|
2089
2504
|
serverUrlRef.current = serverUrl;
|
|
2090
2505
|
const websiteIdRef = useRef6(websiteId);
|
|
2091
2506
|
websiteIdRef.current = websiteId;
|
|
2507
|
+
const toursApiBaseRef = useRef6(toursApiBase);
|
|
2508
|
+
toursApiBaseRef.current = toursApiBase;
|
|
2509
|
+
const userProfileRef = useRef6(userProfile);
|
|
2510
|
+
userProfileRef.current = userProfile;
|
|
2092
2511
|
useEffect8(() => {
|
|
2093
2512
|
if (registeredRef.current) return;
|
|
2094
2513
|
registeredRef.current = true;
|
|
2095
2514
|
const getTagStore = () => tagStoreRef.current;
|
|
2096
2515
|
const getServerUrl = () => serverUrlRef.current;
|
|
2097
2516
|
const getWebsiteId = () => websiteIdRef.current;
|
|
2517
|
+
const getToursApiBase = () => toursApiBaseRef.current;
|
|
2518
|
+
const getUserProfile = () => userProfileRef.current;
|
|
2519
|
+
const workflowToolGetters = {
|
|
2520
|
+
serverUrl: getServerUrl,
|
|
2521
|
+
websiteId: getWebsiteId,
|
|
2522
|
+
toursApiBase: getToursApiBase,
|
|
2523
|
+
userProfile: getUserProfile
|
|
2524
|
+
};
|
|
2098
2525
|
registerAction(BUILTIN_SCREENSHOT_ACTION);
|
|
2099
2526
|
registerAction(createClickAction(getTagStore));
|
|
2100
2527
|
registerAction(createFillAction(getTagStore));
|
|
2101
2528
|
registerAction(createWaitAction(getTagStore));
|
|
2102
2529
|
registerAction(createSearchDocsAction(getServerUrl, getWebsiteId));
|
|
2103
2530
|
registerAction(createSearchTaggedElementsAction(getTagStore));
|
|
2531
|
+
registerAction(createSearchWorkflowsAction(workflowToolGetters));
|
|
2532
|
+
registerAction(createViewWorkflowAction(workflowToolGetters));
|
|
2533
|
+
registerAction(createStartWorkflowAction(workflowToolGetters));
|
|
2104
2534
|
return () => {
|
|
2105
2535
|
unregisterAction(BUILTIN_SCREENSHOT_ACTION.id);
|
|
2106
2536
|
unregisterAction(BUILTIN_ACTION_IDS.click);
|
|
@@ -2108,6 +2538,9 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
|
|
|
2108
2538
|
unregisterAction(BUILTIN_ACTION_IDS.wait);
|
|
2109
2539
|
unregisterAction(BUILTIN_ACTION_IDS.searchDocs);
|
|
2110
2540
|
unregisterAction(BUILTIN_ACTION_IDS.searchTaggedElements);
|
|
2541
|
+
unregisterAction(BUILTIN_ACTION_IDS.searchWorkflows);
|
|
2542
|
+
unregisterAction(BUILTIN_ACTION_IDS.viewWorkflow);
|
|
2543
|
+
unregisterAction(BUILTIN_ACTION_IDS.startWorkflow);
|
|
2111
2544
|
registeredRef.current = false;
|
|
2112
2545
|
};
|
|
2113
2546
|
}, [registerAction, unregisterAction]);
|
|
@@ -2116,6 +2549,56 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
|
|
|
2116
2549
|
// src/constants.ts
|
|
2117
2550
|
var DEFAULT_MODELNEX_SERVER_URL = "https://api.modelnex.com";
|
|
2118
2551
|
|
|
2552
|
+
// src/utils/dev-mode.ts
|
|
2553
|
+
var DEV_MODE_KEY_GLOBAL_NAMES = ["__MODELNEX_DEV_MODE_KEY__", "MODELNEX_DEV_MODE_KEY"];
|
|
2554
|
+
var devModeValidationCache = /* @__PURE__ */ new Map();
|
|
2555
|
+
function normalizeDevModeKey(value) {
|
|
2556
|
+
if (typeof value !== "string") return void 0;
|
|
2557
|
+
const normalized = value.trim();
|
|
2558
|
+
return normalized || void 0;
|
|
2559
|
+
}
|
|
2560
|
+
function resolveInjectedDevModeKey(explicitDevModeKey) {
|
|
2561
|
+
const normalizedExplicitKey = normalizeDevModeKey(explicitDevModeKey);
|
|
2562
|
+
if (normalizedExplicitKey) return normalizedExplicitKey;
|
|
2563
|
+
if (typeof window === "undefined") return void 0;
|
|
2564
|
+
const browserWindow = window;
|
|
2565
|
+
for (const globalName of DEV_MODE_KEY_GLOBAL_NAMES) {
|
|
2566
|
+
const normalizedGlobalKey = normalizeDevModeKey(browserWindow[globalName]);
|
|
2567
|
+
if (normalizedGlobalKey) return normalizedGlobalKey;
|
|
2568
|
+
}
|
|
2569
|
+
const scriptInjectedKey = typeof document !== "undefined" ? normalizeDevModeKey(
|
|
2570
|
+
document.querySelector("script[data-modelnex-dev-mode-key]")?.dataset.modelnexDevModeKey
|
|
2571
|
+
) : void 0;
|
|
2572
|
+
if (scriptInjectedKey) return scriptInjectedKey;
|
|
2573
|
+
return void 0;
|
|
2574
|
+
}
|
|
2575
|
+
async function validateInjectedDevModeKey(serverUrl, websiteId, devModeKey) {
|
|
2576
|
+
const normalizedWebsiteId = websiteId.trim();
|
|
2577
|
+
const normalizedDevModeKey = devModeKey.trim();
|
|
2578
|
+
if (!normalizedWebsiteId || !normalizedDevModeKey) return false;
|
|
2579
|
+
const cacheKey = `${serverUrl}::${normalizedWebsiteId}::${normalizedDevModeKey}`;
|
|
2580
|
+
const cached = devModeValidationCache.get(cacheKey);
|
|
2581
|
+
if (cached) {
|
|
2582
|
+
return cached;
|
|
2583
|
+
}
|
|
2584
|
+
const validationPromise = fetch(
|
|
2585
|
+
`${serverUrl.replace(/\/$/, "")}/api/websites/${encodeURIComponent(normalizedWebsiteId)}/dev-mode/verify`,
|
|
2586
|
+
{
|
|
2587
|
+
method: "POST",
|
|
2588
|
+
headers: {
|
|
2589
|
+
"Content-Type": "application/json"
|
|
2590
|
+
},
|
|
2591
|
+
body: JSON.stringify({ key: normalizedDevModeKey })
|
|
2592
|
+
}
|
|
2593
|
+
).then(async (response) => {
|
|
2594
|
+
if (!response.ok) return false;
|
|
2595
|
+
const payload = await response.json().catch(() => null);
|
|
2596
|
+
return payload?.enabled === true;
|
|
2597
|
+
}).catch(() => false);
|
|
2598
|
+
devModeValidationCache.set(cacheKey, validationPromise);
|
|
2599
|
+
return validationPromise;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2119
2602
|
// src/hooks/useRunCommand.ts
|
|
2120
2603
|
import { useCallback as useCallback5, useContext as useContext2 } from "react";
|
|
2121
2604
|
function searchTaggedElementsForQuery(store, query, limit = 8) {
|
|
@@ -2158,7 +2641,10 @@ function useRunCommand(serverUrlOverride) {
|
|
|
2158
2641
|
if (!res.ok) {
|
|
2159
2642
|
throw new Error(data?.error ?? `Request failed: ${res.status}`);
|
|
2160
2643
|
}
|
|
2161
|
-
return
|
|
2644
|
+
return {
|
|
2645
|
+
...data,
|
|
2646
|
+
debug: sanitizeAgentDebug(data?.debug)
|
|
2647
|
+
};
|
|
2162
2648
|
},
|
|
2163
2649
|
[baseUrl, tagStore]
|
|
2164
2650
|
);
|
|
@@ -2362,135 +2848,6 @@ function readPreviewSessionSuppression() {
|
|
|
2362
2848
|
}
|
|
2363
2849
|
}
|
|
2364
2850
|
|
|
2365
|
-
// src/utils/tourDiscovery.ts
|
|
2366
|
-
async function fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, experienceType = "tour") {
|
|
2367
|
-
try {
|
|
2368
|
-
const params = new URLSearchParams({
|
|
2369
|
-
websiteId,
|
|
2370
|
-
userType
|
|
2371
|
-
});
|
|
2372
|
-
if (userId) params.set("userId", userId);
|
|
2373
|
-
if (experienceType !== "tour") params.set("type", experienceType);
|
|
2374
|
-
const res = await fetch(getTourApiUrl(serverUrl, toursApiBase, `/tours?${params.toString()}`));
|
|
2375
|
-
if (!res.ok) return [];
|
|
2376
|
-
const data = await res.json();
|
|
2377
|
-
return data.tours ?? [];
|
|
2378
|
-
} catch {
|
|
2379
|
-
return [];
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
async function fetchTourById(serverUrl, toursApiBase, tourId, experienceType = "tour", websiteId) {
|
|
2383
|
-
try {
|
|
2384
|
-
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}`), websiteId));
|
|
2385
|
-
if (experienceType !== "tour") {
|
|
2386
|
-
url.searchParams.set("type", experienceType);
|
|
2387
|
-
}
|
|
2388
|
-
const res = await fetch(url.toString());
|
|
2389
|
-
if (!res.ok) {
|
|
2390
|
-
return null;
|
|
2391
|
-
}
|
|
2392
|
-
const data = await res.json();
|
|
2393
|
-
return data.tour ?? null;
|
|
2394
|
-
} catch {
|
|
2395
|
-
return null;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
async function markTourComplete(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
|
|
2399
|
-
try {
|
|
2400
|
-
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/complete`), websiteId));
|
|
2401
|
-
if (experienceType !== "tour") {
|
|
2402
|
-
url.searchParams.set("type", experienceType);
|
|
2403
|
-
}
|
|
2404
|
-
await fetch(url.toString(), {
|
|
2405
|
-
method: "POST",
|
|
2406
|
-
headers: { "Content-Type": "application/json" },
|
|
2407
|
-
body: JSON.stringify({ userId })
|
|
2408
|
-
});
|
|
2409
|
-
} catch {
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
async function markTourDismissed(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
|
|
2413
|
-
try {
|
|
2414
|
-
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/dismiss`), websiteId));
|
|
2415
|
-
if (experienceType !== "tour") {
|
|
2416
|
-
url.searchParams.set("type", experienceType);
|
|
2417
|
-
}
|
|
2418
|
-
await fetch(url.toString(), {
|
|
2419
|
-
method: "POST",
|
|
2420
|
-
headers: { "Content-Type": "application/json" },
|
|
2421
|
-
body: JSON.stringify({ userId })
|
|
2422
|
-
});
|
|
2423
|
-
} catch {
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
async function recordTourEvent(serverUrl, toursApiBase, tourId, userId, eventType, websiteId) {
|
|
2427
|
-
try {
|
|
2428
|
-
const url = withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/events`), websiteId);
|
|
2429
|
-
await fetch(url, {
|
|
2430
|
-
method: "POST",
|
|
2431
|
-
headers: { "Content-Type": "application/json" },
|
|
2432
|
-
body: JSON.stringify({ userId, eventType })
|
|
2433
|
-
});
|
|
2434
|
-
} catch {
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
function getToursBaseUrl(serverUrl, toursApiBase) {
|
|
2438
|
-
if (toursApiBase?.startsWith("/")) {
|
|
2439
|
-
return `${typeof window !== "undefined" ? window.location.origin : ""}${toursApiBase.replace(/\/$/, "")}`;
|
|
2440
|
-
}
|
|
2441
|
-
return serverUrl.replace(/\/$/, "");
|
|
2442
|
-
}
|
|
2443
|
-
function getTourApiUrl(serverUrl, toursApiBase, path) {
|
|
2444
|
-
const base = getToursBaseUrl(serverUrl, toursApiBase);
|
|
2445
|
-
return toursApiBase ? `${base}${path}` : `${base}/api${path}`;
|
|
2446
|
-
}
|
|
2447
|
-
function withWebsiteId(url, websiteId) {
|
|
2448
|
-
if (!websiteId) {
|
|
2449
|
-
return url;
|
|
2450
|
-
}
|
|
2451
|
-
const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
2452
|
-
const resolved = new URL(url, baseOrigin);
|
|
2453
|
-
resolved.searchParams.set("websiteId", websiteId);
|
|
2454
|
-
return resolved.toString();
|
|
2455
|
-
}
|
|
2456
|
-
function normalizeTrigger(trigger) {
|
|
2457
|
-
if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
|
|
2458
|
-
return "feature_launch";
|
|
2459
|
-
}
|
|
2460
|
-
if (trigger === "return_visit") {
|
|
2461
|
-
return "return_visit";
|
|
2462
|
-
}
|
|
2463
|
-
return trigger ?? "first_visit";
|
|
2464
|
-
}
|
|
2465
|
-
function getStartPolicy(tour) {
|
|
2466
|
-
return tour.startPolicy ?? (normalizeTrigger(tour.trigger) === "manual" ? "immediate_start" : "prompt_only");
|
|
2467
|
-
}
|
|
2468
|
-
function getNotificationType(tour) {
|
|
2469
|
-
return tour.notificationType ?? "bubble_card";
|
|
2470
|
-
}
|
|
2471
|
-
function getUserProfileFeatures(userProfile) {
|
|
2472
|
-
const directFeatures = Array.isArray(userProfile.features) ? userProfile.features : [];
|
|
2473
|
-
const nestedFeatures = Array.isArray(userProfile.tourFacts?.features) ? userProfile.tourFacts.features : [];
|
|
2474
|
-
return [...new Set([...directFeatures, ...nestedFeatures].filter((value) => Boolean(value)))];
|
|
2475
|
-
}
|
|
2476
|
-
function isTourEligible(tour, userProfile) {
|
|
2477
|
-
switch (normalizeTrigger(tour.trigger)) {
|
|
2478
|
-
case "first_visit":
|
|
2479
|
-
return !!userProfile.isNewUser;
|
|
2480
|
-
case "return_visit":
|
|
2481
|
-
return userProfile.isNewUser === false;
|
|
2482
|
-
case "feature_launch": {
|
|
2483
|
-
const featureKey = tour.featureKey?.trim();
|
|
2484
|
-
if (!featureKey) return false;
|
|
2485
|
-
return getUserProfileFeatures(userProfile).includes(featureKey);
|
|
2486
|
-
}
|
|
2487
|
-
case "manual":
|
|
2488
|
-
return false;
|
|
2489
|
-
default:
|
|
2490
|
-
return false;
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
2851
|
// src/hooks/useTourPlayback.ts
|
|
2495
2852
|
import { useState as useState7, useRef as useRef8, useCallback as useCallback7, useEffect as useEffect11, useContext as useContext4 } from "react";
|
|
2496
2853
|
|
|
@@ -3195,7 +3552,9 @@ function useTourPlayback({
|
|
|
3195
3552
|
const socket = tourSocketPool.acquire(serverUrl);
|
|
3196
3553
|
socketRef.current = socket;
|
|
3197
3554
|
const handleConnect = () => {
|
|
3198
|
-
|
|
3555
|
+
emitSdkDebugLog("[TourClient] Connected to tour agent server", {
|
|
3556
|
+
socketId: socket.id ?? null
|
|
3557
|
+
}, { devMode: devModeRef.current });
|
|
3199
3558
|
const profile = userProfileRef.current;
|
|
3200
3559
|
const currentWebsiteId = websiteIdRef.current;
|
|
3201
3560
|
if (currentWebsiteId && profile?.userId) {
|
|
@@ -3212,7 +3571,9 @@ function useTourPlayback({
|
|
|
3212
3571
|
setServerState(payload);
|
|
3213
3572
|
};
|
|
3214
3573
|
const handleCommandCancel = (payload) => {
|
|
3215
|
-
|
|
3574
|
+
emitSdkDebugLog("[TourClient] Received command cancel", {
|
|
3575
|
+
commandBatchId: payload.commandBatchId ?? null
|
|
3576
|
+
}, { devMode: devModeRef.current });
|
|
3216
3577
|
if (payload.commandBatchId && activeCommandBatchIdRef.current === payload.commandBatchId) {
|
|
3217
3578
|
activeCommandBatchIdRef.current = null;
|
|
3218
3579
|
activeExecutionTokenRef.current++;
|
|
@@ -3227,14 +3588,22 @@ function useTourPlayback({
|
|
|
3227
3588
|
const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, experienceTypeRef.current);
|
|
3228
3589
|
if (!shouldExecuteTourCommandBatch(isActiveRef.current) || !hasTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
|
|
3229
3590
|
const activeType = experienceTypeRef.current;
|
|
3230
|
-
|
|
3591
|
+
emitSdkDebugLog("[TourClient] Ignoring command batch for inactive playback hook", {
|
|
3592
|
+
experienceType: activeType,
|
|
3593
|
+
stepIndex: payload.stepIndex
|
|
3594
|
+
}, { devMode: devModeRef.current });
|
|
3231
3595
|
return;
|
|
3232
3596
|
}
|
|
3233
3597
|
const emitIfOpen = (ev, data) => {
|
|
3234
3598
|
if (socketRef.current !== socket) return;
|
|
3235
3599
|
emitSocketEvent(socket, ev, data);
|
|
3236
3600
|
};
|
|
3237
|
-
|
|
3601
|
+
const commandBatchId = payload.commandBatchId ?? null;
|
|
3602
|
+
emitSdkDebugLog("[TourClient] Received command batch", {
|
|
3603
|
+
stepIndex: payload.stepIndex,
|
|
3604
|
+
commandCount: Array.isArray(payload.commands) ? payload.commands.length : 0,
|
|
3605
|
+
commandBatchId
|
|
3606
|
+
}, { devMode: devModeRef.current });
|
|
3238
3607
|
runCleanup(pendingManualWaitCleanupRef.current);
|
|
3239
3608
|
pendingManualWaitCleanupRef.current = null;
|
|
3240
3609
|
if (voiceInputResolveRef.current) {
|
|
@@ -3244,7 +3613,6 @@ function useTourPlayback({
|
|
|
3244
3613
|
}
|
|
3245
3614
|
setPlaybackState("executing");
|
|
3246
3615
|
commandInFlightRef.current = true;
|
|
3247
|
-
const commandBatchId = payload.commandBatchId ?? null;
|
|
3248
3616
|
turnIdRef.current = payload.turnId ?? turnIdRef.current;
|
|
3249
3617
|
const clearCommandBatchId = () => {
|
|
3250
3618
|
if (activeCommandBatchIdRef.current === commandBatchId) {
|
|
@@ -3290,7 +3658,7 @@ function useTourPlayback({
|
|
|
3290
3658
|
}
|
|
3291
3659
|
}
|
|
3292
3660
|
if (!payload.commands || !Array.isArray(payload.commands)) {
|
|
3293
|
-
console.warn("[TourClient]
|
|
3661
|
+
console.warn("[TourClient] Command batch payload was invalid.");
|
|
3294
3662
|
commandInFlightRef.current = false;
|
|
3295
3663
|
emitIfOpen("tour:action_result", {
|
|
3296
3664
|
success: false,
|
|
@@ -3339,7 +3707,9 @@ function useTourPlayback({
|
|
|
3339
3707
|
};
|
|
3340
3708
|
const executeOne = async (action) => {
|
|
3341
3709
|
assertNotInterrupted();
|
|
3342
|
-
|
|
3710
|
+
emitSdkDebugLog("[TourClient] Executing action", {
|
|
3711
|
+
actionType: action?.type ?? "unknown"
|
|
3712
|
+
}, { devMode: devModeRef.current });
|
|
3343
3713
|
const currentStep = tourRef.current?.steps?.[stepIndexRef.current] ?? null;
|
|
3344
3714
|
const executeTimeline = async (params = {}) => {
|
|
3345
3715
|
const segments = Array.isArray(params.segments) ? params.segments : [];
|
|
@@ -3443,7 +3813,7 @@ function useTourPlayback({
|
|
|
3443
3813
|
}
|
|
3444
3814
|
removeHighlight();
|
|
3445
3815
|
await performInteractiveClick(targetEl);
|
|
3446
|
-
const { waitForDomSettle: waitForDomSettleClick } = await import("./dom-sync-
|
|
3816
|
+
const { waitForDomSettle: waitForDomSettleClick } = await import("./dom-sync-GABDEODR.mjs");
|
|
3447
3817
|
await waitForDomSettleClick({ timeoutMs: 3e3, debounceMs: 300 });
|
|
3448
3818
|
return { result: "clicked" };
|
|
3449
3819
|
}
|
|
@@ -3480,7 +3850,7 @@ function useTourPlayback({
|
|
|
3480
3850
|
throw new Error("navigate_to_url missing url");
|
|
3481
3851
|
}
|
|
3482
3852
|
await navigateToTourUrl(nextUrl);
|
|
3483
|
-
const { waitForDomSettle: waitForDomSettleNav } = await import("./dom-sync-
|
|
3853
|
+
const { waitForDomSettle: waitForDomSettleNav } = await import("./dom-sync-GABDEODR.mjs");
|
|
3484
3854
|
await waitForDomSettleNav({ timeoutMs: 3e3, debounceMs: 300 });
|
|
3485
3855
|
return { result: nextUrl };
|
|
3486
3856
|
}
|
|
@@ -3504,7 +3874,7 @@ function useTourPlayback({
|
|
|
3504
3874
|
if (action.params?.wait !== false) {
|
|
3505
3875
|
await res.json();
|
|
3506
3876
|
}
|
|
3507
|
-
const { waitForDomSettle } = await import("./dom-sync-
|
|
3877
|
+
const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
|
|
3508
3878
|
await waitForDomSettle({ timeoutMs: 3e3, debounceMs: 300 });
|
|
3509
3879
|
await syncAOM();
|
|
3510
3880
|
return { result: action.params?.command ?? "executed" };
|
|
@@ -3526,7 +3896,7 @@ function useTourPlayback({
|
|
|
3526
3896
|
handleTourEnd();
|
|
3527
3897
|
return { result: "ended" };
|
|
3528
3898
|
}
|
|
3529
|
-
console.warn(
|
|
3899
|
+
console.warn(`[TourClient] Unknown action type: ${String(action?.type ?? "unknown")}. Skipping.`);
|
|
3530
3900
|
return { result: "unknown_action_skipped" };
|
|
3531
3901
|
};
|
|
3532
3902
|
try {
|
|
@@ -3582,14 +3952,15 @@ function useTourPlayback({
|
|
|
3582
3952
|
clearCommandBatchId();
|
|
3583
3953
|
return;
|
|
3584
3954
|
}
|
|
3585
|
-
|
|
3955
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3956
|
+
console.error(`[TourClient] Command batch execution failed: ${errMsg}`);
|
|
3586
3957
|
if (reviewModeRef.current && activeTourId && activePreviewRunId) {
|
|
3587
3958
|
void logPreviewEvent(serverUrl, toursApiBaseRef.current, activeTourId, activePreviewRunId, websiteId, {
|
|
3588
3959
|
stepOrder: stepIndexRef.current,
|
|
3589
3960
|
eventType: "command_batch_failed",
|
|
3590
3961
|
payload: {
|
|
3591
3962
|
commandBatchId,
|
|
3592
|
-
error:
|
|
3963
|
+
error: errMsg,
|
|
3593
3964
|
partialResults: results
|
|
3594
3965
|
},
|
|
3595
3966
|
status: "active",
|
|
@@ -3599,7 +3970,7 @@ function useTourPlayback({
|
|
|
3599
3970
|
emitIfOpen("tour:action_result", {
|
|
3600
3971
|
success: false,
|
|
3601
3972
|
reason: "execution_error",
|
|
3602
|
-
error:
|
|
3973
|
+
error: errMsg,
|
|
3603
3974
|
results,
|
|
3604
3975
|
commandBatchId,
|
|
3605
3976
|
runId: runIdRef.current,
|
|
@@ -3625,7 +3996,10 @@ function useTourPlayback({
|
|
|
3625
3996
|
let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
|
|
3626
3997
|
if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
|
|
3627
3998
|
manualWaitTarget = preferredWaitTarget;
|
|
3628
|
-
|
|
3999
|
+
emitSdkDebugLog("[TourClient] wait_for_input preferred highlighted editable target", {
|
|
4000
|
+
stepIndex: stepIndexRef.current,
|
|
4001
|
+
event: waitEvent
|
|
4002
|
+
}, { devMode: devModeRef.current });
|
|
3629
4003
|
}
|
|
3630
4004
|
if (manualWaitTarget) {
|
|
3631
4005
|
const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
|
|
@@ -3639,7 +4013,10 @@ function useTourPlayback({
|
|
|
3639
4013
|
manualWaitPromise = manualWait.promise;
|
|
3640
4014
|
manualWaitKind = manualWait.kind;
|
|
3641
4015
|
pendingManualWaitCleanupRef.current = manualWait.cleanup;
|
|
3642
|
-
|
|
4016
|
+
emitSdkDebugLog("[TourClient] wait_for_input using fallback editable target", {
|
|
4017
|
+
stepIndex: stepIndexRef.current,
|
|
4018
|
+
event: waitEvent
|
|
4019
|
+
}, { devMode: devModeRef.current });
|
|
3643
4020
|
}
|
|
3644
4021
|
if (!manualWaitPromise && inputLikeWait) {
|
|
3645
4022
|
const firstInput = document.querySelector(
|
|
@@ -3650,7 +4027,10 @@ function useTourPlayback({
|
|
|
3650
4027
|
manualWaitPromise = manualWait.promise;
|
|
3651
4028
|
manualWaitKind = manualWait.kind;
|
|
3652
4029
|
pendingManualWaitCleanupRef.current = manualWait.cleanup;
|
|
3653
|
-
|
|
4030
|
+
emitSdkDebugLog("[TourClient] wait_for_input falling back to first editable element", {
|
|
4031
|
+
stepIndex: stepIndexRef.current,
|
|
4032
|
+
event: waitEvent
|
|
4033
|
+
}, { devMode: devModeRef.current });
|
|
3654
4034
|
}
|
|
3655
4035
|
}
|
|
3656
4036
|
setPlaybackState(manualWaitKind ? "waiting_input" : "waiting_voice");
|
|
@@ -3695,7 +4075,7 @@ function useTourPlayback({
|
|
|
3695
4075
|
if (!transcript) {
|
|
3696
4076
|
return;
|
|
3697
4077
|
}
|
|
3698
|
-
const { waitForDomSettle } = await import("./dom-sync-
|
|
4078
|
+
const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
|
|
3699
4079
|
await waitForDomSettle({ timeoutMs: 1500, debounceMs: 200 });
|
|
3700
4080
|
await syncAOM();
|
|
3701
4081
|
emitIfOpen("tour:user_input", {
|
|
@@ -3732,12 +4112,17 @@ function useTourPlayback({
|
|
|
3732
4112
|
const tour = tourData.tourContext ?? tourRef.current;
|
|
3733
4113
|
const expType = experienceTypeRef.current;
|
|
3734
4114
|
if (tour?.type && tour.type !== expType) {
|
|
3735
|
-
|
|
4115
|
+
emitSdkDebugLog("[TourClient] Ignoring tour start for mismatched experience type", {
|
|
4116
|
+
incomingType: tour.type,
|
|
4117
|
+
hookType: expType
|
|
4118
|
+
}, { devMode: devModeRef.current });
|
|
3736
4119
|
return;
|
|
3737
4120
|
}
|
|
3738
4121
|
const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, expType);
|
|
3739
4122
|
if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
|
|
3740
|
-
|
|
4123
|
+
emitSdkDebugLog("[TourClient] Ignoring tour start because playback ownership is already claimed", {
|
|
4124
|
+
experienceType: expType
|
|
4125
|
+
}, { devMode: devModeRef.current });
|
|
3741
4126
|
return;
|
|
3742
4127
|
}
|
|
3743
4128
|
claimedPlaybackOwnerKeyRef.current = ownerKey;
|
|
@@ -3762,6 +4147,8 @@ function useTourPlayback({
|
|
|
3762
4147
|
},
|
|
3763
4148
|
currentStepOrder: 0
|
|
3764
4149
|
});
|
|
4150
|
+
} else if (tour?.id && userProfile?.userId) {
|
|
4151
|
+
void recordTourEvent(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, "started", websiteId);
|
|
3765
4152
|
}
|
|
3766
4153
|
try {
|
|
3767
4154
|
const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
|
|
@@ -3774,7 +4161,8 @@ function useTourPlayback({
|
|
|
3774
4161
|
});
|
|
3775
4162
|
}
|
|
3776
4163
|
} catch (e) {
|
|
3777
|
-
|
|
4164
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
4165
|
+
console.warn(`[TourClient] Initial DOM sync failed: ${errMsg}`);
|
|
3778
4166
|
}
|
|
3779
4167
|
};
|
|
3780
4168
|
const handleTourUpdate = (payload) => {
|
|
@@ -3812,12 +4200,11 @@ function useTourPlayback({
|
|
|
3812
4200
|
return;
|
|
3813
4201
|
}
|
|
3814
4202
|
if (isDev) {
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
}
|
|
4203
|
+
emitSdkDebugLog(`[Tour Timeline] ${entry.type}`, {
|
|
4204
|
+
stepIndex: entry?.data?.stepIndex ?? null,
|
|
4205
|
+
runId: entry?.data?.runId ?? null,
|
|
4206
|
+
turnId: entry?.data?.turnId ?? null
|
|
4207
|
+
}, { devMode: devModeRef.current, dispatchEvent: true });
|
|
3821
4208
|
}
|
|
3822
4209
|
};
|
|
3823
4210
|
socket.on("connect", handleConnect);
|
|
@@ -3828,7 +4215,7 @@ function useTourPlayback({
|
|
|
3828
4215
|
socket.on("tour:update", handleTourUpdate);
|
|
3829
4216
|
socket.on("tour:end", handleTourEndEvent);
|
|
3830
4217
|
socket.on("tour:debug_log", handleDebugLog);
|
|
3831
|
-
|
|
4218
|
+
emitSdkDebugLog("[ModelNex SDK] Tour playback initialized", void 0, { devMode: devModeRef.current });
|
|
3832
4219
|
return () => {
|
|
3833
4220
|
socket.off("connect", handleConnect);
|
|
3834
4221
|
socket.off("tour:server_state", handleServerState);
|
|
@@ -3939,6 +4326,8 @@ function useTourPlayback({
|
|
|
3939
4326
|
status: "stopped",
|
|
3940
4327
|
currentStepOrder: stepIndexRef.current
|
|
3941
4328
|
});
|
|
4329
|
+
} else if (tourRef.current?.id && userProfile?.userId) {
|
|
4330
|
+
void recordTourEvent(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, "cancelled", websiteId);
|
|
3942
4331
|
}
|
|
3943
4332
|
if (reviewModeRef.current) {
|
|
3944
4333
|
if (tourRef.current?.id) {
|
|
@@ -4011,7 +4400,9 @@ function useTourPlayback({
|
|
|
4011
4400
|
isPlaybackActive: isActiveRef.current,
|
|
4012
4401
|
startRequested: startRequestedRef.current
|
|
4013
4402
|
})) {
|
|
4014
|
-
|
|
4403
|
+
emitSdkDebugLog("[TourClient] Ignoring duplicate start request while playback is already active or starting", {
|
|
4404
|
+
tourId: tour.id
|
|
4405
|
+
}, { devMode: devModeRef.current });
|
|
4015
4406
|
return;
|
|
4016
4407
|
}
|
|
4017
4408
|
setPendingTour(null);
|
|
@@ -4027,7 +4418,9 @@ function useTourPlayback({
|
|
|
4027
4418
|
}
|
|
4028
4419
|
const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, tour.type ?? experienceTypeRef.current);
|
|
4029
4420
|
if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
|
|
4030
|
-
|
|
4421
|
+
emitSdkDebugLog("[TourClient] Ignoring duplicate start request because another hook already owns this experience", {
|
|
4422
|
+
tourId: tour.id
|
|
4423
|
+
}, { devMode: devModeRef.current });
|
|
4031
4424
|
return;
|
|
4032
4425
|
}
|
|
4033
4426
|
claimedPlaybackOwnerKeyRef.current = ownerKey;
|
|
@@ -4050,7 +4443,8 @@ function useTourPlayback({
|
|
|
4050
4443
|
setPreviewRunId(previewRun.id);
|
|
4051
4444
|
previewRunIdRef.current = previewRun.id;
|
|
4052
4445
|
} catch (err) {
|
|
4053
|
-
|
|
4446
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4447
|
+
console.warn(`[TourClient] Failed to create preview run: ${errMsg}`);
|
|
4054
4448
|
setReviewStatusMessage("Preview review logging is unavailable.");
|
|
4055
4449
|
setPreviewRunId(null);
|
|
4056
4450
|
previewRunIdRef.current = null;
|
|
@@ -4099,7 +4493,7 @@ function useTourPlayback({
|
|
|
4099
4493
|
if (!tour) {
|
|
4100
4494
|
clearActiveDraftPreview(experienceType);
|
|
4101
4495
|
persistSuppressedDraftPreview(draftPreview);
|
|
4102
|
-
console.warn(
|
|
4496
|
+
console.warn(`[ModelNex] Tour fetch failed for ${experienceType}:${tourId}. Check the ModelNex server and CORS configuration.`);
|
|
4103
4497
|
return;
|
|
4104
4498
|
}
|
|
4105
4499
|
if (cancelled) {
|
|
@@ -4122,7 +4516,8 @@ function useTourPlayback({
|
|
|
4122
4516
|
await runTour(tour, previewOptions);
|
|
4123
4517
|
}
|
|
4124
4518
|
} catch (err) {
|
|
4125
|
-
|
|
4519
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4520
|
+
console.warn(`[ModelNex] Tour test failed: ${errMsg}`);
|
|
4126
4521
|
}
|
|
4127
4522
|
})();
|
|
4128
4523
|
return () => {
|
|
@@ -4232,7 +4627,8 @@ function useTourPlayback({
|
|
|
4232
4627
|
setPlaybackState("executing");
|
|
4233
4628
|
}
|
|
4234
4629
|
} catch (err) {
|
|
4235
|
-
|
|
4630
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4631
|
+
console.warn(`[TourClient] Failed to submit review feedback: ${errMsg}`);
|
|
4236
4632
|
setReviewStatusMessage("Unable to save correction.");
|
|
4237
4633
|
} finally {
|
|
4238
4634
|
setReviewSubmitting(false);
|
|
@@ -4273,23 +4669,29 @@ function useTourPlayback({
|
|
|
4273
4669
|
}, [voice]);
|
|
4274
4670
|
const handleVoiceInput = useCallback7((transcript) => {
|
|
4275
4671
|
const text = transcript.trim();
|
|
4276
|
-
|
|
4672
|
+
emitSdkDebugLog("[TourAgent] Voice input received", {
|
|
4673
|
+
textLength: text.length
|
|
4674
|
+
}, { devMode: devModeRef.current });
|
|
4277
4675
|
if (!text) return;
|
|
4278
4676
|
if (interruptExecution(text)) {
|
|
4279
4677
|
return;
|
|
4280
4678
|
}
|
|
4281
4679
|
if (voiceInputResolveRef.current) {
|
|
4282
|
-
|
|
4680
|
+
emitSdkDebugLog("[TourAgent] Resolving waiting voice input", void 0, { devMode: devModeRef.current });
|
|
4283
4681
|
voiceInputResolveRef.current(text);
|
|
4284
4682
|
} else if (isSocketWritable(socketRef.current)) {
|
|
4285
|
-
|
|
4683
|
+
emitSdkDebugLog("[TourAgent] Forwarding ambient voice input to server", {
|
|
4684
|
+
textLength: text.length
|
|
4685
|
+
}, { devMode: devModeRef.current });
|
|
4286
4686
|
emitSocketEvent(socketRef.current, "tour:user_input", {
|
|
4287
4687
|
transcript: text,
|
|
4288
4688
|
runId: runIdRef.current,
|
|
4289
4689
|
turnId: turnIdRef.current
|
|
4290
4690
|
});
|
|
4291
4691
|
} else {
|
|
4292
|
-
|
|
4692
|
+
emitSdkDebugLog("[TourAgent] Buffering voice input until socket is ready", {
|
|
4693
|
+
textLength: text.length
|
|
4694
|
+
}, { devMode: devModeRef.current });
|
|
4293
4695
|
pendingInputBufRef.current = text;
|
|
4294
4696
|
}
|
|
4295
4697
|
}, [interruptExecution]);
|
|
@@ -5444,7 +5846,6 @@ function useVoice(serverUrl) {
|
|
|
5444
5846
|
setIsListening(false);
|
|
5445
5847
|
}, [stopLiveSttTransport]);
|
|
5446
5848
|
const startListening = useCallback9((onResult, onInterruption, onError, options = {}) => {
|
|
5447
|
-
console.log("[Voice] startListening called, options:", options);
|
|
5448
5849
|
stopListening();
|
|
5449
5850
|
listeningSessionIdRef.current = createVoiceDebugId("stt");
|
|
5450
5851
|
listeningStartedAtRef.current = performance.now();
|
|
@@ -5658,7 +6059,9 @@ function useVoice(serverUrl) {
|
|
|
5658
6059
|
recorder.start(LIVE_STT_TIMESLICE_MS);
|
|
5659
6060
|
}
|
|
5660
6061
|
setIsListening(true);
|
|
5661
|
-
|
|
6062
|
+
emitVoiceDebug("stt_live_pipeline_active", {
|
|
6063
|
+
listeningSessionId: listeningSessionIdRef.current
|
|
6064
|
+
});
|
|
5662
6065
|
} catch (err) {
|
|
5663
6066
|
const normalizedError = normalizeSttError(err);
|
|
5664
6067
|
console.warn("[Voice] Failed to start live STT recorder:", normalizedError);
|
|
@@ -8405,6 +8808,7 @@ function Tooltip({ children, title }) {
|
|
|
8405
8808
|
);
|
|
8406
8809
|
}
|
|
8407
8810
|
var BUBBLE_EXPANDED_STORAGE_KEY = "modelnex-chat-bubble-expanded";
|
|
8811
|
+
var BUBBLE_DOCKED_STORAGE_KEY = "modelnex-chat-bubble-docked";
|
|
8408
8812
|
var TOUR_MINIMIZED_STORAGE_KEY = "modelnex-tour-bubble-minimized";
|
|
8409
8813
|
var BotIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "var(--modelnex-bubble-icon-size, 20px)", height: "var(--modelnex-bubble-icon-size, 20px)", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
8410
8814
|
/* @__PURE__ */ jsx4("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }),
|
|
@@ -8418,6 +8822,18 @@ var CloseIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "var(--modelnex-bubb
|
|
|
8418
8822
|
/* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
8419
8823
|
] });
|
|
8420
8824
|
var MinimizeIcon = () => /* @__PURE__ */ jsx4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M8 18h8" }) });
|
|
8825
|
+
var DockIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
8826
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 3v10" }),
|
|
8827
|
+
/* @__PURE__ */ jsx4("path", { d: "m8 9 4 4 4-4" }),
|
|
8828
|
+
/* @__PURE__ */ jsx4("path", { d: "M4 17h16" }),
|
|
8829
|
+
/* @__PURE__ */ jsx4("path", { d: "M6 21h12" })
|
|
8830
|
+
] });
|
|
8831
|
+
var UndockIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
8832
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 21V11" }),
|
|
8833
|
+
/* @__PURE__ */ jsx4("path", { d: "m8 15 4-4 4 4" }),
|
|
8834
|
+
/* @__PURE__ */ jsx4("path", { d: "M4 7h16" }),
|
|
8835
|
+
/* @__PURE__ */ jsx4("path", { d: "M6 3h12" })
|
|
8836
|
+
] });
|
|
8421
8837
|
var TrashIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
8422
8838
|
/* @__PURE__ */ jsx4("path", { d: "M3 6h18" }),
|
|
8423
8839
|
/* @__PURE__ */ jsx4("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
@@ -8466,7 +8882,7 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
|
|
|
8466
8882
|
},
|
|
8467
8883
|
children: [
|
|
8468
8884
|
/* @__PURE__ */ jsxs3("span", { children: [
|
|
8469
|
-
"
|
|
8885
|
+
"Execution summary (",
|
|
8470
8886
|
traces.length,
|
|
8471
8887
|
" step",
|
|
8472
8888
|
traces.length !== 1 ? "s" : "",
|
|
@@ -8482,21 +8898,9 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
|
|
|
8482
8898
|
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: command })
|
|
8483
8899
|
] }),
|
|
8484
8900
|
!hasTraceContent ? /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px", padding: "8px", background: "#fef3c7", borderRadius: "4px", borderLeft: "3px solid #f59e0b" }, children: [
|
|
8485
|
-
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#92400e", marginBottom: "4px" }, children: "No
|
|
8486
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#71717a"
|
|
8487
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#fff", borderRadius: "4px", fontSize: "10px", overflow: "auto", maxHeight: "120px", color: "#27272a" }, children: JSON.stringify(debug, null, 2) })
|
|
8901
|
+
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#92400e", marginBottom: "4px" }, children: "No safe debug summary" }),
|
|
8902
|
+
/* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#71717a" }, children: "Verbose prompts, reasoning, and raw tool payloads are intentionally hidden by the SDK." })
|
|
8488
8903
|
] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
8489
|
-
debug.llmInput && traces.length === 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
|
|
8490
|
-
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#3f3f46", marginBottom: "4px" }, children: "Input \u2192 agent" }),
|
|
8491
|
-
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
|
|
8492
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "System" }),
|
|
8493
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "80px", overflow: "auto" }, children: debug.llmInput.systemPrompt ?? "(empty)" })
|
|
8494
|
-
] }),
|
|
8495
|
-
/* @__PURE__ */ jsxs3("div", { children: [
|
|
8496
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "User" }),
|
|
8497
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "80px", overflow: "auto" }, children: debug.llmInput.userMessage ?? "(empty)" })
|
|
8498
|
-
] })
|
|
8499
|
-
] }),
|
|
8500
8904
|
traces.map((t) => {
|
|
8501
8905
|
const isStepExpanded = expandedSteps.has(t.step);
|
|
8502
8906
|
return /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid #e4e4e7" }, children: [
|
|
@@ -8529,31 +8933,6 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
|
|
|
8529
8933
|
}
|
|
8530
8934
|
),
|
|
8531
8935
|
isStepExpanded && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
8532
|
-
t.reasoning && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
|
|
8533
|
-
/* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Reasoning" }),
|
|
8534
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#fef3c7", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "100px", overflow: "auto", borderLeft: "3px solid #f59e0b", color: "#451a03" }, children: t.reasoning })
|
|
8535
|
-
] }),
|
|
8536
|
-
t.llmInput && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
8537
|
-
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
|
|
8538
|
-
/* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Input \u2192 agent" }),
|
|
8539
|
-
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "4px" }, children: [
|
|
8540
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "System" }),
|
|
8541
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "80px", overflow: "auto" }, children: t.llmInput.systemPrompt ?? "(empty)" })
|
|
8542
|
-
] }),
|
|
8543
|
-
/* @__PURE__ */ jsxs3("div", { children: [
|
|
8544
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "User" }),
|
|
8545
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "60px", overflow: "auto" }, children: t.llmInput.userMessage ?? "(empty)" })
|
|
8546
|
-
] })
|
|
8547
|
-
] }),
|
|
8548
|
-
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
|
|
8549
|
-
/* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Output \u2190 agent" }),
|
|
8550
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "60px", overflow: "auto" }, children: t.llmOutput ?? "(empty)" })
|
|
8551
|
-
] })
|
|
8552
|
-
] }),
|
|
8553
|
-
!t.llmInput && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
|
|
8554
|
-
/* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "LLM output" }),
|
|
8555
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "60px", overflow: "auto" }, children: t.llmOutput ?? "(empty)" })
|
|
8556
|
-
] }),
|
|
8557
8936
|
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
|
|
8558
8937
|
/* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Actions" }),
|
|
8559
8938
|
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: JSON.stringify(t.actions, null, 2) })
|
|
@@ -8565,10 +8944,6 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
|
|
|
8565
8944
|
] })
|
|
8566
8945
|
] }, t.step);
|
|
8567
8946
|
}),
|
|
8568
|
-
traces.length === 0 && debug.llmOutput && debug.llmOutput.length > 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
|
|
8569
|
-
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#3f3f46", marginBottom: "4px" }, children: "LLM output" }),
|
|
8570
|
-
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: debug.llmOutput.join("\n\n") })
|
|
8571
|
-
] }),
|
|
8572
8947
|
traces.length === 0 && (debug.actions?.length ?? 0) > 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
|
|
8573
8948
|
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#3f3f46", marginBottom: "4px" }, children: "Executed actions" }),
|
|
8574
8949
|
/* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: JSON.stringify(debug.actions, null, 2) })
|
|
@@ -8638,6 +9013,7 @@ function ModelNexChatBubble({
|
|
|
8638
9013
|
const ctx = useContext5(ModelNexContext);
|
|
8639
9014
|
const [hydrated, setHydrated] = useState13(false);
|
|
8640
9015
|
const [expanded, setExpanded] = useState13(false);
|
|
9016
|
+
const [docked, setDocked] = useState13(false);
|
|
8641
9017
|
const [input, setInput] = useState13("");
|
|
8642
9018
|
const messages = ctx?.chatMessages ?? [];
|
|
8643
9019
|
const setMessages = ctx?.setChatMessages ?? (() => {
|
|
@@ -8692,6 +9068,11 @@ function ModelNexChatBubble({
|
|
|
8692
9068
|
const activePlayback = playbackController.playback;
|
|
8693
9069
|
const activeExperienceType = playbackController.activeExperienceType;
|
|
8694
9070
|
const startingExperienceType = playbackController.startingExperienceType;
|
|
9071
|
+
useEffect17(() => {
|
|
9072
|
+
return registerExperienceToolBridge({
|
|
9073
|
+
startExperience: playbackController.startExperience
|
|
9074
|
+
});
|
|
9075
|
+
}, [playbackController.startExperience]);
|
|
8695
9076
|
const createPlaybackView = useCallback12((experienceType) => {
|
|
8696
9077
|
const isActiveExperience = activePlayback.isActive && activeExperienceType === experienceType;
|
|
8697
9078
|
const pendingTour = playbackController.pendingPrompt?.experienceType === experienceType ? playbackController.pendingPrompt.tour : null;
|
|
@@ -8758,7 +9139,6 @@ function ModelNexChatBubble({
|
|
|
8758
9139
|
tagStore.setTagsBatch(data.tags, true);
|
|
8759
9140
|
lastAutoTaggedUrlRef.current = currentUrl;
|
|
8760
9141
|
localStorage.setItem(storageKey, "true");
|
|
8761
|
-
console.log(`[ModelNex] Auto-tagged ${data.tags.length} elements for ${currentUrl}`);
|
|
8762
9142
|
}
|
|
8763
9143
|
} catch (err) {
|
|
8764
9144
|
console.warn("[ModelNex] Auto-tag error:", err);
|
|
@@ -8789,8 +9169,10 @@ function ModelNexChatBubble({
|
|
|
8789
9169
|
setHydrated(true);
|
|
8790
9170
|
try {
|
|
8791
9171
|
setExpanded(sessionStorage.getItem(BUBBLE_EXPANDED_STORAGE_KEY) === "true");
|
|
9172
|
+
setDocked(sessionStorage.getItem(BUBBLE_DOCKED_STORAGE_KEY) === "true");
|
|
8792
9173
|
} catch {
|
|
8793
9174
|
setExpanded(false);
|
|
9175
|
+
setDocked(false);
|
|
8794
9176
|
}
|
|
8795
9177
|
}, []);
|
|
8796
9178
|
const sttActiveRef = useRef13(false);
|
|
@@ -8850,6 +9232,13 @@ function ModelNexChatBubble({
|
|
|
8850
9232
|
} catch {
|
|
8851
9233
|
}
|
|
8852
9234
|
}, [tourPlayback.isActive, onboardingPlayback.isActive]);
|
|
9235
|
+
const setDockedState = useCallback12((next) => {
|
|
9236
|
+
setDocked(next);
|
|
9237
|
+
try {
|
|
9238
|
+
sessionStorage.setItem(BUBBLE_DOCKED_STORAGE_KEY, String(next));
|
|
9239
|
+
} catch {
|
|
9240
|
+
}
|
|
9241
|
+
}, []);
|
|
8853
9242
|
useEffect17(() => {
|
|
8854
9243
|
if (shouldAutoExpandForPendingPrompt({
|
|
8855
9244
|
pendingPrompt,
|
|
@@ -8913,12 +9302,9 @@ function ModelNexChatBubble({
|
|
|
8913
9302
|
};
|
|
8914
9303
|
const listeningExperience = resolveTourListeningExperience(preferredExperience, listeningState);
|
|
8915
9304
|
preferredListeningExperienceRef.current = listeningExperience;
|
|
8916
|
-
console.log("[ChatBubble] startTourListening called. listeningState:", listeningState, "preferredExperience:", preferredExperience, "listeningExperience:", listeningExperience);
|
|
8917
9305
|
if (!canStartTourListening(listeningState)) {
|
|
8918
|
-
console.log("[ChatBubble] startTourListening bailed out early.");
|
|
8919
9306
|
return;
|
|
8920
9307
|
}
|
|
8921
|
-
console.log("[ChatBubble] Proceeding to startTourListening...");
|
|
8922
9308
|
sttActiveRef.current = true;
|
|
8923
9309
|
updateTourSttError(null);
|
|
8924
9310
|
resetFloatingLiveTranscriptSuppression();
|
|
@@ -9124,7 +9510,7 @@ function ModelNexChatBubble({
|
|
|
9124
9510
|
content: summary || fallbackContent,
|
|
9125
9511
|
summary: summary ?? void 0,
|
|
9126
9512
|
nextSteps: nextSteps ?? void 0,
|
|
9127
|
-
debug: data?.debug ?? void 0
|
|
9513
|
+
debug: devMode ? data?.debug ?? void 0 : void 0
|
|
9128
9514
|
}
|
|
9129
9515
|
]);
|
|
9130
9516
|
} catch (err) {
|
|
@@ -9211,11 +9597,13 @@ function ModelNexChatBubble({
|
|
|
9211
9597
|
fontFamily: "var(--modelnex-font)",
|
|
9212
9598
|
...themeStyles
|
|
9213
9599
|
};
|
|
9600
|
+
const desktopPanelHeight = docked ? "min(calc(100vh - 48px), calc(var(--modelnex-panel-max-height, 600px) + 180px))" : "var(--modelnex-panel-max-height, 600px)";
|
|
9601
|
+
const desktopPanelMaxHeight = docked ? "calc(100vh - 48px)" : "calc(100vh - 120px)";
|
|
9214
9602
|
const panelStyle = {
|
|
9215
9603
|
width: isMobile ? "100%" : "var(--modelnex-panel-width, 380px)",
|
|
9216
9604
|
maxWidth: "calc(100vw - 32px)",
|
|
9217
|
-
height: isMobile ? "100%" :
|
|
9218
|
-
maxHeight: "
|
|
9605
|
+
height: isMobile ? "100%" : desktopPanelHeight,
|
|
9606
|
+
maxHeight: isMobile ? "100%" : desktopPanelMaxHeight,
|
|
9219
9607
|
borderRadius: isMobile ? "0" : "var(--modelnex-radius-panel, 20px)",
|
|
9220
9608
|
border: isMobile ? "none" : "1px solid var(--modelnex-border, #e4e4e7)",
|
|
9221
9609
|
background: "var(--modelnex-bg, #ffffff)",
|
|
@@ -9367,6 +9755,31 @@ function ModelNexChatBubble({
|
|
|
9367
9755
|
] })
|
|
9368
9756
|
] }) }),
|
|
9369
9757
|
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
|
|
9758
|
+
!isMobile && /* @__PURE__ */ jsx4(Tooltip, { title: docked ? "Undock" : "Dock", children: /* @__PURE__ */ jsxs3(
|
|
9759
|
+
"button",
|
|
9760
|
+
{
|
|
9761
|
+
onClick: () => setDockedState(!docked),
|
|
9762
|
+
style: {
|
|
9763
|
+
padding: "8px 10px",
|
|
9764
|
+
borderRadius: "10px",
|
|
9765
|
+
border: "none",
|
|
9766
|
+
background: docked ? "rgba(79,70,229,0.08)" : "transparent",
|
|
9767
|
+
cursor: "pointer",
|
|
9768
|
+
color: docked ? "var(--modelnex-accent, #4f46e5)" : "#71717a",
|
|
9769
|
+
transition: "all 0.2s",
|
|
9770
|
+
display: "flex",
|
|
9771
|
+
alignItems: "center",
|
|
9772
|
+
gap: "6px",
|
|
9773
|
+
fontSize: "12px",
|
|
9774
|
+
fontWeight: 700
|
|
9775
|
+
},
|
|
9776
|
+
"aria-label": docked ? "Undock chat bubble" : "Dock chat bubble",
|
|
9777
|
+
children: [
|
|
9778
|
+
docked ? /* @__PURE__ */ jsx4(UndockIcon, {}) : /* @__PURE__ */ jsx4(DockIcon, {}),
|
|
9779
|
+
/* @__PURE__ */ jsx4("span", { children: docked ? "Undock" : "Dock" })
|
|
9780
|
+
]
|
|
9781
|
+
}
|
|
9782
|
+
) }),
|
|
9370
9783
|
(tourPlayback.isActive || onboardingPlayback.isActive || voice.isSpeaking) && /* @__PURE__ */ jsx4(Tooltip, { title: voice.isMuted ? "Unmute" : "Mute", children: /* @__PURE__ */ jsx4(
|
|
9371
9784
|
"button",
|
|
9372
9785
|
{
|
|
@@ -10140,7 +10553,7 @@ function ModelNexChatBubble({
|
|
|
10140
10553
|
)
|
|
10141
10554
|
}
|
|
10142
10555
|
),
|
|
10143
|
-
msg.role === "assistant" && msg.debug && /* @__PURE__ */ jsx4(AgentTraces, { debug: msg.debug, command: messages[i - 1]?.content ?? "" })
|
|
10556
|
+
msg.role === "assistant" && devMode && msg.debug && /* @__PURE__ */ jsx4(AgentTraces, { debug: msg.debug, command: messages[i - 1]?.content ?? "" })
|
|
10144
10557
|
] }, i)),
|
|
10145
10558
|
loading && /* @__PURE__ */ jsx4("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsxs3(
|
|
10146
10559
|
"div",
|
|
@@ -11119,6 +11532,7 @@ var ModelNexProvider = ({
|
|
|
11119
11532
|
userProfile,
|
|
11120
11533
|
toursApiBase,
|
|
11121
11534
|
devMode,
|
|
11535
|
+
devModeKey,
|
|
11122
11536
|
serverUrl: serverUrlProp
|
|
11123
11537
|
}) => {
|
|
11124
11538
|
const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
|
|
@@ -11141,6 +11555,25 @@ var ModelNexProvider = ({
|
|
|
11141
11555
|
const [voiceMuted, setVoiceMuted] = useState15(false);
|
|
11142
11556
|
const [socketId, setSocketId] = useState15(null);
|
|
11143
11557
|
const [actions, setActions] = useState15(/* @__PURE__ */ new Map());
|
|
11558
|
+
const [validatedBrowserDevMode, setValidatedBrowserDevMode] = useState15(false);
|
|
11559
|
+
const resolvedDevModeKey = useMemo5(() => resolveInjectedDevModeKey(devModeKey), [devModeKey]);
|
|
11560
|
+
useEffect19(() => {
|
|
11561
|
+
let cancelled = false;
|
|
11562
|
+
if (!websiteId || !resolvedDevModeKey) {
|
|
11563
|
+
setValidatedBrowserDevMode(false);
|
|
11564
|
+
return () => {
|
|
11565
|
+
cancelled = true;
|
|
11566
|
+
};
|
|
11567
|
+
}
|
|
11568
|
+
void validateInjectedDevModeKey(serverUrl, websiteId, resolvedDevModeKey).then((enabled) => {
|
|
11569
|
+
if (cancelled) return;
|
|
11570
|
+
setValidatedBrowserDevMode(enabled);
|
|
11571
|
+
});
|
|
11572
|
+
return () => {
|
|
11573
|
+
cancelled = true;
|
|
11574
|
+
};
|
|
11575
|
+
}, [resolvedDevModeKey, serverUrl, websiteId]);
|
|
11576
|
+
const effectiveDevMode = Boolean(devMode) || validatedBrowserDevMode;
|
|
11144
11577
|
const registerAction = useCallback14((action) => {
|
|
11145
11578
|
setActions((prev) => {
|
|
11146
11579
|
const next = new Map(prev);
|
|
@@ -11155,30 +11588,41 @@ var ModelNexProvider = ({
|
|
|
11155
11588
|
return next;
|
|
11156
11589
|
});
|
|
11157
11590
|
}, []);
|
|
11158
|
-
const extractedElements = useAutoExtract();
|
|
11591
|
+
const extractedElements = useAutoExtract(effectiveDevMode);
|
|
11159
11592
|
const tagStore = useTagStore({ serverUrl, websiteId });
|
|
11160
|
-
useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId);
|
|
11593
|
+
useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile);
|
|
11161
11594
|
const CHAT_STORAGE_KEY = "modelnex-chat-messages";
|
|
11162
11595
|
const [chatMessages, setChatMessagesRaw] = useState15([]);
|
|
11163
11596
|
useEffect19(() => {
|
|
11164
11597
|
try {
|
|
11165
11598
|
const stored = sessionStorage.getItem(CHAT_STORAGE_KEY);
|
|
11166
11599
|
if (stored) {
|
|
11167
|
-
setChatMessagesRaw(JSON.parse(stored));
|
|
11600
|
+
setChatMessagesRaw(sanitizeChatMessages(JSON.parse(stored), effectiveDevMode));
|
|
11168
11601
|
}
|
|
11169
11602
|
} catch {
|
|
11170
11603
|
}
|
|
11171
|
-
}, []);
|
|
11604
|
+
}, [effectiveDevMode]);
|
|
11605
|
+
useEffect19(() => {
|
|
11606
|
+
setChatMessagesRaw((prev) => {
|
|
11607
|
+
const next = sanitizeChatMessages(prev, effectiveDevMode);
|
|
11608
|
+
try {
|
|
11609
|
+
sessionStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(next));
|
|
11610
|
+
} catch {
|
|
11611
|
+
}
|
|
11612
|
+
return next;
|
|
11613
|
+
});
|
|
11614
|
+
}, [effectiveDevMode]);
|
|
11172
11615
|
const setChatMessages = useCallback14((action) => {
|
|
11173
11616
|
setChatMessagesRaw((prev) => {
|
|
11174
|
-
const
|
|
11617
|
+
const resolved = typeof action === "function" ? action(prev) : action;
|
|
11618
|
+
const next = sanitizeChatMessages(resolved, effectiveDevMode);
|
|
11175
11619
|
try {
|
|
11176
11620
|
sessionStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(next));
|
|
11177
11621
|
} catch {
|
|
11178
11622
|
}
|
|
11179
11623
|
return next;
|
|
11180
11624
|
});
|
|
11181
|
-
}, []);
|
|
11625
|
+
}, [effectiveDevMode]);
|
|
11182
11626
|
useModelNexSocket({
|
|
11183
11627
|
serverUrl,
|
|
11184
11628
|
actions,
|
|
@@ -11190,7 +11634,8 @@ var ModelNexProvider = ({
|
|
|
11190
11634
|
setStagingFields,
|
|
11191
11635
|
setExecutedFields,
|
|
11192
11636
|
onSocketId: setSocketId,
|
|
11193
|
-
websiteId
|
|
11637
|
+
websiteId,
|
|
11638
|
+
devMode: effectiveDevMode
|
|
11194
11639
|
});
|
|
11195
11640
|
useFieldHighlight(stagingFields, executedFields, setExecutedFields);
|
|
11196
11641
|
useEffect19(() => {
|
|
@@ -11225,9 +11670,9 @@ var ModelNexProvider = ({
|
|
|
11225
11670
|
voiceMuted,
|
|
11226
11671
|
setVoiceMuted,
|
|
11227
11672
|
socketId,
|
|
11228
|
-
devMode
|
|
11673
|
+
devMode: effectiveDevMode
|
|
11229
11674
|
}),
|
|
11230
|
-
[serverUrl, commandUrl, registerAction, unregisterAction, activeAgentActions, stagingFields, highlightActions, studioMode, recordingMode, extractedElements, tagStore, chatMessages, websiteId, userProfile?.userId, userProfile?.type, userProfile?.isNewUser, toursApiBase, voiceMuted, socketId,
|
|
11675
|
+
[serverUrl, commandUrl, registerAction, unregisterAction, activeAgentActions, stagingFields, highlightActions, studioMode, recordingMode, extractedElements, tagStore, chatMessages, websiteId, userProfile?.userId, userProfile?.type, userProfile?.isNewUser, toursApiBase, voiceMuted, socketId, effectiveDevMode]
|
|
11231
11676
|
);
|
|
11232
11677
|
return React8.createElement(
|
|
11233
11678
|
ModelNexContext.Provider,
|