@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.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 ((onboardingPlayback.pendingTour || tourPlayback.pendingTour) && !recordingMode && pendingNotificationType === "bubble_card") {
9123
+ if (shouldAutoExpandForPendingPrompt({
9124
+ pendingPrompt,
9125
+ isPlaybackActive,
9126
+ recordingMode,
9127
+ pendingNotificationType
9128
+ })) {
8775
9129
  setExpandedState(true);
8776
9130
  }
8777
- }, [onboardingPlayback.pendingTour, tourPlayback.pendingTour, recordingMode, pendingNotificationType, setExpandedState]);
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 isPlaybackActive = isTourListeningSessionActive({
9232
+ const isPlaybackActive2 = isTourListeningSessionActive({
8879
9233
  isTourActive: tourPlayback.isActive,
8880
9234
  isOnboardingActive: onboardingPlayback.isActive,
8881
9235
  startingExperienceType
8882
9236
  });
8883
- const becameActive = isPlaybackActive && !previousTourActiveRef.current;
8884
- previousTourActiveRef.current = isPlaybackActive;
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 (!isPlaybackActive) {
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 isPlaybackActive = isTourListeningSessionActive({
9274
+ const isPlaybackActive2 = isTourListeningSessionActive({
8921
9275
  isTourActive: tourPlayback.isActive,
8922
9276
  isOnboardingActive: onboardingPlayback.isActive,
8923
9277
  startingExperienceType
8924
9278
  });
8925
- if (!isPlaybackActive) {
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 isPlaybackActive = isTourListeningSessionActive({
9288
+ const isPlaybackActive2 = isTourListeningSessionActive({
8935
9289
  isTourActive: tourPlayback.isActive,
8936
9290
  isOnboardingActive: onboardingPlayback.isActive,
8937
9291
  startingExperienceType
8938
9292
  });
8939
- if (!isPlaybackActive && sttActiveRef.current) {
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 isPlaybackActive = isTourListeningSessionActive({
9303
+ const isPlaybackActive2 = isTourListeningSessionActive({
8950
9304
  isTourActive: tourPlayback.isActive,
8951
9305
  isOnboardingActive: onboardingPlayback.isActive,
8952
9306
  startingExperienceType
8953
9307
  });
8954
- if (!isPlaybackActive || tourListenReady || !voice.sttSupported || tourSttError === "not-allowed") {
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%" : "var(--modelnex-panel-max-height, 600px)",
9133
- maxHeight: "calc(100vh - 120px)",
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
- !(tourPlayback.isActive || onboardingPlayback.isActive) && (tourPlayback.pendingTour || onboardingPlayback.pendingTour) && pendingNotificationType === "modal" && !recordingMode && (() => {
9162
- const pt = onboardingPlayback.pendingTour || tourPlayback.pendingTour;
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 = onboardingPlayback.pendingTour ? "onboarding" : "tour";
9225
- if (onboardingPlayback.pendingTour) {
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
- !(tourPlayback.isActive || onboardingPlayback.isActive) && (tourPlayback.pendingTour || onboardingPlayback.pendingTour) && pendingNotificationType === "bubble_card" && /* @__PURE__ */ jsxs3(
9769
+ renderPendingPromptInline && pendingPrompt && /* @__PURE__ */ jsx4("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsxs3(
9389
9770
  "div",
9390
9771
  {
9391
9772
  style: {
9392
- margin: "12px",
9393
- padding: "14px",
9394
- borderRadius: "12px",
9395
- border: "1px solid rgba(59,130,246,0.18)",
9396
- background: "linear-gradient(135deg, rgba(59,130,246,0.08) 0%, rgba(59,130,246,0.03) 100%)"
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: { fontSize: "13px", fontWeight: 600, color: "var(--modelnex-fg, #18181b)", marginBottom: "6px" }, children: onboardingPlayback.pendingTour ? "Suggested workflow" : "Suggested tour" }),
9400
- /* @__PURE__ */ jsx4("div", { style: { fontSize: "14px", color: "var(--modelnex-fg, #27272a)", marginBottom: "6px" }, children: (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.name }),
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 = onboardingPlayback.pendingTour ? "onboarding" : "tour";
9405
- if (onboardingPlayback.pendingTour) {
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
- onboardingPlayback.pendingTour ? "workflow" : "tour"
9798
+ pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"
9414
9799
  ] }),
9415
- /* @__PURE__ */ jsx4("button", { className: "btn btn-secondary btn-sm", onClick: onboardingPlayback.pendingTour ? onboardingPlayback.dismissPendingTour : tourPlayback.dismissPendingTour, children: "Not now" })
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(() => {