@modelnex/sdk 0.5.27 → 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.js +430 -134
- package/dist/index.mjs +430 -134
- 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,135 +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
|
-
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
2735
|
// src/hooks/useTourPlayback.ts
|
|
2495
2736
|
import { useState as useState7, useRef as useRef8, useCallback as useCallback7, useEffect as useEffect11, useContext as useContext4 } from "react";
|
|
2496
2737
|
|
|
@@ -8405,6 +8646,7 @@ function Tooltip({ children, title }) {
|
|
|
8405
8646
|
);
|
|
8406
8647
|
}
|
|
8407
8648
|
var BUBBLE_EXPANDED_STORAGE_KEY = "modelnex-chat-bubble-expanded";
|
|
8649
|
+
var BUBBLE_DOCKED_STORAGE_KEY = "modelnex-chat-bubble-docked";
|
|
8408
8650
|
var TOUR_MINIMIZED_STORAGE_KEY = "modelnex-tour-bubble-minimized";
|
|
8409
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: [
|
|
8410
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" }),
|
|
@@ -8418,6 +8660,18 @@ var CloseIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "var(--modelnex-bubb
|
|
|
8418
8660
|
/* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
8419
8661
|
] });
|
|
8420
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
|
+
] });
|
|
8421
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: [
|
|
8422
8676
|
/* @__PURE__ */ jsx4("path", { d: "M3 6h18" }),
|
|
8423
8677
|
/* @__PURE__ */ jsx4("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
@@ -8638,6 +8892,7 @@ function ModelNexChatBubble({
|
|
|
8638
8892
|
const ctx = useContext5(ModelNexContext);
|
|
8639
8893
|
const [hydrated, setHydrated] = useState13(false);
|
|
8640
8894
|
const [expanded, setExpanded] = useState13(false);
|
|
8895
|
+
const [docked, setDocked] = useState13(false);
|
|
8641
8896
|
const [input, setInput] = useState13("");
|
|
8642
8897
|
const messages = ctx?.chatMessages ?? [];
|
|
8643
8898
|
const setMessages = ctx?.setChatMessages ?? (() => {
|
|
@@ -8692,6 +8947,11 @@ function ModelNexChatBubble({
|
|
|
8692
8947
|
const activePlayback = playbackController.playback;
|
|
8693
8948
|
const activeExperienceType = playbackController.activeExperienceType;
|
|
8694
8949
|
const startingExperienceType = playbackController.startingExperienceType;
|
|
8950
|
+
useEffect17(() => {
|
|
8951
|
+
return registerExperienceToolBridge({
|
|
8952
|
+
startExperience: playbackController.startExperience
|
|
8953
|
+
});
|
|
8954
|
+
}, [playbackController.startExperience]);
|
|
8695
8955
|
const createPlaybackView = useCallback12((experienceType) => {
|
|
8696
8956
|
const isActiveExperience = activePlayback.isActive && activeExperienceType === experienceType;
|
|
8697
8957
|
const pendingTour = playbackController.pendingPrompt?.experienceType === experienceType ? playbackController.pendingPrompt.tour : null;
|
|
@@ -8789,8 +9049,10 @@ function ModelNexChatBubble({
|
|
|
8789
9049
|
setHydrated(true);
|
|
8790
9050
|
try {
|
|
8791
9051
|
setExpanded(sessionStorage.getItem(BUBBLE_EXPANDED_STORAGE_KEY) === "true");
|
|
9052
|
+
setDocked(sessionStorage.getItem(BUBBLE_DOCKED_STORAGE_KEY) === "true");
|
|
8792
9053
|
} catch {
|
|
8793
9054
|
setExpanded(false);
|
|
9055
|
+
setDocked(false);
|
|
8794
9056
|
}
|
|
8795
9057
|
}, []);
|
|
8796
9058
|
const sttActiveRef = useRef13(false);
|
|
@@ -8850,6 +9112,13 @@ function ModelNexChatBubble({
|
|
|
8850
9112
|
} catch {
|
|
8851
9113
|
}
|
|
8852
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
|
+
}, []);
|
|
8853
9122
|
useEffect17(() => {
|
|
8854
9123
|
if (shouldAutoExpandForPendingPrompt({
|
|
8855
9124
|
pendingPrompt,
|
|
@@ -9211,11 +9480,13 @@ function ModelNexChatBubble({
|
|
|
9211
9480
|
fontFamily: "var(--modelnex-font)",
|
|
9212
9481
|
...themeStyles
|
|
9213
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)";
|
|
9214
9485
|
const panelStyle = {
|
|
9215
9486
|
width: isMobile ? "100%" : "var(--modelnex-panel-width, 380px)",
|
|
9216
9487
|
maxWidth: "calc(100vw - 32px)",
|
|
9217
|
-
height: isMobile ? "100%" :
|
|
9218
|
-
maxHeight: "
|
|
9488
|
+
height: isMobile ? "100%" : desktopPanelHeight,
|
|
9489
|
+
maxHeight: isMobile ? "100%" : desktopPanelMaxHeight,
|
|
9219
9490
|
borderRadius: isMobile ? "0" : "var(--modelnex-radius-panel, 20px)",
|
|
9220
9491
|
border: isMobile ? "none" : "1px solid var(--modelnex-border, #e4e4e7)",
|
|
9221
9492
|
background: "var(--modelnex-bg, #ffffff)",
|
|
@@ -9367,6 +9638,31 @@ function ModelNexChatBubble({
|
|
|
9367
9638
|
] })
|
|
9368
9639
|
] }) }),
|
|
9369
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
|
+
) }),
|
|
9370
9666
|
(tourPlayback.isActive || onboardingPlayback.isActive || voice.isSpeaking) && /* @__PURE__ */ jsx4(Tooltip, { title: voice.isMuted ? "Unmute" : "Mute", children: /* @__PURE__ */ jsx4(
|
|
9371
9667
|
"button",
|
|
9372
9668
|
{
|
|
@@ -11157,7 +11453,7 @@ var ModelNexProvider = ({
|
|
|
11157
11453
|
}, []);
|
|
11158
11454
|
const extractedElements = useAutoExtract();
|
|
11159
11455
|
const tagStore = useTagStore({ serverUrl, websiteId });
|
|
11160
|
-
useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId);
|
|
11456
|
+
useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile);
|
|
11161
11457
|
const CHAT_STORAGE_KEY = "modelnex-chat-messages";
|
|
11162
11458
|
const [chatMessages, setChatMessagesRaw] = useState15([]);
|
|
11163
11459
|
useEffect19(() => {
|