@modelnex/sdk 0.5.38 → 0.5.40

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.
Files changed (3) hide show
  1. package/dist/index.js +127 -26
  2. package/dist/index.mjs +127 -26
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3305,6 +3305,22 @@ function createTourSocketPool({
3305
3305
  }
3306
3306
  var tourSocketPool = createTourSocketPool();
3307
3307
 
3308
+ // src/utils/reviewModeToggle.ts
3309
+ function isReviewModeEnabled(devMode, requestedReviewMode) {
3310
+ return Boolean(devMode && requestedReviewMode);
3311
+ }
3312
+ function getReviewModeToggleConfig(playbackState) {
3313
+ const isPaused = playbackState === "paused";
3314
+ return {
3315
+ icon: isPaused ? "\u25B6" : "\u23F8",
3316
+ label: isPaused ? "Continue Workflow" : "Pause for Feedback",
3317
+ shouldResume: isPaused
3318
+ };
3319
+ }
3320
+ function shouldCaptureReviewDraft(isReviewMode, playbackState) {
3321
+ return isReviewMode && playbackState === "paused";
3322
+ }
3323
+
3308
3324
  // src/utils/tourTransport.ts
3309
3325
  function compactCaptureEventForTransport(event) {
3310
3326
  return {
@@ -3726,6 +3742,20 @@ function isValueBearingElement(element) {
3726
3742
  element && (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)
3727
3743
  );
3728
3744
  }
3745
+ var DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS = 700;
3746
+ var DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS = 250;
3747
+ function getManualInputCommitPolicy(target) {
3748
+ const tagName = String(target.tagName || "").toLowerCase();
3749
+ const role = String(target.role || "").toLowerCase();
3750
+ const inputType = String(target.type || "").toLowerCase();
3751
+ const isContentEditable = Boolean(target.isContentEditable);
3752
+ const isSelectionLike = tagName === "select" || role === "combobox" || ["checkbox", "radio", "range", "color", "date", "datetime-local", "month", "time", "week"].includes(inputType);
3753
+ return {
3754
+ idleCommitMs: isSelectionLike ? DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS : DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS,
3755
+ commitOnBlur: !isSelectionLike || tagName === "select" || role === "combobox" || isContentEditable,
3756
+ commitOnChange: true
3757
+ };
3758
+ }
3729
3759
  function resolveWaitTargetElement(element) {
3730
3760
  if (isValueBearingElement(element) || element.isContentEditable) {
3731
3761
  return element;
@@ -3745,9 +3775,40 @@ function readWaitTargetValue(element) {
3745
3775
  if (isValueBearingElement(element)) {
3746
3776
  return element.value.trim();
3747
3777
  }
3778
+ const genericValue = element.value;
3779
+ if (typeof genericValue === "string") {
3780
+ return genericValue.trim();
3781
+ }
3782
+ if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
3783
+ return String(genericValue).trim();
3784
+ }
3748
3785
  if (element.isContentEditable) {
3749
3786
  return (element.textContent || "").trim();
3750
3787
  }
3788
+ const ariaValueText = element.getAttribute("aria-valuetext");
3789
+ if (typeof ariaValueText === "string" && ariaValueText.trim()) {
3790
+ return ariaValueText.trim();
3791
+ }
3792
+ const ariaValueNow = element.getAttribute("aria-valuenow");
3793
+ if (typeof ariaValueNow === "string" && ariaValueNow.trim()) {
3794
+ return ariaValueNow.trim();
3795
+ }
3796
+ if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3797
+ const nestedControl = element.querySelector(
3798
+ 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3799
+ );
3800
+ if (nestedControl && nestedControl !== element) {
3801
+ const nestedValue = readWaitTargetValue(nestedControl);
3802
+ if (nestedValue) {
3803
+ return nestedValue;
3804
+ }
3805
+ }
3806
+ return (element.textContent || "").trim();
3807
+ }
3808
+ const shadowInput = findInputInShadowRoot(element);
3809
+ if (shadowInput) {
3810
+ return shadowInput.value.trim();
3811
+ }
3751
3812
  return "";
3752
3813
  }
3753
3814
  function buildManualCompletionTranscript(step, eventName) {
@@ -3769,6 +3830,12 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3769
3830
  const target = resolveWaitTargetElement(rawTarget);
3770
3831
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3771
3832
  const isInputLikeEvent = isInputLikeWait(eventName, step);
3833
+ const commitPolicy = getManualInputCommitPolicy({
3834
+ tagName: target.tagName,
3835
+ role: target.getAttribute("role"),
3836
+ type: target.type ?? null,
3837
+ isContentEditable: target.isContentEditable
3838
+ });
3772
3839
  if (isInputLikeEvent && readWaitTargetValue(target)) {
3773
3840
  const initialValue = readWaitTargetValue(target);
3774
3841
  return {
@@ -3780,6 +3847,8 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3780
3847
  let cleanup = () => void 0;
3781
3848
  const promise = new Promise((resolve) => {
3782
3849
  const listeners2 = [];
3850
+ let observer = null;
3851
+ let idleTimer = null;
3783
3852
  let settled = false;
3784
3853
  const finish = () => {
3785
3854
  if (settled) return;
@@ -3792,20 +3861,61 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3792
3861
  listeners2.push({ node, type, handler });
3793
3862
  };
3794
3863
  if (isInputLikeEvent) {
3795
- const handleInputLikeEvent = () => {
3864
+ const clearIdleCommit = () => {
3865
+ if (idleTimer) {
3866
+ clearTimeout(idleTimer);
3867
+ idleTimer = null;
3868
+ }
3869
+ };
3870
+ const scheduleIdleCommit = () => {
3871
+ const currentValue = readWaitTargetValue(target);
3872
+ if (!currentValue) {
3873
+ clearIdleCommit();
3874
+ return;
3875
+ }
3876
+ clearIdleCommit();
3877
+ idleTimer = setTimeout(() => {
3878
+ idleTimer = null;
3879
+ finish();
3880
+ }, commitPolicy.idleCommitMs);
3881
+ };
3882
+ const handleImmediateCommitEvent = () => {
3796
3883
  const currentValue = readWaitTargetValue(target);
3797
3884
  if (currentValue) {
3885
+ clearIdleCommit();
3798
3886
  finish();
3799
3887
  }
3800
3888
  };
3801
- addListener(target, "input", handleInputLikeEvent);
3802
- addListener(target, "change", handleInputLikeEvent);
3803
- addListener(target, "blur", handleInputLikeEvent);
3889
+ addListener(target, "input", scheduleIdleCommit);
3890
+ if (commitPolicy.commitOnChange) {
3891
+ addListener(target, "change", handleImmediateCommitEvent);
3892
+ }
3893
+ if (commitPolicy.commitOnBlur) {
3894
+ addListener(target, "blur", handleImmediateCommitEvent);
3895
+ }
3896
+ if (typeof MutationObserver !== "undefined") {
3897
+ observer = new MutationObserver(() => {
3898
+ scheduleIdleCommit();
3899
+ });
3900
+ observer.observe(target, {
3901
+ subtree: true,
3902
+ childList: true,
3903
+ characterData: true,
3904
+ attributes: true,
3905
+ attributeFilter: ["value", "aria-valuetext", "aria-valuenow", "aria-activedescendant"]
3906
+ });
3907
+ }
3804
3908
  } else {
3805
3909
  const handleActionEvent = () => finish();
3806
3910
  addListener(target, eventName, handleActionEvent);
3807
3911
  }
3808
3912
  cleanup = () => {
3913
+ if (idleTimer) {
3914
+ clearTimeout(idleTimer);
3915
+ idleTimer = null;
3916
+ }
3917
+ observer?.disconnect();
3918
+ observer = null;
3809
3919
  listeners2.forEach(({ node, type, handler }) => node.removeEventListener(type, handler));
3810
3920
  listeners2.length = 0;
3811
3921
  };
@@ -4826,7 +4936,7 @@ function useTourPlayback({
4826
4936
  }
4827
4937
  claimedPlaybackOwnerKeyRef.current = ownerKey;
4828
4938
  startRequestedRef.current = true;
4829
- const shouldReview = Boolean(options?.reviewMode);
4939
+ const shouldReview = isReviewModeEnabled(devModeRef.current, Boolean(options?.reviewMode));
4830
4940
  resetCaptionSuppression();
4831
4941
  setReviewStatusMessage(null);
4832
4942
  setIsReviewMode(shouldReview);
@@ -8804,19 +8914,6 @@ function isTourListeningSessionActive(state) {
8804
8914
  return state.isTourActive || state.isOnboardingActive || Boolean(state.startingExperienceType);
8805
8915
  }
8806
8916
 
8807
- // src/utils/reviewModeToggle.ts
8808
- function getReviewModeToggleConfig(playbackState) {
8809
- const isPaused = playbackState === "paused";
8810
- return {
8811
- icon: isPaused ? "\u25B6" : "\u23F8",
8812
- label: isPaused ? "Continue Workflow" : "Pause for Feedback",
8813
- shouldResume: isPaused
8814
- };
8815
- }
8816
- function shouldCaptureReviewDraft(isReviewMode, playbackState) {
8817
- return isReviewMode && playbackState === "paused";
8818
- }
8819
-
8820
8917
  // src/utils/reviewVoiceRouting.ts
8821
8918
  function resolveSinglePlaybackTranscriptTarget(snapshot) {
8822
8919
  return shouldCaptureReviewDraft(snapshot.isReviewMode, snapshot.playbackState) ? "review_draft" : "playback";
@@ -9613,6 +9710,7 @@ function ModelNexChatBubble({
9613
9710
  const tourPlayback = (0, import_react18.useMemo)(() => createPlaybackView("tour"), [createPlaybackView]);
9614
9711
  const onboardingPlayback = (0, import_react18.useMemo)(() => createPlaybackView("onboarding"), [createPlaybackView]);
9615
9712
  const tourReviewToggle = getReviewModeToggleConfig(tourPlayback.playbackState);
9713
+ const tourReviewModeEnabled = isReviewModeEnabled(devMode, tourPlayback.isReviewMode);
9616
9714
  const lastAutoTaggedUrlRef = (0, import_react18.useRef)(null);
9617
9715
  const handleAutoTag = (0, import_react18.useCallback)(async () => {
9618
9716
  if (!ctx) return;
@@ -9660,6 +9758,7 @@ function ModelNexChatBubble({
9660
9758
  }
9661
9759
  }, [authoringMode, handleAutoTag, ctx?.extractedElements.length, window.location.pathname]);
9662
9760
  const onboardingReviewToggle = getReviewModeToggleConfig(onboardingPlayback.playbackState);
9761
+ const onboardingReviewModeEnabled = isReviewModeEnabled(devMode, onboardingPlayback.isReviewMode);
9663
9762
  const pendingPrompt = playbackController.pendingPrompt;
9664
9763
  const pendingNotificationType = (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.notificationType ?? "bubble_card";
9665
9764
  const isPlaybackActive = tourPlayback.isActive || onboardingPlayback.isActive;
@@ -10429,7 +10528,7 @@ function ModelNexChatBubble({
10429
10528
  ]
10430
10529
  }
10431
10530
  ) }),
10432
- tourPlayback.isActive && tourPlayback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10531
+ tourPlayback.isActive && tourReviewModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10433
10532
  "div",
10434
10533
  {
10435
10534
  style: {
@@ -10724,7 +10823,7 @@ function ModelNexChatBubble({
10724
10823
  ] })
10725
10824
  ] })
10726
10825
  ] }),
10727
- onboardingPlayback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10826
+ onboardingReviewModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10728
10827
  "div",
10729
10828
  {
10730
10829
  style: {
@@ -10830,7 +10929,7 @@ function ModelNexChatBubble({
10830
10929
  }
10831
10930
  ),
10832
10931
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
10833
- !onboardingPlayback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
10932
+ !onboardingReviewModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
10834
10933
  "button",
10835
10934
  {
10836
10935
  onClick: onboardingPlayback.repeatStep,
@@ -10842,7 +10941,7 @@ function ModelNexChatBubble({
10842
10941
  "button",
10843
10942
  {
10844
10943
  onClick: onboardingPlayback.skipTour,
10845
- style: { flex: onboardingPlayback.isReviewMode ? 1 : 0.6, borderRadius: "12px", border: "1px solid rgba(220,38,38,0.18)", background: "#fff5f5", color: "#b91c1c", padding: "11px 14px", cursor: "pointer", fontWeight: 700, fontSize: "13px" },
10944
+ style: { flex: onboardingReviewModeEnabled ? 1 : 0.6, borderRadius: "12px", border: "1px solid rgba(220,38,38,0.18)", background: "#fff5f5", color: "#b91c1c", padding: "11px 14px", cursor: "pointer", fontWeight: 700, fontSize: "13px" },
10846
10945
  children: "Exit"
10847
10946
  }
10848
10947
  )
@@ -10883,7 +10982,7 @@ function ModelNexChatBubble({
10883
10982
  ) })
10884
10983
  ] }),
10885
10984
  tourCurrentStep && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { borderRadius: "14px", background: TOUR_THEME.card, border: `1px solid ${TOUR_THEME.cardBorder}`, padding: "14px" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "15px", lineHeight: 1.55 }, children: tourCurrentStep.ask || tourCurrentStep.narration || tourCurrentStep.goal }) }),
10886
- tourPlayback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10985
+ tourReviewModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10887
10986
  "div",
10888
10987
  {
10889
10988
  style: {
@@ -11337,6 +11436,7 @@ function ModelNexOnboardingPanel({
11337
11436
  }) {
11338
11437
  const ctx = (0, import_react19.useContext)(ModelNexContext);
11339
11438
  const serverUrl = ctx?.serverUrl ?? DEFAULT_MODELNEX_SERVER_URL;
11439
+ const devMode = ctx?.devMode ?? false;
11340
11440
  const voice = useVoice(serverUrl);
11341
11441
  const audioLevels = useAudioLevel(voice.isListening);
11342
11442
  const [input, setInput] = (0, import_react19.useState)("");
@@ -11372,6 +11472,7 @@ function ModelNexOnboardingPanel({
11372
11472
  handleVoiceInput: playback.handleVoiceInput
11373
11473
  });
11374
11474
  const reviewToggle = getReviewModeToggleConfig(playback.playbackState);
11475
+ const reviewModeEnabled = isReviewModeEnabled(devMode, playback.isReviewMode);
11375
11476
  (0, import_react19.useEffect)(() => {
11376
11477
  playbackVoiceRoutingRef.current = {
11377
11478
  isReviewMode: playback.isReviewMode,
@@ -11796,7 +11897,7 @@ function ModelNexOnboardingPanel({
11796
11897
  ] })
11797
11898
  ] }),
11798
11899
  sttError === "not-allowed" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: "12px", color: "#b45309", lineHeight: 1.4, padding: "8px 12px", borderRadius: "10px", background: "#fef3c7" }, children: "Microphone blocked. Allow access in your browser to use voice commands." }),
11799
- playback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
11900
+ reviewModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
11800
11901
  "div",
11801
11902
  {
11802
11903
  style: {
@@ -11907,7 +12008,7 @@ function ModelNexOnboardingPanel({
11907
12008
  ] })
11908
12009
  ] }),
