@midscene/visualizer 1.5.2 → 1.5.3-beta-20260305031559.0

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 (40) hide show
  1. package/dist/es/component/blackboard/index.css +82 -4
  2. package/dist/es/component/blackboard/index.mjs +73 -301
  3. package/dist/es/component/player/index.css +144 -119
  4. package/dist/es/component/player/index.mjs +468 -830
  5. package/dist/es/component/player/remotion/StepScene.mjs +190 -0
  6. package/dist/es/component/player/remotion/derive-frame-state.mjs +207 -0
  7. package/dist/es/component/player/remotion/export-branded-video.mjs +210 -0
  8. package/dist/es/component/player/remotion/frame-calculator.mjs +149 -0
  9. package/dist/es/component/player/use-frame-player.mjs +88 -0
  10. package/dist/es/component/universal-playground/index.mjs +14 -1
  11. package/dist/es/hooks/usePlaygroundExecution.mjs +11 -7
  12. package/dist/es/store/store.mjs +9 -0
  13. package/dist/es/utils/replay-scripts.mjs +2 -1
  14. package/dist/lib/component/blackboard/index.css +82 -4
  15. package/dist/lib/component/blackboard/index.js +73 -307
  16. package/dist/lib/component/player/index.css +144 -119
  17. package/dist/lib/component/player/index.js +466 -828
  18. package/dist/lib/component/player/remotion/StepScene.js +224 -0
  19. package/dist/lib/component/player/remotion/derive-frame-state.js +241 -0
  20. package/dist/lib/component/player/remotion/export-branded-video.js +244 -0
  21. package/dist/lib/component/player/remotion/frame-calculator.js +186 -0
  22. package/dist/lib/component/player/use-frame-player.js +122 -0
  23. package/dist/lib/component/universal-playground/index.js +14 -1
  24. package/dist/lib/hooks/usePlaygroundExecution.js +11 -7
  25. package/dist/lib/store/store.js +9 -0
  26. package/dist/lib/utils/replay-scripts.js +2 -1
  27. package/dist/types/component/blackboard/index.d.ts +0 -4
  28. package/dist/types/component/player/index.d.ts +0 -1
  29. package/dist/types/component/player/remotion/StepScene.d.ts +9 -0
  30. package/dist/types/component/player/remotion/derive-frame-state.d.ts +38 -0
  31. package/dist/types/component/player/remotion/export-branded-video.d.ts +2 -0
  32. package/dist/types/component/player/remotion/frame-calculator.d.ts +35 -0
  33. package/dist/types/component/player/use-frame-player.d.ts +17 -0
  34. package/dist/types/hooks/usePlaygroundExecution.d.ts +15 -1
  35. package/dist/types/store/store.d.ts +2 -0
  36. package/dist/types/utils/replay-scripts.d.ts +1 -0
  37. package/package.json +5 -8
  38. package/dist/es/utils/pixi-loader.mjs +0 -42
  39. package/dist/lib/utils/pixi-loader.js +0 -82
  40. package/dist/types/utils/pixi-loader.d.ts +0 -5
