@modelnex/sdk 0.5.26 → 0.5.27

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 CHANGED
@@ -133,7 +133,7 @@ interface TourStep {
133
133
  /** Optional onboarding-specific step metadata */
134
134
  onboarding?: OnboardingStepMetadata;
135
135
  }
136
- type TourTrigger = 'first_visit' | 'feature_launch' | 'feature_unlocked' | 'feature_unlock' | 'new_feature' | 'manual';
136
+ type TourTrigger = 'first_visit' | 'return_visit' | 'feature_launch' | 'feature_unlocked' | 'feature_unlock' | 'new_feature' | 'manual';
137
137
  type TourStartPolicy = 'immediate_start' | 'prompt_only' | 'manual_only';
138
138
  type TourNotificationType = 'bubble_card' | 'modal';
139
139
  interface TourVoiceConfig {
@@ -200,6 +200,12 @@ interface UserProfile {
200
200
  type: string;
201
201
  /** Whether this is a new user — triggers first_visit tours */
202
202
  isNewUser?: boolean;
203
+ /** Optional feature facts used for feature-triggered tours */
204
+ features?: string[];
205
+ /** Backward-compatible facts bag for feature-triggered tours */
206
+ tourFacts?: {
207
+ features?: string[];
208
+ };
203
209
  /** User ID for per-user completion state */
204
210
  userId?: string;
205
211
  }
package/dist/index.d.ts CHANGED
@@ -133,7 +133,7 @@ interface TourStep {
133
133
  /** Optional onboarding-specific step metadata */
134
134
  onboarding?: OnboardingStepMetadata;
135
135
  }
136
- type TourTrigger = 'first_visit' | 'feature_launch' | 'feature_unlocked' | 'feature_unlock' | 'new_feature' | 'manual';
136
+ type TourTrigger = 'first_visit' | 'return_visit' | 'feature_launch' | 'feature_unlocked' | 'feature_unlock' | 'new_feature' | 'manual';
137
137
  type TourStartPolicy = 'immediate_start' | 'prompt_only' | 'manual_only';
138
138
  type TourNotificationType = 'bubble_card' | 'modal';
139
139
  interface TourVoiceConfig {
@@ -200,6 +200,12 @@ interface UserProfile {
200
200
  type: string;
201
201
  /** Whether this is a new user — triggers first_visit tours */
202
202
  isNewUser?: boolean;
203
+ /** Optional feature facts used for feature-triggered tours */
204
+ features?: string[];
205
+ /** Backward-compatible facts bag for feature-triggered tours */
206
+ tourFacts?: {
207
+ features?: string[];
208
+ };
203
209
  /** User ID for per-user completion state */
204
210
  userId?: string;
205
211
  }
package/dist/index.js CHANGED
@@ -2667,6 +2667,9 @@ function normalizeTrigger(trigger) {
2667
2667
  if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
2668
2668
  return "feature_launch";
2669
2669
  }
2670
+ if (trigger === "return_visit") {
2671
+ return "return_visit";
2672
+ }
2670
2673
  return trigger ?? "first_visit";
2671
2674
  }
2672
2675
  function getStartPolicy(tour) {
@@ -2675,10 +2678,22 @@ function getStartPolicy(tour) {
2675
2678
  function getNotificationType(tour) {
2676
2679
  return tour.notificationType ?? "bubble_card";
2677
2680
  }
2681
+ function getUserProfileFeatures(userProfile) {
2682
+ const directFeatures = Array.isArray(userProfile.features) ? userProfile.features : [];
2683
+ const nestedFeatures = Array.isArray(userProfile.tourFacts?.features) ? userProfile.tourFacts.features : [];
2684
+ return [...new Set([...directFeatures, ...nestedFeatures].filter((value) => Boolean(value)))];
2685
+ }
2678
2686
  function isTourEligible(tour, userProfile) {
2679
2687
  switch (normalizeTrigger(tour.trigger)) {
2680
2688
  case "first_visit":
2681
2689
  return !!userProfile.isNewUser;
2690
+ case "return_visit":
2691
+ return userProfile.isNewUser === false;
2692
+ case "feature_launch": {
2693
+ const featureKey = tour.featureKey?.trim();
2694
+ if (!featureKey) return false;
2695
+ return getUserProfileFeatures(userProfile).includes(featureKey);
2696
+ }
2682
2697
  case "manual":
2683
2698
  return false;
2684
2699
  default:
@@ -8196,6 +8211,58 @@ function buildTranscriptPreviewLines(transcript, options = {}) {
8196
8211
  return lines.slice(-maxLines);
8197
8212
  }
8198
8213
 
8214
+ // src/utils/pendingPromptUi.ts
8215
+ function isPendingReviewPrompt(pendingPrompt) {
8216
+ return Boolean(pendingPrompt?.options?.reviewMode);
8217
+ }
8218
+ function shouldRenderPendingPromptInline({
8219
+ pendingPrompt,
8220
+ isPlaybackActive,
8221
+ recordingMode
8222
+ }) {
8223
+ return Boolean(
8224
+ pendingPrompt && !isPlaybackActive && !recordingMode && !isPendingReviewPrompt(pendingPrompt)
8225
+ );
8226
+ }
8227
+ function shouldRenderPendingPromptModal({
8228
+ pendingPrompt,
8229
+ isPlaybackActive,
8230
+ recordingMode,
8231
+ pendingNotificationType
8232
+ }) {
8233
+ return Boolean(
8234
+ pendingPrompt && !isPlaybackActive && !recordingMode && isPendingReviewPrompt(pendingPrompt) && pendingNotificationType === "modal"
8235
+ );
8236
+ }
8237
+ function shouldAutoExpandForPendingPrompt({
8238
+ pendingPrompt,
8239
+ isPlaybackActive,
8240
+ recordingMode,
8241
+ pendingNotificationType
8242
+ }) {
8243
+ if (!pendingPrompt || isPlaybackActive || recordingMode) return false;
8244
+ return pendingNotificationType === "bubble_card" || shouldRenderPendingPromptInline({
8245
+ pendingPrompt,
8246
+ isPlaybackActive,
8247
+ recordingMode
8248
+ });
8249
+ }
8250
+ function getPendingPromptTitle(pendingPrompt) {
8251
+ return `I found a ${pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"} called "${pendingPrompt.tour.name}". Would you like to start it now?`;
8252
+ }
8253
+ function getPendingPromptReason(pendingPrompt) {
8254
+ if (pendingPrompt.tour.trigger === "first_visit") {
8255
+ return "This was surfaced because the current user matches the configured first-visit trigger.";
8256
+ }
8257
+ if (pendingPrompt.tour.trigger === "return_visit") {
8258
+ return "This was surfaced because the current user matches the configured return-visit trigger.";
8259
+ }
8260
+ if (pendingPrompt.tour.featureKey) {
8261
+ return `This was surfaced because the "${pendingPrompt.tour.featureKey}" trigger condition matched.`;
8262
+ }
8263
+ return "This was surfaced because the configured trigger condition matched.";
8264
+ }
8265
+
8199
8266
  // src/utils/floatingLiveTranscript.ts
8200
8267
  var floatingTranscriptEl = null;
8201
8268
  var liveTranscriptSuppressed = false;
@@ -8915,7 +8982,20 @@ function ModelNexChatBubble({
8915
8982
  }
8916
8983
  }, [devMode, handleAutoTag, ctx?.extractedElements.length, window.location.pathname]);
8917
8984
  const onboardingReviewToggle = getReviewModeToggleConfig(onboardingPlayback.playbackState);
8985
+ const pendingPrompt = playbackController.pendingPrompt;
8918
8986
  const pendingNotificationType = (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.notificationType ?? "bubble_card";
8987
+ const isPlaybackActive = tourPlayback.isActive || onboardingPlayback.isActive;
8988
+ const renderPendingPromptInline = shouldRenderPendingPromptInline({
8989
+ pendingPrompt,
8990
+ isPlaybackActive,
8991
+ recordingMode
8992
+ });
8993
+ const renderPendingPromptModal = shouldRenderPendingPromptModal({
8994
+ pendingPrompt,
8995
+ isPlaybackActive,
8996
+ recordingMode,
8997
+ pendingNotificationType
8998
+ });
8919
8999
  (0, import_react18.useEffect)(() => {
8920
9000
  setHydrated(true);
8921
9001
  try {
@@ -8982,10 +9062,15 @@ function ModelNexChatBubble({
8982
9062
  }
8983
9063
  }, [tourPlayback.isActive, onboardingPlayback.isActive]);
8984
9064
  (0, import_react18.useEffect)(() => {
8985
- if ((onboardingPlayback.pendingTour || tourPlayback.pendingTour) && !recordingMode && pendingNotificationType === "bubble_card") {
9065
+ if (shouldAutoExpandForPendingPrompt({
9066
+ pendingPrompt,
9067
+ isPlaybackActive,
9068
+ recordingMode,
9069
+ pendingNotificationType
9070
+ })) {
8986
9071
  setExpandedState(true);
8987
9072
  }
8988
- }, [onboardingPlayback.pendingTour, tourPlayback.pendingTour, recordingMode, pendingNotificationType, setExpandedState]);
9073
+ }, [isPlaybackActive, pendingNotificationType, pendingPrompt, recordingMode, setExpandedState]);
8989
9074
  const preferredListeningExperienceRef = (0, import_react18.useRef)(null);
8990
9075
  const playbackVoiceRoutingRef = (0, import_react18.useRef)({
8991
9076
  activeExperienceType,
@@ -9086,13 +9171,13 @@ function ModelNexChatBubble({
9086
9171
  updateTourSttError
9087
9172
  ]);
9088
9173
  (0, import_react18.useEffect)(() => {
9089
- const isPlaybackActive = isTourListeningSessionActive({
9174
+ const isPlaybackActive2 = isTourListeningSessionActive({
9090
9175
  isTourActive: tourPlayback.isActive,
9091
9176
  isOnboardingActive: onboardingPlayback.isActive,
9092
9177
  startingExperienceType
9093
9178
  });
9094
- const becameActive = isPlaybackActive && !previousTourActiveRef.current;
9095
- previousTourActiveRef.current = isPlaybackActive;
9179
+ const becameActive = isPlaybackActive2 && !previousTourActiveRef.current;
9180
+ previousTourActiveRef.current = isPlaybackActive2;
9096
9181
  if (becameActive) {
9097
9182
  try {
9098
9183
  sessionStorage.setItem(TOUR_MINIMIZED_STORAGE_KEY, "true");
@@ -9101,7 +9186,7 @@ function ModelNexChatBubble({
9101
9186
  setExpandedState(false, { rememberTourMinimize: true });
9102
9187
  updateTourSttError(null);
9103
9188
  }
9104
- if (!isPlaybackActive) {
9189
+ if (!isPlaybackActive2) {
9105
9190
  try {
9106
9191
  sessionStorage.removeItem(TOUR_MINIMIZED_STORAGE_KEY);
9107
9192
  } catch {
@@ -9128,12 +9213,12 @@ function ModelNexChatBubble({
9128
9213
  }
9129
9214
  }, [recordingMode, setExpandedState]);
9130
9215
  (0, import_react18.useEffect)(() => {
9131
- const isPlaybackActive = isTourListeningSessionActive({
9216
+ const isPlaybackActive2 = isTourListeningSessionActive({
9132
9217
  isTourActive: tourPlayback.isActive,
9133
9218
  isOnboardingActive: onboardingPlayback.isActive,
9134
9219
  startingExperienceType
9135
9220
  });
9136
- if (!isPlaybackActive) {
9221
+ if (!isPlaybackActive2) {
9137
9222
  updateTourListenReady(false);
9138
9223
  setTourLiveTranscript("");
9139
9224
  hideFloatingLiveTranscript();
@@ -9142,12 +9227,12 @@ function ModelNexChatBubble({
9142
9227
  updateTourListenReady(Boolean(voice.isListening && sttActiveRef.current));
9143
9228
  }, [tourPlayback.isActive, onboardingPlayback.isActive, voice.isListening, startingExperienceType, updateTourListenReady]);
9144
9229
  (0, import_react18.useEffect)(() => {
9145
- const isPlaybackActive = isTourListeningSessionActive({
9230
+ const isPlaybackActive2 = isTourListeningSessionActive({
9146
9231
  isTourActive: tourPlayback.isActive,
9147
9232
  isOnboardingActive: onboardingPlayback.isActive,
9148
9233
  startingExperienceType
9149
9234
  });
9150
- if (!isPlaybackActive && sttActiveRef.current) {
9235
+ if (!isPlaybackActive2 && sttActiveRef.current) {
9151
9236
  sttActiveRef.current = false;
9152
9237
  updateTourListenReady(false);
9153
9238
  updateTourSttError(null);
@@ -9157,12 +9242,12 @@ function ModelNexChatBubble({
9157
9242
  }
9158
9243
  }, [tourPlayback.isActive, onboardingPlayback.isActive, voice, startingExperienceType, updateTourListenReady, updateTourSttError]);
9159
9244
  (0, import_react18.useEffect)(() => {
9160
- const isPlaybackActive = isTourListeningSessionActive({
9245
+ const isPlaybackActive2 = isTourListeningSessionActive({
9161
9246
  isTourActive: tourPlayback.isActive,
9162
9247
  isOnboardingActive: onboardingPlayback.isActive,
9163
9248
  startingExperienceType
9164
9249
  });
9165
- if (!isPlaybackActive || tourListenReady || !voice.sttSupported || tourSttError === "not-allowed") {
9250
+ if (!isPlaybackActive2 || tourListenReady || !voice.sttSupported || tourSttError === "not-allowed") {
9166
9251
  return;
9167
9252
  }
9168
9253
  const enableTourListeningFromGesture = (event) => {
@@ -9369,8 +9454,8 @@ function ModelNexChatBubble({
9369
9454
  return (0, import_react_dom.createPortal)(
9370
9455
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: containerStyle, className, "data-modelnex-internal": "true", onMouseDown: stopEventPropagation, onClick: stopEventPropagation, children: [
9371
9456
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: GLOBAL_STYLES }),
9372
- !(tourPlayback.isActive || onboardingPlayback.isActive) && (tourPlayback.pendingTour || onboardingPlayback.pendingTour) && pendingNotificationType === "modal" && !recordingMode && (() => {
9373
- const pt = onboardingPlayback.pendingTour || tourPlayback.pendingTour;
9457
+ renderPendingPromptModal && (() => {
9458
+ const pt = pendingPrompt?.tour;
9374
9459
  const mc = pt?.presentation?.modalConfig || {};
9375
9460
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
9376
9461
  "div",
@@ -9432,8 +9517,8 @@ function ModelNexChatBubble({
9432
9517
  "button",
9433
9518
  {
9434
9519
  onClick: () => {
9435
- const preferredExperience = onboardingPlayback.pendingTour ? "onboarding" : "tour";
9436
- if (onboardingPlayback.pendingTour) {
9520
+ const preferredExperience = pendingPrompt?.experienceType === "onboarding" ? "onboarding" : "tour";
9521
+ if (preferredExperience === "onboarding") {
9437
9522
  onboardingPlayback.acceptPendingTour();
9438
9523
  } else {
9439
9524
  tourPlayback.acceptPendingTour();
@@ -9596,24 +9681,28 @@ function ModelNexChatBubble({
9596
9681
  children: "Microphone access is blocked. Use the input box below, or allow microphone permission and tap the mic again."
9597
9682
  }
9598
9683
  ),
9599
- !(tourPlayback.isActive || onboardingPlayback.isActive) && (tourPlayback.pendingTour || onboardingPlayback.pendingTour) && pendingNotificationType === "bubble_card" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
9684
+ renderPendingPromptInline && pendingPrompt && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
9600
9685
  "div",
9601
9686
  {
9602
9687
  style: {
9603
- margin: "12px",
9604
- padding: "14px",
9605
- borderRadius: "12px",
9606
- border: "1px solid rgba(59,130,246,0.18)",
9607
- background: "linear-gradient(135deg, rgba(59,130,246,0.08) 0%, rgba(59,130,246,0.03) 100%)"
9688
+ maxWidth: "85%",
9689
+ padding: "12px 16px",
9690
+ borderRadius: "var(--modelnex-radius-inner, 16px)",
9691
+ borderBottomLeftRadius: 4,
9692
+ fontSize: "14px",
9693
+ lineHeight: 1.55,
9694
+ background: "var(--modelnex-bg, #fff)",
9695
+ color: "var(--modelnex-fg, #18181b)",
9696
+ border: "1px solid var(--modelnex-border, #e4e4e7)",
9697
+ boxShadow: "0 2px 4px rgba(0,0,0,0.02)"
9608
9698
  },
9609
9699
  children: [
9610
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "13px", fontWeight: 600, color: "var(--modelnex-fg, #18181b)", marginBottom: "6px" }, children: onboardingPlayback.pendingTour ? "Suggested workflow" : "Suggested tour" }),
9611
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "14px", color: "var(--modelnex-fg, #27272a)", marginBottom: "6px" }, children: (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.name }),
9612
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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." }),
9700
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontWeight: 500, marginBottom: "8px" }, children: getPendingPromptTitle(pendingPrompt) }),
9701
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "12px", color: "#52525b", marginBottom: "12px" }, children: getPendingPromptReason(pendingPrompt) }),
9613
9702
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
9614
9703
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { className: "btn btn-primary btn-sm", onClick: () => {
9615
- const preferredExperience = onboardingPlayback.pendingTour ? "onboarding" : "tour";
9616
- if (onboardingPlayback.pendingTour) {
9704
+ const preferredExperience = pendingPrompt.experienceType === "onboarding" ? "onboarding" : "tour";
9705
+ if (preferredExperience === "onboarding") {
9617
9706
  onboardingPlayback.acceptPendingTour();
9618
9707
  } else {
9619
9708
  tourPlayback.acceptPendingTour();
@@ -9621,13 +9710,13 @@ function ModelNexChatBubble({
9621
9710
  startTourListening(preferredExperience);
9622
9711
  }, children: [
9623
9712
  "Start ",
9624
- onboardingPlayback.pendingTour ? "workflow" : "tour"
9713
+ pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"
9625
9714
  ] }),
9626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "btn btn-secondary btn-sm", onClick: onboardingPlayback.pendingTour ? onboardingPlayback.dismissPendingTour : tourPlayback.dismissPendingTour, children: "Not now" })
9715
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "btn btn-secondary btn-sm", onClick: pendingPrompt.experienceType === "onboarding" ? onboardingPlayback.dismissPendingTour : tourPlayback.dismissPendingTour, children: "Not now" })
9627
9716
  ] })
9628
9717
  ]
9629
9718
  }
9630
- ),
9719
+ ) }),
9631
9720
  tourPlayback.isActive && tourPlayback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
9632
9721
  "div",
9633
9722
  {
@@ -10206,7 +10295,7 @@ function ModelNexChatBubble({
10206
10295
  )
10207
10296
  ] })
10208
10297
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
10209
- messages.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
10298
+ messages.length === 0 && !loading && !renderPendingPromptInline && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
10210
10299
  "div",
10211
10300
  {
10212
10301
  style: {
package/dist/index.mjs CHANGED
@@ -2457,6 +2457,9 @@ function normalizeTrigger(trigger) {
2457
2457
  if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
2458
2458
  return "feature_launch";
2459
2459
  }
2460
+ if (trigger === "return_visit") {
2461
+ return "return_visit";
2462
+ }
2460
2463
  return trigger ?? "first_visit";
2461
2464
  }
2462
2465
  function getStartPolicy(tour) {
@@ -2465,10 +2468,22 @@ function getStartPolicy(tour) {
2465
2468
  function getNotificationType(tour) {
2466
2469
  return tour.notificationType ?? "bubble_card";
2467
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
+ }
2468
2476
  function isTourEligible(tour, userProfile) {
2469
2477
  switch (normalizeTrigger(tour.trigger)) {
2470
2478
  case "first_visit":
2471
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
+ }
2472
2487
  case "manual":
2473
2488
  return false;
2474
2489
  default:
@@ -7985,6 +8000,58 @@ function buildTranscriptPreviewLines(transcript, options = {}) {
7985
8000
  return lines.slice(-maxLines);
7986
8001
  }
7987
8002
 
8003
+ // src/utils/pendingPromptUi.ts
8004
+ function isPendingReviewPrompt(pendingPrompt) {
8005
+ return Boolean(pendingPrompt?.options?.reviewMode);
8006
+ }
8007
+ function shouldRenderPendingPromptInline({
8008
+ pendingPrompt,
8009
+ isPlaybackActive,
8010
+ recordingMode
8011
+ }) {
8012
+ return Boolean(
8013
+ pendingPrompt && !isPlaybackActive && !recordingMode && !isPendingReviewPrompt(pendingPrompt)
8014
+ );
8015
+ }
8016
+ function shouldRenderPendingPromptModal({
8017
+ pendingPrompt,
8018
+ isPlaybackActive,
8019
+ recordingMode,
8020
+ pendingNotificationType
8021
+ }) {
8022
+ return Boolean(
8023
+ pendingPrompt && !isPlaybackActive && !recordingMode && isPendingReviewPrompt(pendingPrompt) && pendingNotificationType === "modal"
8024
+ );
8025
+ }
8026
+ function shouldAutoExpandForPendingPrompt({
8027
+ pendingPrompt,
8028
+ isPlaybackActive,
8029
+ recordingMode,
8030
+ pendingNotificationType
8031
+ }) {
8032
+ if (!pendingPrompt || isPlaybackActive || recordingMode) return false;
8033
+ return pendingNotificationType === "bubble_card" || shouldRenderPendingPromptInline({
8034
+ pendingPrompt,
8035
+ isPlaybackActive,
8036
+ recordingMode
8037
+ });
8038
+ }
8039
+ function getPendingPromptTitle(pendingPrompt) {
8040
+ return `I found a ${pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"} called "${pendingPrompt.tour.name}". Would you like to start it now?`;
8041
+ }
8042
+ function getPendingPromptReason(pendingPrompt) {
8043
+ if (pendingPrompt.tour.trigger === "first_visit") {
8044
+ return "This was surfaced because the current user matches the configured first-visit trigger.";
8045
+ }
8046
+ if (pendingPrompt.tour.trigger === "return_visit") {
8047
+ return "This was surfaced because the current user matches the configured return-visit trigger.";
8048
+ }
8049
+ if (pendingPrompt.tour.featureKey) {
8050
+ return `This was surfaced because the "${pendingPrompt.tour.featureKey}" trigger condition matched.`;
8051
+ }
8052
+ return "This was surfaced because the configured trigger condition matched.";
8053
+ }
8054
+
7988
8055
  // src/utils/floatingLiveTranscript.ts
7989
8056
  var floatingTranscriptEl = null;
7990
8057
  var liveTranscriptSuppressed = false;
@@ -8704,7 +8771,20 @@ function ModelNexChatBubble({
8704
8771
  }
8705
8772
  }, [devMode, handleAutoTag, ctx?.extractedElements.length, window.location.pathname]);
8706
8773
  const onboardingReviewToggle = getReviewModeToggleConfig(onboardingPlayback.playbackState);
8774
+ const pendingPrompt = playbackController.pendingPrompt;
8707
8775
  const pendingNotificationType = (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.notificationType ?? "bubble_card";
8776
+ const isPlaybackActive = tourPlayback.isActive || onboardingPlayback.isActive;
8777
+ const renderPendingPromptInline = shouldRenderPendingPromptInline({
8778
+ pendingPrompt,
8779
+ isPlaybackActive,
8780
+ recordingMode
8781
+ });
8782
+ const renderPendingPromptModal = shouldRenderPendingPromptModal({
8783
+ pendingPrompt,
8784
+ isPlaybackActive,
8785
+ recordingMode,
8786
+ pendingNotificationType
8787
+ });
8708
8788
  useEffect17(() => {
8709
8789
  setHydrated(true);
8710
8790
  try {
@@ -8771,10 +8851,15 @@ function ModelNexChatBubble({
8771
8851
  }
8772
8852
  }, [tourPlayback.isActive, onboardingPlayback.isActive]);
8773
8853
  useEffect17(() => {
8774
- if ((onboardingPlayback.pendingTour || tourPlayback.pendingTour) && !recordingMode && pendingNotificationType === "bubble_card") {
8854
+ if (shouldAutoExpandForPendingPrompt({
8855
+ pendingPrompt,
8856
+ isPlaybackActive,
8857
+ recordingMode,
8858
+ pendingNotificationType
8859
+ })) {
8775
8860
  setExpandedState(true);
8776
8861
  }
8777
- }, [onboardingPlayback.pendingTour, tourPlayback.pendingTour, recordingMode, pendingNotificationType, setExpandedState]);
8862
+ }, [isPlaybackActive, pendingNotificationType, pendingPrompt, recordingMode, setExpandedState]);
8778
8863
  const preferredListeningExperienceRef = useRef13(null);
8779
8864
  const playbackVoiceRoutingRef = useRef13({
8780
8865
  activeExperienceType,
@@ -8875,13 +8960,13 @@ function ModelNexChatBubble({
8875
8960
  updateTourSttError
8876
8961
  ]);
8877
8962
  useEffect17(() => {
8878
- const isPlaybackActive = isTourListeningSessionActive({
8963
+ const isPlaybackActive2 = isTourListeningSessionActive({
8879
8964
  isTourActive: tourPlayback.isActive,
8880
8965
  isOnboardingActive: onboardingPlayback.isActive,
8881
8966
  startingExperienceType
8882
8967
  });
8883
- const becameActive = isPlaybackActive && !previousTourActiveRef.current;
8884
- previousTourActiveRef.current = isPlaybackActive;
8968
+ const becameActive = isPlaybackActive2 && !previousTourActiveRef.current;
8969
+ previousTourActiveRef.current = isPlaybackActive2;
8885
8970
  if (becameActive) {
8886
8971
  try {
8887
8972
  sessionStorage.setItem(TOUR_MINIMIZED_STORAGE_KEY, "true");
@@ -8890,7 +8975,7 @@ function ModelNexChatBubble({
8890
8975
  setExpandedState(false, { rememberTourMinimize: true });
8891
8976
  updateTourSttError(null);
8892
8977
  }
8893
- if (!isPlaybackActive) {
8978
+ if (!isPlaybackActive2) {
8894
8979
  try {
8895
8980
  sessionStorage.removeItem(TOUR_MINIMIZED_STORAGE_KEY);
8896
8981
  } catch {
@@ -8917,12 +9002,12 @@ function ModelNexChatBubble({
8917
9002
  }
8918
9003
  }, [recordingMode, setExpandedState]);
8919
9004
  useEffect17(() => {
8920
- const isPlaybackActive = isTourListeningSessionActive({
9005
+ const isPlaybackActive2 = isTourListeningSessionActive({
8921
9006
  isTourActive: tourPlayback.isActive,
8922
9007
  isOnboardingActive: onboardingPlayback.isActive,
8923
9008
  startingExperienceType
8924
9009
  });
8925
- if (!isPlaybackActive) {
9010
+ if (!isPlaybackActive2) {
8926
9011
  updateTourListenReady(false);
8927
9012
  setTourLiveTranscript("");
8928
9013
  hideFloatingLiveTranscript();
@@ -8931,12 +9016,12 @@ function ModelNexChatBubble({
8931
9016
  updateTourListenReady(Boolean(voice.isListening && sttActiveRef.current));
8932
9017
  }, [tourPlayback.isActive, onboardingPlayback.isActive, voice.isListening, startingExperienceType, updateTourListenReady]);
8933
9018
  useEffect17(() => {
8934
- const isPlaybackActive = isTourListeningSessionActive({
9019
+ const isPlaybackActive2 = isTourListeningSessionActive({
8935
9020
  isTourActive: tourPlayback.isActive,
8936
9021
  isOnboardingActive: onboardingPlayback.isActive,
8937
9022
  startingExperienceType
8938
9023
  });
8939
- if (!isPlaybackActive && sttActiveRef.current) {
9024
+ if (!isPlaybackActive2 && sttActiveRef.current) {
8940
9025
  sttActiveRef.current = false;
8941
9026
  updateTourListenReady(false);
8942
9027
  updateTourSttError(null);
@@ -8946,12 +9031,12 @@ function ModelNexChatBubble({
8946
9031
  }
8947
9032
  }, [tourPlayback.isActive, onboardingPlayback.isActive, voice, startingExperienceType, updateTourListenReady, updateTourSttError]);
8948
9033
  useEffect17(() => {
8949
- const isPlaybackActive = isTourListeningSessionActive({
9034
+ const isPlaybackActive2 = isTourListeningSessionActive({
8950
9035
  isTourActive: tourPlayback.isActive,
8951
9036
  isOnboardingActive: onboardingPlayback.isActive,
8952
9037
  startingExperienceType
8953
9038
  });
8954
- if (!isPlaybackActive || tourListenReady || !voice.sttSupported || tourSttError === "not-allowed") {
9039
+ if (!isPlaybackActive2 || tourListenReady || !voice.sttSupported || tourSttError === "not-allowed") {
8955
9040
  return;
8956
9041
  }
8957
9042
  const enableTourListeningFromGesture = (event) => {
@@ -9158,8 +9243,8 @@ function ModelNexChatBubble({
9158
9243
  return createPortal(
9159
9244
  /* @__PURE__ */ jsxs3("div", { style: containerStyle, className, "data-modelnex-internal": "true", onMouseDown: stopEventPropagation, onClick: stopEventPropagation, children: [
9160
9245
  /* @__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;
9246
+ renderPendingPromptModal && (() => {
9247
+ const pt = pendingPrompt?.tour;
9163
9248
  const mc = pt?.presentation?.modalConfig || {};
9164
9249
  return /* @__PURE__ */ jsx4(
9165
9250
  "div",
@@ -9221,8 +9306,8 @@ function ModelNexChatBubble({
9221
9306
  "button",
9222
9307
  {
9223
9308
  onClick: () => {
9224
- const preferredExperience = onboardingPlayback.pendingTour ? "onboarding" : "tour";
9225
- if (onboardingPlayback.pendingTour) {
9309
+ const preferredExperience = pendingPrompt?.experienceType === "onboarding" ? "onboarding" : "tour";
9310
+ if (preferredExperience === "onboarding") {
9226
9311
  onboardingPlayback.acceptPendingTour();
9227
9312
  } else {
9228
9313
  tourPlayback.acceptPendingTour();
@@ -9385,24 +9470,28 @@ function ModelNexChatBubble({
9385
9470
  children: "Microphone access is blocked. Use the input box below, or allow microphone permission and tap the mic again."
9386
9471
  }
9387
9472
  ),
9388
- !(tourPlayback.isActive || onboardingPlayback.isActive) && (tourPlayback.pendingTour || onboardingPlayback.pendingTour) && pendingNotificationType === "bubble_card" && /* @__PURE__ */ jsxs3(
9473
+ renderPendingPromptInline && pendingPrompt && /* @__PURE__ */ jsx4("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsxs3(
9389
9474
  "div",
9390
9475
  {
9391
9476
  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%)"
9477
+ maxWidth: "85%",
9478
+ padding: "12px 16px",
9479
+ borderRadius: "var(--modelnex-radius-inner, 16px)",
9480
+ borderBottomLeftRadius: 4,
9481
+ fontSize: "14px",
9482
+ lineHeight: 1.55,
9483
+ background: "var(--modelnex-bg, #fff)",
9484
+ color: "var(--modelnex-fg, #18181b)",
9485
+ border: "1px solid var(--modelnex-border, #e4e4e7)",
9486
+ boxShadow: "0 2px 4px rgba(0,0,0,0.02)"
9397
9487
  },
9398
9488
  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." }),
9489
+ /* @__PURE__ */ jsx4("div", { style: { fontWeight: 500, marginBottom: "8px" }, children: getPendingPromptTitle(pendingPrompt) }),
9490
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: "12px", color: "#52525b", marginBottom: "12px" }, children: getPendingPromptReason(pendingPrompt) }),
9402
9491
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
9403
9492
  /* @__PURE__ */ jsxs3("button", { className: "btn btn-primary btn-sm", onClick: () => {
9404
- const preferredExperience = onboardingPlayback.pendingTour ? "onboarding" : "tour";
9405
- if (onboardingPlayback.pendingTour) {
9493
+ const preferredExperience = pendingPrompt.experienceType === "onboarding" ? "onboarding" : "tour";
9494
+ if (preferredExperience === "onboarding") {
9406
9495
  onboardingPlayback.acceptPendingTour();
9407
9496
  } else {
9408
9497
  tourPlayback.acceptPendingTour();
@@ -9410,13 +9499,13 @@ function ModelNexChatBubble({
9410
9499
  startTourListening(preferredExperience);
9411
9500
  }, children: [
9412
9501
  "Start ",
9413
- onboardingPlayback.pendingTour ? "workflow" : "tour"
9502
+ pendingPrompt.experienceType === "onboarding" ? "workflow" : "tour"
9414
9503
  ] }),
9415
- /* @__PURE__ */ jsx4("button", { className: "btn btn-secondary btn-sm", onClick: onboardingPlayback.pendingTour ? onboardingPlayback.dismissPendingTour : tourPlayback.dismissPendingTour, children: "Not now" })
9504
+ /* @__PURE__ */ jsx4("button", { className: "btn btn-secondary btn-sm", onClick: pendingPrompt.experienceType === "onboarding" ? onboardingPlayback.dismissPendingTour : tourPlayback.dismissPendingTour, children: "Not now" })
9416
9505
  ] })
9417
9506
  ]
9418
9507
  }
9419
- ),
9508
+ ) }),
9420
9509
  tourPlayback.isActive && tourPlayback.isReviewMode && /* @__PURE__ */ jsxs3(
9421
9510
  "div",
9422
9511
  {
@@ -9995,7 +10084,7 @@ function ModelNexChatBubble({
9995
10084
  )
9996
10085
  ] })
9997
10086
  ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
9998
- messages.length === 0 && !loading && /* @__PURE__ */ jsx4(
10087
+ messages.length === 0 && !loading && !renderPendingPromptInline && /* @__PURE__ */ jsx4(
9999
10088
  "div",
10000
10089
  {
10001
10090
  style: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.26",
3
+ "version": "0.5.27",
4
4
  "description": "React SDK for natural language control of web apps via AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",