11909
12010
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "0 18px 18px", display: "flex", gap: "8px" }, children: [
11910
- playback.isReviewMode && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
12011
+ reviewModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
11911
12012
  "button",
11912
12013
  {
11913
12014
  type: "button",
@@ -11934,7 +12035,7 @@ function ModelNexOnboardingPanel({
11934
12035
  "button",
11935
12036
  {
11936
12037
  onClick: playback.repeatStep,
11937
- style: { flex: playback.isReviewMode ? 0.6 : 1, borderRadius: "12px", border: "1px solid rgba(15,23,42,0.12)", background: "#fff", padding: "11px 14px", cursor: "pointer", fontWeight: 600 },
12038
+ style: { flex: reviewModeEnabled ? 0.6 : 1, borderRadius: "12px", border: "1px solid rgba(15,23,42,0.12)", background: "#fff", padding: "11px 14px", cursor: "pointer", fontWeight: 600 },
11938
12039
  children: "Repeat"
11939
12040
  }
11940
12041
  ),
package/dist/index.mjs CHANGED
@@ -3094,6 +3094,22 @@ function createTourSocketPool({
3094
3094
  }
3095
3095
  var tourSocketPool = createTourSocketPool();
3096
3096
 
3097
+ // src/utils/reviewModeToggle.ts
3098
+ function isReviewModeEnabled(devMode, requestedReviewMode) {
3099
+ return Boolean(devMode && requestedReviewMode);
3100
+ }
3101
+ function getReviewModeToggleConfig(playbackState) {
3102
+ const isPaused = playbackState === "paused";
3103
+ return {
3104
+ icon: isPaused ? "\u25B6" : "\u23F8",
3105
+ label: isPaused ? "Continue Workflow" : "Pause for Feedback",
3106
+ shouldResume: isPaused
3107
+ };
3108
+ }
3109
+ function shouldCaptureReviewDraft(isReviewMode, playbackState) {
3110
+ return isReviewMode && playbackState === "paused";
3111
+ }
3112
+
3097
3113
  // src/utils/tourTransport.ts
3098
3114
  function compactCaptureEventForTransport(event) {
3099
3115
  return {
@@ -3515,6 +3531,20 @@ function isValueBearingElement(element) {
3515
3531
  element && (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)
3516
3532
  );
3517
3533
  }
3534
+ var DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS = 700;
3535
+ var DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS = 250;
3536
+ function getManualInputCommitPolicy(target) {
3537
+ const tagName = String(target.tagName || "").toLowerCase();
3538
+ const role = String(target.role || "").toLowerCase();
3539
+ const inputType = String(target.type || "").toLowerCase();
3540
+ const isContentEditable = Boolean(target.isContentEditable);
3541
+ const isSelectionLike = tagName === "select" || role === "combobox" || ["checkbox", "radio", "range", "color", "date", "datetime-local", "month", "time", "week"].includes(inputType);
3542
+ return {
3543
+ idleCommitMs: isSelectionLike ? DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS : DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS,
3544
+ commitOnBlur: !isSelectionLike || tagName === "select" || role === "combobox" || isContentEditable,
3545
+ commitOnChange: true
3546
+ };
3547
+ }
3518
3548
  function resolveWaitTargetElement(element) {
3519
3549
  if (isValueBearingElement(element) || element.isContentEditable) {
3520
3550
  return element;
@@ -3534,9 +3564,40 @@ function readWaitTargetValue(element) {
3534
3564
  if (isValueBearingElement(element)) {
3535
3565
  return element.value.trim();
3536
3566
  }
3567
+ const genericValue = element.value;
3568
+ if (typeof genericValue === "string") {
3569
+ return genericValue.trim();
3570
+ }
3571
+ if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
3572
+ return String(genericValue).trim();
3573
+ }
3537
3574
  if (element.isContentEditable) {
3538
3575
  return (element.textContent || "").trim();
3539
3576
  }
3577
+ const ariaValueText = element.getAttribute("aria-valuetext");
3578
+ if (typeof ariaValueText === "string" && ariaValueText.trim()) {
3579
+ return ariaValueText.trim();
3580
+ }
3581
+ const ariaValueNow = element.getAttribute("aria-valuenow");
3582
+ if (typeof ariaValueNow === "string" && ariaValueNow.trim()) {
3583
+ return ariaValueNow.trim();
3584
+ }
3585
+ if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3586
+ const nestedControl = element.querySelector(
3587
+ 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3588
+ );
3589
+ if (nestedControl && nestedControl !== element) {
3590
+ const nestedValue = readWaitTargetValue(nestedControl);
3591
+ if (nestedValue) {
3592
+ return nestedValue;
3593
+ }
3594
+ }
3595
+ return (element.textContent || "").trim();
3596
+ }
3597
+ const shadowInput = findInputInShadowRoot(element);
3598
+ if (shadowInput) {
3599
+ return shadowInput.value.trim();
3600
+ }
3540
3601
  return "";
3541
3602
  }
3542
3603
  function buildManualCompletionTranscript(step, eventName) {
@@ -3558,6 +3619,12 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3558
3619
  const target = resolveWaitTargetElement(rawTarget);
3559
3620
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3560
3621
  const isInputLikeEvent = isInputLikeWait(eventName, step);
3622
+ const commitPolicy = getManualInputCommitPolicy({
3623
+ tagName: target.tagName,
3624
+ role: target.getAttribute("role"),
3625
+ type: target.type ?? null,
3626
+ isContentEditable: target.isContentEditable
3627
+ });
3561
3628
  if (isInputLikeEvent && readWaitTargetValue(target)) {
3562
3629
  const initialValue = readWaitTargetValue(target);
3563
3630
  return {
@@ -3569,6 +3636,8 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3569
3636
  let cleanup = () => void 0;
3570
3637
  const promise = new Promise((resolve) => {
3571
3638
  const listeners2 = [];
3639
+ let observer = null;
3640
+ let idleTimer = null;
3572
3641
  let settled = false;
3573
3642
  const finish = () => {
3574
3643
  if (settled) return;
@@ -3581,20 +3650,61 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3581
3650
  listeners2.push({ node, type, handler });
3582
3651
  };
3583
3652
  if (isInputLikeEvent) {
3584
- const handleInputLikeEvent = () => {
3653
+ const clearIdleCommit = () => {
3654
+ if (idleTimer) {
3655
+ clearTimeout(idleTimer);
3656
+ idleTimer = null;
3657
+ }
3658
+ };
3659
+ const scheduleIdleCommit = () => {
3660
+ const currentValue = readWaitTargetValue(target);
3661
+ if (!currentValue) {
3662
+ clearIdleCommit();
3663
+ return;
3664
+ }
3665
+ clearIdleCommit();
3666
+ idleTimer = setTimeout(() => {
3667
+ idleTimer = null;
3668
+ finish();
3669
+ }, commitPolicy.idleCommitMs);
3670
+ };
3671
+ const handleImmediateCommitEvent = () => {
3585
3672
  const currentValue = readWaitTargetValue(target);
3586
3673
  if (currentValue) {
3674
+ clearIdleCommit();
3587
3675
  finish();
3588
3676
  }
3589
3677
  };
3590
- addListener(target, "input", handleInputLikeEvent);
3591
- addListener(target, "change", handleInputLikeEvent);
3592
- addListener(target, "blur", handleInputLikeEvent);
3678
+ addListener(target, "input", scheduleIdleCommit);
3679
+ if (commitPolicy.commitOnChange) {
3680
+ addListener(target, "change", handleImmediateCommitEvent);
3681
+ }
3682
+ if (commitPolicy.commitOnBlur) {
3683
+ addListener(target, "blur", handleImmediateCommitEvent);
3684
+ }
3685
+ if (typeof MutationObserver !== "undefined") {
3686
+ observer = new MutationObserver(() => {
3687
+ scheduleIdleCommit();
3688
+ });
3689
+ observer.observe(target, {
3690
+ subtree: true,
3691
+ childList: true,
3692
+ characterData: true,
3693
+ attributes: true,
3694
+ attributeFilter: ["value", "aria-valuetext", "aria-valuenow", "aria-activedescendant"]
3695
+ });
3696
+ }
3593
3697
  } else {
3594
3698
  const handleActionEvent = () => finish();
3595
3699
  addListener(target, eventName, handleActionEvent);
3596
3700
  }
3597
3701
  cleanup = () => {
3702
+ if (idleTimer) {
3703
+ clearTimeout(idleTimer);
3704
+ idleTimer = null;
3705
+ }
3706
+ observer?.disconnect();
3707
+ observer = null;
3598
3708
  listeners2.forEach(({ node, type, handler }) => node.removeEventListener(type, handler));
3599
3709
  listeners2.length = 0;
3600
3710
  };
@@ -4615,7 +4725,7 @@ function useTourPlayback({
4615
4725
  }
4616
4726
  claimedPlaybackOwnerKeyRef.current = ownerKey;
4617
4727
  startRequestedRef.current = true;
4618
- const shouldReview = Boolean(options?.reviewMode);
4728
+ const shouldReview = isReviewModeEnabled(devModeRef.current, Boolean(options?.reviewMode));
4619
4729
  resetCaptionSuppression();
4620
4730
  setReviewStatusMessage(null);
4621
4731
  setIsReviewMode(shouldReview);
@@ -8592,19 +8702,6 @@ function isTourListeningSessionActive(state) {
8592
8702
  return state.isTourActive || state.isOnboardingActive || Boolean(state.startingExperienceType);
8593
8703
  }
8594
8704
 
8595
- // src/utils/reviewModeToggle.ts
8596
- function getReviewModeToggleConfig(playbackState) {
8597
- const isPaused = playbackState === "paused";
8598
- return {
8599
- icon: isPaused ? "\u25B6" : "\u23F8",
8600
- label: isPaused ? "Continue Workflow" : "Pause for Feedback",
8601
- shouldResume: isPaused
8602
- };
8603
- }
8604
- function shouldCaptureReviewDraft(isReviewMode, playbackState) {
8605
- return isReviewMode && playbackState === "paused";
8606
- }
8607
-
8608
8705
  // src/utils/reviewVoiceRouting.ts
8609
8706
  function resolveSinglePlaybackTranscriptTarget(snapshot) {
8610
8707
  return shouldCaptureReviewDraft(snapshot.isReviewMode, snapshot.playbackState) ? "review_draft" : "playback";
@@ -9401,6 +9498,7 @@ function ModelNexChatBubble({
9401
9498
  const tourPlayback = useMemo3(() => createPlaybackView("tour"), [createPlaybackView]);
9402
9499
  const onboardingPlayback = useMemo3(() => createPlaybackView("onboarding"), [createPlaybackView]);
9403
9500
  const tourReviewToggle = getReviewModeToggleConfig(tourPlayback.playbackState);
9501
+ const tourReviewModeEnabled = isReviewModeEnabled(devMode, tourPlayback.isReviewMode);
9404
9502
  const lastAutoTaggedUrlRef = useRef13(null);
9405
9503
  const handleAutoTag = useCallback12(async () => {
9406
9504
  if (!ctx) return;
@@ -9448,6 +9546,7 @@ function ModelNexChatBubble({
9448
9546
  }
9449
9547
  }, [authoringMode, handleAutoTag, ctx?.extractedElements.length, window.location.pathname]);
9450
9548
  const onboardingReviewToggle = getReviewModeToggleConfig(onboardingPlayback.playbackState);
9549
+ const onboardingReviewModeEnabled = isReviewModeEnabled(devMode, onboardingPlayback.isReviewMode);
9451
9550
  const pendingPrompt = playbackController.pendingPrompt;
9452
9551
  const pendingNotificationType = (onboardingPlayback.pendingTour || tourPlayback.pendingTour)?.notificationType ?? "bubble_card";
9453
9552
  const isPlaybackActive = tourPlayback.isActive || onboardingPlayback.isActive;
@@ -10217,7 +10316,7 @@ function ModelNexChatBubble({
10217
10316
  ]
10218
10317
  }
10219
10318
  ) }),
10220
- tourPlayback.isActive && tourPlayback.isReviewMode && /* @__PURE__ */ jsxs3(
10319
+ tourPlayback.isActive && tourReviewModeEnabled && /* @__PURE__ */ jsxs3(
10221
10320
  "div",
10222
10321
  {
10223
10322
  style: {
@@ -10512,7 +10611,7 @@ function ModelNexChatBubble({
10512
10611
  ] })
10513
10612
  ] })
10514
10613
  ] }),
10515
- onboardingPlayback.isReviewMode && /* @__PURE__ */ jsxs3(
10614
+ onboardingReviewModeEnabled && /* @__PURE__ */ jsxs3(
10516
10615
  "div",
10517
10616
  {
10518
10617
  style: {
@@ -10618,7 +10717,7 @@ function ModelNexChatBubble({
10618
10717
  }
10619
10718
  ),
10620
10719
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
10621
- !onboardingPlayback.isReviewMode && /* @__PURE__ */ jsx4(
10720
+ !onboardingReviewModeEnabled && /* @__PURE__ */ jsx4(
10622
10721
  "button",
10623
10722
  {
10624
10723
  onClick: onboardingPlayback.repeatStep,
@@ -10630,7 +10729,7 @@ function ModelNexChatBubble({
10630
10729
  "button",
10631
10730
  {
10632
10731
  onClick: onboardingPlayback.skipTour,
10633
- style: { flex: onboardingPlayback.isReviewMode ? 1 : 0.6, borderRadius: "12px", border: "1px solid rgba(220,38,38,0.18)", background: "#fff5f5", color: "#b91c1c", padding: "11px 14px", cursor: "pointer", fontWeight: 700, fontSize: "13px" },
10732
+ style: { flex: onboardingReviewModeEnabled ? 1 : 0.6, borderRadius: "12px", border: "1px solid rgba(220,38,38,0.18)", background: "#fff5f5", color: "#b91c1c", padding: "11px 14px", cursor: "pointer", fontWeight: 700, fontSize: "13px" },
10634
10733
  children: "Exit"
10635
10734
  }
10636
10735
  )
@@ -10671,7 +10770,7 @@ function ModelNexChatBubble({
10671
10770
  ) })
10672
10771
  ] }),
10673
10772
  tourCurrentStep && /* @__PURE__ */ jsx4("div", { style: { borderRadius: "14px", background: TOUR_THEME.card, border: `1px solid ${TOUR_THEME.cardBorder}`, padding: "14px" }, children: /* @__PURE__ */ jsx4("div", { style: { fontSize: "15px", lineHeight: 1.55 }, children: tourCurrentStep.ask || tourCurrentStep.narration || tourCurrentStep.goal }) }),
10674
- tourPlayback.isReviewMode && /* @__PURE__ */ jsxs3(
10773
+ tourReviewModeEnabled && /* @__PURE__ */ jsxs3(
10675
10774
  "div",
10676
10775
  {
10677
10776
  style: {
@@ -11125,6 +11224,7 @@ function ModelNexOnboardingPanel({
11125
11224
  }) {
11126
11225
  const ctx = useContext6(ModelNexContext);
11127
11226
  const serverUrl = ctx?.serverUrl ?? DEFAULT_MODELNEX_SERVER_URL;
11227
+ const devMode = ctx?.devMode ?? false;
11128
11228
  const voice = useVoice(serverUrl);
11129
11229
  const audioLevels = useAudioLevel(voice.isListening);
11130
11230
  const [input, setInput] = useState14("");
@@ -11160,6 +11260,7 @@ function ModelNexOnboardingPanel({
11160
11260
  handleVoiceInput: playback.handleVoiceInput
11161
11261
  });
11162
11262
  const reviewToggle = getReviewModeToggleConfig(playback.playbackState);
11263
+ const reviewModeEnabled = isReviewModeEnabled(devMode, playback.isReviewMode);
11163
11264
  useEffect18(() => {
11164
11265
  playbackVoiceRoutingRef.current = {
11165
11266
  isReviewMode: playback.isReviewMode,
@@ -11584,7 +11685,7 @@ function ModelNexOnboardingPanel({
11584
11685
  ] })
11585
11686
  ] }),
11586
11687
  sttError === "not-allowed" && /* @__PURE__ */ jsx5("div", { style: { fontSize: "12px", color: "#b45309", lineHeight: 1.4, padding: "8px 12px", borderRadius: "10px", background: "#fef3c7" }, children: "Microphone blocked. Allow access in your browser to use voice commands." }),
11587
- playback.isReviewMode && /* @__PURE__ */ jsxs4(
11688
+ reviewModeEnabled && /* @__PURE__ */ jsxs4(
11588
11689
  "div",
11589
11690
  {
11590
11691
  style: {
@@ -11695,7 +11796,7 @@ function ModelNexOnboardingPanel({
11695
11796
  ] })
11696
11797
  ] }),
11697
11798
  /* @__PURE__ */ jsxs4("div", { style: { padding: "0 18px 18px", display: "flex", gap: "8px" }, children: [
11698
- playback.isReviewMode && /* @__PURE__ */ jsxs4(
11799
+ reviewModeEnabled && /* @__PURE__ */ jsxs4(
11699
11800
  "button",
11700
11801
  {
11701
11802
  type: "button",
@@ -11722,7 +11823,7 @@ function ModelNexOnboardingPanel({
11722
11823
  "button",
11723
11824
  {
11724
11825
  onClick: playback.repeatStep,
11725
- style: { flex: playback.isReviewMode ? 0.6 : 1, borderRadius: "12px", border: "1px solid rgba(15,23,42,0.12)", background: "#fff", padding: "11px 14px", cursor: "pointer", fontWeight: 600 },
11826
+ style: { flex: reviewModeEnabled ? 0.6 : 1, borderRadius: "12px", border: "1px solid rgba(15,23,42,0.12)", background: "#fff", padding: "11px 14px", cursor: "pointer", fontWeight: 600 },
11726
11827
  children: "Repeat"
11727
11828
  }
11728
11829
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.38",
3
+ "version": "0.5.40",
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",