@modelnex/sdk 0.5.32 → 0.5.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2832,6 +2832,91 @@ async function validateInjectedDevModeKey(serverUrl, websiteId, devModeKey) {
2832
2832
  return validationPromise;
2833
2833
  }
2834
2834
 
2835
+ // src/utils/recordingPersistence.ts
2836
+ var RECORDING_SESSION_STORAGE_KEY = "modelnex-recording-session";
2837
+ var RESTORABLE_RECORDING_PHASES = /* @__PURE__ */ new Set([
2838
+ "active",
2839
+ "selecting",
2840
+ "configuring",
2841
+ "narrating",
2842
+ "reviewing",
2843
+ "saved",
2844
+ "finishing"
2845
+ ]);
2846
+ var TOUR_STEP_TYPES = /* @__PURE__ */ new Set([
2847
+ "narrate",
2848
+ "act",
2849
+ "input",
2850
+ "ask_and_fill",
2851
+ "user_input",
2852
+ "ask_or_fill",
2853
+ "wait_then_act"
2854
+ ]);
2855
+ function canUseSessionStorage() {
2856
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
2857
+ }
2858
+ function isExperienceType(value) {
2859
+ return value === "tour" || value === "onboarding";
2860
+ }
2861
+ function isTourStepType(value) {
2862
+ return typeof value === "string" && TOUR_STEP_TYPES.has(value);
2863
+ }
2864
+ function isPlainObject(value) {
2865
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2866
+ }
2867
+ function sanitizePersistedRecordingSession(raw) {
2868
+ if (!isPlainObject(raw)) return null;
2869
+ if (raw.version !== 1) return null;
2870
+ if (!isExperienceType(raw.experienceType)) return null;
2871
+ if (typeof raw.phase !== "string" || !RESTORABLE_RECORDING_PHASES.has(raw.phase)) return null;
2872
+ if (!Array.isArray(raw.steps) || !Array.isArray(raw.captureEvents) || !Array.isArray(raw.pendingClicks)) return null;
2873
+ if (!isTourStepType(raw.selectedStepType)) return null;
2874
+ return {
2875
+ version: 1,
2876
+ experienceType: raw.experienceType,
2877
+ phase: raw.phase,
2878
+ steps: raw.steps,
2879
+ captureEvents: raw.captureEvents,
2880
+ capturedTranscript: typeof raw.capturedTranscript === "string" ? raw.capturedTranscript : "",
2881
+ pendingClicks: raw.pendingClicks,
2882
+ selectedStepType: raw.selectedStepType
2883
+ };
2884
+ }
2885
+ function readPersistedRecordingSession() {
2886
+ if (!canUseSessionStorage()) return null;
2887
+ try {
2888
+ const raw = window.sessionStorage.getItem(RECORDING_SESSION_STORAGE_KEY);
2889
+ if (!raw) return null;
2890
+ const parsed = sanitizePersistedRecordingSession(JSON.parse(raw));
2891
+ if (parsed) {
2892
+ return parsed;
2893
+ }
2894
+ } catch {
2895
+ }
2896
+ clearPersistedRecordingSession();
2897
+ return null;
2898
+ }
2899
+ function readPersistedRecordingExperienceType() {
2900
+ return readPersistedRecordingSession()?.experienceType ?? null;
2901
+ }
2902
+ function hasPersistedRecordingSession() {
2903
+ return readPersistedRecordingSession() !== null;
2904
+ }
2905
+ function persistRecordingSession(session) {
2906
+ if (!canUseSessionStorage()) return;
2907
+ try {
2908
+ window.sessionStorage.setItem(RECORDING_SESSION_STORAGE_KEY, JSON.stringify(session));
2909
+ } catch {
2910
+ }
2911
+ }
2912
+ function clearPersistedRecordingSession() {
2913
+ if (!canUseSessionStorage()) return;
2914
+ try {
2915
+ window.sessionStorage.removeItem(RECORDING_SESSION_STORAGE_KEY);
2916
+ } catch {
2917
+ }
2918
+ }
2919
+
2835
2920
  // src/hooks/useRunCommand.ts
2836
2921
  var import_react9 = require("react");
