@modelnex/sdk 0.5.26 → 0.5.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +535 -150
- package/dist/index.mjs +535 -150
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1617,8 +1617,153 @@ async function captureScreenshot(selector) {
|
|
|
1617
1617
|
return canvas.toDataURL("image/png");
|
|
1618
1618
|
}
|
|
1619
1619
|
|
|
1620
|
+
// src/utils/experience-tool-bridge.ts
|
|
1621
|
+
var activeBridge = null;
|
|
1622
|
+
function registerExperienceToolBridge(bridge) {
|
|
1623
|
+
activeBridge = bridge;
|
|
1624
|
+
return () => {
|
|
1625
|
+
if (activeBridge === bridge) {
|
|
1626
|
+
activeBridge = null;
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function getExperienceToolBridge() {
|
|
1631
|
+
return activeBridge;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/utils/tourDiscovery.ts
|
|
1635
|
+
async function fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, experienceType = "tour") {
|
|
1636
|
+
try {
|
|
1637
|
+
const params = new URLSearchParams({
|
|
1638
|
+
websiteId,
|
|
1639
|
+
userType
|
|
1640
|
+
});
|
|
1641
|
+
if (userId) params.set("userId", userId);
|
|
1642
|
+
if (experienceType !== "tour") params.set("type", experienceType);
|
|
1643
|
+
const res = await fetch(getTourApiUrl(serverUrl, toursApiBase, `/tours?${params.toString()}`));
|
|
1644
|
+
if (!res.ok) return [];
|
|
1645
|
+
const data = await res.json();
|
|
1646
|
+
return data.tours ?? [];
|
|
1647
|
+
} catch {
|
|
1648
|
+
return [];
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
async function fetchTourById(serverUrl, toursApiBase, tourId, experienceType = "tour", websiteId) {
|
|
1652
|
+
try {
|
|
1653
|
+
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}`), websiteId));
|
|
1654
|
+
if (experienceType !== "tour") {
|
|
1655
|
+
url.searchParams.set("type", experienceType);
|
|
1656
|
+
}
|
|
1657
|
+
const res = await fetch(url.toString());
|
|
1658
|
+
if (!res.ok) {
|
|
1659
|
+
return null;
|
|
1660
|
+
}
|
|
1661
|
+
const data = await res.json();
|
|
1662
|
+
return data.tour ?? null;
|
|
1663
|
+
} catch {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
async function markTourComplete(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
|
|
1668
|
+
try {
|
|
1669
|
+
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/complete`), websiteId));
|
|
1670
|
+
if (experienceType !== "tour") {
|
|
1671
|
+
url.searchParams.set("type", experienceType);
|
|
1672
|
+
}
|
|
1673
|
+
await fetch(url.toString(), {
|
|
1674
|
+
method: "POST",
|
|
1675
|
+
headers: { "Content-Type": "application/json" },
|
|
1676
|
+
body: JSON.stringify({ userId })
|
|
1677
|
+
});
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
async function markTourDismissed(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
|
|
1682
|
+
try {
|
|
1683
|
+
const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/dismiss`), websiteId));
|
|
1684
|
+
if (experienceType !== "tour") {
|
|
1685
|
+
url.searchParams.set("type", experienceType);
|
|
1686
|
+
}
|
|
1687
|
+
await fetch(url.toString(), {
|
|
1688
|
+
method: "POST",
|
|
1689
|
+
headers: { "Content-Type": "application/json" },
|
|
1690
|
+
body: JSON.stringify({ userId })
|
|
1691
|
+
});
|
|
1692
|
+
} catch {
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
async function recordTourEvent(serverUrl, toursApiBase, tourId, userId, eventType, websiteId) {
|
|
1696
|
+
try {
|
|
1697
|
+
const url = withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/events`), websiteId);
|
|
1698
|
+
await fetch(url, {
|
|
1699
|
+
method: "POST",
|
|
1700
|
+
headers: { "Content-Type": "application/json" },
|
|
1701
|
+
body: JSON.stringify({ userId, eventType })
|
|
1702
|
+
});
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
function getToursBaseUrl(serverUrl, toursApiBase) {
|
|
1707
|
+
if (toursApiBase?.startsWith("/")) {
|
|
1708
|
+
return `${typeof window !== "undefined" ? window.location.origin : ""}${toursApiBase.replace(/\/$/, "")}`;
|
|
1709
|
+
}
|
|
1710
|
+
return serverUrl.replace(/\/$/, "");
|
|
1711
|
+
}
|
|
1712
|
+
function getTourApiUrl(serverUrl, toursApiBase, path) {
|
|
1713
|
+
const base = getToursBaseUrl(serverUrl, toursApiBase);
|
|
1714
|
+
return toursApiBase ? `${base}${path}` : `${base}/api${path}`;
|
|
1715
|
+
}
|
|
1716
|
+
function withWebsiteId(url, websiteId) {
|
|
1717
|
+
if (!websiteId) {
|
|
1718
|
+
return url;
|
|
1719
|
+
}
|
|
1720
|
+
const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
1721
|
+
const resolved = new URL(url, baseOrigin);
|
|
1722
|
+
resolved.searchParams.set("websiteId", websiteId);
|
|
1723
|
+
return resolved.toString();
|
|
1724
|
+
}
|
|
1725
|
+
function normalizeTrigger(trigger) {
|
|
1726
|
+
if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
|
|
1727
|
+
return "feature_launch";
|
|
1728
|
+
}
|
|
1729
|
+
if (trigger === "return_visit") {
|
|
1730
|
+
return "return_visit";
|
|
1731
|
+
}
|
|
1732
|
+
return trigger ?? "first_visit";
|
|
1733
|
+
}
|
|
1734
|
+
function getStartPolicy(tour) {
|
|
1735
|
+
return tour.startPolicy ?? (normalizeTrigger(tour.trigger) === "manual" ? "immediate_start" : "prompt_only");
|
|
1736
|
+
}
|
|
1737
|
+
function getNotificationType(tour) {
|
|
1738
|
+
return tour.notificationType ?? "bubble_card";
|
|
1739
|
+
}
|
|
1740
|
+
function getUserProfileFeatures(userProfile) {
|
|
1741
|
+
const directFeatures = Array.isArray(userProfile.features) ? userProfile.features : [];
|
|
1742
|
+
const nestedFeatures = Array.isArray(userProfile.tourFacts?.features) ? userProfile.tourFacts.features : [];
|
|
1743
|
+
return [...new Set([...directFeatures, ...nestedFeatures].filter((value) => Boolean(value)))];
|
|
1744
|
+
}
|
|
1745
|
+
function isTourEligible(tour, userProfile) {
|
|
1746
|
+
switch (normalizeTrigger(tour.trigger)) {
|
|
1747
|
+
case "first_visit":
|
|
1748
|
+
return !!userProfile.isNewUser;
|
|
1749
|
+
case "return_visit":
|
|
1750
|
+
return userProfile.isNewUser === false;
|
|
1751
|
+
case "feature_launch": {
|
|
1752
|
+
const featureKey = tour.featureKey?.trim();
|
|
1753
|
+
if (!featureKey) return false;
|
|
1754
|
+
return getUserProfileFeatures(userProfile).includes(featureKey);
|
|
1755
|
+
}
|
|
1756
|
+
case "manual":
|
|
1757
|
+
return false;
|
|
1758
|
+
default:
|
|
1759
|
+
return false;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1620
1763
|
// src/hooks/useBuiltinActions.ts
|
|
1621
1764
|
var resolutionCache = /* @__PURE__ */ new Map();
|
|
1765
|
+
var DEFAULT_WORKFLOW_SEARCH_LIMIT = 5;
|
|
1766
|
+
var WORKFLOW_TYPES = ["onboarding", "tour"];
|
|
1622
1767
|
function safeQueryAll2(selector) {
|
|
1623
1768
|
try {
|
|
1624
1769
|
return Array.from(document.querySelectorAll(selector)).filter(
|
|
@@ -1793,6 +1938,137 @@ function lastResortScan(fingerprint, options, elements) {
|
|
|
1793
1938
|
function cacheResolution(originalFp, selector, resolvedFp) {
|
|
1794
1939
|
resolutionCache.set(originalFp, { selector, resolvedFingerprint: resolvedFp });
|
|
1795
1940
|
}
|
|
1941
|
+
function normalizeWorkflowQuery(value) {
|
|
1942
|
+
return value.trim().toLowerCase();
|
|
1943
|
+
}
|
|
1944
|
+
function getWorkflowSearchText(tour) {
|
|
1945
|
+
const firstSteps = (tour.steps || []).slice(0, 6).flatMap((step) => [step.goal, step.narration, step.ask]).filter(Boolean).join(" ");
|
|
1946
|
+
return [
|
|
1947
|
+
tour.name,
|
|
1948
|
+
tour.type,
|
|
1949
|
+
tour.trigger,
|
|
1950
|
+
tour.startPolicy,
|
|
1951
|
+
tour.featureKey,
|
|
1952
|
+
tour.goal?.primaryAction,
|
|
1953
|
+
tour.goal?.successMetric,
|
|
1954
|
+
...tour.targetUserTypes,
|
|
1955
|
+
firstSteps
|
|
1956
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
1957
|
+
}
|
|
1958
|
+
function scoreWorkflowMatch(tour, query) {
|
|
1959
|
+
const normalizedQuery = normalizeWorkflowQuery(query);
|
|
1960
|
+
if (!normalizedQuery) return 1;
|
|
1961
|
+
const name = tour.name.toLowerCase();
|
|
1962
|
+
const featureKey = (tour.featureKey || "").toLowerCase();
|
|
1963
|
+
const haystack = getWorkflowSearchText(tour);
|
|
1964
|
+
if (name === normalizedQuery) return 140;
|
|
1965
|
+
if (featureKey && featureKey === normalizedQuery) return 125;
|
|
1966
|
+
if (name.includes(normalizedQuery)) return 115;
|
|
1967
|
+
if (featureKey && featureKey.includes(normalizedQuery)) return 100;
|
|
1968
|
+
if (haystack.includes(normalizedQuery)) return 85;
|
|
1969
|
+
const words = normalizedQuery.split(/\s+/).filter((word) => word.length > 1);
|
|
1970
|
+
if (words.length === 0) return 0;
|
|
1971
|
+
const matched = words.filter((word) => haystack.includes(word));
|
|
1972
|
+
if (matched.length === 0) return 0;
|
|
1973
|
+
const ratio = matched.length / words.length;
|
|
1974
|
+
return Math.round(ratio * 70);
|
|
1975
|
+
}
|
|
1976
|
+
function summarizeWorkflowStep(step, index) {
|
|
1977
|
+
const detail = step.goal || step.ask || step.narration || step.rawNarration || "No description";
|
|
1978
|
+
return `${index + 1}. [${step.type}] ${detail}`;
|
|
1979
|
+
}
|
|
1980
|
+
function formatWorkflowSummary(tour, options) {
|
|
1981
|
+
const stepLimit = options?.stepLimit ?? 4;
|
|
1982
|
+
const parts = [
|
|
1983
|
+
`**${tour.name}**`,
|
|
1984
|
+
`ID: ${tour.id}`,
|
|
1985
|
+
`Type: ${tour.type || "tour"}`,
|
|
1986
|
+
`Trigger: ${tour.trigger}`,
|
|
1987
|
+
`Start policy: ${tour.startPolicy || "prompt_only"}`,
|
|
1988
|
+
tour.featureKey ? `Feature key: ${tour.featureKey}` : null,
|
|
1989
|
+
tour.targetUserTypes?.length ? `Target users: ${tour.targetUserTypes.join(", ")}` : null,
|
|
1990
|
+
`Steps: ${tour.steps?.length ?? 0}`,
|
|
1991
|
+
tour.goal?.primaryAction ? `Primary action: ${tour.goal.primaryAction}` : null
|
|
1992
|
+
].filter(Boolean);
|
|
1993
|
+
if (options?.includeSteps) {
|
|
1994
|
+
const stepPreview = (tour.steps || []).slice(0, stepLimit).map((step, index) => summarizeWorkflowStep(step, index)).join("\n");
|
|
1995
|
+
if (stepPreview) {
|
|
1996
|
+
parts.push(`Steps preview:
|
|
1997
|
+
${stepPreview}`);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
return parts.join("\n");
|
|
2001
|
+
}
|
|
2002
|
+
function getRequestedWorkflowTypes(experienceType) {
|
|
2003
|
+
if (!experienceType || experienceType === "all") {
|
|
2004
|
+
return WORKFLOW_TYPES;
|
|
2005
|
+
}
|
|
2006
|
+
return [experienceType];
|
|
2007
|
+
}
|
|
2008
|
+
async function fetchAvailableWorkflows(getters, experienceType) {
|
|
2009
|
+
const serverUrl = getters.serverUrl();
|
|
2010
|
+
const websiteId = getters.websiteId();
|
|
2011
|
+
if (!serverUrl) {
|
|
2012
|
+
throw new Error("Server URL is not configured.");
|
|
2013
|
+
}
|
|
2014
|
+
if (!websiteId) {
|
|
2015
|
+
throw new Error("websiteId is required to search workflows.");
|
|
2016
|
+
}
|
|
2017
|
+
const userProfile = getters.userProfile();
|
|
2018
|
+
const userType = userProfile?.type || "";
|
|
2019
|
+
const userId = userProfile?.userId;
|
|
2020
|
+
const toursApiBase = getters.toursApiBase();
|
|
2021
|
+
const requestedTypes = getRequestedWorkflowTypes(experienceType);
|
|
2022
|
+
const lists = await Promise.all(
|
|
2023
|
+
requestedTypes.map(
|
|
2024
|
+
(type) => fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, type)
|
|
2025
|
+
)
|
|
2026
|
+
);
|
|
2027
|
+
return lists.flatMap(
|
|
2028
|
+
(list, index) => list.map((tour) => ({
|
|
2029
|
+
...tour,
|
|
2030
|
+
type: tour.type || requestedTypes[index]
|
|
2031
|
+
}))
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
async function resolveWorkflowFromInput(getters, params) {
|
|
2035
|
+
const serverUrl = getters.serverUrl();
|
|
2036
|
+
const websiteId = getters.websiteId();
|
|
2037
|
+
const toursApiBase = getters.toursApiBase();
|
|
2038
|
+
if (!serverUrl) {
|
|
2039
|
+
throw new Error("Server URL is not configured.");
|
|
2040
|
+
}
|
|
2041
|
+
if (params.workflowId) {
|
|
2042
|
+
const requestedTypes = getRequestedWorkflowTypes(params.experienceType);
|
|
2043
|
+
for (const type of requestedTypes) {
|
|
2044
|
+
const workflow = await fetchTourById(serverUrl, toursApiBase, params.workflowId, type, websiteId);
|
|
2045
|
+
if (workflow) {
|
|
2046
|
+
return { workflow: { ...workflow, type: workflow.type || type } };
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
const query = params.name?.trim();
|
|
2051
|
+
if (!query) {
|
|
2052
|
+
return { workflow: null, reason: "Provide either workflowId or name." };
|
|
2053
|
+
}
|
|
2054
|
+
const workflows = await fetchAvailableWorkflows(getters, params.experienceType);
|
|
2055
|
+
const ranked = workflows.map((workflow) => ({ workflow, score: scoreWorkflowMatch(workflow, query) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
|
|
2056
|
+
if (ranked.length === 0) {
|
|
2057
|
+
return {
|
|
2058
|
+
workflow: null,
|
|
2059
|
+
reason: `No workflow matched "${query}". Try search_workflows first for available options.`
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
if (ranked.length > 1 && ranked[0].score === ranked[1].score) {
|
|
2063
|
+
return {
|
|
2064
|
+
workflow: null,
|
|
2065
|
+
reason: `Multiple workflows matched "${query}". Try view_workflow with an exact workflowId.
|
|
2066
|
+
|
|
2067
|
+
` + ranked.slice(0, 3).map(({ workflow }) => formatWorkflowSummary(workflow)).join("\n\n---\n\n")
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
return { workflow: ranked[0].workflow };
|
|
2071
|
+
}
|
|
1796
2072
|
var screenshotSchema = z2.object({
|
|
1797
2073
|
selector: z2.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport.")
|
|
1798
2074
|
});
|
|
@@ -2073,15 +2349,91 @@ function createSearchTaggedElementsAction(getTagStore) {
|
|
|
2073
2349
|
}
|
|
2074
2350
|
};
|
|
2075
2351
|
}
|
|
2352
|
+
var workflowExperienceTypeSchema = z2.enum(["onboarding", "tour", "all"]);
|
|
2353
|
+
var searchWorkflowsSchema = z2.object({
|
|
2354
|
+
query: z2.string().optional().describe("Optional workflow name, feature, or task phrase to search for. Omit to list available workflows."),
|
|
2355
|
+
experienceType: workflowExperienceTypeSchema.optional().describe("Which workflow type to search: onboarding, tour, or all. Default: onboarding."),
|
|
2356
|
+
limit: z2.number().optional().describe(`Maximum number of workflows to return (default: ${DEFAULT_WORKFLOW_SEARCH_LIMIT}).`)
|
|
2357
|
+
});
|
|
2358
|
+
function createSearchWorkflowsAction(getters) {
|
|
2359
|
+
return {
|
|
2360
|
+
id: "search_workflows",
|
|
2361
|
+
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.",
|
|
2362
|
+
schema: searchWorkflowsSchema,
|
|
2363
|
+
execute: async (params) => {
|
|
2364
|
+
const workflows = await fetchAvailableWorkflows(getters, params.experienceType ?? "onboarding");
|
|
2365
|
+
if (workflows.length === 0) {
|
|
2366
|
+
return "No workflows are currently available for this website/user.";
|
|
2367
|
+
}
|
|
2368
|
+
const query = params.query?.trim();
|
|
2369
|
+
const limit = params.limit ?? DEFAULT_WORKFLOW_SEARCH_LIMIT;
|
|
2370
|
+
const ranked = workflows.map((workflow) => ({
|
|
2371
|
+
workflow,
|
|
2372
|
+
score: query ? scoreWorkflowMatch(workflow, query) : 1
|
|
2373
|
+
})).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
2374
|
+
if (ranked.length === 0) {
|
|
2375
|
+
return `No workflows matched "${query}".`;
|
|
2376
|
+
}
|
|
2377
|
+
return ranked.map(({ workflow }) => formatWorkflowSummary(workflow)).join("\n\n---\n\n");
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
var resolveWorkflowSchema = z2.object({
|
|
2382
|
+
workflowId: z2.string().optional().describe("Exact workflow id when known."),
|
|
2383
|
+
name: z2.string().optional().describe("Workflow name or a close name match when the id is unknown."),
|
|
2384
|
+
experienceType: workflowExperienceTypeSchema.optional().describe("Which workflow type to search: onboarding, tour, or all. Default: onboarding.")
|
|
2385
|
+
});
|
|
2386
|
+
function createViewWorkflowAction(getters) {
|
|
2387
|
+
return {
|
|
2388
|
+
id: "view_workflow",
|
|
2389
|
+
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.",
|
|
2390
|
+
schema: resolveWorkflowSchema,
|
|
2391
|
+
execute: async (params) => {
|
|
2392
|
+
const { workflow, reason } = await resolveWorkflowFromInput(getters, params);
|
|
2393
|
+
if (!workflow) {
|
|
2394
|
+
return reason || "Workflow not found.";
|
|
2395
|
+
}
|
|
2396
|
+
return formatWorkflowSummary(workflow, { includeSteps: true, stepLimit: 5 });
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
var startWorkflowSchema = resolveWorkflowSchema.extend({
|
|
2401
|
+
reviewMode: z2.boolean().optional().describe("Optional. Start in review mode instead of normal playback. Default: false.")
|
|
2402
|
+
});
|
|
2403
|
+
function createStartWorkflowAction(getters) {
|
|
2404
|
+
return {
|
|
2405
|
+
id: "start_workflow",
|
|
2406
|
+
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.",
|
|
2407
|
+
schema: startWorkflowSchema,
|
|
2408
|
+
execute: async (params) => {
|
|
2409
|
+
const { workflow, reason } = await resolveWorkflowFromInput(getters, params);
|
|
2410
|
+
if (!workflow) {
|
|
2411
|
+
return reason || "Workflow not found.";
|
|
2412
|
+
}
|
|
2413
|
+
const bridge = getExperienceToolBridge();
|
|
2414
|
+
if (!bridge) {
|
|
2415
|
+
return "Workflow launch is unavailable because no chat playback controller is mounted.";
|
|
2416
|
+
}
|
|
2417
|
+
bridge.startExperience(workflow, workflow.type, params.reviewMode ? {
|
|
2418
|
+
reviewMode: true,
|
|
2419
|
+
reviewMetadata: { trigger: "agent_tool" }
|
|
2420
|
+
} : void 0);
|
|
2421
|
+
return `Started workflow "${workflow.name}" (${workflow.id}).`;
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2076
2425
|
var BUILTIN_ACTION_IDS = {
|
|
2077
2426
|
screenshot: "take_screenshot",
|
|
2078
2427
|
click: "click_element",
|
|
2079
2428
|
fill: "fill_input",
|
|
2080
2429
|
wait: "request_user_action",
|
|
2081
2430
|
searchDocs: "search_docs",
|
|
2082
|
-
searchTaggedElements: "search_tagged_elements"
|
|
2431
|
+
searchTaggedElements: "search_tagged_elements",
|
|
2432
|
+
searchWorkflows: "search_workflows",
|
|
2433
|
+
viewWorkflow: "view_workflow",
|
|
2434
|
+
startWorkflow: "start_workflow"
|
|
2083
2435
|
};
|
|
2084
|
-
function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId) {
|
|
2436
|
+
function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile) {
|
|
2085
2437
|
const registeredRef = useRef6(false);
|
|
2086
2438
|
const tagStoreRef = useRef6(tagStore);
|
|
2087
2439
|
tagStoreRef.current = tagStore;
|
|
@@ -2089,18 +2441,33 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
|
|
|
2089
2441
|
serverUrlRef.current = serverUrl;
|
|
2090
2442
|
const websiteIdRef = useRef6(websiteId);
|
|
2091
2443
|
websiteIdRef.current = websiteId;
|
|
2444
|
+
const toursApiBaseRef = useRef6(toursApiBase);
|
|
2445
|
+
toursApiBaseRef.current = toursApiBase;
|
|
2446
|
+
const userProfileRef = useRef6(userProfile);
|
|
2447
|
+
userProfileRef.current = userProfile;
|
|
2092
2448
|
useEffect8(() => {
|
|
2093
2449
|
if (registeredRef.current) return;
|
|
2094
2450
|
registeredRef.current = true;
|
|
2095
2451
|
const getTagStore = () => tagStoreRef.current;
|
|
2096
2452
|
const getServerUrl = () => serverUrlRef.current;
|
|
2097
2453
|
const getWebsiteId = () => websiteIdRef.current;
|
|
2454
|
+
const getToursApiBase = () => toursApiBaseRef.current;
|
|
2455
|
+
const getUserProfile = () => userProfileRef.current;
|
|
2456
|
+
const workflowToolGetters = {
|
|
2457
|
+
serverUrl: getServerUrl,
|
|
2458
|
+
websiteId: getWebsiteId,
|
|
2459
|
+
toursApiBase: getToursApiBase,
|
|
2460
|
+
userProfile: getUserProfile
|
|
2461
|
+
};
|
|
2098
2462
|
registerAction(BUILTIN_SCREENSHOT_ACTION);
|
|
2099
2463
|
registerAction(createClickAction(getTagStore));
|
|
2100
2464
|
registerAction(createFillAction(getTagStore));
|
|
2101
2465
|
registerAction(createWaitAction(getTagStore));
|
|
2102
2466
|
registerAction(createSearchDocsAction(getServerUrl, getWebsiteId));
|
|
2103
2467
|
registerAction(createSearchTaggedElementsAction(getTagStore));
|
|
2468
|
+
registerAction(createSearchWorkflowsAction(workflowToolGetters));
|
|
2469
|
+
registerAction(createViewWorkflowAction(workflowToolGetters));
|
|
2470
|
+
registerAction(createStartWorkflowAction(workflowToolGetters));
|
|
2104
2471
|
return () => {
|
|
2105
2472
|
unregisterAction(BUILTIN_SCREENSHOT_ACTION.id);
|
|
2106
2473
|
unregisterAction(BUILTIN_ACTION_IDS.click);
|
|
@@ -2108,6 +2475,9 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
|
|
|
2108
2475
|
unregisterAction(BUILTIN_ACTION_IDS.wait);
|
|
2109
2476
|
unregisterAction(BUILTIN_ACTION_IDS.searchDocs);
|
|
2110
2477
|
unregisterAction(BUILTIN_ACTION_IDS.searchTaggedElements);
|
|
2478
|
+
unregisterAction(BUILTIN_ACTION_IDS.searchWorkflows);
|
|
2479
|
+
unregisterAction(BUILTIN_ACTION_IDS.viewWorkflow);
|
|
2480
|
+
unregisterAction(BUILTIN_ACTION_IDS.startWorkflow);
|
|
2111
2481
|
registeredRef.current = false;
|
|
2112
2482
|
};
|
|
2113
2483
|
}, [registerAction, unregisterAction]);
|
|
@@ -2362,120 +2732,6 @@ function readPreviewSessionSuppression() {
|
|
|
2362
2732
|
}
|
|
2363
2733
|
}
|
|
2364
2734
|
|
|
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
|
-
return trigger ?? "first_visit";
|
|
2461
|
-
}
|
|
2462
|
-
function getStartPolicy(tour) {
|
|
2463
|
-
return tour.startPolicy ?? (normalizeTrigger(tour.trigger) === "manual" ? "immediate_start" : "prompt_only");
|
|
2464
|
-
}
|
|
2465
|
-
function getNotificationType(tour) {
|
|
2466
|
-
return tour.notificationType ?? "bubble_card";
|
|
2467
|
-
}
|
|
2468
|
-
function isTourEligible(tour, userProfile) {
|
|
2469
|
-
switch (normalizeTrigger(tour.trigger)) {
|
|
2470
|
-
case "first_visit":
|
|
2471
|
-
return !!userProfile.isNewUser;
|
|
2472
|
-
case "manual":
|
|
2473
|
-
return false;
|
|
2474
|
-
default:
|
|
2475
|
-
return false;
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
2735
|
// src/hooks/useTourPlayback.ts
|
|
2480
2736
|
import { useState as useState7, useRef as useRef8, useCallback as useCallback7, useEffect as useEffect11, useContext as useContext4 } from "react";
|
|
2481
2737
|
|
|
@@ -7985,6 +8241,58 @@ function buildTranscriptPreviewLines(transcript, options = {}) {
|
|
|
7985
8241
|
return lines.slice(-maxLines);
|
|
7986
8242
|
}
|
|
7987
8243
|
|
|
8244
|
+
// src/utils/pendingPromptUi.ts
|
|
8245
|
+
function isPendingReviewPrompt(pendingPrompt) {
|
|
8246
|
+
return Boolean(pendingPrompt?.options?.reviewMode);
|
|
8247
|
+
}
|
|
8248
|
+
function shouldRenderPendingPromptInline({
|
|
8249
|
+
pendingPrompt,
|
|
8250
|
+
isPlaybackActive,
|
|
8251
|
+
recordingMode
|
|
8252
|
+
}) {
|
|
8253
|
+
return Boolean(
|
|
8254
|
+
pendingPrompt && !isPlaybackActive && !recordingMode && !isPendingReviewPrompt(pendingPrompt)
|
|
8255
|
+
);
|
|
8256
|
+
}
|
|
8257
|
+
function shouldRenderPendingPromptModal({
|
|
8258
|
+
pendingPrompt,
|
|
8259
|
+
isPlaybackActive,
|
|
8260
|
+
recordingMode,
|
|
8261
|
+
pendingNotificationType
|
|
8262
|
+
}) {
|
|
8263
|
+
return Boolean(
|
|
8264
|
+
pendingPrompt && !isPlaybackActive && !recordingMode && isPendingReviewPrompt(pendingPrompt) && pendingNotificationType === "modal"
|
|
8265
|
+
);
|
|
8266
|
+
}
|
|
8267
|
+
function shouldAutoExpandForPendingPrompt({
|
|
8268
|
+
pendingPrompt,
|
|
8269
|
+
isPlaybackActive,
|
|
8270
|
+
recordingMode,
|
|
8271
|
+
pendingNotificationType
|
|
8272
|
+
}) {
|
|
8273
|
+
if (!pendingPrompt || isPlaybackActive || recordingMode) return false;
|
|
8274
|
+
return pendingNotificationType === "bubble_card" || shouldRenderPendingPromptInline({
|
|
8275
|
+
pendingPrompt,
|
|
8276
|
+
isPlaybackActive,
|
|
8277
|
+
recordingMode
|
|
8278
|
+
});
|
|
8279
|
+
}
|
|
8280
|
+
function getPendingPromptTitle(pendingPrompt) {
|
|
8281
|
+
return `I found a ${pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"} called "${pendingPrompt.tour.name}". Would you like to start it now?`;
|
|
8282
|
+
}
|
|
8283
|
+
function getPendingPromptReason(pendingPrompt) {
|
|
8284
|
+
if (pendingPrompt.tour.trigger === "first_visit") {
|
|
8285
|
+
return "This was surfaced because the current user matches the configured first-visit trigger.";
|
|
8286
|
+
}
|
|
8287
|
+
if (pendingPrompt.tour.trigger === "return_visit") {
|
|
8288
|
+
return "This was surfaced because the current user matches the configured return-visit trigger.";
|
|
8289
|
+
}
|
|
8290
|
+
if (pendingPrompt.tour.featureKey) {
|
|
8291
|
+
return `This was surfaced because the "${pendingPrompt.tour.featureKey}" trigger condition matched.`;
|
|
8292
|
+
}
|
|
8293
|
+
return "This was surfaced because the configured trigger condition matched.";
|
|
8294
|
+
}
|
|
8295
|
+
|
|
7988
8296
|
// src/utils/floatingLiveTranscript.ts
|
|
7989
8297
|
var floatingTranscriptEl = null;
|
|
7990
8298
|
var liveTranscriptSuppressed = false;
|
|
@@ -8338,6 +8646,7 @@ function Tooltip({ children, title }) {
|
|
|
8338
8646
|
);
|
|
8339
8647
|
}
|
|
8340
8648
|
var BUBBLE_EXPANDED_STORAGE_KEY = "modelnex-chat-bubble-expanded";
|
|
8649
|
+
var BUBBLE_DOCKED_STORAGE_KEY = "modelnex-chat-bubble-docked";
|
|
8341
8650
|
var TOUR_MINIMIZED_STORAGE_KEY = "modelnex-tour-bubble-minimized";
|
|
8342
8651
|
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: [
|
|
8343
8652
|
/* @__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" }),
|
|
@@ -8351,6 +8660,18 @@ var CloseIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "var(--modelnex-bubb
|
|
|
8351
8660
|
/* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
8352
8661
|
] });
|
|
8353
8662
|
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" }) });
|
|
8663
|
+
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: [
|
|
8664
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 3v10" }),
|
|
8665
|
+
/* @__PURE__ */ jsx4("path", { d: "m8 9 4 4 4-4" }),
|
|
8666
|
+
/* @__PURE__ */ jsx4("path", { d: "M4 17h16" }),
|
|
8667
|
+
/* @__PURE__ */ jsx4("path", { d: "M6 21h12" })
|
|
8668
|
+
] });
|
|
8669
|
+
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: [
|
|
8670
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 21V11" }),
|
|
8671
|
+
/* @__PURE__ */ jsx4("path", { d: "m8 15 4-4 4 4" }),
|
|
8672
|
+
/* @__PURE__ */ jsx4("path", { d: "M4 7h16" }),
|
|
8673
|
+
/* @__PURE__ */ jsx4("path", { d: "M6 3h12" })
|
|
8674
|
+
] });
|
|
8354
8675
|
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: [
|
|
8355
8676
|
/* @__PURE__ */ jsx4("path", { d: "M3 6h18" }),
|
|
8356
8677
|
/* @__PURE__ */ jsx4("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
@@ -8571,6 +8892,7 @@ function ModelNexChatBubble({
|
|
|
8571
8892
|
const ctx = useContext5(ModelNexContext);
|
|
8572
8893
|
const [hydrated, setHydrated] = useState13(false);
|
|
8573
8894
|
const [expanded, setExpanded] = useState13(false);
|
|
8895
|
+
const [docked, setDocked] = useState13(false);
|
|
8574
8896
|
const [input, setInput] = useState13("");
|
|
8575
8897
|
const messages = ctx?.chatMessages ?? [];
|
|
8576
8898
|
const setMessages = ctx?.setChatMessages ?? (() => {
|
|
@@ -8625,6 +8947,11 @@ function ModelNexChatBubble({
|
|
|
8625
8947
|
const activePlayback = playbackController.playback;
|
|
8626
8948
|
const activeExperienceType = playbackController.activeExperienceType;
|
|
8627
8949
|
const startingExperienceType = playbackController.startingExperienceType;
|
|
8950
|
+
useEffect17(() => {
|
|
8951
|
+
return registerExperienceToolBridge({
|
|
8952
|
+
startExperience: playbackController.startExperience
|
|
8953
|
+
});
|
|
8954
|
+
}, [playbackController.startExperience]);
|
|
8628
8955
|
const createPlaybackView = useCallback12((experienceType) => {
|
|
8629
8956
|
const isActiveExperience = activePlayback.isActive && activeExperienceType === experienceType;
|
|
8630
8957
|
const pendingTour = playbackController.pendingPrompt?.experienceType === experienceType ? playbackController.pendingPrompt.tour : null;
|
|
@@ -8704,13 +9031,28 @@ function ModelNexChatBubble({
|
|
|
8704
9031
|
}
|
|
8705
9032
|
}, [devMode, handleAutoTag, ctx?.extractedElements.length, window.location.pathname]);
|
|
8706
9033
|
const onboardingReviewToggle = getReviewModeToggleConfig(onboardingPlayback.playbackState);
|
|
9034
|
+
const pendingPrompt = playbackController.pendingPrompt;
|
|
8707
9035
|
const pendingNotificationType = (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.notificationType ?? "bubble_card";
|
|
9036
|
+
const isPlaybackActive = tourPlayback.isActive || onboardingPlayback.isActive;
|
|
9037
|
+
const renderPendingPromptInline = shouldRenderPendingPromptInline({
|
|
9038
|
+
pendingPrompt,
|
|
9039
|
+
isPlaybackActive,
|
|
9040
|
+
recordingMode
|
|
9041
|
+
});
|
|
9042
|
+
const renderPendingPromptModal = shouldRenderPendingPromptModal({
|
|
9043
|
+
pendingPrompt,
|
|
9044
|
+
isPlaybackActive,
|
|
9045
|
+
recordingMode,
|
|
9046
|
+
pendingNotificationType
|
|
9047
|
+
});
|
|
8708
9048
|
useEffect17(() => {
|
|
8709
9049
|
setHydrated(true);
|
|
8710
9050
|
try {
|
|
8711
9051
|
setExpanded(sessionStorage.getItem(BUBBLE_EXPANDED_STORAGE_KEY) === "true");
|
|
9052
|
+
setDocked(sessionStorage.getItem(BUBBLE_DOCKED_STORAGE_KEY) === "true");
|
|
8712
9053
|
} catch {
|
|
8713
9054
|
setExpanded(false);
|
|
9055
|
+
setDocked(false);
|
|
8714
9056
|
}
|
|
8715
9057
|
}, []);
|
|
8716
9058
|
const sttActiveRef = useRef13(false);
|
|
@@ -8770,11 +9112,23 @@ function ModelNexChatBubble({
|
|
|
8770
9112
|
} catch {
|
|
8771
9113
|
}
|
|
8772
9114
|
}, [tourPlayback.isActive, onboardingPlayback.isActive]);
|
|
9115
|
+
const setDockedState = useCallback12((next) => {
|
|
9116
|
+
setDocked(next);
|
|
9117
|
+
try {
|
|
9118
|
+
sessionStorage.setItem(BUBBLE_DOCKED_STORAGE_KEY, String(next));
|
|
9119
|
+
} catch {
|
|
9120
|
+
}
|
|
9121
|
+
}, []);
|
|
8773
9122
|
useEffect17(() => {
|
|
8774
|
-
if ((
|
|
9123
|
+
if (shouldAutoExpandForPendingPrompt({
|
|
9124
|
+
pendingPrompt,
|
|
9125
|
+
isPlaybackActive,
|
|
9126
|
+
recordingMode,
|
|
9127
|
+
pendingNotificationType
|
|
9128
|
+
})) {
|
|
8775
9129
|
setExpandedState(true);
|
|
8776
9130
|
}
|
|
8777
|
-
}, [
|
|
9131
|
+
}, [isPlaybackActive, pendingNotificationType, pendingPrompt, recordingMode, setExpandedState]);
|
|
8778
9132
|
const preferredListeningExperienceRef = useRef13(null);
|
|
8779
9133
|
const playbackVoiceRoutingRef = useRef13({
|
|
8780
9134
|
activeExperienceType,
|
|
@@ -8875,13 +9229,13 @@ function ModelNexChatBubble({
|
|
|
8875
9229
|
updateTourSttError
|
|
8876
9230
|
]);
|
|
8877
9231
|
useEffect17(() => {
|
|
8878
|
-
const
|
|
9232
|
+
const isPlaybackActive2 = isTourListeningSessionActive({
|
|
8879
9233
|
isTourActive: tourPlayback.isActive,
|
|
8880
9234
|
isOnboardingActive: onboardingPlayback.isActive,
|
|
8881
9235
|
startingExperienceType
|
|
8882
9236
|
});
|
|
8883
|
-
const becameActive =
|
|
8884
|
-
previousTourActiveRef.current =
|
|
9237
|
+
const becameActive = isPlaybackActive2 && !previousTourActiveRef.current;
|
|
9238
|
+
previousTourActiveRef.current = isPlaybackActive2;
|
|
8885
9239
|
if (becameActive) {
|
|
8886
9240
|
try {
|
|
8887
9241
|
sessionStorage.setItem(TOUR_MINIMIZED_STORAGE_KEY, "true");
|
|
@@ -8890,7 +9244,7 @@ function ModelNexChatBubble({
|
|
|
8890
9244
|
setExpandedState(false, { rememberTourMinimize: true });
|
|
8891
9245
|
updateTourSttError(null);
|
|
8892
9246
|
}
|
|
8893
|
-
if (!
|
|
9247
|
+
if (!isPlaybackActive2) {
|
|
8894
9248
|
try {
|
|
8895
9249
|
sessionStorage.removeItem(TOUR_MINIMIZED_STORAGE_KEY);
|
|
8896
9250
|
} catch {
|
|
@@ -8917,12 +9271,12 @@ function ModelNexChatBubble({
|
|
|
8917
9271
|
}
|
|
8918
9272
|
}, [recordingMode, setExpandedState]);
|
|
8919
9273
|
useEffect17(() => {
|
|
8920
|
-
const
|
|
9274
|
+
const isPlaybackActive2 = isTourListeningSessionActive({
|
|
8921
9275
|
isTourActive: tourPlayback.isActive,
|
|
8922
9276
|
isOnboardingActive: onboardingPlayback.isActive,
|
|
8923
9277
|
startingExperienceType
|
|
8924
9278
|
});
|
|
8925
|
-
if (!
|
|
9279
|
+
if (!isPlaybackActive2) {
|
|
8926
9280
|
updateTourListenReady(false);
|
|
8927
9281
|
setTourLiveTranscript("");
|
|
8928
9282
|
hideFloatingLiveTranscript();
|
|
@@ -8931,12 +9285,12 @@ function ModelNexChatBubble({
|
|
|
8931
9285
|
updateTourListenReady(Boolean(voice.isListening && sttActiveRef.current));
|
|
8932
9286
|
}, [tourPlayback.isActive, onboardingPlayback.isActive, voice.isListening, startingExperienceType, updateTourListenReady]);
|
|
8933
9287
|
useEffect17(() => {
|
|
8934
|
-
const
|
|
9288
|
+
const isPlaybackActive2 = isTourListeningSessionActive({
|
|
8935
9289
|
isTourActive: tourPlayback.isActive,
|
|
8936
9290
|
isOnboardingActive: onboardingPlayback.isActive,
|
|
8937
9291
|
startingExperienceType
|
|
8938
9292
|
});
|
|
8939
|
-
if (!
|
|
9293
|
+
if (!isPlaybackActive2 && sttActiveRef.current) {
|
|
8940
9294
|
sttActiveRef.current = false;
|
|
8941
9295
|
updateTourListenReady(false);
|
|
8942
9296
|
updateTourSttError(null);
|
|
@@ -8946,12 +9300,12 @@ function ModelNexChatBubble({
|
|
|
8946
9300
|
}
|
|
8947
9301
|
}, [tourPlayback.isActive, onboardingPlayback.isActive, voice, startingExperienceType, updateTourListenReady, updateTourSttError]);
|
|
8948
9302
|
useEffect17(() => {
|
|
8949
|
-
const
|
|
9303
|
+
const isPlaybackActive2 = isTourListeningSessionActive({
|
|
8950
9304
|
isTourActive: tourPlayback.isActive,
|
|
8951
9305
|
isOnboardingActive: onboardingPlayback.isActive,
|
|
8952
9306
|
startingExperienceType
|
|
8953
9307
|
});
|
|
8954
|
-
if (!
|
|
9308
|
+
if (!isPlaybackActive2 || tourListenReady || !voice.sttSupported || tourSttError === "not-allowed") {
|
|
8955
9309
|
return;
|
|
8956
9310
|
}
|
|
8957
9311
|
const enableTourListeningFromGesture = (event) => {
|
|
@@ -9126,11 +9480,13 @@ function ModelNexChatBubble({
|
|
|
9126
9480
|
fontFamily: "var(--modelnex-font)",
|
|
9127
9481
|
...themeStyles
|
|
9128
9482
|
};
|
|
9483
|
+
const desktopPanelHeight = docked ? "min(calc(100vh - 48px), calc(var(--modelnex-panel-max-height, 600px) + 180px))" : "var(--modelnex-panel-max-height, 600px)";
|
|
9484
|
+
const desktopPanelMaxHeight = docked ? "calc(100vh - 48px)" : "calc(100vh - 120px)";
|
|
9129
9485
|
const panelStyle = {
|
|
9130
9486
|
width: isMobile ? "100%" : "var(--modelnex-panel-width, 380px)",
|
|
9131
9487
|
maxWidth: "calc(100vw - 32px)",
|
|
9132
|
-
height: isMobile ? "100%" :
|
|
9133
|
-
maxHeight: "
|
|
9488
|
+
height: isMobile ? "100%" : desktopPanelHeight,
|
|
9489
|
+
maxHeight: isMobile ? "100%" : desktopPanelMaxHeight,
|
|
9134
9490
|
borderRadius: isMobile ? "0" : "var(--modelnex-radius-panel, 20px)",
|
|
9135
9491
|
border: isMobile ? "none" : "1px solid var(--modelnex-border, #e4e4e7)",
|
|
9136
9492
|
background: "var(--modelnex-bg, #ffffff)",
|
|
@@ -9158,8 +9514,8 @@ function ModelNexChatBubble({
|
|
|
9158
9514
|
return createPortal(
|
|
9159
9515
|
/* @__PURE__ */ jsxs3("div", { style: containerStyle, className, "data-modelnex-internal": "true", onMouseDown: stopEventPropagation, onClick: stopEventPropagation, children: [
|
|
9160
9516
|
/* @__PURE__ */ jsx4("style", { children: GLOBAL_STYLES }),
|
|
9161
|
-
|
|
9162
|
-
const pt =
|
|
9517
|
+
renderPendingPromptModal && (() => {
|
|
9518
|
+
const pt = pendingPrompt?.tour;
|
|
9163
9519
|
const mc = pt?.presentation?.modalConfig || {};
|
|
9164
9520
|
return /* @__PURE__ */ jsx4(
|
|
9165
9521
|
"div",
|
|
@@ -9221,8 +9577,8 @@ function ModelNexChatBubble({
|
|
|
9221
9577
|
"button",
|
|
9222
9578
|
{
|
|
9223
9579
|
onClick: () => {
|
|
9224
|
-
const preferredExperience =
|
|
9225
|
-
if (
|
|
9580
|
+
const preferredExperience = pendingPrompt?.experienceType === "onboarding" ? "onboarding" : "tour";
|
|
9581
|
+
if (preferredExperience === "onboarding") {
|
|
9226
9582
|
onboardingPlayback.acceptPendingTour();
|
|
9227
9583
|
} else {
|
|
9228
9584
|
tourPlayback.acceptPendingTour();
|
|
@@ -9282,6 +9638,31 @@ function ModelNexChatBubble({
|
|
|
9282
9638
|
] })
|
|
9283
9639
|
] }) }),
|
|
9284
9640
|
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
|
|
9641
|
+
!isMobile && /* @__PURE__ */ jsx4(Tooltip, { title: docked ? "Undock" : "Dock", children: /* @__PURE__ */ jsxs3(
|
|
9642
|
+
"button",
|
|
9643
|
+
{
|
|
9644
|
+
onClick: () => setDockedState(!docked),
|
|
9645
|
+
style: {
|
|
9646
|
+
padding: "8px 10px",
|
|
9647
|
+
borderRadius: "10px",
|
|
9648
|
+
border: "none",
|
|
9649
|
+
background: docked ? "rgba(79,70,229,0.08)" : "transparent",
|
|
9650
|
+
cursor: "pointer",
|
|
9651
|
+
color: docked ? "var(--modelnex-accent, #4f46e5)" : "#71717a",
|
|
9652
|
+
transition: "all 0.2s",
|
|
9653
|
+
display: "flex",
|
|
9654
|
+
alignItems: "center",
|
|
9655
|
+
gap: "6px",
|
|
9656
|
+
fontSize: "12px",
|
|
9657
|
+
fontWeight: 700
|
|
9658
|
+
},
|
|
9659
|
+
"aria-label": docked ? "Undock chat bubble" : "Dock chat bubble",
|
|
9660
|
+
children: [
|
|
9661
|
+
docked ? /* @__PURE__ */ jsx4(UndockIcon, {}) : /* @__PURE__ */ jsx4(DockIcon, {}),
|
|
9662
|
+
/* @__PURE__ */ jsx4("span", { children: docked ? "Undock" : "Dock" })
|
|
9663
|
+
]
|
|
9664
|
+
}
|
|
9665
|
+
) }),
|
|
9285
9666
|
(tourPlayback.isActive || onboardingPlayback.isActive || voice.isSpeaking) && /* @__PURE__ */ jsx4(Tooltip, { title: voice.isMuted ? "Unmute" : "Mute", children: /* @__PURE__ */ jsx4(
|
|
9286
9667
|
"button",
|
|
9287
9668
|
{
|
|
@@ -9385,24 +9766,28 @@ function ModelNexChatBubble({
|
|
|
9385
9766
|
children: "Microphone access is blocked. Use the input box below, or allow microphone permission and tap the mic again."
|
|
9386
9767
|
}
|
|
9387
9768
|
),
|
|
9388
|
-
|
|
9769
|
+
renderPendingPromptInline && pendingPrompt && /* @__PURE__ */ jsx4("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsxs3(
|
|
9389
9770
|
"div",
|
|
9390
9771
|
{
|
|
9391
9772
|
style: {
|
|
9392
|
-
|
|
9393
|
-
padding: "
|
|
9394
|
-
borderRadius: "
|
|
9395
|
-
|
|
9396
|
-
|
|
9773
|
+
maxWidth: "85%",
|
|
9774
|
+
padding: "12px 16px",
|
|
9775
|
+
borderRadius: "var(--modelnex-radius-inner, 16px)",
|
|
9776
|
+
borderBottomLeftRadius: 4,
|
|
9777
|
+
fontSize: "14px",
|
|
9778
|
+
lineHeight: 1.55,
|
|
9779
|
+
background: "var(--modelnex-bg, #fff)",
|
|
9780
|
+
color: "var(--modelnex-fg, #18181b)",
|
|
9781
|
+
border: "1px solid var(--modelnex-border, #e4e4e7)",
|
|
9782
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.02)"
|
|
9397
9783
|
},
|
|
9398
9784
|
children: [
|
|
9399
|
-
/* @__PURE__ */ jsx4("div", { style: {
|
|
9400
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "
|
|
9401
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: "12px", color: "#52525b", lineHeight: 1.45, marginBottom: "12px" }, children: (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.trigger === "first_visit" ? "Start a short walkthrough for this user now?" : (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.featureKey ? `A walkthrough is available for ${(onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.featureKey}.` : "A walkthrough is available for this user." }),
|
|
9785
|
+
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 500, marginBottom: "8px" }, children: getPendingPromptTitle(pendingPrompt) }),
|
|
9786
|
+
/* @__PURE__ */ jsx4("div", { style: { fontSize: "12px", color: "#52525b", marginBottom: "12px" }, children: getPendingPromptReason(pendingPrompt) }),
|
|
9402
9787
|
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
9403
9788
|
/* @__PURE__ */ jsxs3("button", { className: "btn btn-primary btn-sm", onClick: () => {
|
|
9404
|
-
const preferredExperience =
|
|
9405
|
-
if (
|
|
9789
|
+
const preferredExperience = pendingPrompt.experienceType === "onboarding" ? "onboarding" : "tour";
|
|
9790
|
+
if (preferredExperience === "onboarding") {
|
|
9406
9791
|
onboardingPlayback.acceptPendingTour();
|
|
9407
9792
|
} else {
|
|
9408
9793
|
tourPlayback.acceptPendingTour();
|
|
@@ -9410,13 +9795,13 @@ function ModelNexChatBubble({
|
|
|
9410
9795
|
startTourListening(preferredExperience);
|
|
9411
9796
|
}, children: [
|
|
9412
9797
|
"Start ",
|
|
9413
|
-
|
|
9798
|
+
pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"
|
|
9414
9799
|
] }),
|
|
9415
|
-
/* @__PURE__ */ jsx4("button", { className: "btn btn-secondary btn-sm", onClick:
|
|
9800
|
+
/* @__PURE__ */ jsx4("button", { className: "btn btn-secondary btn-sm", onClick: pendingPrompt.experienceType === "onboarding" ? onboardingPlayback.dismissPendingTour : tourPlayback.dismissPendingTour, children: "Not now" })
|
|
9416
9801
|
] })
|
|
9417
9802
|
]
|
|
9418
9803
|
}
|
|
9419
|
-
),
|
|
9804
|
+
) }),
|
|
9420
9805
|
tourPlayback.isActive && tourPlayback.isReviewMode && /* @__PURE__ */ jsxs3(
|
|
9421
9806
|
"div",
|
|
9422
9807
|
{
|
|
@@ -9995,7 +10380,7 @@ function ModelNexChatBubble({
|
|
|
9995
10380
|
)
|
|
9996
10381
|
] })
|
|
9997
10382
|
] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
9998
|
-
messages.length === 0 && !loading && /* @__PURE__ */ jsx4(
|
|
10383
|
+
messages.length === 0 && !loading && !renderPendingPromptInline && /* @__PURE__ */ jsx4(
|
|
9999
10384
|
"div",
|
|
10000
10385
|
{
|
|
10001
10386
|
style: {
|
|
@@ -11068,7 +11453,7 @@ var ModelNexProvider = ({
|
|
|
11068
11453
|
}, []);
|
|
11069
11454
|
const extractedElements = useAutoExtract();
|
|
11070
11455
|
const tagStore = useTagStore({ serverUrl, websiteId });
|
|
11071
|
-
useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId);
|
|
11456
|
+
useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile);
|
|
11072
11457
|
const CHAT_STORAGE_KEY = "modelnex-chat-messages";
|
|
11073
11458
|
const [chatMessages, setChatMessagesRaw] = useState15([]);
|
|
11074
11459
|
useEffect19(() => {
|