@@ -0,0 +1,149 @@
1
+ const FPS = 30;
2
+ function calculateFrameMap(scripts) {
3
+ let baseImageWidth = 1920;
4
+ let baseImageHeight = 1080;
5
+ for (const s of scripts)if (('img' === s.type || 'insight' === s.type) && s.img) {
6
+ baseImageWidth = s.imageWidth || 1920;
7
+ baseImageHeight = s.imageHeight || 1080;
8
+ break;
9
+ }
10
+ const scriptFrames = [];
11
+ let currentFrame = 0;
12
+ for (const script of scripts){
13
+ const durationMs = script.duration;
14
+ switch(script.type){
15
+ case 'sleep':
16
+ {
17
+ const frames = Math.ceil(durationMs / 1000 * FPS);
18
+ scriptFrames.push({
19
+ type: 'sleep',
20
+ startFrame: currentFrame,
21
+ durationInFrames: frames,
22
+ title: script.title,
23
+ subTitle: script.subTitle,
24
+ taskId: script.taskId
25
+ });
26
+ currentFrame += frames;
27
+ break;
28
+ }
29
+ case 'img':
30
+ {
31
+ const frames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
32
+ const camera = script.camera;
33
+ const iw = script.imageWidth || baseImageWidth;
34
+ const ih = script.imageHeight || baseImageHeight;
35
+ const sf = {
36
+ type: 'img',
37
+ startFrame: currentFrame,
38
+ durationInFrames: frames,
39
+ img: script.img,
40
+ imageWidth: iw,
41
+ imageHeight: ih,
42
+ title: script.title,
43
+ subTitle: script.subTitle,
44
+ taskId: script.taskId
45
+ };
46
+ if (camera) {
47
+ var _camera_pointerLeft, _camera_pointerTop;
48
+ sf.cameraTarget = {
49
+ left: camera.left,
50
+ top: camera.top,
51
+ width: camera.width,
52
+ pointerLeft: null != (_camera_pointerLeft = camera.pointerLeft) ? _camera_pointerLeft : Math.round(iw / 2),
53
+ pointerTop: null != (_camera_pointerTop = camera.pointerTop) ? _camera_pointerTop : Math.round(ih / 2)
54
+ };
55
+ }
56
+ scriptFrames.push(sf);
57
+ currentFrame += frames;
58
+ break;
59
+ }
60
+ case 'insight':
61
+ {
62
+ const insightPhaseFrames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
63
+ const cameraDurationMs = script.insightCameraDuration || 0;
64
+ const cameraPhaseFrames = Math.ceil(cameraDurationMs / 1000 * FPS);
65
+ const totalFrames = insightPhaseFrames + cameraPhaseFrames;
66
+ const iw = script.imageWidth || baseImageWidth;
67
+ const ih = script.imageHeight || baseImageHeight;
68
+ const camera = script.camera;
69
+ const sf = {
70
+ type: 'insight',
71
+ startFrame: currentFrame,
72
+ durationInFrames: totalFrames,
73
+ img: script.img,
74
+ imageWidth: iw,
75
+ imageHeight: ih,
76
+ insightPhaseFrames,
77
+ cameraPhaseFrames,
78
+ highlightElement: script.highlightElement,
79
+ searchArea: script.searchArea,
80
+ title: script.title,
81
+ subTitle: script.subTitle,
82
+ taskId: script.taskId
83
+ };
84
+ if (camera) {
85
+ var _camera_pointerLeft1, _camera_pointerTop1;
86
+ sf.cameraTarget = {
87
+ left: camera.left,
88
+ top: camera.top,
89
+ width: camera.width,
90
+ pointerLeft: null != (_camera_pointerLeft1 = camera.pointerLeft) ? _camera_pointerLeft1 : Math.round(iw / 2),
91
+ pointerTop: null != (_camera_pointerTop1 = camera.pointerTop) ? _camera_pointerTop1 : Math.round(ih / 2)
92
+ };
93
+ }
94
+ scriptFrames.push(sf);
95
+ currentFrame += totalFrames;
96
+ break;
97
+ }
98
+ case 'clear-insight':
99
+ {
100
+ const frames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
101
+ scriptFrames.push({
102
+ type: 'clear-insight',
103
+ startFrame: currentFrame,
104
+ durationInFrames: frames,
105
+ title: script.title,
106
+ subTitle: script.subTitle,
107
+ taskId: script.taskId
108
+ });
109
+ currentFrame += frames;
110
+ break;
111
+ }
112
+ case 'spinning-pointer':
113
+ {
114
+ const frames = Math.max(Math.ceil(durationMs / 1000 * FPS), 1);
115
+ scriptFrames.push({
116
+ type: 'spinning-pointer',
117
+ startFrame: currentFrame,
118
+ durationInFrames: frames,
119
+ title: script.title,
120
+ subTitle: script.subTitle,
121
+ taskId: script.taskId
122
+ });
123
+ currentFrame += frames;
124
+ break;
125
+ }
126
+ case 'pointer':
127
+ scriptFrames.push({
128
+ type: 'pointer',
129
+ startFrame: currentFrame,
130
+ durationInFrames: 0,
131
+ pointerImg: script.img,
132
+ title: script.title,
133
+ subTitle: script.subTitle,
134
+ taskId: script.taskId
135
+ });
136
+ break;
137
+ }
138
+ }
139
+ const stepsDurationInFrames = Math.max(currentFrame, 1);
140
+ return {
141
+ scriptFrames,
142
+ totalDurationInFrames: stepsDurationInFrames,
143
+ fps: FPS,
144
+ stepsDurationInFrames,
145
+ imageWidth: baseImageWidth,
146
+ imageHeight: baseImageHeight
147
+ };
148
+ }
149
+ export { FPS, calculateFrameMap };
@@ -0,0 +1,88 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ function useFramePlayer(options) {
3
+ const { durationInFrames, fps, autoPlay = false, loop = false } = options;
4
+ const [currentFrame, setCurrentFrame] = useState(0);
5
+ const [playing, setPlaying] = useState(autoPlay);
6
+ const playingRef = useRef(playing);
7
+ const frameRef = useRef(currentFrame);
8
+ var _options_playbackRate;
9
+ const rateRef = useRef(null != (_options_playbackRate = options.playbackRate) ? _options_playbackRate : 1);
10
+ const durationRef = useRef(durationInFrames);
11
+ const fpsRef = useRef(fps);
12
+ const loopRef = useRef(loop);
13
+ playingRef.current = playing;
14
+ frameRef.current = currentFrame;
15
+ var _options_playbackRate1;
16
+ rateRef.current = null != (_options_playbackRate1 = options.playbackRate) ? _options_playbackRate1 : 1;
17
+ durationRef.current = durationInFrames;
18
+ fpsRef.current = fps;
19
+ loopRef.current = loop;
20
+ useEffect(()=>{
21
+ if (!playing) return;
22
+ let rafId;
23
+ let lastTime = null;
24
+ let accumulated = 0;
25
+ const tick = (now)=>{
26
+ if (null !== lastTime) {
27
+ const delta = (now - lastTime) * rateRef.current;
28
+ accumulated += delta;
29
+ const frameDuration = 1000 / fpsRef.current;
30
+ while(accumulated >= frameDuration){
31
+ accumulated -= frameDuration;
32
+ const next = frameRef.current + 1;
33
+ if (next >= durationRef.current) if (loopRef.current) {
34
+ frameRef.current = 0;
35
+ setCurrentFrame(0);
36
+ } else {
37
+ frameRef.current = durationRef.current - 1;
38
+ setCurrentFrame(durationRef.current - 1);
39
+ setPlaying(false);
40
+ return;
41
+ }
42
+ else {
43
+ frameRef.current = next;
44
+ setCurrentFrame(next);
45
+ }
46
+ }
47
+ }
48
+ lastTime = now;
49
+ rafId = requestAnimationFrame(tick);
50
+ };
51
+ rafId = requestAnimationFrame(tick);
52
+ return ()=>cancelAnimationFrame(rafId);
53
+ }, [
54
+ playing
55
+ ]);
56
+ const play = useCallback(()=>{
57
+ if (frameRef.current >= durationRef.current - 1) {
58
+ frameRef.current = 0;
59
+ setCurrentFrame(0);
60
+ }
61
+ setPlaying(true);
62
+ }, []);
63
+ const pause = useCallback(()=>setPlaying(false), []);
64
+ const toggle = useCallback(()=>{
65
+ if (playingRef.current) setPlaying(false);
66
+ else {
67
+ if (frameRef.current >= durationRef.current - 1) {
68
+ frameRef.current = 0;
69
+ setCurrentFrame(0);
70
+ }
71
+ setPlaying(true);
72
+ }
73
+ }, []);
74
+ const seekTo = useCallback((frame)=>{
75
+ const clamped = Math.max(0, Math.min(frame, durationRef.current - 1));
76
+ frameRef.current = clamped;
77
+ setCurrentFrame(clamped);
78
+ }, []);
79
+ return {
80
+ currentFrame,
81
+ playing,
82
+ play,
83
+ pause,
84
+ toggle,
85
+ seekTo
86
+ };
87
+ }
88
+ export { useFramePlayer };
@@ -100,7 +100,20 @@ function UniversalPlayground({ playgroundSDK, storage, contextProvider, config:
100
100
  playgroundSDK
101
101
  ]);
