@midscene/visualizer 1.0.1-beta-20251027074226.0 → 1.0.1-beta-20251028021317.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.
@@ -0,0 +1,170 @@
1
+ .screenshot-viewer {
2
+ flex-direction: column;
3
+ height: 100%;
4
+ display: flex;
5
+ }
6
+
7
+ .screenshot-viewer.offline, .screenshot-viewer.loading, .screenshot-viewer.error {
8
+ text-align: center;
9
+ color: #666;
10
+ justify-content: center;
11
+ align-items: center;
12
+ }
13
+
14
+ .screenshot-viewer.offline .screenshot-placeholder h3, .screenshot-viewer.loading .screenshot-placeholder h3, .screenshot-viewer.error .screenshot-placeholder h3 {
15
+ color: #1890ff;
16
+ text-transform: capitalize;
17
+ margin-bottom: 12px;
18
+ font-size: 18px;
19
+ }
20
+
21
+ .screenshot-viewer.offline .screenshot-placeholder p, .screenshot-viewer.loading .screenshot-placeholder p, .screenshot-viewer.error .screenshot-placeholder p {
22
+ color: #666;
23
+ margin: 0;
24
+ }
25
+
26
+ .screenshot-viewer.offline .screenshot-placeholder p.error-message, .screenshot-viewer.loading .screenshot-placeholder p.error-message, .screenshot-viewer.error .screenshot-placeholder p.error-message {
27
+ color: #ff4d4f;
28
+ }
29
+
30
+ .screenshot-viewer .screenshot-header {
31
+ justify-content: space-between;
32
+ align-items: center;
33
+ height: 56px;
34
+ display: flex;
35
+ }
36
+
37
+ .screenshot-viewer .screenshot-header .screenshot-title {
38
+ flex-direction: column;
39
+ gap: 4px;
40
+ display: flex;
41
+ }
42
+
43
+ .screenshot-viewer .screenshot-header .screenshot-title h3 {
44
+ color: #000;
45
+ text-transform: capitalize;
46
+ align-items: center;
47
+ gap: 8px;
48
+ margin: 0;
49
+ font-size: 14px;
50
+ font-weight: 600;
51
+ display: flex;
52
+ }
53
+
54
+ .screenshot-viewer .screenshot-header .screenshot-title .screenshot-subtitle {
55
+ color: #999;
56
+ margin: 0;
57
+ font-size: 12px;
58
+ }
59
+
60
+ .screenshot-viewer .screenshot-container {
61
+ background: #f2f4f7;
62
+ border-radius: 16px;
63
+ flex-direction: column;
64
+ flex: 1;
65
+ padding: 0 15px;
66
+ display: flex;
67
+ overflow: hidden;
68
+ }
69
+
70
+ .screenshot-viewer .screenshot-container .screenshot-overlay {
71
+ z-index: 10;
72
+ justify-content: space-between;
73
+ align-items: flex-start;
74
+ padding: 12px 5px 8px;
75
+ display: flex;
76
+ }
77
+
78
+ .screenshot-viewer .screenshot-container .screenshot-overlay .device-name-overlay {
79
+ color: rgba(0, 0, 0, .85);
80
+ text-transform: capitalize;
81
+ align-items: center;
82
+ gap: 8px;
83
+ font-size: 12px;
84
+ font-weight: 500;
85
+ display: flex;
86
+ }
87
+
88
+ .screenshot-viewer .screenshot-container .screenshot-overlay .device-name-overlay .info-icon {
89
+ opacity: .8;
90
+ cursor: pointer;
91
+ font-size: 12px;
92
+ }
93
+
94
+ .screenshot-viewer .screenshot-container .screenshot-overlay .device-name-overlay .info-icon:hover {
95
+ opacity: 1;
96
+ }
97
+
98
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls {
99
+ opacity: 1;
100
+ box-sizing: border-box;
101
+ border-radius: 4px;
102
+ align-items: center;
103
+ gap: 10px;
104
+ height: 18px;
105
+ padding: 4px 8px;
106
+ display: flex;
107
+ }
108
+
109
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .last-update-time {
110
+ color: #666;
111
+ white-space: nowrap;
112
+ text-overflow: ellipsis;
113
+ font-size: 12px;
114
+ overflow: hidden;
115
+ }
116
+
117
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .operation-indicator {
118
+ align-items: center;
119
+ gap: 4px;
120
+ font-size: 12px;
121
+ display: flex;
122
+ }
123
+
124
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .ant-btn {
125
+ box-shadow: none;
126
+ background: none;
127
+ border: none;
128
+ justify-content: center;
129
+ align-items: center;
130
+ width: 16px;
131
+ min-width: 16px;
132
+ height: 16px;
133
+ padding: 0;
134
+ display: flex;
135
+ }
136
+
137
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .ant-btn:hover {
138
+ background-color: rgba(0, 0, 0, .06);
139
+ border-radius: 2px;
140
+ transition: all .2s;
141
+ transform: scale(1.1);
142
+ }
143
+
144
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .ant-btn .anticon {
145
+ color: #666;
146
+ font-size: 12px;
147
+ transition: color .2s;
148
+ }
149
+
150
+ .screenshot-viewer .screenshot-container .screenshot-content {
151
+ flex: 1;
152
+ justify-content: center;
153
+ align-items: center;
154
+ min-height: 0;
155
+ display: flex;
156
+ }
157
+
158
+ .screenshot-viewer .screenshot-container .screenshot-content .screenshot-image {
159
+ object-fit: contain;
160
+ border-radius: 12px;
161
+ max-width: 100%;
162
+ height: auto;
163
+ max-height: 100%;
164
+ }
165
+
166
+ .screenshot-viewer .screenshot-container .screenshot-content .screenshot-placeholder {
167
+ text-align: center;
168
+ color: #999;
169
+ }
170
+
@@ -0,0 +1,258 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { InfoCircleOutlined, ReloadOutlined } from "@ant-design/icons";
3
+ import { Button, Spin, Tooltip } from "antd";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+ import "./index.css";
6
+ function ScreenshotViewer(param) {
7
+ let { getScreenshot, getInterfaceInfo, serverOnline, isUserOperating = false } = param;
8
+ const [screenshot, setScreenshot] = useState(null);
9
+ const [loading, setLoading] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const [lastUpdateTime, setLastUpdateTime] = useState(0);
12
+ const [interfaceInfo, setInterfaceInfo] = useState(null);
13
+ const pollingIntervalRef = useRef(null);
14
+ const isPollingPausedRef = useRef(false);
15
+ const fetchScreenshot = useCallback(async function() {
16
+ let isManual = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : false;
17
+ if (!serverOnline) return;
18
+ setLoading(true);
19
+ if (isManual) setError(null);
20
+ try {
21
+ const result = await getScreenshot();
22
+ console.log('Screenshot API response:', result);
23
+ if (null == result ? void 0 : result.screenshot) {
24
+ const screenshotData = result.screenshot.toString().trim();
25
+ if (screenshotData) {
26
+ setScreenshot(screenshotData);
27
+ setError(null);
28
+ setLastUpdateTime(Date.now());
29
+ } else setError('Empty screenshot data received');
30
+ } else setError('No screenshot data in response');
31
+ } catch (err) {
32
+ console.error('Screenshot fetch error:', err);
33
+ setError(err instanceof Error ? err.message : 'Failed to fetch screenshot');
34
+ } finally{
35
+ setLoading(false);
36
+ }
37
+ }, [
38
+ getScreenshot,
39
+ serverOnline
40
+ ]);
41
+ const fetchInterfaceInfo = useCallback(async ()=>{
42
+ if (!serverOnline || !getInterfaceInfo) return;
43
+ try {
44
+ const info = await getInterfaceInfo();
45
+ if (info) setInterfaceInfo(info);
46
+ } catch (err) {
47
+ console.error('Interface info fetch error:', err);
48
+ }
49
+ }, [
50
+ getInterfaceInfo,
51
+ serverOnline
52
+ ]);
53
+ const startPolling = useCallback(()=>{
54
+ if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current);
55
+ console.log('Starting screenshot polling (5s interval)');
56
+ pollingIntervalRef.current = setInterval(()=>{
57
+ if (!isPollingPausedRef.current && serverOnline) fetchScreenshot(false);
58
+ }, 5000);
59
+ }, [
60
+ fetchScreenshot,
61
+ serverOnline
62
+ ]);
63
+ const stopPolling = useCallback(()=>{
64
+ if (pollingIntervalRef.current) {
65
+ console.log('Stopping screenshot polling');
66
+ clearInterval(pollingIntervalRef.current);
67
+ pollingIntervalRef.current = null;
68
+ }
69
+ }, []);
70
+ const pausePolling = useCallback(()=>{
71
+ console.log('Pausing screenshot polling');
72
+ isPollingPausedRef.current = true;
73
+ }, []);
74
+ const resumePolling = useCallback(()=>{
75
+ console.log('Resuming screenshot polling');
76
+ isPollingPausedRef.current = false;
77
+ }, []);
78
+ const handleManualRefresh = useCallback(()=>{
79
+ fetchScreenshot(true);
80
+ }, [
81
+ fetchScreenshot
82
+ ]);
83
+ useEffect(()=>{
84
+ if (!serverOnline) {
85
+ setScreenshot(null);
86
+ setError(null);
87
+ setInterfaceInfo(null);
88
+ stopPolling();
89
+ return;
90
+ }
91
+ fetchScreenshot(false);
92
+ fetchInterfaceInfo();
93
+ startPolling();
94
+ return ()=>{
95
+ stopPolling();
96
+ };
97
+ }, [
98
+ serverOnline,
99
+ startPolling,
100
+ stopPolling,
101
+ fetchScreenshot,
102
+ fetchInterfaceInfo
103
+ ]);
104
+ useEffect(()=>{
105
+ if (!serverOnline) return;
106
+ if (isUserOperating) pausePolling();
107
+ else {
108
+ resumePolling();
109
+ fetchScreenshot(false);
110
+ }
111
+ }, [
112
+ isUserOperating,
113
+ pausePolling,
114
+ resumePolling,
115
+ fetchScreenshot,
116
+ serverOnline
117
+ ]);
118
+ useEffect(()=>()=>{
119
+ stopPolling();
120
+ }, [
121
+ stopPolling
122
+ ]);
123
+ if (!serverOnline) return /*#__PURE__*/ jsx("div", {
124
+ className: "screenshot-viewer offline",
125
+ children: /*#__PURE__*/ jsxs("div", {
126
+ className: "screenshot-placeholder",
127
+ children: [
128
+ /*#__PURE__*/ jsx("h3", {
129
+ children: "\uD83D\uDCF1 Screen Preview"
130
+ }),
131
+ /*#__PURE__*/ jsx("p", {
132
+ children: "Start the playground server to see real-time screenshots"
133
+ })
134
+ ]
135
+ })
136
+ });
137
+ if (loading && !screenshot) return /*#__PURE__*/ jsxs("div", {
138
+ className: "screenshot-viewer loading",
139
+ children: [
140
+ /*#__PURE__*/ jsx(Spin, {
141
+ size: "large"
142
+ }),
143
+ /*#__PURE__*/ jsx("p", {
144
+ children: "Loading screenshot..."
145
+ })
146
+ ]
147
+ });
148
+ if (error && !screenshot) return /*#__PURE__*/ jsx("div", {
149
+ className: "screenshot-viewer error",
150
+ children: /*#__PURE__*/ jsxs("div", {
151
+ className: "screenshot-placeholder",
152
+ children: [
153
+ /*#__PURE__*/ jsx("h3", {
154
+ children: "\uD83D\uDCF1 Screen Preview"
155
+ }),
156
+ /*#__PURE__*/ jsx("p", {
157
+ className: "error-message",
158
+ children: error
159
+ })
160
+ ]
161
+ })
162
+ });
163
+ const formatLastUpdateTime = (timestamp)=>{
164
+ if (!timestamp) return '';
165
+ const now = Date.now();
166
+ const diff = Math.floor((now - timestamp) / 1000);
167
+ if (diff < 60) return `${diff}s ago`;
168
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
169
+ return new Date(timestamp).toLocaleTimeString();
170
+ };
171
+ return /*#__PURE__*/ jsxs("div", {
172
+ className: "screenshot-viewer",
173
+ children: [
174
+ /*#__PURE__*/ jsx("div", {
175
+ className: "screenshot-header",
176
+ children: /*#__PURE__*/ jsx("div", {
177
+ className: "screenshot-title",
178
+ children: /*#__PURE__*/ jsx("h3", {
179
+ children: (null == interfaceInfo ? void 0 : interfaceInfo.type) ? interfaceInfo.type : 'Device Name'
180
+ })
181
+ })
182
+ }),
183
+ /*#__PURE__*/ jsxs("div", {
184
+ className: "screenshot-container",
185
+ children: [
186
+ /*#__PURE__*/ jsxs("div", {
187
+ className: "screenshot-overlay",
188
+ children: [
189
+ /*#__PURE__*/ jsxs("div", {
190
+ className: "device-name-overlay",
191
+ children: [
192
+ "Device Name",
193
+ /*#__PURE__*/ jsx(Tooltip, {
194
+ title: null == interfaceInfo ? void 0 : interfaceInfo.description,
195
+ children: /*#__PURE__*/ jsx(InfoCircleOutlined, {
196
+ size: 16,
197
+ className: "info-icon"
198
+ })
199
+ })
200
+ ]
201
+ }),
202
+ /*#__PURE__*/ jsxs("div", {
203
+ className: "screenshot-controls",
204
+ children: [
205
+ lastUpdateTime > 0 && /*#__PURE__*/ jsxs("span", {
206
+ className: "last-update-time",
207
+ children: [
208
+ "Last updated ",
209
+ formatLastUpdateTime(lastUpdateTime)
210
+ ]
211
+ }),
212
+ /*#__PURE__*/ jsx(Tooltip, {
213
+ title: "Refresh screenshot",
214
+ children: /*#__PURE__*/ jsx(Button, {
215
+ icon: /*#__PURE__*/ jsx(ReloadOutlined, {}),
216
+ onClick: handleManualRefresh,
217
+ loading: loading,
218
+ size: "small"
219
+ })
220
+ }),
221
+ isUserOperating && /*#__PURE__*/ jsxs("span", {
222
+ className: "operation-indicator",
223
+ children: [
224
+ /*#__PURE__*/ jsx(Spin, {
225
+ size: "small"
226
+ }),
227
+ " Operating..."
228
+ ]
229
+ })
230
+ ]
231
+ })
232
+ ]
233
+ }),
234
+ /*#__PURE__*/ jsx("div", {
235
+ className: "screenshot-content",
236
+ children: screenshot ? /*#__PURE__*/ jsx("img", {
237
+ src: screenshot.startsWith('data:image/') ? screenshot : `data:image/png;base64,${screenshot}`,
238
+ alt: "Device Screenshot",
239
+ className: "screenshot-image",
240
+ onLoad: ()=>console.log('Screenshot image loaded successfully'),
241
+ onError: (e)=>{
242
+ console.error('Screenshot image load error:', e);
243
+ console.error('Screenshot data preview:', screenshot.substring(0, 100));
244
+ setError('Failed to load screenshot image');
245
+ }
246
+ }) : /*#__PURE__*/ jsx("div", {
247
+ className: "screenshot-placeholder",
248
+ children: /*#__PURE__*/ jsx("p", {
249
+ children: "No screenshot available"
250
+ })
251
+ })
252
+ })
253
+ ]
254
+ })
255
+ ]
256
+ });
257
+ }
258
+ export { ScreenshotViewer as default };
package/dist/es/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import "./component/playground/index.css";
2
2
  import "./component/universal-playground/index.css";
