@midscene/visualizer 1.7.6 → 1.7.7-beta-20260428102047.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.
- package/dist/es/component/player/index.mjs +23 -18
- package/dist/es/component/player/report-download.mjs +61 -0
- package/dist/es/component/player/scenes/export-branded-video.mjs +19 -7
- package/dist/es/component/playground-result/index.mjs +11 -6
- package/dist/es/component/screenshot-viewer/index.css +17 -0
- package/dist/es/component/screenshot-viewer/index.mjs +38 -28
- package/dist/es/component/universal-playground/index.mjs +2 -1
- package/dist/lib/component/player/index.js +23 -18
- package/dist/lib/component/player/report-download.js +98 -0
- package/dist/lib/component/player/scenes/export-branded-video.js +22 -7
- package/dist/lib/component/playground-result/index.js +11 -6
- package/dist/lib/component/screenshot-viewer/index.css +17 -0
- package/dist/lib/component/screenshot-viewer/index.js +38 -28
- package/dist/lib/component/universal-playground/index.js +2 -1
- package/dist/types/component/player/index.d.ts +2 -0
- package/dist/types/component/player/report-download.d.ts +32 -0
- package/dist/types/component/player/scenes/export-branded-video.d.ts +16 -1
- package/dist/types/component/playground-result/index.d.ts +2 -2
- package/dist/types/component/screenshot-viewer/index.d.ts +4 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/types.d.ts +10 -0
- package/package.json +5 -5
|
@@ -7,6 +7,7 @@ import { Dropdown, Spin, Switch, Tooltip, message } from "antd";
|
|
|
7
7
|
import global_perspective from "../../icons/global-perspective.mjs";
|
|
8
8
|
import player_setting from "../../icons/player-setting.mjs";
|
|
9
9
|
import { useGlobalPreference } from "../../store/store.mjs";
|
|
10
|
+
import { triggerReportDownload } from "./report-download.mjs";
|
|
10
11
|
import { StepsTimeline } from "./scenes/StepScene.mjs";
|
|
11
12
|
import { exportBrandedVideo } from "./scenes/export-branded-video.mjs";
|
|
12
13
|
import { calculateFrameMap } from "./scenes/frame-calculator.mjs";
|
|
@@ -38,22 +39,6 @@ function _async_to_generator(fn) {
|
|
|
38
39
|
});
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
const downloadReport = (content)=>{
|
|
42
|
-
const blob = new Blob([
|
|
43
|
-
content
|
|
44
|
-
], {
|
|
45
|
-
type: 'text/html'
|
|
46
|
-
});
|
|
47
|
-
const url = URL.createObjectURL(blob);
|
|
48
|
-
const a = document.createElement('a');
|
|
49
|
-
a.href = url;
|
|
50
|
-
a.download = 'midscene_report.html';
|
|
51
|
-
a.style.display = 'none';
|
|
52
|
-
document.body.appendChild(a);
|
|
53
|
-
a.click();
|
|
54
|
-
document.body.removeChild(a);
|
|
55
|
-
setTimeout(()=>URL.revokeObjectURL(url), 0);
|
|
56
|
-
};
|
|
57
42
|
function deriveTaskId(scriptFrames, stepsFrame) {
|
|
58
43
|
let taskId = null;
|
|
59
44
|
for (const sf of scriptFrames){
|
|
@@ -168,6 +153,21 @@ function Player(props) {
|
|
|
168
153
|
frameMap,
|
|
169
154
|
player.currentFrame
|
|
170
155
|
]);
|
|
156
|
+
const handleDownloadReport = useCallback(()=>_async_to_generator(function*() {
|
|
157
|
+
if (!(null == props ? void 0 : props.reportFileContent)) return;
|
|
158
|
+
try {
|
|
159
|
+
yield triggerReportDownload({
|
|
160
|
+
content: props.reportFileContent,
|
|
161
|
+
onDownloadReport: props.onDownloadReport
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
165
|
+
message.error(`Failed to download report: ${errorMessage}`);
|
|
166
|
+
}
|
|
167
|
+
})(), [
|
|
168
|
+
null == props ? void 0 : props.onDownloadReport,
|
|
169
|
+
null == props ? void 0 : props.reportFileContent
|
|
170
|
+
]);
|
|
171
171
|
const subtitle = useMemo(()=>{
|
|
172
172
|
if (!currentFrameState) return null;
|
|
173
173
|
if (!currentFrameState.title && !currentFrameState.subTitle) return null;
|
|
@@ -249,7 +249,9 @@ function Player(props) {
|
|
|
249
249
|
setIsExporting(true);
|
|
250
250
|
setExportProgress(0);
|
|
251
251
|
try {
|
|
252
|
-
yield exportBrandedVideo(frameMap,
|
|
252
|
+
yield exportBrandedVideo(frameMap, {
|
|
253
|
+
autoZoom
|
|
254
|
+
}, (pct)=>setExportProgress(Math.round(100 * pct)));
|
|
253
255
|
message.success('Video exported');
|
|
254
256
|
} catch (e) {
|
|
255
257
|
console.error('Export failed:', e);
|
|
@@ -259,6 +261,7 @@ function Player(props) {
|
|
|
259
261
|
setExportProgress(0);
|
|
260
262
|
}
|
|
261
263
|
})(), [
|
|
264
|
+
autoZoom,
|
|
262
265
|
frameMap,
|
|
263
266
|
isExporting
|
|
264
267
|
]);
|
|
@@ -423,7 +426,9 @@ function Player(props) {
|
|
|
423
426
|
title: "Download Report",
|
|
424
427
|
children: /*#__PURE__*/ jsx("div", {
|
|
425
428
|
className: "status-icon",
|
|
426
|
-
onClick: ()=>
|
|
429
|
+
onClick: ()=>{
|
|
430
|
+
handleDownloadReport();
|
|
431
|
+
},
|
|
427
432
|
children: /*#__PURE__*/ jsx(DownloadOutlined, {})
|
|
428
433
|
})
|
|
429
434
|
}) : null,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
|
+
try {
|
|
3
|
+
var info = gen[key](arg);
|
|
4
|
+
var value = info.value;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
reject(error);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (info.done) resolve(value);
|
|
10
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
11
|
+
}
|
|
12
|
+
function _async_to_generator(fn) {
|
|
13
|
+
return function() {
|
|
14
|
+
var self = this, args = arguments;
|
|
15
|
+
return new Promise(function(resolve, reject) {
|
|
16
|
+
var gen = fn.apply(self, args);
|
|
17
|
+
function _next(value) {
|
|
18
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
19
|
+
}
|
|
20
|
+
function _throw(err) {
|
|
21
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
22
|
+
}
|
|
23
|
+
_next(void 0);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const DEFAULT_REPORT_FILE_NAME = 'midscene_report.html';
|
|
28
|
+
function triggerReportDownload(options) {
|
|
29
|
+
return _async_to_generator(function*() {
|
|
30
|
+
const { content, defaultFileName = DEFAULT_REPORT_FILE_NAME, onDownloadReport, documentRef, urlRef, blobFactory, scheduleRevoke } = options;
|
|
31
|
+
if (onDownloadReport) return void (yield onDownloadReport({
|
|
32
|
+
content,
|
|
33
|
+
defaultFileName
|
|
34
|
+
}));
|
|
35
|
+
const activeDocument = null != documentRef ? documentRef : globalThis.document;
|
|
36
|
+
if (!activeDocument) throw new Error('Report download requires a document context.');
|
|
37
|
+
const activeUrl = null != urlRef ? urlRef : globalThis.URL;
|
|
38
|
+
if (!(null == activeUrl ? void 0 : activeUrl.createObjectURL) || !(null == activeUrl ? void 0 : activeUrl.revokeObjectURL)) throw new Error('Report download requires URL.createObjectURL support.');
|
|
39
|
+
const createBlob = null != blobFactory ? blobFactory : (parts, blobOptions)=>new Blob(parts, blobOptions);
|
|
40
|
+
const blob = createBlob([
|
|
41
|
+
content
|
|
42
|
+
], {
|
|
43
|
+
type: 'text/html'
|
|
44
|
+
});
|
|
45
|
+
const url = activeUrl.createObjectURL(blob);
|
|
46
|
+
const anchor = activeDocument.createElement('a');
|
|
47
|
+
anchor.href = url;
|
|
48
|
+
anchor.download = defaultFileName;
|
|
49
|
+
anchor.style.display = 'none';
|
|
50
|
+
activeDocument.body.appendChild(anchor);
|
|
51
|
+
try {
|
|
52
|
+
anchor.click();
|
|
53
|
+
} finally{
|
|
54
|
+
activeDocument.body.removeChild(anchor);
|
|
55
|
+
(null != scheduleRevoke ? scheduleRevoke : (callback)=>setTimeout(callback, 0))(()=>{
|
|
56
|
+
activeUrl.revokeObjectURL(url);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
}
|
|
61
|
+
export { DEFAULT_REPORT_FILE_NAME, triggerReportDownload };
|
|
@@ -38,6 +38,18 @@ function clamp(v, lo, hi) {
|
|
|
38
38
|
function lerp(a, b, t) {
|
|
39
39
|
return a + (b - a) * t;
|
|
40
40
|
}
|
|
41
|
+
function resolveExportCamera(prevCamera, camera, imageWidth, progress, autoZoom) {
|
|
42
|
+
if (!autoZoom) return {
|
|
43
|
+
camLeft: 0,
|
|
44
|
+
camTop: 0,
|
|
45
|
+
camWidth: imageWidth
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
camLeft: lerp(prevCamera.left, camera.left, progress),
|
|
49
|
+
camTop: lerp(prevCamera.top, camera.top, progress),
|
|
50
|
+
camWidth: lerp(prevCamera.width, camera.width, progress)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
41
53
|
function loadImage(src) {
|
|
42
54
|
return new Promise((resolve, reject)=>{
|
|
43
55
|
const img = new Image();
|
|
@@ -95,16 +107,14 @@ function drawSpinningPointer(ctx, img, x, y, elapsedMs) {
|
|
|
95
107
|
ctx.drawImage(img, -11, -14, 22, 28);
|
|
96
108
|
ctx.restore();
|
|
97
109
|
}
|
|
98
|
-
function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg) {
|
|
110
|
+
function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg, autoZoom) {
|
|
99
111
|
const { scriptFrames, imageWidth: baseW, imageHeight: baseH, fps } = frameMap;
|
|
100
112
|
const st = deriveFrameState(scriptFrames, stepsFrame, baseW, baseH, fps);
|
|
101
113
|
if (!st.img) return;
|
|
102
114
|
const { img, prevImg, imageWidth: imgW, imageHeight: imgH, camera, prevCamera, pointerMoved, imageChanged, rawProgress, frameInScript: fInScript, spinning, spinningElapsedMs, insights } = st;
|
|
103
115
|
const pT = pointerMoved ? Math.min(rawProgress / POINTER_PHASE, 1) : rawProgress;
|
|
104
116
|
const cT = pointerMoved ? rawProgress <= POINTER_PHASE ? 0 : Math.min((rawProgress - POINTER_PHASE) / (1 - POINTER_PHASE), 1) : rawProgress;
|
|
105
|
-
const camL =
|
|
106
|
-
const camT2 = lerp(prevCamera.top, camera.top, cT);
|
|
107
|
-
const camW = lerp(prevCamera.width, camera.width, cT);
|
|
117
|
+
const { camLeft: camL, camTop: camT2, camWidth: camW } = resolveExportCamera(prevCamera, camera, imgW, cT, autoZoom);
|
|
108
118
|
const ptrX = lerp(prevCamera.pointerLeft, camera.pointerLeft, pT);
|
|
109
119
|
const ptrY = lerp(prevCamera.pointerTop, camera.pointerTop, pT);
|
|
110
120
|
const zoom = imgW / camW;
|
|
@@ -141,9 +151,11 @@ function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg) {
|
|
|
141
151
|
if (spinning && spinnerImg) drawSpinningPointer(ctx, spinnerImg, sX, sY, spinningElapsedMs);
|
|
142
152
|
if (!spinning && hasPtrData && cursorImg) ctx.drawImage(cursorImg, sX - 3, sY - 2, 22, 28);
|
|
143
153
|
}
|
|
144
|
-
function exportBrandedVideo(frameMap, onProgress) {
|
|
154
|
+
function exportBrandedVideo(frameMap, options, onProgress) {
|
|
145
155
|
return _async_to_generator(function*() {
|
|
146
156
|
const { totalDurationInFrames: total, fps } = frameMap;
|
|
157
|
+
var _options_autoZoom;
|
|
158
|
+
const autoZoom = null != (_options_autoZoom = null == options ? void 0 : options.autoZoom) ? _options_autoZoom : true;
|
|
147
159
|
const imgSrcs = new Set();
|
|
148
160
|
for (const sf of frameMap.scriptFrames)if (sf.img) imgSrcs.add(sf.img);
|
|
149
161
|
const imgCache = new Map();
|
|
@@ -200,7 +212,7 @@ function exportBrandedVideo(frameMap, onProgress) {
|
|
|
200
212
|
if (targetFrame > lastFrame) {
|
|
201
213
|
lastFrame = targetFrame;
|
|
202
214
|
ctx.clearRect(0, 0, W, H);
|
|
203
|
-
drawSteps(ctx, targetFrame, frameMap, imgCache, cursorImg, spinnerImg);
|
|
215
|
+
drawSteps(ctx, targetFrame, frameMap, imgCache, cursorImg, spinnerImg, autoZoom);
|
|
204
216
|
null == onProgress || onProgress((targetFrame + 1) / total);
|
|
205
217
|
}
|
|
206
218
|
if (targetFrame < total - 1) requestAnimationFrame(tick);
|
|
@@ -210,4 +222,4 @@ function exportBrandedVideo(frameMap, onProgress) {
|
|
|
210
222
|
});
|
|
211
223
|
})();
|
|
212
224
|
}
|
|
213
|
-
export { exportBrandedVideo };
|
|
225
|
+
export { exportBrandedVideo, resolveExportCamera };
|
|
@@ -6,7 +6,7 @@ import { emptyResultTip, serverLaunchTip } from "../misc/index.mjs";
|
|
|
6
6
|
import { Player } from "../player/index.mjs";
|
|
7
7
|
import shiny_text from "../shiny-text/index.mjs";
|
|
8
8
|
import "./index.css";
|
|
9
|
-
const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, replayScriptsInfo, replayCounter, loadingProgressText, verticalMode = false, notReadyMessage, fitMode, autoZoom, actionType, canDownloadReport })=>{
|
|
9
|
+
const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, replayScriptsInfo, replayCounter, loadingProgressText, verticalMode = false, notReadyMessage, fitMode, autoZoom, actionType, canDownloadReport, onDownloadReport })=>{
|
|
10
10
|
let resultWrapperClassName = 'result-wrapper';
|
|
11
11
|
if (verticalMode) resultWrapperClassName += ' vertical-mode-result';
|
|
12
12
|
if (replayScriptsInfo && verticalMode) resultWrapperClassName += ' result-wrapper-compact';
|
|
@@ -78,7 +78,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
78
78
|
reportFileContent: result.reportHTML || null,
|
|
79
79
|
fitMode: fitMode,
|
|
80
80
|
autoZoom: autoZoom,
|
|
81
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
81
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
82
|
+
onDownloadReport: onDownloadReport
|
|
82
83
|
}, replayCounter)
|
|
83
84
|
})
|
|
84
85
|
]
|
|
@@ -129,7 +130,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
129
130
|
reportFileContent: reportContent,
|
|
130
131
|
fitMode: fitMode,
|
|
131
132
|
autoZoom: autoZoom,
|
|
132
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
133
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
134
|
+
onDownloadReport: onDownloadReport
|
|
133
135
|
}, replayCounter)
|
|
134
136
|
})
|
|
135
137
|
]
|
|
@@ -145,7 +147,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
145
147
|
reportFileContent: reportContent,
|
|
146
148
|
fitMode: fitMode,
|
|
147
149
|
autoZoom: autoZoom,
|
|
148
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
150
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
151
|
+
onDownloadReport: onDownloadReport
|
|
149
152
|
}, replayCounter);
|
|
150
153
|
} else if (shouldPrioritizeResult && (null == result ? void 0 : result.result) !== void 0 && (null == result ? void 0 : result.reportHTML)) {
|
|
151
154
|
const resultOutput = 'string' == typeof (null == result ? void 0 : result.result) ? /*#__PURE__*/ jsx("pre", {
|
|
@@ -187,7 +190,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
187
190
|
reportFileContent: result.reportHTML,
|
|
188
191
|
fitMode: fitMode,
|
|
189
192
|
autoZoom: autoZoom,
|
|
190
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
193
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
194
|
+
onDownloadReport: onDownloadReport
|
|
191
195
|
}, replayCounter)
|
|
192
196
|
})
|
|
193
197
|
]
|
|
@@ -203,7 +207,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
203
207
|
reportFileContent: result.reportHTML,
|
|
204
208
|
fitMode: fitMode,
|
|
205
209
|
autoZoom: autoZoom,
|
|
206
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
210
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
211
|
+
onDownloadReport: onDownloadReport
|
|
207
212
|
}, replayCounter);
|
|
208
213
|
else if ((null == result ? void 0 : result.result) !== void 0) resultDataToShow = 'string' == typeof (null == result ? void 0 : result.result) ? /*#__PURE__*/ jsx("pre", {
|
|
209
214
|
children: null == result ? void 0 : result.result
|
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
display: flex;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
.screenshot-viewer.screen-only {
|
|
8
|
+
min-height: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.screenshot-viewer.screen-only > .screenshot-content {
|
|
12
|
+
flex: 1;
|
|
13
|
+
min-height: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.screenshot-viewer.screen-only > .screenshot-content .screenshot-image {
|
|
17
|
+
border-radius: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
max-width: none;
|
|
20
|
+
height: 100%;
|
|
21
|
+
max-height: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
.screenshot-viewer.offline, .screenshot-viewer.loading, .screenshot-viewer.error {
|
|
8
25
|
text-align: center;
|
|
9
26
|
color: #666;
|
|
@@ -29,13 +29,18 @@ function _async_to_generator(fn) {
|
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating = false, mjpegUrl }) {
|
|
32
|
+
function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating = false, mjpegUrl, mode = 'default' }) {
|
|
33
33
|
const [screenshot, setScreenshot] = useState(null);
|
|
34
34
|
const [loading, setLoading] = useState(false);
|
|
35
35
|
const [error, setError] = useState(null);
|
|
36
36
|
const [lastUpdateTime, setLastUpdateTime] = useState(0);
|
|
37
37
|
const [interfaceInfo, setInterfaceInfo] = useState(null);
|
|
38
38
|
const isMjpeg = Boolean(mjpegUrl && serverOnline);
|
|
39
|
+
const showChrome = 'screen-only' !== mode;
|
|
40
|
+
const rootClassName = [
|
|
41
|
+
'screenshot-viewer',
|
|
42
|
+
'screen-only' === mode && 'screen-only'
|
|
43
|
+
].filter(Boolean).join(' ');
|
|
39
44
|
const pollingIntervalRef = useRef(null);
|
|
40
45
|
const isPollingPausedRef = useRef(false);
|
|
41
46
|
const fetchScreenshot = useCallback((isManual = false)=>_async_to_generator(function*() {
|
|
@@ -148,7 +153,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
148
153
|
stopPolling
|
|
149
154
|
]);
|
|
150
155
|
if (!serverOnline) return /*#__PURE__*/ jsx("div", {
|
|
151
|
-
className:
|
|
156
|
+
className: `${rootClassName} offline`,
|
|
152
157
|
children: /*#__PURE__*/ jsxs("div", {
|
|
153
158
|
className: "screenshot-placeholder",
|
|
154
159
|
children: [
|
|
@@ -162,7 +167,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
162
167
|
})
|
|
163
168
|
});
|
|
164
169
|
if (!isMjpeg && loading && !screenshot) return /*#__PURE__*/ jsxs("div", {
|
|
165
|
-
className:
|
|
170
|
+
className: `${rootClassName} loading`,
|
|
166
171
|
children: [
|
|
167
172
|
/*#__PURE__*/ jsx(Spin, {
|
|
168
173
|
size: "large"
|
|
@@ -173,7 +178,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
173
178
|
]
|
|
174
179
|
});
|
|
175
180
|
if (!isMjpeg && error && !screenshot) return /*#__PURE__*/ jsx("div", {
|
|
176
|
-
className:
|
|
181
|
+
className: `${rootClassName} error`,
|
|
177
182
|
children: /*#__PURE__*/ jsxs("div", {
|
|
178
183
|
className: "screenshot-placeholder",
|
|
179
184
|
children: [
|
|
@@ -195,8 +200,35 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
195
200
|
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
196
201
|
return new Date(timestamp).toLocaleTimeString();
|
|
197
202
|
};
|
|
203
|
+
const screenshotContent = /*#__PURE__*/ jsx("div", {
|
|
204
|
+
className: "screenshot-content",
|
|
205
|
+
children: isMjpeg ? /*#__PURE__*/ jsx("img", {
|
|
206
|
+
src: mjpegUrl,
|
|
207
|
+
alt: "Device Live Stream",
|
|
208
|
+
className: "screenshot-image"
|
|
209
|
+
}) : screenshot ? /*#__PURE__*/ jsx("img", {
|
|
210
|
+
src: screenshot.startsWith('data:image/') ? screenshot : `data:image/png;base64,${screenshot}`,
|
|
211
|
+
alt: "Device Screenshot",
|
|
212
|
+
className: "screenshot-image",
|
|
213
|
+
onLoad: ()=>console.log('Screenshot image loaded successfully'),
|
|
214
|
+
onError: (e)=>{
|
|
215
|
+
console.error('Screenshot image load error:', e);
|
|
216
|
+
console.error('Screenshot data preview:', screenshot.substring(0, 100));
|
|
217
|
+
setError('Failed to load screenshot image');
|
|
218
|
+
}
|
|
219
|
+
}) : /*#__PURE__*/ jsx("div", {
|
|
220
|
+
className: "screenshot-placeholder",
|
|
221
|
+
children: /*#__PURE__*/ jsx("p", {
|
|
222
|
+
children: "No screenshot available"
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
if (!showChrome) return /*#__PURE__*/ jsx("div", {
|
|
227
|
+
className: rootClassName,
|
|
228
|
+
children: screenshotContent
|
|
229
|
+
});
|
|
198
230
|
return /*#__PURE__*/ jsxs("div", {
|
|
199
|
-
className:
|
|
231
|
+
className: rootClassName,
|
|
200
232
|
children: [
|
|
201
233
|
/*#__PURE__*/ jsx("div", {
|
|
202
234
|
className: "screenshot-header",
|
|
@@ -258,29 +290,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
258
290
|
})
|
|
259
291
|
]
|
|
260
292
|
}),
|
|
261
|
-
|
|
262
|
-
className: "screenshot-content",
|
|
263
|
-
children: isMjpeg ? /*#__PURE__*/ jsx("img", {
|
|
264
|
-
src: mjpegUrl,
|
|
265
|
-
alt: "Device Live Stream",
|
|
266
|
-
className: "screenshot-image"
|
|
267
|
-
}) : screenshot ? /*#__PURE__*/ jsx("img", {
|
|
268
|
-
src: screenshot.startsWith('data:image/') ? screenshot : `data:image/png;base64,${screenshot}`,
|
|
269
|
-
alt: "Device Screenshot",
|
|
270
|
-
className: "screenshot-image",
|
|
271
|
-
onLoad: ()=>console.log('Screenshot image loaded successfully'),
|
|
272
|
-
onError: (e)=>{
|
|
273
|
-
console.error('Screenshot image load error:', e);
|
|
274
|
-
console.error('Screenshot data preview:', screenshot.substring(0, 100));
|
|
275
|
-
setError('Failed to load screenshot image');
|
|
276
|
-
}
|
|
277
|
-
}) : /*#__PURE__*/ jsx("div", {
|
|
278
|
-
className: "screenshot-placeholder",
|
|
279
|
-
children: /*#__PURE__*/ jsx("p", {
|
|
280
|
-
children: "No screenshot available"
|
|
281
|
-
})
|
|
282
|
-
})
|
|
283
|
-
})
|
|
293
|
+
screenshotContent
|
|
284
294
|
]
|
|
285
295
|
})
|
|
286
296
|
]
|
|
@@ -335,7 +335,8 @@ function UniversalPlayground({ playgroundSDK, storage, contextProvider, config:
|
|
|
335
335
|
loadingProgressText: item.loadingProgressText || '',
|
|
336
336
|
verticalMode: item.verticalMode || false,
|
|
337
337
|
fitMode: "width",
|
|
338
|
-
actionType: item.actionType
|
|
338
|
+
actionType: item.actionType,
|
|
339
|
+
onDownloadReport: componentConfig.onDownloadReport
|
|
339
340
|
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
340
341
|
children: [
|
|
341
342
|
/*#__PURE__*/ jsx("div", {
|
|
@@ -46,6 +46,7 @@ var global_perspective_js_default = /*#__PURE__*/ __webpack_require__.n(global_p
|
|
|
46
46
|
const player_setting_js_namespaceObject = require("../../icons/player-setting.js");
|
|
47
47
|
var player_setting_js_default = /*#__PURE__*/ __webpack_require__.n(player_setting_js_namespaceObject);
|
|
48
48
|
const store_js_namespaceObject = require("../../store/store.js");
|
|
49
|
+
const external_report_download_js_namespaceObject = require("./report-download.js");
|
|
49
50
|
const StepScene_js_namespaceObject = require("./scenes/StepScene.js");
|
|
50
51
|
const export_branded_video_js_namespaceObject = require("./scenes/export-branded-video.js");
|
|
51
52
|
const frame_calculator_js_namespaceObject = require("./scenes/frame-calculator.js");
|
|
@@ -77,22 +78,6 @@ function _async_to_generator(fn) {
|
|
|
77
78
|
});
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
|
-
const downloadReport = (content)=>{
|
|
81
|
-
const blob = new Blob([
|
|
82
|
-
content
|
|
83
|
-
], {
|
|
84
|
-
type: 'text/html'
|
|
85
|
-
});
|
|
86
|
-
const url = URL.createObjectURL(blob);
|
|
87
|
-
const a = document.createElement('a');
|
|
88
|
-
a.href = url;
|
|
89
|
-
a.download = 'midscene_report.html';
|
|
90
|
-
a.style.display = 'none';
|
|
91
|
-
document.body.appendChild(a);
|
|
92
|
-
a.click();
|
|
93
|
-
document.body.removeChild(a);
|
|
94
|
-
setTimeout(()=>URL.revokeObjectURL(url), 0);
|
|
95
|
-
};
|
|
96
81
|
function deriveTaskId(scriptFrames, stepsFrame) {
|
|
97
82
|
let taskId = null;
|
|
98
83
|
for (const sf of scriptFrames){
|
|
@@ -207,6 +192,21 @@ function Player(props) {
|
|
|
207
192
|
frameMap,
|
|
208
193
|
player.currentFrame
|
|
209
194
|
]);
|
|
195
|
+
const handleDownloadReport = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
|
|
196
|
+
if (!(null == props ? void 0 : props.reportFileContent)) return;
|
|
197
|
+
try {
|
|
198
|
+
yield (0, external_report_download_js_namespaceObject.triggerReportDownload)({
|
|
199
|
+
content: props.reportFileContent,
|
|
200
|
+
onDownloadReport: props.onDownloadReport
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
204
|
+
external_antd_namespaceObject.message.error(`Failed to download report: ${errorMessage}`);
|
|
205
|
+
}
|
|
206
|
+
})(), [
|
|
207
|
+
null == props ? void 0 : props.onDownloadReport,
|
|
208
|
+
null == props ? void 0 : props.reportFileContent
|
|
209
|
+
]);
|
|
210
210
|
const subtitle = (0, external_react_namespaceObject.useMemo)(()=>{
|
|
211
211
|
if (!currentFrameState) return null;
|
|
212
212
|
if (!currentFrameState.title && !currentFrameState.subTitle) return null;
|
|
@@ -288,7 +288,9 @@ function Player(props) {
|
|
|
288
288
|
setIsExporting(true);
|
|
289
289
|
setExportProgress(0);
|
|
290
290
|
try {
|
|
291
|
-
yield (0, export_branded_video_js_namespaceObject.exportBrandedVideo)(frameMap,
|
|
291
|
+
yield (0, export_branded_video_js_namespaceObject.exportBrandedVideo)(frameMap, {
|
|
292
|
+
autoZoom
|
|
293
|
+
}, (pct)=>setExportProgress(Math.round(100 * pct)));
|
|
292
294
|
external_antd_namespaceObject.message.success('Video exported');
|
|
293
295
|
} catch (e) {
|
|
294
296
|
console.error('Export failed:', e);
|
|
@@ -298,6 +300,7 @@ function Player(props) {
|
|
|
298
300
|
setExportProgress(0);
|
|
299
301
|
}
|
|
300
302
|
})(), [
|
|
303
|
+
autoZoom,
|
|
301
304
|
frameMap,
|
|
302
305
|
isExporting
|
|
303
306
|
]);
|
|
@@ -462,7 +465,9 @@ function Player(props) {
|
|
|
462
465
|
title: "Download Report",
|
|
463
466
|
children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
464
467
|
className: "status-icon",
|
|
465
|
-
onClick: ()=>
|
|
468
|
+
onClick: ()=>{
|
|
469
|
+
handleDownloadReport();
|
|
470
|
+
},
|
|
466
471
|
children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(icons_namespaceObject.DownloadOutlined, {})
|
|
467
472
|
})
|
|
468
473
|
}) : null,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
DEFAULT_REPORT_FILE_NAME: ()=>DEFAULT_REPORT_FILE_NAME,
|
|
28
|
+
triggerReportDownload: ()=>triggerReportDownload
|
|
29
|
+
});
|
|
30
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
31
|
+
try {
|
|
32
|
+
var info = gen[key](arg);
|
|
33
|
+
var value = info.value;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
reject(error);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (info.done) resolve(value);
|
|
39
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
40
|
+
}
|
|
41
|
+
function _async_to_generator(fn) {
|
|
42
|
+
return function() {
|
|
43
|
+
var self = this, args = arguments;
|
|
44
|
+
return new Promise(function(resolve, reject) {
|
|
45
|
+
var gen = fn.apply(self, args);
|
|
46
|
+
function _next(value) {
|
|
47
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
48
|
+
}
|
|
49
|
+
function _throw(err) {
|
|
50
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
51
|
+
}
|
|
52
|
+
_next(void 0);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const DEFAULT_REPORT_FILE_NAME = 'midscene_report.html';
|
|
57
|
+
function triggerReportDownload(options) {
|
|
58
|
+
return _async_to_generator(function*() {
|
|
59
|
+
const { content, defaultFileName = DEFAULT_REPORT_FILE_NAME, onDownloadReport, documentRef, urlRef, blobFactory, scheduleRevoke } = options;
|
|
60
|
+
if (onDownloadReport) return void (yield onDownloadReport({
|
|
61
|
+
content,
|
|
62
|
+
defaultFileName
|
|
63
|
+
}));
|
|
64
|
+
const activeDocument = null != documentRef ? documentRef : globalThis.document;
|
|
65
|
+
if (!activeDocument) throw new Error('Report download requires a document context.');
|
|
66
|
+
const activeUrl = null != urlRef ? urlRef : globalThis.URL;
|
|
67
|
+
if (!(null == activeUrl ? void 0 : activeUrl.createObjectURL) || !(null == activeUrl ? void 0 : activeUrl.revokeObjectURL)) throw new Error('Report download requires URL.createObjectURL support.');
|
|
68
|
+
const createBlob = null != blobFactory ? blobFactory : (parts, blobOptions)=>new Blob(parts, blobOptions);
|
|
69
|
+
const blob = createBlob([
|
|
70
|
+
content
|
|
71
|
+
], {
|
|
72
|
+
type: 'text/html'
|
|
73
|
+
});
|
|
74
|
+
const url = activeUrl.createObjectURL(blob);
|
|
75
|
+
const anchor = activeDocument.createElement('a');
|
|
76
|
+
anchor.href = url;
|
|
77
|
+
anchor.download = defaultFileName;
|
|
78
|
+
anchor.style.display = 'none';
|
|
79
|
+
activeDocument.body.appendChild(anchor);
|
|
80
|
+
try {
|
|
81
|
+
anchor.click();
|
|
82
|
+
} finally{
|
|
83
|
+
activeDocument.body.removeChild(anchor);
|
|
84
|
+
(null != scheduleRevoke ? scheduleRevoke : (callback)=>setTimeout(callback, 0))(()=>{
|
|
85
|
+
activeUrl.revokeObjectURL(url);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
}
|
|
90
|
+
exports.DEFAULT_REPORT_FILE_NAME = __webpack_exports__.DEFAULT_REPORT_FILE_NAME;
|
|
91
|
+
exports.triggerReportDownload = __webpack_exports__.triggerReportDownload;
|
|
92
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
93
|
+
"DEFAULT_REPORT_FILE_NAME",
|
|
94
|
+
"triggerReportDownload"
|
|
95
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
96
|
+
Object.defineProperty(exports, '__esModule', {
|
|
97
|
+
value: true
|
|
98
|
+
});
|
|
@@ -24,6 +24,7 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
resolveExportCamera: ()=>resolveExportCamera,
|
|
27
28
|
exportBrandedVideo: ()=>exportBrandedVideo
|
|
28
29
|
});
|
|
29
30
|
const index_js_namespaceObject = require("../../../utils/index.js");
|
|
@@ -66,6 +67,18 @@ function clamp(v, lo, hi) {
|
|
|
66
67
|
function lerp(a, b, t) {
|
|
67
68
|
return a + (b - a) * t;
|
|
68
69
|
}
|
|
70
|
+
function resolveExportCamera(prevCamera, camera, imageWidth, progress, autoZoom) {
|
|
71
|
+
if (!autoZoom) return {
|
|
72
|
+
camLeft: 0,
|
|
73
|
+
camTop: 0,
|
|
74
|
+
camWidth: imageWidth
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
camLeft: lerp(prevCamera.left, camera.left, progress),
|
|
78
|
+
camTop: lerp(prevCamera.top, camera.top, progress),
|
|
79
|
+
camWidth: lerp(prevCamera.width, camera.width, progress)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
69
82
|
function loadImage(src) {
|
|
70
83
|
return new Promise((resolve, reject)=>{
|
|
71
84
|
const img = new Image();
|
|
@@ -123,16 +136,14 @@ function drawSpinningPointer(ctx, img, x, y, elapsedMs) {
|
|
|
123
136
|
ctx.drawImage(img, -11, -14, 22, 28);
|
|
124
137
|
ctx.restore();
|
|
125
138
|
}
|
|
126
|
-
function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg) {
|
|
139
|
+
function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg, autoZoom) {
|
|
127
140
|
const { scriptFrames, imageWidth: baseW, imageHeight: baseH, fps } = frameMap;
|
|
128
141
|
const st = (0, external_derive_frame_state_js_namespaceObject.deriveFrameState)(scriptFrames, stepsFrame, baseW, baseH, fps);
|
|
129
142
|
if (!st.img) return;
|
|
130
143
|
const { img, prevImg, imageWidth: imgW, imageHeight: imgH, camera, prevCamera, pointerMoved, imageChanged, rawProgress, frameInScript: fInScript, spinning, spinningElapsedMs, insights } = st;
|
|
131
144
|
const pT = pointerMoved ? Math.min(rawProgress / POINTER_PHASE, 1) : rawProgress;
|
|
132
145
|
const cT = pointerMoved ? rawProgress <= POINTER_PHASE ? 0 : Math.min((rawProgress - POINTER_PHASE) / (1 - POINTER_PHASE), 1) : rawProgress;
|
|
133
|
-
const camL =
|
|
134
|
-
const camT2 = lerp(prevCamera.top, camera.top, cT);
|
|
135
|
-
const camW = lerp(prevCamera.width, camera.width, cT);
|
|
146
|
+
const { camLeft: camL, camTop: camT2, camWidth: camW } = resolveExportCamera(prevCamera, camera, imgW, cT, autoZoom);
|
|
136
147
|
const ptrX = lerp(prevCamera.pointerLeft, camera.pointerLeft, pT);
|
|
137
148
|
const ptrY = lerp(prevCamera.pointerTop, camera.pointerTop, pT);
|
|
138
149
|
const zoom = imgW / camW;
|
|
@@ -169,9 +180,11 @@ function drawSteps(ctx, stepsFrame, frameMap, imgCache, cursorImg, spinnerImg) {
|
|
|
169
180
|
if (spinning && spinnerImg) drawSpinningPointer(ctx, spinnerImg, sX, sY, spinningElapsedMs);
|
|
170
181
|
if (!spinning && hasPtrData && cursorImg) ctx.drawImage(cursorImg, sX - 3, sY - 2, 22, 28);
|
|
171
182
|
}
|
|
172
|
-
function exportBrandedVideo(frameMap, onProgress) {
|
|
183
|
+
function exportBrandedVideo(frameMap, options, onProgress) {
|
|
173
184
|
return _async_to_generator(function*() {
|
|
174
185
|
const { totalDurationInFrames: total, fps } = frameMap;
|
|
186
|
+
var _options_autoZoom;
|
|
187
|
+
const autoZoom = null != (_options_autoZoom = null == options ? void 0 : options.autoZoom) ? _options_autoZoom : true;
|
|
175
188
|
const imgSrcs = new Set();
|
|
176
189
|
for (const sf of frameMap.scriptFrames)if (sf.img) imgSrcs.add(sf.img);
|
|
177
190
|
const imgCache = new Map();
|
|
@@ -228,7 +241,7 @@ function exportBrandedVideo(frameMap, onProgress) {
|
|
|
228
241
|
if (targetFrame > lastFrame) {
|
|
229
242
|
lastFrame = targetFrame;
|
|
230
243
|
ctx.clearRect(0, 0, W, H);
|
|
231
|
-
drawSteps(ctx, targetFrame, frameMap, imgCache, cursorImg, spinnerImg);
|
|
244
|
+
drawSteps(ctx, targetFrame, frameMap, imgCache, cursorImg, spinnerImg, autoZoom);
|
|
232
245
|
null == onProgress || onProgress((targetFrame + 1) / total);
|
|
233
246
|
}
|
|
234
247
|
if (targetFrame < total - 1) requestAnimationFrame(tick);
|
|
@@ -239,8 +252,10 @@ function exportBrandedVideo(frameMap, onProgress) {
|
|
|
239
252
|
})();
|
|
240
253
|
}
|
|
241
254
|
exports.exportBrandedVideo = __webpack_exports__.exportBrandedVideo;
|
|
255
|
+
exports.resolveExportCamera = __webpack_exports__.resolveExportCamera;
|
|
242
256
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
243
|
-
"exportBrandedVideo"
|
|
257
|
+
"exportBrandedVideo",
|
|
258
|
+
"resolveExportCamera"
|
|
244
259
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
245
260
|
Object.defineProperty(exports, '__esModule', {
|
|
246
261
|
value: true
|
|
@@ -44,7 +44,7 @@ const external_player_index_js_namespaceObject = require("../player/index.js");
|
|
|
44
44
|
const external_shiny_text_index_js_namespaceObject = require("../shiny-text/index.js");
|
|
45
45
|
var external_shiny_text_index_js_default = /*#__PURE__*/ __webpack_require__.n(external_shiny_text_index_js_namespaceObject);
|
|
46
46
|
require("./index.css");
|
|
47
|
-
const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, replayScriptsInfo, replayCounter, loadingProgressText, verticalMode = false, notReadyMessage, fitMode, autoZoom, actionType, canDownloadReport })=>{
|
|
47
|
+
const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, replayScriptsInfo, replayCounter, loadingProgressText, verticalMode = false, notReadyMessage, fitMode, autoZoom, actionType, canDownloadReport, onDownloadReport })=>{
|
|
48
48
|
let resultWrapperClassName = 'result-wrapper';
|
|
49
49
|
if (verticalMode) resultWrapperClassName += ' vertical-mode-result';
|
|
50
50
|
if (replayScriptsInfo && verticalMode) resultWrapperClassName += ' result-wrapper-compact';
|
|
@@ -116,7 +116,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
116
116
|
reportFileContent: result.reportHTML || null,
|
|
117
117
|
fitMode: fitMode,
|
|
118
118
|
autoZoom: autoZoom,
|
|
119
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
119
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
120
|
+
onDownloadReport: onDownloadReport
|
|
120
121
|
}, replayCounter)
|
|
121
122
|
})
|
|
122
123
|
]
|
|
@@ -167,7 +168,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
167
168
|
reportFileContent: reportContent,
|
|
168
169
|
fitMode: fitMode,
|
|
169
170
|
autoZoom: autoZoom,
|
|
170
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
171
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
172
|
+
onDownloadReport: onDownloadReport
|
|
171
173
|
}, replayCounter)
|
|
172
174
|
})
|
|
173
175
|
]
|
|
@@ -183,7 +185,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
183
185
|
reportFileContent: reportContent,
|
|
184
186
|
fitMode: fitMode,
|
|
185
187
|
autoZoom: autoZoom,
|
|
186
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
188
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
189
|
+
onDownloadReport: onDownloadReport
|
|
187
190
|
}, replayCounter);
|
|
188
191
|
} else if (shouldPrioritizeResult && (null == result ? void 0 : result.result) !== void 0 && (null == result ? void 0 : result.reportHTML)) {
|
|
189
192
|
const resultOutput = 'string' == typeof (null == result ? void 0 : result.result) ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("pre", {
|
|
@@ -225,7 +228,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
225
228
|
reportFileContent: result.reportHTML,
|
|
226
229
|
fitMode: fitMode,
|
|
227
230
|
autoZoom: autoZoom,
|
|
228
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
231
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
232
|
+
onDownloadReport: onDownloadReport
|
|
229
233
|
}, replayCounter)
|
|
230
234
|
})
|
|
231
235
|
]
|
|
@@ -241,7 +245,8 @@ const PlaygroundResultView = ({ result, loading, serverValid, serviceMode, repla
|
|
|
241
245
|
reportFileContent: result.reportHTML,
|
|
242
246
|
fitMode: fitMode,
|
|
243
247
|
autoZoom: autoZoom,
|
|
244
|
-
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode
|
|
248
|
+
canDownloadReport: null != canDownloadReport ? canDownloadReport : 'In-Browser' !== serviceMode,
|
|
249
|
+
onDownloadReport: onDownloadReport
|
|
245
250
|
}, replayCounter);
|
|
246
251
|
else if ((null == result ? void 0 : result.result) !== void 0) resultDataToShow = 'string' == typeof (null == result ? void 0 : result.result) ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("pre", {
|
|
247
252
|
children: null == result ? void 0 : result.result
|
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
display: flex;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
.screenshot-viewer.screen-only {
|
|
8
|
+
min-height: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.screenshot-viewer.screen-only > .screenshot-content {
|
|
12
|
+
flex: 1;
|
|
13
|
+
min-height: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.screenshot-viewer.screen-only > .screenshot-content .screenshot-image {
|
|
17
|
+
border-radius: 0;
|
|
18
|
+
width: 100%;
|
|
19
|
+
max-width: none;
|
|
20
|
+
height: 100%;
|
|
21
|
+
max-height: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
.screenshot-viewer.offline, .screenshot-viewer.loading, .screenshot-viewer.error {
|
|
8
25
|
text-align: center;
|
|
9
26
|
color: #666;
|
|
@@ -57,13 +57,18 @@ function _async_to_generator(fn) {
|
|
|
57
57
|
});
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
-
function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating = false, mjpegUrl }) {
|
|
60
|
+
function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating = false, mjpegUrl, mode = 'default' }) {
|
|
61
61
|
const [screenshot, setScreenshot] = (0, external_react_namespaceObject.useState)(null);
|
|
62
62
|
const [loading, setLoading] = (0, external_react_namespaceObject.useState)(false);
|
|
63
63
|
const [error, setError] = (0, external_react_namespaceObject.useState)(null);
|
|
64
64
|
const [lastUpdateTime, setLastUpdateTime] = (0, external_react_namespaceObject.useState)(0);
|
|
65
65
|
const [interfaceInfo, setInterfaceInfo] = (0, external_react_namespaceObject.useState)(null);
|
|
66
66
|
const isMjpeg = Boolean(mjpegUrl && serverOnline);
|
|
67
|
+
const showChrome = 'screen-only' !== mode;
|
|
68
|
+
const rootClassName = [
|
|
69
|
+
'screenshot-viewer',
|
|
70
|
+
'screen-only' === mode && 'screen-only'
|
|
71
|
+
].filter(Boolean).join(' ');
|
|
67
72
|
const pollingIntervalRef = (0, external_react_namespaceObject.useRef)(null);
|
|
68
73
|
const isPollingPausedRef = (0, external_react_namespaceObject.useRef)(false);
|
|
69
74
|
const fetchScreenshot = (0, external_react_namespaceObject.useCallback)((isManual = false)=>_async_to_generator(function*() {
|
|
@@ -176,7 +181,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
176
181
|
stopPolling
|
|
177
182
|
]);
|
|
178
183
|
if (!serverOnline) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
179
|
-
className:
|
|
184
|
+
className: `${rootClassName} offline`,
|
|
180
185
|
children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
|
|
181
186
|
className: "screenshot-placeholder",
|
|
182
187
|
children: [
|
|
@@ -190,7 +195,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
190
195
|
})
|
|
191
196
|
});
|
|
192
197
|
if (!isMjpeg && loading && !screenshot) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
|
|
193
|
-
className:
|
|
198
|
+
className: `${rootClassName} loading`,
|
|
194
199
|
children: [
|
|
195
200
|
/*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Spin, {
|
|
196
201
|
size: "large"
|
|
@@ -201,7 +206,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
201
206
|
]
|
|
202
207
|
});
|
|
203
208
|
if (!isMjpeg && error && !screenshot) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
204
|
-
className:
|
|
209
|
+
className: `${rootClassName} error`,
|
|
205
210
|
children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
|
|
206
211
|
className: "screenshot-placeholder",
|
|
207
212
|
children: [
|
|
@@ -223,8 +228,35 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
223
228
|
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
224
229
|
return new Date(timestamp).toLocaleTimeString();
|
|
225
230
|
};
|
|
231
|
+
const screenshotContent = /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
232
|
+
className: "screenshot-content",
|
|
233
|
+
children: isMjpeg ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("img", {
|
|
234
|
+
src: mjpegUrl,
|
|
235
|
+
alt: "Device Live Stream",
|
|
236
|
+
className: "screenshot-image"
|
|
237
|
+
}) : screenshot ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("img", {
|
|
238
|
+
src: screenshot.startsWith('data:image/') ? screenshot : `data:image/png;base64,${screenshot}`,
|
|
239
|
+
alt: "Device Screenshot",
|
|
240
|
+
className: "screenshot-image",
|
|
241
|
+
onLoad: ()=>console.log('Screenshot image loaded successfully'),
|
|
242
|
+
onError: (e)=>{
|
|
243
|
+
console.error('Screenshot image load error:', e);
|
|
244
|
+
console.error('Screenshot data preview:', screenshot.substring(0, 100));
|
|
245
|
+
setError('Failed to load screenshot image');
|
|
246
|
+
}
|
|
247
|
+
}) : /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
248
|
+
className: "screenshot-placeholder",
|
|
249
|
+
children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("p", {
|
|
250
|
+
children: "No screenshot available"
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
if (!showChrome) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
255
|
+
className: rootClassName,
|
|
256
|
+
children: screenshotContent
|
|
257
|
+
});
|
|
226
258
|
return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
|
|
227
|
-
className:
|
|
259
|
+
className: rootClassName,
|
|
228
260
|
children: [
|
|
229
261
|
/*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
230
262
|
className: "screenshot-header",
|
|
@@ -286,29 +318,7 @@ function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUse
|
|
|
286
318
|
})
|
|
287
319
|
]
|
|
288
320
|
}),
|
|
289
|
-
|
|
290
|
-
className: "screenshot-content",
|
|
291
|
-
children: isMjpeg ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("img", {
|
|
292
|
-
src: mjpegUrl,
|
|
293
|
-
alt: "Device Live Stream",
|
|
294
|
-
className: "screenshot-image"
|
|
295
|
-
}) : screenshot ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("img", {
|
|
296
|
-
src: screenshot.startsWith('data:image/') ? screenshot : `data:image/png;base64,${screenshot}`,
|
|
297
|
-
alt: "Device Screenshot",
|
|
298
|
-
className: "screenshot-image",
|
|
299
|
-
onLoad: ()=>console.log('Screenshot image loaded successfully'),
|
|
300
|
-
onError: (e)=>{
|
|
301
|
-
console.error('Screenshot image load error:', e);
|
|
302
|
-
console.error('Screenshot data preview:', screenshot.substring(0, 100));
|
|
303
|
-
setError('Failed to load screenshot image');
|
|
304
|
-
}
|
|
305
|
-
}) : /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
306
|
-
className: "screenshot-placeholder",
|
|
307
|
-
children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("p", {
|
|
308
|
-
children: "No screenshot available"
|
|
309
|
-
})
|
|
310
|
-
})
|
|
311
|
-
})
|
|
321
|
+
screenshotContent
|
|
312
322
|
]
|
|
313
323
|
})
|
|
314
324
|
]
|
|
@@ -376,7 +376,8 @@ function UniversalPlayground({ playgroundSDK, storage, contextProvider, config:
|
|
|
376
376
|
loadingProgressText: item.loadingProgressText || '',
|
|
377
377
|
verticalMode: item.verticalMode || false,
|
|
378
378
|
fitMode: "width",
|
|
379
|
-
actionType: item.actionType
|
|
379
|
+
actionType: item.actionType,
|
|
380
|
+
onDownloadReport: componentConfig.onDownloadReport
|
|
380
381
|
}) : /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
|
|
381
382
|
children: [
|
|
382
383
|
/*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import './index.less';
|
|
2
|
+
import type { ReportDownloadHandler } from '../../types';
|
|
2
3
|
import type { AnimationScript } from '../../utils/replay-scripts';
|
|
3
4
|
export declare function Player(props?: {
|
|
4
5
|
replayScripts?: AnimationScript[];
|
|
@@ -9,5 +10,6 @@ export declare function Player(props?: {
|
|
|
9
10
|
fitMode?: 'width' | 'height';
|
|
10
11
|
autoZoom?: boolean;
|
|
11
12
|
canDownloadReport?: boolean;
|
|
13
|
+
onDownloadReport?: ReportDownloadHandler;
|
|
12
14
|
onTaskChange?: (taskId: string | null) => void;
|
|
13
15
|
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ReportDownloadHandler } from '../../types';
|
|
2
|
+
export declare const DEFAULT_REPORT_FILE_NAME = "midscene_report.html";
|
|
3
|
+
interface AnchorLike {
|
|
4
|
+
href: string;
|
|
5
|
+
download: string;
|
|
6
|
+
style: {
|
|
7
|
+
display: string;
|
|
8
|
+
};
|
|
9
|
+
click: () => void;
|
|
10
|
+
}
|
|
11
|
+
interface DocumentLike {
|
|
12
|
+
body: {
|
|
13
|
+
appendChild: (node: AnchorLike) => void;
|
|
14
|
+
removeChild: (node: AnchorLike) => void;
|
|
15
|
+
};
|
|
16
|
+
createElement: (tagName: string) => AnchorLike;
|
|
17
|
+
}
|
|
18
|
+
interface UrlLike {
|
|
19
|
+
createObjectURL: (blob: Blob) => string;
|
|
20
|
+
revokeObjectURL: (url: string) => void;
|
|
21
|
+
}
|
|
22
|
+
interface TriggerReportDownloadOptions {
|
|
23
|
+
content: string;
|
|
24
|
+
defaultFileName?: string;
|
|
25
|
+
onDownloadReport?: ReportDownloadHandler;
|
|
26
|
+
documentRef?: DocumentLike;
|
|
27
|
+
urlRef?: UrlLike;
|
|
28
|
+
blobFactory?: (parts: BlobPart[], options: BlobPropertyBag) => Blob;
|
|
29
|
+
scheduleRevoke?: (callback: () => void) => void;
|
|
30
|
+
}
|
|
31
|
+
export declare function triggerReportDownload(options: TriggerReportDownloadOptions): Promise<void>;
|
|
32
|
+
export {};
|
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
import type { FrameMap } from './frame-calculator';
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function resolveExportCamera(prevCamera: {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
width: number;
|
|
6
|
+
}, camera: {
|
|
7
|
+
left: number;
|
|
8
|
+
top: number;
|
|
9
|
+
width: number;
|
|
10
|
+
}, imageWidth: number, progress: number, autoZoom: boolean): {
|
|
11
|
+
camLeft: number;
|
|
12
|
+
camTop: number;
|
|
13
|
+
camWidth: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function exportBrandedVideo(frameMap: FrameMap, options?: {
|
|
16
|
+
autoZoom?: boolean;
|
|
17
|
+
}, onProgress?: (pct: number) => void): Promise<void>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import type { PlaygroundResult as PlaygroundResultType } from '../../types';
|
|
3
|
-
import type { ServiceModeType } from '../../types';
|
|
2
|
+
import type { PlaygroundResult as PlaygroundResultType, ReportDownloadHandler, ServiceModeType } from '../../types';
|
|
4
3
|
import type { ReplayScriptsInfo } from '../../utils/replay-scripts';
|
|
5
4
|
import './index.less';
|
|
6
5
|
interface PlaygroundResultProps {
|
|
@@ -17,6 +16,7 @@ interface PlaygroundResultProps {
|
|
|
17
16
|
autoZoom?: boolean;
|
|
18
17
|
actionType?: string;
|
|
19
18
|
canDownloadReport?: boolean;
|
|
19
|
+
onDownloadReport?: ReportDownloadHandler;
|
|
20
20
|
}
|
|
21
21
|
export declare const PlaygroundResultView: React.FC<PlaygroundResultProps>;
|
|
22
22
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import './index.less';
|
|
3
|
+
export type ScreenshotViewerMode = 'default' | 'screen-only';
|
|
2
4
|
interface ScreenshotViewerProps {
|
|
3
5
|
getScreenshot: () => Promise<{
|
|
4
6
|
screenshot: string;
|
|
@@ -11,6 +13,7 @@ interface ScreenshotViewerProps {
|
|
|
11
13
|
serverOnline: boolean;
|
|
12
14
|
isUserOperating?: boolean;
|
|
13
15
|
mjpegUrl?: string;
|
|
16
|
+
mode?: ScreenshotViewerMode;
|
|
14
17
|
}
|
|
15
|
-
export default function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating, mjpegUrl, }: ScreenshotViewerProps):
|
|
18
|
+
export default function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating, mjpegUrl, mode, }: ScreenshotViewerProps): React.JSX.Element;
|
|
16
19
|
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -18,10 +18,11 @@ export { PromptInput } from './component/prompt-input';
|
|
|
18
18
|
export { Player } from './component/player';
|
|
19
19
|
export { Blackboard } from './component/blackboard';
|
|
20
20
|
export { default as ScreenshotViewer } from './component/screenshot-viewer';
|
|
21
|
+
export type { ScreenshotViewerMode } from './component/screenshot-viewer';
|
|
21
22
|
export { actionNameForType, staticAgentFromContext, getPlaceholderForType, } from './utils/playground-utils';
|
|
22
23
|
export { timeStr, filterBase64Value } from './utils';
|
|
23
24
|
export { default as ShinyText } from './component/shiny-text';
|
|
24
25
|
export { UniversalPlayground, default as UniversalPlaygroundDefault, } from './component/universal-playground';
|
|
25
|
-
export type { UniversalPlaygroundProps, PlaygroundSDKLike, StorageProvider, ContextProvider, UniversalPlaygroundConfig, PlaygroundBranding, InfoListItem, FormValue, ExecutionOptions, ProgressCallback, DeviceType, ExecutionUxHint, ExecutionUxConfig, PromptInputChromeConfig, } from './types';
|
|
26
|
+
export type { UniversalPlaygroundProps, PlaygroundSDKLike, StorageProvider, ContextProvider, UniversalPlaygroundConfig, PlaygroundBranding, InfoListItem, FormValue, ExecutionOptions, ProgressCallback, DeviceType, ExecutionUxHint, ExecutionUxConfig, PromptInputChromeConfig, ReportDownloadHandler, ReportDownloadRequest, } from './types';
|
|
26
27
|
export { LocalStorageProvider, MemoryStorageProvider, NoOpStorageProvider, IndexedDBStorageProvider, createStorageProvider, detectBestStorageType, StorageType, } from './component/universal-playground/providers/storage-provider';
|
|
27
28
|
export { BaseContextProvider, AgentContextProvider, StaticContextProvider, NoOpContextProvider, } from './component/universal-playground/providers/context-provider';
|
package/dist/types/types.d.ts
CHANGED
|
@@ -163,6 +163,11 @@ export interface InfoListItem {
|
|
|
163
163
|
*/
|
|
164
164
|
actionKind?: string;
|
|
165
165
|
}
|
|
166
|
+
export interface ReportDownloadRequest {
|
|
167
|
+
content: string;
|
|
168
|
+
defaultFileName: string;
|
|
169
|
+
}
|
|
170
|
+
export type ReportDownloadHandler = (request: ReportDownloadRequest) => void | Promise<void>;
|
|
166
171
|
export interface UniversalPlaygroundConfig {
|
|
167
172
|
showContextPreview?: boolean;
|
|
168
173
|
storageNamespace?: string;
|
|
@@ -193,6 +198,11 @@ export interface UniversalPlaygroundConfig {
|
|
|
193
198
|
* grouping, no connector) so existing hosts keep their behaviour.
|
|
194
199
|
*/
|
|
195
200
|
executionFlow?: ExecutionFlowConfig;
|
|
201
|
+
/**
|
|
202
|
+
* Optional host-provided report download hook.
|
|
203
|
+
* Defaults to the browser Blob download flow when omitted.
|
|
204
|
+
*/
|
|
205
|
+
onDownloadReport?: ReportDownloadHandler;
|
|
196
206
|
}
|
|
197
207
|
export interface ExecutionFlowConfig {
|
|
198
208
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/visualizer",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.7-beta-20260428102047.0",
|
|
4
4
|
"repository": "https://github.com/web-infra-dev/midscene",
|
|
5
5
|
"homepage": "https://midscenejs.com/",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"antd": "^5.21.6",
|
|
59
59
|
"buffer": "6.0.3",
|
|
60
60
|
"dayjs": "^1.11.11",
|
|
61
|
-
"@midscene/
|
|
62
|
-
"@midscene/
|
|
63
|
-
"@midscene/web": "1.7.
|
|
64
|
-
"@midscene/shared": "1.7.
|
|
61
|
+
"@midscene/playground": "1.7.7-beta-20260428102047.0",
|
|
62
|
+
"@midscene/core": "1.7.7-beta-20260428102047.0",
|
|
63
|
+
"@midscene/web": "1.7.7-beta-20260428102047.0",
|
|
64
|
+
"@midscene/shared": "1.7.7-beta-20260428102047.0"
|
|
65
65
|
},
|
|
66
66
|
"license": "MIT",
|
|
67
67
|
"scripts": {
|