2837
2922
  function searchTaggedElementsForQuery(store, query, limit = 8) {
@@ -6768,14 +6853,20 @@ function useRecordingMode({
6768
6853
  onPreview,
6769
6854
  experienceType = "tour"
6770
6855
  }) {
6771
- const [phase, setPhase] = (0, import_react16.useState)("idle");
6772
- const [steps, setSteps] = (0, import_react16.useState)([]);
6856
+ const restoredSessionRef = (0, import_react16.useRef)(void 0);
6857
+ if (restoredSessionRef.current === void 0) {
6858
+ restoredSessionRef.current = readPersistedRecordingSession();
6859
+ }
6860
+ const restoredSession = restoredSessionRef.current;
6861
+ const restoredPhase = restoredSession ? "active" : "idle";
6862
+ const [phase, setPhase] = (0, import_react16.useState)(restoredPhase);
6863
+ const [steps, setSteps] = (0, import_react16.useState)(() => restoredSession?.steps ?? []);
6773
6864
  const [selectedElement, setSelectedElement] = (0, import_react16.useState)(null);
6774
- const [selectedStepType, setSelectedStepType] = (0, import_react16.useState)("ask_or_fill");
6865
+ const [selectedStepType, setSelectedStepType] = (0, import_react16.useState)(() => restoredSession?.selectedStepType ?? "ask_or_fill");
6775
6866
  const [pendingNarration, setPendingNarration] = (0, import_react16.useState)("");
6776
6867
  const [polishedNarration, setPolishedNarration] = (0, import_react16.useState)("");
6777
- const [captureEvents, setCaptureEvents] = (0, import_react16.useState)([]);
6778
- const [capturedTranscript, setCapturedTranscript] = (0, import_react16.useState)("");
6868
+ const [captureEvents, setCaptureEvents] = (0, import_react16.useState)(() => restoredSession?.captureEvents ?? []);
6869
+ const [capturedTranscript, setCapturedTranscript] = (0, import_react16.useState)(() => restoredSession?.capturedTranscript ?? "");
6779
6870
  const [isVoiceCaptureActive, setIsVoiceCaptureActive] = (0, import_react16.useState)(false);
6780
6871
  const stepsRef = (0, import_react16.useRef)([]);
6781
6872
  stepsRef.current = steps;
@@ -6786,7 +6877,7 @@ function useRecordingMode({
6786
6877
  });
6787
6878
  }, [voice]);
6788
6879
  captureEventsRef.current = captureEvents;
6789
- const pendingClicksRef = (0, import_react16.useRef)([]);
6880
+ const pendingClicksRef = (0, import_react16.useRef)(restoredSession?.pendingClicks ?? []);
6790
6881
  const shouldKeepVoiceCaptureRef = (0, import_react16.useRef)(false);
6791
6882
  const resumeVoiceAfterNarrationRef = (0, import_react16.useRef)(false);
6792
6883
  const lastAutoNoteRef = (0, import_react16.useRef)("");
@@ -7361,6 +7452,22 @@ function useRecordingMode({
7361
7452
  }
7362
7453
  }
7363
7454
  }, [isRecording, phase, markStep, undoLastStep, previewSteps, approveNarration, redoNarration]);