3
+ import "./components/screenshot-viewer/index.css";
3
4
  import { allScriptsFromDump, generateAnimationScripts } from "./utils/replay-scripts.mjs";
4
5
  import { useEnvConfig } from "./store/store.mjs";
5
6
  import { colorForName, globalThemeConfig, highlightColorForType } from "./utils/color.mjs";
@@ -16,10 +17,11 @@ import { ContextPreview } from "./component/context-preview/index.mjs";
16
17
  import { PromptInput } from "./component/prompt-input/index.mjs";
17
18
  import { Player } from "./component/player/index.mjs";
18
19
  import { Blackboard } from "./component/blackboard/index.mjs";
20
+ import screenshot_viewer from "./components/screenshot-viewer/index.mjs";
19
21
  import { actionNameForType, getPlaceholderForType, staticAgentFromContext } from "./utils/playground-utils.mjs";
20
22
  import { filterBase64Value, timeStr } from "./utils/index.mjs";
21
23
  import shiny_text from "./component/shiny-text/index.mjs";
22
24
  import universal_playground, { UniversalPlayground } from "./component/universal-playground/index.mjs";
23
25
  import { IndexedDBStorageProvider, LocalStorageProvider, MemoryStorageProvider, NoOpStorageProvider, StorageType, createStorageProvider, detectBestStorageType } from "./component/universal-playground/providers/storage-provider.mjs";