102
102
  const { loading, setLoading, infoList, setInfoList, actionSpace, actionSpaceLoading, uiContextPreview, setUiContextPreview, showScrollToBottomButton, verticalMode, replayCounter, setReplayCounter, infoListRef, currentRunningIdRef, interruptedFlagRef, clearInfoList, handleScrollToBottom } = usePlaygroundState(playgroundSDK, effectiveStorage, contextProvider, branding.targetName);
103
- const { handleRun: executeAction, handleStop, canStop } = usePlaygroundExecution(playgroundSDK, effectiveStorage, actionSpace, loading, setLoading, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef);
103
+ const { handleRun: executeAction, handleStop, canStop } = usePlaygroundExecution({
104
+ playgroundSDK,
105
+ storage: effectiveStorage,
106
+ actionSpace,
107
+ loading,
108
+ setLoading,
109
+ setInfoList,
110
+ replayCounter,
111
+ setReplayCounter,
112
+ verticalMode,
113
+ currentRunningIdRef,
114
+ interruptedFlagRef,
115
+ deviceType: componentConfig.deviceType
116
+ });
104
117
  useEffect(()=>{
105
118
  if ((null == playgroundSDK ? void 0 : playgroundSDK.overrideConfig) && config) playgroundSDK.overrideConfig(config).catch((error)=>{
106
119
  console.error('Failed to override SDK config:', error);
@@ -88,7 +88,7 @@ function buildProgressContent(task) {
88
88
  const description = paramStr(task);
89
89
  return description ? `${action} - ${description}` : action;
90
90
  }
91
- function wrapExecutionDumpForReplay(dump) {
91
+ function wrapExecutionDumpForReplay(dump, deviceType) {
92
92
  const modelBriefsSet = new Set();
93
93
  if ((null == dump ? void 0 : dump.tasks) && Array.isArray(dump.tasks)) dump.tasks.forEach((task)=>{
94
94
  if (task.usage) {
@@ -106,10 +106,12 @@ function wrapExecutionDumpForReplay(dump) {
106
106
  modelBriefs,
107
107
  executions: [
108
108
  dump
109
- ]
109
+ ],
110
+ deviceType
110
111
  };
111
112
  }
112
- function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, setLoading, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef) {
113
+ function usePlaygroundExecution(options) {
114
+ const { playgroundSDK, storage, actionSpace, loading, setLoading, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef, deviceType } = options;
113
115
  const { deepLocate, deepThink, screenshotIncluded, domIncluded } = useEnvConfig();
114
116
  const handleRun = useCallback((value)=>_async_to_generator(function*() {
115
117
  if (!playgroundSDK) return void console.warn('PlaygroundSDK is not available');
@@ -206,7 +208,7 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
206
208
  let counter = replayCounter;
207
209
  if (null == result ? void 0 : result.dump) {
208
210
  if (result.dump.tasks && Array.isArray(result.dump.tasks)) {
209
- const groupedDump = wrapExecutionDumpForReplay(result.dump);
211
+ const groupedDump = wrapExecutionDumpForReplay(result.dump, deviceType);
210
212
  const info = allScriptsFromDump(groupedDump);
211
213
  setReplayCounter((c)=>c + 1);
212
214
  replayInfo = info;
@@ -264,7 +266,8 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
264
266
  deepLocate,
265
267
  deepThink,
266
268
  screenshotIncluded,
267
- domIncluded
269
+ domIncluded,
270
+ deviceType
268
271
  ]);
269
272
  const handleStop = useCallback(()=>_async_to_generator(function*() {
270
273
  const thisRunningId = currentRunningIdRef.current;
@@ -291,7 +294,7 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
291
294
  let replayInfo = null;
292
295
  let counter = replayCounter;
293
296
  if ((null == (_executionData_dump = executionData.dump) ? void 0 : _executionData_dump.tasks) && Array.isArray(executionData.dump.tasks)) {
294
- const groupedDump = wrapExecutionDumpForReplay(executionData.dump);
297
+ const groupedDump = wrapExecutionDumpForReplay(executionData.dump, deviceType);
295
298
  replayInfo = allScriptsFromDump(groupedDump);
296
299
  setReplayCounter((c)=>c + 1);
297
300
  counter = replayCounter + 1;
@@ -349,7 +352,8 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
349
352
  setLoading,
350
353
  setInfoList,
351
354
  verticalMode,
352
- replayCounter
355
+ replayCounter,
356
+ deviceType
353
357
  ]);
354
358
  const canStop = loading && !!currentRunningIdRef.current && !!playgroundSDK && !!playgroundSDK.cancelExecution;
355
359
  return {
@@ -33,6 +33,7 @@ const ELEMENTS_VISIBLE_KEY = 'midscene-elements-visible';
33
33
  const MODEL_CALL_DETAILS_KEY = 'midscene-model-call-details';
34
34
  const DARK_MODE_KEY = 'midscene-dark-mode';
35
35
  const PLAYBACK_SPEED_KEY = 'midscene-playback-speed';
36
+ const SUBTITLE_ENABLED_KEY = 'midscene-subtitle-enabled';
36
37
  const parseBooleanParam = (value)=>{
37
38
  if (null === value) return;
38
39
  const normalized = value.trim().toLowerCase();
@@ -62,6 +63,7 @@ const useGlobalPreference = store_create((set)=>{
62
63
  const savedDarkMode = 'true' === localStorage.getItem(DARK_MODE_KEY);
63
64
  const parsedPlaybackSpeed = Number.parseFloat(localStorage.getItem(PLAYBACK_SPEED_KEY) || '1');
64
65
  const savedPlaybackSpeed = Number.isNaN(parsedPlaybackSpeed) ? 1 : parsedPlaybackSpeed;
66
+ const savedSubtitleEnabled = 'false' !== localStorage.getItem(SUBTITLE_ENABLED_KEY);
65
67
  const autoZoomFromQuery = getQueryPreference('focusOnCursor');
66
68
  const elementsVisibleFromQuery = getQueryPreference('showElementMarkers');
67
69
  const darkModeFromQuery = getQueryPreference('darkMode');
@@ -114,6 +116,13 @@ const useGlobalPreference = store_create((set)=>{
114
116
  playbackSpeed: speed
115
117
  });
116
118
  localStorage.setItem(PLAYBACK_SPEED_KEY, speed.toString());
119
+ },
120
+ subtitleEnabled: savedSubtitleEnabled,
121
+ setSubtitleEnabled: (enabled)=>{
122
+ set({
123
+ subtitleEnabled: enabled
124
+ });
125
+ localStorage.setItem(SUBTITLE_ENABLED_KEY, enabled.toString());
117
126
  }
118
127
  };
119
128
  });
@@ -158,7 +158,8 @@ const allScriptsFromDump = (dump)=>{
158
158
  width: firstWidth,
159
159
  height: firstHeight,
160
160
  sdkVersion,
161
- modelBriefs
161
+ modelBriefs,
162
+ deviceType: normalizedDump.deviceType
162
163
  };
163
164
  };
164
165
  const generateAnimationScripts = (execution, task, imageWidth, imageHeight, executionIndex = 0)=>{
@@ -21,21 +21,99 @@
21
21
  max-width: 500px;
22
22
  }
23
23
 
24
- .blackboard-filter {
25
- margin: 10px 0;
24
+ .blackboard-main-content {
25
+ position: relative;
26
+ overflow: hidden;
26
27
  }
27
28
 
28
- .blackboard-main-content canvas {
29
+ .blackboard-screenshot {
29
30
  box-sizing: border-box;
30
31
  border: 1px solid #888;
31
32
  width: 100%;
33
+ display: block;
34
+ }
35
+
36
+ .blackboard-overlay {
37
+ pointer-events: none;
38
+ width: 100%;
39
+ position: absolute;
40
+ top: 0;
41
+ left: 0;
42
+ }
43
+
44
+ .blackboard-rect {
45
+ box-sizing: border-box;
46
+ pointer-events: none;
47
+ position: absolute;
48
+ }
49
+
50
+ .blackboard-rect-label {
51
+ white-space: nowrap;
52
+ padding: 1px 4px;
53
+ font-size: 14px;
54
+ font-weight: 600;
55
+ line-height: 1.4;
56
+ position: absolute;
57
+ bottom: 100%;
58
+ left: 0;
59
+ }
60
+
61
+ .blackboard-rect-search {
62
+ background: rgba(2, 131, 145, .4);
63
+ border: 1px solid #028391;
64
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4);
65
+ }
66
+
67
+ .blackboard-rect-search .blackboard-rect-label {
68
+ color: #028391;
69
+ }
70
+
71
+ .blackboard-rect-highlight {
72
+ background: rgba(253, 89, 7, .4);
73
+ border: 1px solid #fd5907;
74
+ animation: 1.2s ease-in-out infinite blackboard-pulse;
75
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4);
76
+ }
77
+
78
+ .blackboard-rect-highlight .blackboard-rect-label {
79
+ color: #000;
80
+ }
81
+
82
+ .blackboard-point {
83
+ pointer-events: none;
84
+ background: rgba(253, 89, 7, .4);
85
+ border: 1px solid #fd5907;
86
+ border-radius: 50%;
87
+ width: 20px;
88
+ height: 20px;
89
+ margin-top: -10px;
90
+ margin-left: -10px;
91
+ animation: 1.2s ease-in-out infinite blackboard-pulse;
92
+ position: absolute;
93
+ box-shadow: 0 0 8px rgba(253, 89, 7, .5);
94
+ }
95
+
96
+ @keyframes blackboard-pulse {
97
+ 0%, 100% {
98
+ opacity: .4;
99
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4);
100
+ }
101
+
102
+ 50% {
103
+ opacity: 1;
104
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4), 0 0 16px rgba(253, 89, 7, .5);
105
+ }
32
106
  }
33
107
 
34
108
  [data-theme="dark"] .blackboard .footer, [data-theme="dark"] .blackboard .bottom-tip-item {
35
109
  color: rgba(255, 255, 255, .45);
36
110
  }
37
111
 
38
- [data-theme="dark"] .blackboard-main-content canvas {
112
+ [data-theme="dark"] .blackboard-screenshot {
39
113
  border-color: rgba(255, 255, 255, .12);
40
114
  }
41
115
 
116
+ [data-theme="dark"] .blackboard-rect-highlight .blackboard-rect-label {
117
+ color: #fff;
118
+ }
119
+