7455
+ (0, import_react16.useEffect)(() => {
7456
+ if (!isRecording && steps.length === 0 && captureEvents.length === 0 && !capturedTranscript.trim()) {
7457
+ clearPersistedRecordingSession();
7458
+ return;
7459
+ }
7460
+ persistRecordingSession({
7461
+ version: 1,
7462
+ experienceType,
7463
+ phase,
7464
+ steps,
7465
+ captureEvents,
7466
+ capturedTranscript,
7467
+ pendingClicks: pendingClicksRef.current,
7468
+ selectedStepType
7469
+ });
7470
+ }, [captureEvents, capturedTranscript, experienceType, isRecording, phase, selectedStepType, steps]);
7364
7471
  return {
7365
7472
  phase,
7366
7473
  steps,
@@ -9300,7 +9407,9 @@ function ModelNexChatBubble({
9300
9407
  const [savedDraft, setSavedDraft] = (0, import_react18.useState)(null);
9301
9408
  const [reviewDraft, setReviewDraft] = (0, import_react18.useState)("");
9302
9409
  const [tourLiveTranscript, setTourLiveTranscript] = (0, import_react18.useState)("");
9303
- const [activeRecordingExperienceType, setActiveRecordingExperienceType] = (0, import_react18.useState)(recordingExperienceType);
9410
+ const [activeRecordingExperienceType, setActiveRecordingExperienceType] = (0, import_react18.useState)(
9411
+ () => readPersistedRecordingExperienceType() ?? recordingExperienceType
9412
+ );
9304
9413
  const recording = useRecordingMode({
9305
9414
  serverUrl,
9306
9415
  toursApiBase: ctx?.toursApiBase,
@@ -9310,6 +9419,16 @@ function ModelNexChatBubble({
9310
9419
  });
9311
9420
  const isGeneratingDraft = isRecordingDraftGenerating(recording.phase);
9312
9421
  const showRecordingOverlay = recordingMode && shouldShowRecordingOverlay(recording.phase);
9422
+ (0, import_react18.useEffect)(() => {
9423
+ const shouldBeRecording = recording.phase !== "idle";
9424
+ if (shouldBeRecording && !recordingMode) {
9425
+ setRecordingMode(true);
9426
+ return;
9427
+ }
9428
+ if (!shouldBeRecording && recordingMode && !showStopModal && !savedDraft) {
9429
+ setRecordingMode(false);
9430
+ }
9431
+ }, [recording.phase, recordingMode, savedDraft, setRecordingMode, showStopModal]);
9313
9432
  const playbackController = useExperiencePlaybackController({
9314
9433
  serverUrl,
9315
9434
  commandUrl: ctx?.commandUrl,
@@ -11815,7 +11934,7 @@ var ModelNexProvider = ({
11815
11934
  const [executedFields, setExecutedFields] = (0, import_react21.useState)(/* @__PURE__ */ new Set());
11816
11935
  const [highlightActions, setHighlightActions] = (0, import_react21.useState)(false);
11817
11936
  const [studioMode, setStudioMode] = (0, import_react21.useState)(false);
11818
- const [recordingMode, setRecordingMode] = (0, import_react21.useState)(false);
11937
+ const [recordingMode, setRecordingMode] = (0, import_react21.useState)(() => hasPersistedRecordingSession());
11819
11938
  const [voiceMuted, setVoiceMuted] = (0, import_react21.useState)(false);
11820
11939
  const [socketId, setSocketId] = (0, import_react21.useState)(null);
11821
11940
  const [actions, setActions] = (0, import_react21.useState)(/* @__PURE__ */ new Map());
package/dist/index.mjs CHANGED
@@ -2623,6 +2623,91 @@ async function validateInjectedDevModeKey(serverUrl, websiteId, devModeKey) {
2623
2623
  return validationPromise;
2624
2624
  }
2625
2625
 
2626
+ // src/utils/recordingPersistence.ts
2627
+ var RECORDING_SESSION_STORAGE_KEY = "modelnex-recording-session";
2628
+ var RESTORABLE_RECORDING_PHASES = /* @__PURE__ */ new Set([
2629
+ "active",
2630
+ "selecting",
2631
+ "configuring",
2632
+ "narrating",
2633
+ "reviewing",
2634
+ "saved",
2635
+ "finishing"
2636
+ ]);
2637
+ var TOUR_STEP_TYPES = /* @__PURE__ */ new Set([
2638
+ "narrate",
2639
+ "act",
2640
+ "input",
2641
+ "ask_and_fill",
2642
+ "user_input",
2643
+ "ask_or_fill",
2644
+ "wait_then_act"
2645
+ ]);
2646
+ function canUseSessionStorage() {
2647
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
2648
+ }
2649
+ function isExperienceType(value) {
2650
+ return value === "tour" || value === "onboarding";
2651
+ }
2652
+ function isTourStepType(value) {
2653
+ return typeof value === "string" && TOUR_STEP_TYPES.has(value);
2654
+ }
2655
+ function isPlainObject(value) {
2656
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2657
+ }
2658
+ function sanitizePersistedRecordingSession(raw) {
2659
+ if (!isPlainObject(raw)) return null;
2660
+ if (raw.version !== 1) return null;
2661
+ if (!isExperienceType(raw.experienceType)) return null;
2662
+ if (typeof raw.phase !== "string" || !RESTORABLE_RECORDING_PHASES.has(raw.phase)) return null;
2663
+ if (!Array.isArray(raw.steps) || !Array.isArray(raw.captureEvents) || !Array.isArray(raw.pendingClicks)) return null;
2664
+ if (!isTourStepType(raw.selectedStepType)) return null;
2665
+ return {
2666
+ version: 1,
2667
+ experienceType: raw.experienceType,
2668
+ phase: raw.phase,
2669
+ steps: raw.steps,
2670
+ captureEvents: raw.captureEvents,
2671
+ capturedTranscript: typeof raw.capturedTranscript === "string" ? raw.capturedTranscript : "",
2672
+ pendingClicks: raw.pendingClicks,
2673
+ selectedStepType: raw.selectedStepType
2674
+ };
2675
+ }
2676
+ function readPersistedRecordingSession() {
2677
+ if (!canUseSessionStorage()) return null;
2678
+ try {
2679
+ const raw = window.sessionStorage.getItem(RECORDING_SESSION_STORAGE_KEY);
2680
+ if (!raw) return null;
2681
+ const parsed = sanitizePersistedRecordingSession(JSON.parse(raw));
2682
+ if (parsed) {
2683
+ return parsed;
2684
+ }
2685
+ } catch {
2686
+ }
2687
+ clearPersistedRecordingSession();
2688
+ return null;
2689
+ }
2690
+ function readPersistedRecordingExperienceType() {
2691
+ return readPersistedRecordingSession()?.experienceType ?? null;
2692
+ }
2693
+ function hasPersistedRecordingSession() {
2694
+ return readPersistedRecordingSession() !== null;
2695
+ }
2696
+ function persistRecordingSession(session) {
2697
+ if (!canUseSessionStorage()) return;
2698
+ try {
2699
+ window.sessionStorage.setItem(RECORDING_SESSION_STORAGE_KEY, JSON.stringify(session));
2700
+ } catch {
2701
+ }
2702
+ }
2703
+ function clearPersistedRecordingSession() {
2704
+ if (!canUseSessionStorage()) return;
2705
+ try {
2706
+ window.sessionStorage.removeItem(RECORDING_SESSION_STORAGE_KEY);
2707
+ } catch {
2708
+ }
2709
+ }
2710
+
2626
2711
  // src/hooks/useRunCommand.ts
2627
2712
  import { useCallback as useCallback5, useContext as useContext2 } from "react";
2628
2713
  function searchTaggedElementsForQuery(store, query, limit = 8) {
@@ -6558,14 +6643,20 @@ function useRecordingMode({
6558
6643
  onPreview,
6559
6644
  experienceType = "tour"
6560
6645
  }) {
6561
- const [phase, setPhase] = useState11("idle");
6562
- const [steps, setSteps] = useState11([]);
6646
+ const restoredSessionRef = useRef12(void 0);
6647
+ if (restoredSessionRef.current === void 0) {
6648
+ restoredSessionRef.current = readPersistedRecordingSession();
6649
+ }
6650
+ const restoredSession = restoredSessionRef.current;
6651
+ const restoredPhase = restoredSession ? "active" : "idle";
6652
+ const [phase, setPhase] = useState11(restoredPhase);
6653
+ const [steps, setSteps] = useState11(() => restoredSession?.steps ?? []);
6563
6654
  const [selectedElement, setSelectedElement] = useState11(null);
6564
- const [selectedStepType, setSelectedStepType] = useState11("ask_or_fill");
6655
+ const [selectedStepType, setSelectedStepType] = useState11(() => restoredSession?.selectedStepType ?? "ask_or_fill");
6565
6656
  const [pendingNarration, setPendingNarration] = useState11("");
6566
6657
  const [polishedNarration, setPolishedNarration] = useState11("");
6567
- const [captureEvents, setCaptureEvents] = useState11([]);
6568
- const [capturedTranscript, setCapturedTranscript] = useState11("");
6658
+ const [captureEvents, setCaptureEvents] = useState11(() => restoredSession?.captureEvents ?? []);
6659
+ const [capturedTranscript, setCapturedTranscript] = useState11(() => restoredSession?.capturedTranscript ?? "");
6569
6660
  const [isVoiceCaptureActive, setIsVoiceCaptureActive] = useState11(false);
6570
6661
  const stepsRef = useRef12([]);
6571
6662
  stepsRef.current = steps;
@@ -6576,7 +6667,7 @@ function useRecordingMode({
6576
6667
  });
6577
6668
  }, [voice]);
6578
6669
  captureEventsRef.current = captureEvents;
6579
- const pendingClicksRef = useRef12([]);
6670
+ const pendingClicksRef = useRef12(restoredSession?.pendingClicks ?? []);
6580
6671
  const shouldKeepVoiceCaptureRef = useRef12(false);
6581
6672
  const resumeVoiceAfterNarrationRef = useRef12(false);
6582
6673
  const lastAutoNoteRef = useRef12("");
@@ -7151,6 +7242,22 @@ function useRecordingMode({
7151
7242
  }
7152
7243
  }
7153
7244
  }, [isRecording, phase, markStep, undoLastStep, previewSteps, approveNarration, redoNarration]);
7245
+ useEffect15(() => {
7246
+ if (!isRecording && steps.length === 0 && captureEvents.length === 0 && !capturedTranscript.trim()) {
7247
+ clearPersistedRecordingSession();
7248
+ return;
7249
+ }
7250
+ persistRecordingSession({
7251
+ version: 1,
7252
+ experienceType,
7253
+ phase,
7254
+ steps,
7255
+ captureEvents,
7256
+ capturedTranscript,
7257
+ pendingClicks: pendingClicksRef.current,
7258
+ selectedStepType
7259
+ });
7260
+ }, [captureEvents, capturedTranscript, experienceType, isRecording, phase, selectedStepType, steps]);
7154
7261
  return {
7155
7262
  phase,
7156
7263
  steps,
@@ -9090,7 +9197,9 @@ function ModelNexChatBubble({
9090
9197
  const [savedDraft, setSavedDraft] = useState13(null);
9091
9198
  const [reviewDraft, setReviewDraft] = useState13("");
9092
9199
  const [tourLiveTranscript, setTourLiveTranscript] = useState13("");
9093
- const [activeRecordingExperienceType, setActiveRecordingExperienceType] = useState13(recordingExperienceType);
9200
+ const [activeRecordingExperienceType, setActiveRecordingExperienceType] = useState13(
9201
+ () => readPersistedRecordingExperienceType() ?? recordingExperienceType
9202
+ );
9094
9203
  const recording = useRecordingMode({
9095
9204
  serverUrl,
9096
9205
  toursApiBase: ctx?.toursApiBase,
@@ -9100,6 +9209,16 @@ function ModelNexChatBubble({
9100
9209
  });
9101
9210
  const isGeneratingDraft = isRecordingDraftGenerating(recording.phase);
9102
9211
  const showRecordingOverlay = recordingMode && shouldShowRecordingOverlay(recording.phase);
9212
+ useEffect17(() => {
9213
+ const shouldBeRecording = recording.phase !== "idle";
9214
+ if (shouldBeRecording && !recordingMode) {
9215
+ setRecordingMode(true);
9216
+ return;
9217
+ }
9218
+ if (!shouldBeRecording && recordingMode && !showStopModal && !savedDraft) {
9219
+ setRecordingMode(false);
9220
+ }
9221
+ }, [recording.phase, recordingMode, savedDraft, setRecordingMode, showStopModal]);
9103
9222
  const playbackController = useExperiencePlaybackController({
9104
9223
  serverUrl,
9105
9224
  commandUrl: ctx?.commandUrl,
@@ -11605,7 +11724,7 @@ var ModelNexProvider = ({
11605
11724
  const [executedFields, setExecutedFields] = useState15(/* @__PURE__ */ new Set());
11606
11725
  const [highlightActions, setHighlightActions] = useState15(false);
11607
11726
  const [studioMode, setStudioMode] = useState15(false);
11608
- const [recordingMode, setRecordingMode] = useState15(false);
11727
+ const [recordingMode, setRecordingMode] = useState15(() => hasPersistedRecordingSession());
11609
11728
  const [voiceMuted, setVoiceMuted] = useState15(false);
11610
11729
  const [socketId, setSocketId] = useState15(null);
11611
11730
  const [actions, setActions] = useState15(/* @__PURE__ */ new Map());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.32",
3
+ "version": "0.5.33",
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",