24
26
  import { AgentContextProvider, BaseContextProvider, NoOpContextProvider, StaticContextProvider } from "./component/universal-playground/providers/context-provider.mjs";
25
- export { AgentContextProvider, BaseContextProvider, Blackboard, ContextPreview, EnvConfig, EnvConfigReminder, IndexedDBStorageProvider, LocalStorageProvider, Logo, MemoryStorageProvider, NavActions, NoOpContextProvider, NoOpStorageProvider, Player, PlaygroundResultView, PromptInput, ServiceModeControl, shiny_text as ShinyText, StaticContextProvider, StorageType, UniversalPlayground, universal_playground as UniversalPlaygroundDefault, actionNameForType, allScriptsFromDump, colorForName, createStorageProvider, detectBestStorageType, filterBase64Value, generateAnimationScripts, getPlaceholderForType, globalThemeConfig, highlightColorForType, iconForStatus, safeOverrideAIConfig, staticAgentFromContext, timeCostStrElement, timeStr, useEnvConfig, useSafeOverrideAIConfig, useServerValid };
27
+ export { AgentContextProvider, BaseContextProvider, Blackboard, ContextPreview, EnvConfig, EnvConfigReminder, IndexedDBStorageProvider, LocalStorageProvider, Logo, MemoryStorageProvider, NavActions, NoOpContextProvider, NoOpStorageProvider, Player, PlaygroundResultView, PromptInput, screenshot_viewer as ScreenshotViewer, ServiceModeControl, shiny_text as ShinyText, StaticContextProvider, StorageType, UniversalPlayground, universal_playground as UniversalPlaygroundDefault, actionNameForType, allScriptsFromDump, colorForName, createStorageProvider, detectBestStorageType, filterBase64Value, generateAnimationScripts, getPlaceholderForType, globalThemeConfig, highlightColorForType, iconForStatus, safeOverrideAIConfig, staticAgentFromContext, timeCostStrElement, timeStr, useEnvConfig, useSafeOverrideAIConfig, useServerValid };
@@ -0,0 +1,170 @@
1
+ .screenshot-viewer {
2
+ flex-direction: column;
3
+ height: 100%;
4
+ display: flex;
5
+ }
6
+
7
+ .screenshot-viewer.offline, .screenshot-viewer.loading, .screenshot-viewer.error {
8
+ text-align: center;
9
+ color: #666;
10
+ justify-content: center;
11
+ align-items: center;
12
+ }
13
+
14
+ .screenshot-viewer.offline .screenshot-placeholder h3, .screenshot-viewer.loading .screenshot-placeholder h3, .screenshot-viewer.error .screenshot-placeholder h3 {
15
+ color: #1890ff;
16
+ text-transform: capitalize;
17
+ margin-bottom: 12px;
18
+ font-size: 18px;
19
+ }
20
+
21
+ .screenshot-viewer.offline .screenshot-placeholder p, .screenshot-viewer.loading .screenshot-placeholder p, .screenshot-viewer.error .screenshot-placeholder p {
22
+ color: #666;
23
+ margin: 0;
24
+ }
25
+
26
+ .screenshot-viewer.offline .screenshot-placeholder p.error-message, .screenshot-viewer.loading .screenshot-placeholder p.error-message, .screenshot-viewer.error .screenshot-placeholder p.error-message {
27
+ color: #ff4d4f;
28
+ }
29
+
30
+ .screenshot-viewer .screenshot-header {
31
+ justify-content: space-between;
32
+ align-items: center;
33
+ height: 56px;
34
+ display: flex;
35
+ }
36
+
37
+ .screenshot-viewer .screenshot-header .screenshot-title {
38
+ flex-direction: column;
39
+ gap: 4px;
40
+ display: flex;
41
+ }
42
+
43
+ .screenshot-viewer .screenshot-header .screenshot-title h3 {
44
+ color: #000;
45
+ text-transform: capitalize;
46
+ align-items: center;
47
+ gap: 8px;
48
+ margin: 0;
49
+ font-size: 14px;
50
+ font-weight: 600;
51
+ display: flex;
52
+ }
53
+
54
+ .screenshot-viewer .screenshot-header .screenshot-title .screenshot-subtitle {
55
+ color: #999;
56
+ margin: 0;
57
+ font-size: 12px;
58
+ }
59
+
60
+ .screenshot-viewer .screenshot-container {
61
+ background: #f2f4f7;
62
+ border-radius: 16px;
63
+ flex-direction: column;
64
+ flex: 1;
65
+ padding: 0 15px;
66
+ display: flex;
67
+ overflow: hidden;
68
+ }
69
+
70
+ .screenshot-viewer .screenshot-container .screenshot-overlay {
71
+ z-index: 10;
72
+ justify-content: space-between;
73
+ align-items: flex-start;
74
+ padding: 12px 5px 8px;
75
+ display: flex;
76
+ }
77
+
78
+ .screenshot-viewer .screenshot-container .screenshot-overlay .device-name-overlay {
79
+ color: rgba(0, 0, 0, .85);
80
+ text-transform: capitalize;
81
+ align-items: center;
82
+ gap: 8px;
83
+ font-size: 12px;
84
+ font-weight: 500;
85
+ display: flex;
86
+ }
87
+
88
+ .screenshot-viewer .screenshot-container .screenshot-overlay .device-name-overlay .info-icon {
89
+ opacity: .8;
90
+ cursor: pointer;
91
+ font-size: 12px;
92
+ }
93
+
94
+ .screenshot-viewer .screenshot-container .screenshot-overlay .device-name-overlay .info-icon:hover {
95
+ opacity: 1;
96
+ }
97
+
98
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls {
99
+ opacity: 1;
100
+ box-sizing: border-box;
101
+ border-radius: 4px;
102
+ align-items: center;
103
+ gap: 10px;
104
+ height: 18px;
105
+ padding: 4px 8px;
106
+ display: flex;
107
+ }
108
+
109
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .last-update-time {
110
+ color: #666;
111
+ white-space: nowrap;
112
+ text-overflow: ellipsis;
113
+ font-size: 12px;
114
+ overflow: hidden;
115
+ }
116
+
117
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .operation-indicator {
118
+ align-items: center;
119
+ gap: 4px;
120
+ font-size: 12px;
121
+ display: flex;
122
+ }
123
+
124
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .ant-btn {
125
+ box-shadow: none;
126
+ background: none;
127
+ border: none;
128
+ justify-content: center;
129
+ align-items: center;
130
+ width: 16px;
131
+ min-width: 16px;
132
+ height: 16px;
133
+ padding: 0;
134
+ display: flex;
135
+ }
136
+
137
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .ant-btn:hover {
138
+ background-color: rgba(0, 0, 0, .06);
139
+ border-radius: 2px;
140
+ transition: all .2s;
141
+ transform: scale(1.1);
142
+ }
143
+
144
+ .screenshot-viewer .screenshot-container .screenshot-overlay .screenshot-controls .ant-btn .anticon {
145
+ color: #666;
146
+ font-size: 12px;
147
+ transition: color .2s;
148
+ }
149
+
150
+ .screenshot-viewer .screenshot-container .screenshot-content {
151
+ flex: 1;
152
+ justify-content: center;
153
+ align-items: center;
154
+ min-height: 0;
155
+ display: flex;
156
+ }
157
+
158
+ .screenshot-viewer .screenshot-container .screenshot-content .screenshot-image {
159
+ object-fit: contain;
160
+ border-radius: 12px;
161
+ max-width: 100%;
162
+ height: auto;
163
+ max-height: 100%;
164
+ }
165
+
166
+ .screenshot-viewer .screenshot-container .screenshot-content .screenshot-placeholder {
167
+ text-align: center;
168
+ color: #999;
169
+ }
170
+
@@ -0,0 +1,292 @@
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: ()=>ScreenshotViewer
28
+ });
29
+ const jsx_runtime_namespaceObject = require("react/jsx-runtime");
30
+ const icons_namespaceObject = require("@ant-design/icons");
31
+ const external_antd_namespaceObject = require("antd");
32
+ const external_react_namespaceObject = require("react");
33
+ require("./index.css");
34
+ function ScreenshotViewer(param) {
35
+ let { getScreenshot, getInterfaceInfo, serverOnline, isUserOperating = false } = param;
36
+ const [screenshot, setScreenshot] = (0, external_react_namespaceObject.useState)(null);
37
+ const [loading, setLoading] = (0, external_react_namespaceObject.useState)(false);
38
+ const [error, setError] = (0, external_react_namespaceObject.useState)(null);
39
+ const [lastUpdateTime, setLastUpdateTime] = (0, external_react_namespaceObject.useState)(0);
40
+ const [interfaceInfo, setInterfaceInfo] = (0, external_react_namespaceObject.useState)(null);
41
+ const pollingIntervalRef = (0, external_react_namespaceObject.useRef)(null);
42
+ const isPollingPausedRef = (0, external_react_namespaceObject.useRef)(false);
43
+ const fetchScreenshot = (0, external_react_namespaceObject.useCallback)(async function() {
44
+ let isManual = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : false;
45
+ if (!serverOnline) return;
46
+ setLoading(true);
47
+ if (isManual) setError(null);
48
+ try {
49
+ const result = await getScreenshot();
50
+ console.log('Screenshot API response:', result);
51
+ if (null == result ? void 0 : result.screenshot) {
52
+ const screenshotData = result.screenshot.toString().trim();
53
+ if (screenshotData) {
54
+ setScreenshot(screenshotData);
55
+ setError(null);
56
+ setLastUpdateTime(Date.now());
57
+ } else setError('Empty screenshot data received');
58
+ } else setError('No screenshot data in response');
59
+ } catch (err) {
60
+ console.error('Screenshot fetch error:', err);
61
+ setError(err instanceof Error ? err.message : 'Failed to fetch screenshot');
62
+ } finally{
63
+ setLoading(false);
64
+ }
65
+ }, [
66
+ getScreenshot,
67
+ serverOnline
68
+ ]);
69
+ const fetchInterfaceInfo = (0, external_react_namespaceObject.useCallback)(async ()=>{
70
+ if (!serverOnline || !getInterfaceInfo) return;
71
+ try {
72
+ const info = await getInterfaceInfo();
73
+ if (info) setInterfaceInfo(info);
74
+ } catch (err) {
75
+ console.error('Interface info fetch error:', err);
76
+ }
77
+ }, [
78
+ getInterfaceInfo,
79
+ serverOnline
80
+ ]);
81
+ const startPolling = (0, external_react_namespaceObject.useCallback)(()=>{
82
+ if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current);
83
+ console.log('Starting screenshot polling (5s interval)');
84
+ pollingIntervalRef.current = setInterval(()=>{
85
+ if (!isPollingPausedRef.current && serverOnline) fetchScreenshot(false);
86
+ }, 5000);
87
+ }, [
88
+ fetchScreenshot,
89
+ serverOnline
90
+ ]);
91
+ const stopPolling = (0, external_react_namespaceObject.useCallback)(()=>{
92
+ if (pollingIntervalRef.current) {
93
+ console.log('Stopping screenshot polling');
94
+ clearInterval(pollingIntervalRef.current);
95
+ pollingIntervalRef.current = null;
96
+ }
97
+ }, []);
98
+ const pausePolling = (0, external_react_namespaceObject.useCallback)(()=>{
99
+ console.log('Pausing screenshot polling');
100
+ isPollingPausedRef.current = true;
101
+ }, []);
102
+ const resumePolling = (0, external_react_namespaceObject.useCallback)(()=>{
103
+ console.log('Resuming screenshot polling');
104
+ isPollingPausedRef.current = false;
105
+ }, []);
106
+ const handleManualRefresh = (0, external_react_namespaceObject.useCallback)(()=>{
107
+ fetchScreenshot(true);
108
+ }, [
109
+ fetchScreenshot
110
+ ]);
111
+ (0, external_react_namespaceObject.useEffect)(()=>{
112
+ if (!serverOnline) {
113
+ setScreenshot(null);
114
+ setError(null);
115
+ setInterfaceInfo(null);
116
+ stopPolling();
117
+ return;
118
+ }
119
+ fetchScreenshot(false);
120
+ fetchInterfaceInfo();
121
+ startPolling();
122
+ return ()=>{
123
+ stopPolling();
124
+ };
125
+ }, [
126
+ serverOnline,
127
+ startPolling,
128
+ stopPolling,
129
+ fetchScreenshot,
130
+ fetchInterfaceInfo
131
+ ]);
132
+ (0, external_react_namespaceObject.useEffect)(()=>{
133
+ if (!serverOnline) return;
134
+ if (isUserOperating) pausePolling();
135
+ else {
136
+ resumePolling();
137
+ fetchScreenshot(false);
138
+ }
139
+ }, [
140
+ isUserOperating,
141
+ pausePolling,
142
+ resumePolling,
143
+ fetchScreenshot,
144
+ serverOnline
145
+ ]);
146
+ (0, external_react_namespaceObject.useEffect)(()=>()=>{
147
+ stopPolling();
148
+ }, [
149
+ stopPolling
150
+ ]);
151
+ if (!serverOnline) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
152
+ className: "screenshot-viewer offline",
153
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
154
+ className: "screenshot-placeholder",
155
+ children: [
156
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("h3", {
157
+ children: "\uD83D\uDCF1 Screen Preview"
158
+ }),
159
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("p", {
160
+ children: "Start the playground server to see real-time screenshots"
161
+ })
162
+ ]
163
+ })
164
+ });
165
+ if (loading && !screenshot) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
166
+ className: "screenshot-viewer loading",
167
+ children: [
168
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Spin, {
169
+ size: "large"
170
+ }),
171
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("p", {
172
+ children: "Loading screenshot..."
173
+ })
174
+ ]
175
+ });
176
+ if (error && !screenshot) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
177
+ className: "screenshot-viewer error",
178
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
179
+ className: "screenshot-placeholder",
180
+ children: [
181
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("h3", {
182
+ children: "\uD83D\uDCF1 Screen Preview"
183
+ }),
184
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("p", {
185
+ className: "error-message",
186
+ children: error
187
+ })
188
+ ]
189
+ })
190
+ });
191
+ const formatLastUpdateTime = (timestamp)=>{
192
+ if (!timestamp) return '';
193
+ const now = Date.now();
194
+ const diff = Math.floor((now - timestamp) / 1000);
195
+ if (diff < 60) return `${diff}s ago`;
196
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
197
+ return new Date(timestamp).toLocaleTimeString();
198
+ };
199
+ return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
200
+ className: "screenshot-viewer",
201
+ children: [
202
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
203
+ className: "screenshot-header",
204
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
205
+ className: "screenshot-title",
206
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("h3", {
207
+ children: (null == interfaceInfo ? void 0 : interfaceInfo.type) ? interfaceInfo.type : 'Device Name'
208
+ })
209
+ })
210
+ }),
211
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
212
+ className: "screenshot-container",
213
+ children: [
214
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
215
+ className: "screenshot-overlay",
216
+ children: [
217
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
218
+ className: "device-name-overlay",
219
+ children: [
220
+ "Device Name",
221
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Tooltip, {
222
+ title: null == interfaceInfo ? void 0 : interfaceInfo.description,
223
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(icons_namespaceObject.InfoCircleOutlined, {
224
+ size: 16,
225
+ className: "info-icon"
226
+ })
227
+ })
228
+ ]
229
+ }),
230
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
231
+ className: "screenshot-controls",
232
+ children: [
233
+ lastUpdateTime > 0 && /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("span", {
234
+ className: "last-update-time",
235
+ children: [
236
+ "Last updated ",
237
+ formatLastUpdateTime(lastUpdateTime)
238
+ ]
239
+ }),
240
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Tooltip, {
241
+ title: "Refresh screenshot",
242
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Button, {
243
+ icon: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(icons_namespaceObject.ReloadOutlined, {}),
244
+ onClick: handleManualRefresh,
245
+ loading: loading,
246
+ size: "small"
247
+ })
248
+ }),
249
+ isUserOperating && /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("span", {
250
+ className: "operation-indicator",
251
+ children: [
252
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Spin, {
253
+ size: "small"
254
+ }),
255
+ " Operating..."
256
+ ]
257
+ })
258
+ ]
259
+ })
260
+ ]
261
+ }),
262
+ /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
263
+ className: "screenshot-content",
264
+ children: screenshot ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("img", {
265
+ src: screenshot.startsWith('data:image/') ? screenshot : `data:image/png;base64,${screenshot}`,
266
+ alt: "Device Screenshot",
267
+ className: "screenshot-image",
268
+ onLoad: ()=>console.log('Screenshot image loaded successfully'),
269
+ onError: (e)=>{
270
+ console.error('Screenshot image load error:', e);
271
+ console.error('Screenshot data preview:', screenshot.substring(0, 100));
272
+ setError('Failed to load screenshot image');
273
+ }
274
+ }) : /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
275
+ className: "screenshot-placeholder",
276
+ children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("p", {
277
+ children: "No screenshot available"
278
+ })
279
+ })
280
+ })
281
+ ]
282
+ })
283
+ ]
284
+ });
285
+ }
286
+ exports["default"] = __webpack_exports__["default"];
287
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
288
+ "default"
289
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
290
+ Object.defineProperty(exports, '__esModule', {
291
+ value: true
292
+ });
package/dist/lib/index.js CHANGED
@@ -63,6 +63,7 @@ __webpack_require__.d(__webpack_exports__, {
63
63
  PromptInput: ()=>prompt_input_index_js_namespaceObject.PromptInput,
64
64
  StaticContextProvider: ()=>context_provider_js_namespaceObject.StaticContextProvider,
65
65
  Blackboard: ()=>blackboard_index_js_namespaceObject.Blackboard,
66
+ ScreenshotViewer: ()=>screenshot_viewer_index_js_default(),
66
67
  Player: ()=>player_index_js_namespaceObject.Player,
67
68
  AgentContextProvider: ()=>context_provider_js_namespaceObject.AgentContextProvider,
68
69
  EnvConfigReminder: ()=>env_config_reminder_index_js_namespaceObject.EnvConfigReminder,
@@ -76,6 +77,7 @@ __webpack_require__.d(__webpack_exports__, {
76
77
  });
77
78
  require("./component/playground/index.css");
78
79
  require("./component/universal-playground/index.css");
80
+ require("./components/screenshot-viewer/index.css");
79
81
  const replay_scripts_js_namespaceObject = require("./utils/replay-scripts.js");
80
82
  const store_js_namespaceObject = require("./store/store.js");
81
83
  const color_js_namespaceObject = require("./utils/color.js");
@@ -92,6 +94,8 @@ const context_preview_index_js_namespaceObject = require("./component/context-pr
92
94
  const prompt_input_index_js_namespaceObject = require("./component/prompt-input/index.js");
93
95
  const player_index_js_namespaceObject = require("./component/player/index.js");
94
96
  const blackboard_index_js_namespaceObject = require("./component/blackboard/index.js");
97
+ const screenshot_viewer_index_js_namespaceObject = require("./components/screenshot-viewer/index.js");
98
+ var screenshot_viewer_index_js_default = /*#__PURE__*/ __webpack_require__.n(screenshot_viewer_index_js_namespaceObject);
95
99
  const playground_utils_js_namespaceObject = require("./utils/playground-utils.js");
96
100
  const external_utils_index_js_namespaceObject = require("./utils/index.js");
97
101
  const shiny_text_index_js_namespaceObject = require("./component/shiny-text/index.js");
@@ -116,6 +120,7 @@ exports.NoOpStorageProvider = __webpack_exports__.NoOpStorageProvider;
116
120
  exports.Player = __webpack_exports__.Player;
117
121
  exports.PlaygroundResultView = __webpack_exports__.PlaygroundResultView;
118
122
  exports.PromptInput = __webpack_exports__.PromptInput;
123
+ exports.ScreenshotViewer = __webpack_exports__.ScreenshotViewer;
119
124
  exports.ServiceModeControl = __webpack_exports__.ServiceModeControl;
120
125
  exports.ShinyText = __webpack_exports__.ShinyText;
121
126
  exports.StaticContextProvider = __webpack_exports__.StaticContextProvider;
@@ -157,6 +162,7 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
157
162
  "Player",
158
163
  "PlaygroundResultView",
159
164
  "PromptInput",
165
+ "ScreenshotViewer",
160
166
  "ServiceModeControl",
161
167
  "ShinyText",
162
168
  "StaticContextProvider",
@@ -0,0 +1,15 @@
1
+ import './index.less';
2
+ interface ScreenshotViewerProps {
3
+ getScreenshot: () => Promise<{
4
+ screenshot: string;
5
+ timestamp: number;
6
+ } | null>;
7
+ getInterfaceInfo?: () => Promise<{
8
+ type: string;
9
+ description?: string;
10
+ } | null>;
11
+ serverOnline: boolean;
12
+ isUserOperating?: boolean;
13
+ }
14
+ export default function ScreenshotViewer({ getScreenshot, getInterfaceInfo, serverOnline, isUserOperating, }: ScreenshotViewerProps): import("react").JSX.Element;
15
+ export {};
@@ -1,5 +1,6 @@
1
1
  import './component/playground/index.less';
2
2
  import './component/universal-playground/index.less';
3
+ import './components/screenshot-viewer/index.less';
3
4
  export { type AnimationScript, type ReplayScriptsInfo, allScriptsFromDump, generateAnimationScripts, } from './utils/replay-scripts';
4
5
  export { useEnvConfig } from './store/store';
5
6
  export { colorForName, highlightColorForType, globalThemeConfig, } from './utils/color';
@@ -18,6 +19,7 @@ export { ContextPreview } from './component/context-preview';
18
19
  export { PromptInput } from './component/prompt-input';
19
20
  export { Player } from './component/player';
20
21
  export { Blackboard } from './component/blackboard';
22
+ export { default as ScreenshotViewer } from './components/screenshot-viewer';
21
23
  export { actionNameForType, staticAgentFromContext, getPlaceholderForType, } from './utils/playground-utils';
22
24
  export { timeStr, filterBase64Value } from './utils';
23
25
  export { default as ShinyText } from './component/shiny-text';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/visualizer",
3
- "version": "1.0.1-beta-20251027074226.0",
3
+ "version": "1.0.1-beta-20251028021317.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",
@@ -60,10 +60,10 @@
60
60
  "antd": "^5.21.6",
61
61
  "buffer": "6.0.3",
62
62
  "dayjs": "^1.11.11",
63
- "@midscene/core": "1.0.1-beta-20251027074226.0",
64
- "@midscene/web": "1.0.1-beta-20251027074226.0",
65
- "@midscene/playground": "1.0.1-beta-20251027074226.0",
66
- "@midscene/shared": "1.0.1-beta-20251027074226.0"
63
+ "@midscene/core": "1.0.1-beta-20251028021317.0",
64
+ "@midscene/playground": "1.0.1-beta-20251028021317.0",
65
+ "@midscene/web": "1.0.1-beta-20251028021317.0",
66
+ "@midscene/shared": "1.0.1-beta-20251028021317.0"
67
67
  },
68
68
  "license": "MIT",
69
69
  "scripts": {