@midscene/visualizer 0.28.7-beta-20250912013851.0 → 0.28.7-beta-20250912113807.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.
@@ -1,9 +1,26 @@
1
+ .history-modal-overlay {
2
+ z-index: 1000;
3
+ background: rgba(0, 0, 0, .45);
4
+ justify-content: stretch;
5
+ align-items: flex-end;
6
+ display: flex;
7
+ position: absolute;
8
+ top: 0;
9
+ bottom: 0;
10
+ left: 0;
11
+ right: 0;
12
+ }
13
+
1
14
  .history-modal-container {
15
+ z-index: 1001;
16
+ background: #fff;
2
17
  border-radius: 12px 12px 0 0;
3
18
  flex-direction: column;
4
- height: 70vh;
19
+ width: 100%;
20
+ height: 400px;
5
21
  display: flex;
6
22
  overflow: hidden;
23
+ box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
7
24
  }
8
25
 
9
26
  .history-modal-container .history-modal-header {
@@ -114,19 +131,3 @@
114
131
  padding: 40px 20px;
115
132
  }
116
133
 
117
- .ant-modal-wrap .ant-modal-content {
118
- animation: .3s cubic-bezier(.4, 0, .2, 1) forwards slideUpFromBottom !important;
119
- }
120
-
121
- @keyframes slideUpFromBottom {
122
- 0% {
123
- opacity: 0;
124
- transform: translateY(100%);
125
- }
126
-
127
- 100% {
128
- opacity: 1;
129
- transform: translateY(0);
130
- }
131
- }
132
-
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Button, Input, Modal, Typography } from "antd";
2
+ import { Button, Input, Typography } from "antd";
3
3
  import { useMemo, useState } from "react";
4
4
  import icons_close from "../../icons/close.mjs";
5
5
  import icons_history from "../../icons/history.mjs";
@@ -63,50 +63,12 @@ const HistorySelector = (param)=>{
63
63
  height: 24
64
64
  })
65
65
  }),
66
- /*#__PURE__*/ jsx(Modal, {
67
- open: isModalOpen,
68
- onCancel: ()=>setIsModalOpen(false),
69
- footer: null,
70
- width: "100%",
71
- closable: false,
72
- centered: false,
73
- transitionName: "",
74
- maskTransitionName: "",
75
- style: {
76
- margin: 0,
77
- padding: 0,
78
- maxWidth: 'none',
79
- top: 'auto',
80
- bottom: 0
81
- },
82
- styles: {
83
- wrapper: {
84
- alignItems: 'flex-end',
85
- justifyContent: 'center',
86
- paddingBottom: 0,
87
- display: 'flex'
88
- },
89
- body: {
90
- height: '70vh',
91
- padding: 0,
92
- margin: 0
93
- },
94
- content: {
95
- height: '70vh',
96
- borderRadius: '12px 12px 0 0',
97
- margin: 0,
98
- padding: 0,
99
- marginBottom: 0,
100
- position: 'fixed',
101
- bottom: 0,
102
- left: 0,
103
- right: 0
104
- }
105
- },
106
- maskClosable: true,
107
- destroyOnClose: true,
66
+ isModalOpen && /*#__PURE__*/ jsx("div", {
67
+ className: "history-modal-overlay",
68
+ onClick: ()=>setIsModalOpen(false),
108
69
  children: /*#__PURE__*/ jsxs("div", {
109
70
  className: "history-modal-container",
71
+ onClick: (e)=>e.stopPropagation(),
110
72
  children: [
111
73
  /*#__PURE__*/ jsxs("div", {
112
74
  className: "history-modal-header",
@@ -1,6 +1,5 @@
1
1
  .result-wrapper {
2
2
  justify-content: center;
3
- align-items: center;
4
3
  height: 100%;
5
4
  margin: 4px 0;
6
5
  display: flex;
@@ -40,22 +39,48 @@
40
39
  .prompt-input-wrapper .mode-radio-group-wrapper {
41
40
  justify-content: space-between;
42
41
  align-items: center;
42
+ gap: 8px;
43
43
  display: flex;
44
44
  }
45
45
 
46
46
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group {
47
+ scrollbar-width: none;
48
+ -ms-overflow-style: none;
47
49
  align-items: center;
50
+ gap: 8px;
51
+ width: 350px;
52
+ min-width: 350px;
53
+ max-width: 350px;
48
54
  height: 100%;
49
55
  display: flex;
56
+ overflow-x: auto;
57
+ overflow-y: hidden;
58
+ }
59
+
60
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group::-webkit-scrollbar {
61
+ display: none;
62
+ }
63
+
64
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item {
65
+ flex-shrink: 0;
66
+ margin: 0 !important;
67
+ }
68
+
69
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item .ant-radio-group {
70
+ flex-wrap: nowrap;
71
+ gap: 8px;
72
+ display: flex;
50
73
  }
51
74
 
52
75
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-radio-button-wrapper {
53
76
  height: 24px;
54
77
  box-shadow: none;
78
+ white-space: nowrap;
55
79
  background-color: #f7f7f7;
56
80
  border: none;
57
81
  border-radius: 11px;
58
- margin-right: 8px;
82
+ flex-shrink: 0;
83
+ margin-right: 0;
59
84
  padding: 0 8px;
60
85
  font-size: 12px;
61
86
  line-height: 24px;
@@ -79,12 +104,18 @@
79
104
  color: #fff;
80
105
  }
81
106
 
107
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-dropdown-trigger {
108
+ flex-shrink: 0;
109
+ }
110
+
82
111
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .more-apis-button {
83
112
  height: 24px;
84
113
  box-shadow: none;
114
+ white-space: nowrap;
85
115
  background-color: #f7f7f7;
86
116
  border: none;
87
117
  border-radius: 11px;
118
+ flex-shrink: 0;
88
119
  align-items: center;
89
120
  gap: 2px;
90
121
  max-width: 160px;
@@ -114,6 +145,7 @@
114
145
  }
115
146
 
116
147
  .prompt-input-wrapper .mode-radio-group-wrapper .action-icons {
148
+ flex-shrink: 0;
117
149
  align-items: center;
118
150
  display: flex;
119
151
  }
@@ -1,6 +1,5 @@
1
1
  .result-wrapper {
2
2
  justify-content: center;
3
- align-items: center;
4
3
  height: 100%;
5
4
  margin: 4px 0;
6
5
  display: flex;
@@ -54,8 +54,7 @@ const PlaygroundResultView = (param)=>{
54
54
  display: 'flex',
55
55
  flexDirection: 'column',
56
56
  flex: '1 1 auto',
57
- justifyContent: 'center',
58
- alignItems: 'center'
57
+ justifyContent: 'center'
59
58
  },
60
59
  children: resultDataToShow
61
60
  });
@@ -5,22 +5,48 @@
5
5
  .prompt-input-wrapper .mode-radio-group-wrapper {
6
6
  justify-content: space-between;
7
7
  align-items: center;
8
+ gap: 8px;
8
9
  display: flex;
9
10
  }
10
11
 
11
12
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group {
13
+ scrollbar-width: none;
14
+ -ms-overflow-style: none;
12
15
  align-items: center;
16
+ gap: 8px;
17
+ width: 350px;
18
+ min-width: 350px;
19
+ max-width: 350px;
13
20
  height: 100%;
14
21
  display: flex;
22
+ overflow-x: auto;
23
+ overflow-y: hidden;
24
+ }
25
+
26
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group::-webkit-scrollbar {
27
+ display: none;
28
+ }
29
+
30
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item {
31
+ flex-shrink: 0;
32
+ margin: 0 !important;
33
+ }
34
+
35
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item .ant-radio-group {
36
+ flex-wrap: nowrap;
37
+ gap: 8px;
38
+ display: flex;
15
39
  }
16
40
 
17
41
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-radio-button-wrapper {
18
42
  height: 24px;
19
43
  box-shadow: none;
44
+ white-space: nowrap;
20
45
  background-color: #f7f7f7;
21
46
  border: none;
22
47
  border-radius: 11px;
23
- margin-right: 8px;
48
+ flex-shrink: 0;
49
+ margin-right: 0;
24
50
  padding: 0 8px;
25
51
  font-size: 12px;
26
52
  line-height: 24px;
@@ -44,12 +70,18 @@
44
70
  color: #fff;
45
71
  }
46
72
 
73
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-dropdown-trigger {
74
+ flex-shrink: 0;
75
+ }
76
+
47
77
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .more-apis-button {
48
78
  height: 24px;
49
79
  box-shadow: none;
80
+ white-space: nowrap;
50
81
  background-color: #f7f7f7;
51
82
  border: none;
52
83
  border-radius: 11px;
84
+ flex-shrink: 0;
53
85
  align-items: center;
54
86
  gap: 2px;
55
87
  max-width: 160px;
@@ -79,6 +111,7 @@
79
111
  }
80
112
 
81
113
  .prompt-input-wrapper .mode-radio-group-wrapper .action-icons {
114
+ flex-shrink: 0;
82
115
  align-items: center;
83
116
  display: flex;
84
117
  }
@@ -4,13 +4,13 @@
4
4
  width: 100%;
5
5
  height: 100vh;
6
6
  display: flex;
7
+ position: relative;
7
8
  }
8
9
 
9
10
  .playground-container .command-form {
10
11
  flex-direction: column;
11
12
  width: 100%;
12
13
  height: 100%;
13
- padding: 0 12px;
14
14
  display: flex;
15
15
  }
16
16
 
@@ -119,7 +119,7 @@
119
119
 
120
120
  .playground-container .user-message-container .user-message-bubble {
121
121
  color: rgba(0, 0, 0, .85);
122
- text-align: center;
122
+ text-align: left;
123
123
  background: #f2f4f7;
124
124
  border-radius: 12px;
125
125
  max-width: 80%;
@@ -11,7 +11,12 @@ import { PlaygroundResultView } from "../playground-result/index.mjs";
11
11
  import "./index.css";
12
12
  import avatar from "../../icons/avatar.mjs";
13
13
  import { PromptInput } from "../prompt-input/index.mjs";
14
+ import { LocalStorageProvider } from "./providers/storage-provider.mjs";
14
15
  const { Text } = Typography;
16
+ function getSDKId(sdk) {
17
+ if (sdk.id && 'string' == typeof sdk.id) return `agent-${sdk.id}`;
18
+ return 'playground-default';
19
+ }
15
20
  function ErrorMessage(param) {
16
21
  let { error } = param;
17
22
  if (!error) return null;
@@ -36,9 +41,13 @@ function UniversalPlayground(param) {
36
41
  let { playgroundSDK, storage, contextProvider, config: componentConfig = {}, branding = {}, className = '', dryMode = false, showContextPreview = true } = param;
37
42
  const [form] = Form.useForm();
38
43
  const { deepThink, screenshotIncluded, domIncluded, config } = useEnvConfig();
39
- const enablePersistence = false !== componentConfig.enablePersistence;
40
- const { loading, setLoading, infoList, setInfoList, actionSpace, actionSpaceLoading, uiContextPreview, setUiContextPreview, showScrollToBottomButton, verticalMode, replayCounter, setReplayCounter, infoListRef, currentRunningIdRef, interruptedFlagRef, clearInfoList, handleScrollToBottom } = usePlaygroundState(playgroundSDK, storage, contextProvider, enablePersistence);
41
- const { handleRun: executeAction, handleStop, canStop } = usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, setLoading, infoList, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef);
44
+ const effectiveStorage = (()=>{
45
+ if (storage) return storage;
46
+ const namespace = componentConfig.storageNamespace || getSDKId(playgroundSDK);
47
+ return new LocalStorageProvider(namespace);
48
+ })();
49
+ const { loading, setLoading, infoList, setInfoList, actionSpace, actionSpaceLoading, uiContextPreview, setUiContextPreview, showScrollToBottomButton, verticalMode, replayCounter, setReplayCounter, infoListRef, currentRunningIdRef, interruptedFlagRef, clearInfoList, handleScrollToBottom } = usePlaygroundState(playgroundSDK, effectiveStorage, contextProvider);
50
+ const { handleRun: executeAction, handleStop, canStop } = usePlaygroundExecution(playgroundSDK, effectiveStorage, actionSpace, loading, setLoading, infoList, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef);
42
51
  useEffect(()=>{
43
52
  const completeConfig = {
44
53
  ...config,
@@ -46,7 +55,7 @@ function UniversalPlayground(param) {
46
55
  screenshotIncluded,
47
56
  domIncluded
48
57
  };
49
- if (playgroundSDK.overrideConfig) playgroundSDK.overrideConfig(completeConfig).catch((error)=>{
58
+ if (null == playgroundSDK ? void 0 : playgroundSDK.overrideConfig) playgroundSDK.overrideConfig(completeConfig).catch((error)=>{
50
59
  console.error('Failed to override SDK config:', error);
51
60
  });
52
61
  }, [
@@ -245,7 +254,7 @@ function UniversalPlayground(param) {
245
254
  /*#__PURE__*/ jsxs("div", {
246
255
  className: "bottom-input-section",
247
256
  children: [
248
- !componentConfig.serverMode && /*#__PURE__*/ jsx(EnvConfigReminder, {}),
257
+ componentConfig.showEnvConfigReminder ? /*#__PURE__*/ jsx(EnvConfigReminder, {}) : null,
249
258
  /*#__PURE__*/ jsx(PromptInput, {
250
259
  runButtonEnabled: runButtonEnabled,
251
260
  form: form,
@@ -7,6 +7,7 @@ const noReplayAPIs = [
7
7
  ];
8
8
  function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, setLoading, infoList, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef) {
9
9
  const handleRun = useCallback(async (value)=>{
10
+ if (!playgroundSDK) return void console.warn('PlaygroundSDK is not available');
10
11
  const thisRunningId = Date.now();
11
12
  const actionType = value.type;
12
13
  const displayContent = `${value.type}: ${value.prompt || JSON.stringify(value.params)}`;
@@ -39,18 +40,23 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
39
40
  try {
40
41
  currentRunningIdRef.current = thisRunningId;
41
42
  interruptedFlagRef.current[thisRunningId] = false;
43
+ if (playgroundSDK.onProgressUpdate) playgroundSDK.onProgressUpdate(()=>{});
42
44
  if (playgroundSDK.onProgressUpdate) playgroundSDK.onProgressUpdate((tip)=>{
43
45
  if (interruptedFlagRef.current[thisRunningId]) return;
44
- const progressItem = {
45
- id: `progress-${thisRunningId}-${Date.now()}`,
46
- type: 'progress',
47
- content: tip,
48
- timestamp: new Date()
49
- };
50
- setInfoList((prev)=>[
46
+ setInfoList((prev)=>{
47
+ const lastItem = prev[prev.length - 1];
48
+ if (lastItem && 'progress' === lastItem.type && lastItem.content === tip) return prev;
49
+ const progressItem = {
50
+ id: `progress-${thisRunningId}-${Date.now()}`,
51
+ type: 'progress',
52
+ content: tip,
53
+ timestamp: new Date()
54
+ };
55
+ return [
51
56
  ...prev,
52
57
  progressItem
53
- ]);
58
+ ];
59
+ });
54
60
  });
55
61
  result.result = await playgroundSDK.executeAction(actionType, value, {
56
62
  requestId: thisRunningId.toString()
@@ -128,10 +134,11 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
128
134
  ]);
129
135
  const handleStop = useCallback(async ()=>{
130
136
  const thisRunningId = currentRunningIdRef.current;
131
- if (thisRunningId && playgroundSDK.cancelExecution) try {
137
+ if (thisRunningId && playgroundSDK && playgroundSDK.cancelExecution) try {
132
138
  await playgroundSDK.cancelExecution(thisRunningId.toString());
133
139
  interruptedFlagRef.current[thisRunningId] = true;
134
140
  setLoading(false);
141
+ if (playgroundSDK.onProgressUpdate) playgroundSDK.onProgressUpdate(()=>{});
135
142
  setInfoList((prev)=>prev.map((item)=>item.id === `system-${thisRunningId}` && item.loading ? {
136
143
  ...item,
137
144
  content: 'Operation stopped',
@@ -158,7 +165,7 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
158
165
  setLoading,
159
166
  setInfoList
160
167
  ]);
161
- const canStop = loading && !!currentRunningIdRef.current && !!playgroundSDK.cancelExecution;
168
+ const canStop = loading && !!currentRunningIdRef.current && !!playgroundSDK && !!playgroundSDK.cancelExecution;
162
169
  return {
163
170
  handleRun,
164
171
  handleStop,
@@ -1,7 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { WELCOME_MESSAGE_TEMPLATE } from "../utils/constants.mjs";
3
3
  function usePlaygroundState(playgroundSDK, storage, contextProvider) {
4
- let enablePersistence = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : true;
5
4
  const [loading, setLoading] = useState(false);
6
5
  const [infoList, setInfoList] = useState([]);
7
6
  const [actionSpace, setActionSpace] = useState([]);
@@ -20,7 +19,7 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
20
19
  id: 'welcome',
21
20
  timestamp: new Date()
22
21
  };
23
- if (enablePersistence && (null == storage ? void 0 : storage.loadMessages)) try {
22
+ if (null == storage ? void 0 : storage.loadMessages) try {
24
23
  const storedMessages = await storage.loadMessages();
25
24
  const hasWelcomeMessage = storedMessages.some((msg)=>'welcome' === msg.id);
26
25
  hasWelcomeMessage ? setInfoList(storedMessages) : setInfoList([
@@ -40,13 +39,12 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
40
39
  if (0 === infoList.length) initializeMessages();
41
40
  }, []);
42
41
  useEffect(()=>{
43
- if (enablePersistence && (null == storage ? void 0 : storage.saveMessages) && infoList.length > 1) storage.saveMessages(infoList).catch((error)=>{
42
+ if ((null == storage ? void 0 : storage.saveMessages) && infoList.length > 1) storage.saveMessages(infoList).catch((error)=>{
44
43
  console.error('Failed to save messages:', error);
45
44
  });
46
45
  }, [
47
46
  infoList,
48
- storage,
49
- enablePersistence
47
+ storage
50
48
  ]);
51
49
  useEffect(()=>{
52
50
  if (!(null == contextProvider ? void 0 : contextProvider.getUIContext) || uiContextPreview) return;
@@ -62,6 +60,7 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
62
60
  setActionSpaceLoading(true);
63
61
  try {
64
62
  var _contextProvider_getUIContext;
63
+ if (!playgroundSDK) return void setActionSpace([]);
65
64
  const context = uiContextPreview || await (null == contextProvider ? void 0 : null == (_contextProvider_getUIContext = contextProvider.getUIContext) ? void 0 : _contextProvider_getUIContext.call(contextProvider));
66
65
  const space = await playgroundSDK.getActionSpace(context);
67
66
  setActionSpace(space || []);
@@ -135,14 +134,13 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
135
134
  setInfoList([
136
135
  welcomeMessage
137
136
  ]);
138
- if (enablePersistence && (null == storage ? void 0 : storage.clearMessages)) try {
137
+ if (null == storage ? void 0 : storage.clearMessages) try {
139
138
  await storage.clearMessages();
140
139
  } catch (error) {
141
140
  console.error('Failed to clear stored messages:', error);
142
141
  }
143
142
  }, [
144
- storage,
145
- enablePersistence
143
+ storage
146
144
  ]);
147
145
  const refreshContext = useCallback(async ()=>{
148
146
  if (null == contextProvider ? void 0 : contextProvider.refreshContext) try {
@@ -1,9 +1,26 @@
1
+ .history-modal-overlay {
2
+ z-index: 1000;
3
+ background: rgba(0, 0, 0, .45);
4
+ justify-content: stretch;
5
+ align-items: flex-end;
6
+ display: flex;
7
+ position: absolute;
8
+ top: 0;
9
+ bottom: 0;
10
+ left: 0;
11
+ right: 0;
12
+ }
13
+
1
14
  .history-modal-container {
15
+ z-index: 1001;
16
+ background: #fff;
2
17
  border-radius: 12px 12px 0 0;
3
18
  flex-direction: column;
4
- height: 70vh;
19
+ width: 100%;
20
+ height: 400px;
5
21
  display: flex;
6
22
  overflow: hidden;
23
+ box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
7
24
  }
8
25
 
9
26
  .history-modal-container .history-modal-header {
@@ -114,19 +131,3 @@
114
131
  padding: 40px 20px;
115
132
  }
116
133
 
117
- .ant-modal-wrap .ant-modal-content {
118
- animation: .3s cubic-bezier(.4, 0, .2, 1) forwards slideUpFromBottom !important;
119
- }
120
-
121
- @keyframes slideUpFromBottom {
122
- 0% {
123
- opacity: 0;
124
- transform: translateY(100%);
125
- }
126
-
127
- 100% {
128
- opacity: 1;
129
- transform: translateY(0);
130
- }
131
- }
132
-
@@ -103,50 +103,12 @@ const HistorySelector = (param)=>{
103
103
  height: 24
104
104
  })
105
105
  }),
106
- /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.Modal, {
107
- open: isModalOpen,
108
- onCancel: ()=>setIsModalOpen(false),
109
- footer: null,
110
- width: "100%",
111
- closable: false,
112
- centered: false,
113
- transitionName: "",
114
- maskTransitionName: "",
115
- style: {
116
- margin: 0,
117
- padding: 0,
118
- maxWidth: 'none',
119
- top: 'auto',
120
- bottom: 0
121
- },
122
- styles: {
123
- wrapper: {
124
- alignItems: 'flex-end',
125
- justifyContent: 'center',
126
- paddingBottom: 0,
127
- display: 'flex'
128
- },
129
- body: {
130
- height: '70vh',
131
- padding: 0,
132
- margin: 0
133
- },
134
- content: {
135
- height: '70vh',
136
- borderRadius: '12px 12px 0 0',
137
- margin: 0,
138
- padding: 0,
139
- marginBottom: 0,
140
- position: 'fixed',
141
- bottom: 0,
142
- left: 0,
143
- right: 0
144
- }
145
- },
146
- maskClosable: true,
147
- destroyOnClose: true,
106
+ isModalOpen && /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
107
+ className: "history-modal-overlay",
108
+ onClick: ()=>setIsModalOpen(false),
148
109
  children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
149
110
  className: "history-modal-container",
111
+ onClick: (e)=>e.stopPropagation(),
150
112
  children: [
151
113
  /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
152
114
  className: "history-modal-header",
@@ -1,6 +1,5 @@
1
1
  .result-wrapper {
2
2
  justify-content: center;
3
- align-items: center;
4
3
  height: 100%;
5
4
  margin: 4px 0;
6
5
  display: flex;
@@ -40,22 +39,48 @@
40
39
  .prompt-input-wrapper .mode-radio-group-wrapper {
41
40
  justify-content: space-between;
42
41
  align-items: center;
42
+ gap: 8px;
43
43
  display: flex;
44
44
  }
45
45
 
46
46
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group {
47
+ scrollbar-width: none;
48
+ -ms-overflow-style: none;
47
49
  align-items: center;
50
+ gap: 8px;
51
+ width: 350px;
52
+ min-width: 350px;
53
+ max-width: 350px;
48
54
  height: 100%;
49
55
  display: flex;
56
+ overflow-x: auto;
57
+ overflow-y: hidden;
58
+ }
59
+
60
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group::-webkit-scrollbar {
61
+ display: none;
62
+ }
63
+
64
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item {
65
+ flex-shrink: 0;
66
+ margin: 0 !important;
67
+ }
68
+
69
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item .ant-radio-group {
70
+ flex-wrap: nowrap;
71
+ gap: 8px;
72
+ display: flex;
50
73
  }
51
74
 
52
75
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-radio-button-wrapper {
53
76
  height: 24px;
54
77
  box-shadow: none;
78
+ white-space: nowrap;
55
79
  background-color: #f7f7f7;
56
80
  border: none;
57
81
  border-radius: 11px;
58
- margin-right: 8px;
82
+ flex-shrink: 0;
83
+ margin-right: 0;
59
84
  padding: 0 8px;
60
85
  font-size: 12px;
61
86
  line-height: 24px;
@@ -79,12 +104,18 @@
79
104
  color: #fff;
80
105
  }
81
106
 
107
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-dropdown-trigger {
108
+ flex-shrink: 0;
109
+ }
110
+
82
111
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .more-apis-button {
83
112
  height: 24px;
84
113
  box-shadow: none;
114
+ white-space: nowrap;
85
115
  background-color: #f7f7f7;
86
116
  border: none;
87
117
  border-radius: 11px;
118
+ flex-shrink: 0;
88
119
  align-items: center;
89
120
  gap: 2px;
90
121
  max-width: 160px;
@@ -114,6 +145,7 @@
114
145
  }
115
146
 
116
147
  .prompt-input-wrapper .mode-radio-group-wrapper .action-icons {
148
+ flex-shrink: 0;
117
149
  align-items: center;
118
150
  display: flex;
119
151
  }
@@ -1,6 +1,5 @@
1
1
  .result-wrapper {
2
2
  justify-content: center;
3
- align-items: center;
4
3
  height: 100%;
5
4
  margin: 4px 0;
6
5
  display: flex;
@@ -92,8 +92,7 @@ const PlaygroundResultView = (param)=>{
92
92
  display: 'flex',
93
93
  flexDirection: 'column',
94
94
  flex: '1 1 auto',
95
- justifyContent: 'center',
96
- alignItems: 'center'
95
+ justifyContent: 'center'
97
96
  },
98
97
  children: resultDataToShow
99
98
  });
@@ -5,22 +5,48 @@
5
5
  .prompt-input-wrapper .mode-radio-group-wrapper {
6
6
  justify-content: space-between;
7
7
  align-items: center;
8
+ gap: 8px;
8
9
  display: flex;
9
10
  }
10
11
 
11
12
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group {
13
+ scrollbar-width: none;
14
+ -ms-overflow-style: none;
12
15
  align-items: center;
16
+ gap: 8px;
17
+ width: 350px;
18
+ min-width: 350px;
19
+ max-width: 350px;
13
20
  height: 100%;
14
21
  display: flex;
22
+ overflow-x: auto;
23
+ overflow-y: hidden;
24
+ }
25
+
26
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group::-webkit-scrollbar {
27
+ display: none;
28
+ }
29
+
30
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item {
31
+ flex-shrink: 0;
32
+ margin: 0 !important;
33
+ }
34
+
35
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-form-item .ant-radio-group {
36
+ flex-wrap: nowrap;
37
+ gap: 8px;
38
+ display: flex;
15
39
  }
16
40
 
17
41
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-radio-button-wrapper {
18
42
  height: 24px;
19
43
  box-shadow: none;
44
+ white-space: nowrap;
20
45
  background-color: #f7f7f7;
21
46
  border: none;
22
47
  border-radius: 11px;
23
- margin-right: 8px;
48
+ flex-shrink: 0;
49
+ margin-right: 0;
24
50
  padding: 0 8px;
25
51
  font-size: 12px;
26
52
  line-height: 24px;
@@ -44,12 +70,18 @@
44
70
  color: #fff;
45
71
  }
46
72
 
73
+ .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .ant-dropdown-trigger {
74
+ flex-shrink: 0;
75
+ }
76
+
47
77
  .prompt-input-wrapper .mode-radio-group-wrapper .mode-radio-group .more-apis-button {
48
78
  height: 24px;
49
79
  box-shadow: none;
80
+ white-space: nowrap;
50
81
  background-color: #f7f7f7;
51
82
  border: none;
52
83
  border-radius: 11px;
84
+ flex-shrink: 0;
53
85
  align-items: center;
54
86
  gap: 2px;
55
87
  max-width: 160px;
@@ -79,6 +111,7 @@
79
111
  }
80
112
 
81
113
  .prompt-input-wrapper .mode-radio-group-wrapper .action-icons {
114
+ flex-shrink: 0;
82
115
  align-items: center;
83
116
  display: flex;
84
117
  }
@@ -4,13 +4,13 @@
4
4
  width: 100%;
5
5
  height: 100vh;
6
6
  display: flex;
7
+ position: relative;
7
8
  }
8
9
 
9
10
  .playground-container .command-form {
10
11
  flex-direction: column;
11
12
  width: 100%;
12
13
  height: 100%;
13
- padding: 0 12px;
14
14
  display: flex;
15
15
  }
16
16
 
@@ -119,7 +119,7 @@
119
119
 
120
120
  .playground-container .user-message-container .user-message-bubble {
121
121
  color: rgba(0, 0, 0, .85);
122
- text-align: center;
122
+ text-align: left;
123
123
  background: #f2f4f7;
124
124
  border-radius: 12px;
125
125
  max-width: 80%;
@@ -51,7 +51,12 @@ require("./index.css");
51
51
  const avatar_js_namespaceObject = require("../../icons/avatar.js");
52
52
  var avatar_js_default = /*#__PURE__*/ __webpack_require__.n(avatar_js_namespaceObject);
53
53
  const external_prompt_input_index_js_namespaceObject = require("../prompt-input/index.js");
54
+ const storage_provider_js_namespaceObject = require("./providers/storage-provider.js");
54
55
  const { Text } = external_antd_namespaceObject.Typography;
56
+ function getSDKId(sdk) {
57
+ if (sdk.id && 'string' == typeof sdk.id) return `agent-${sdk.id}`;
58
+ return 'playground-default';
59
+ }
55
60
  function ErrorMessage(param) {
56
61
  let { error } = param;
57
62
  if (!error) return null;
@@ -76,9 +81,13 @@ function UniversalPlayground(param) {
76
81
  let { playgroundSDK, storage, contextProvider, config: componentConfig = {}, branding = {}, className = '', dryMode = false, showContextPreview = true } = param;
77
82
  const [form] = external_antd_namespaceObject.Form.useForm();
78
83
  const { deepThink, screenshotIncluded, domIncluded, config } = (0, store_js_namespaceObject.useEnvConfig)();
79
- const enablePersistence = false !== componentConfig.enablePersistence;
80
- const { loading, setLoading, infoList, setInfoList, actionSpace, actionSpaceLoading, uiContextPreview, setUiContextPreview, showScrollToBottomButton, verticalMode, replayCounter, setReplayCounter, infoListRef, currentRunningIdRef, interruptedFlagRef, clearInfoList, handleScrollToBottom } = (0, usePlaygroundState_js_namespaceObject.usePlaygroundState)(playgroundSDK, storage, contextProvider, enablePersistence);
81
- const { handleRun: executeAction, handleStop, canStop } = (0, usePlaygroundExecution_js_namespaceObject.usePlaygroundExecution)(playgroundSDK, storage, actionSpace, loading, setLoading, infoList, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef);
84
+ const effectiveStorage = (()=>{
85
+ if (storage) return storage;
86
+ const namespace = componentConfig.storageNamespace || getSDKId(playgroundSDK);
87
+ return new storage_provider_js_namespaceObject.LocalStorageProvider(namespace);
88
+ })();
89
+ const { loading, setLoading, infoList, setInfoList, actionSpace, actionSpaceLoading, uiContextPreview, setUiContextPreview, showScrollToBottomButton, verticalMode, replayCounter, setReplayCounter, infoListRef, currentRunningIdRef, interruptedFlagRef, clearInfoList, handleScrollToBottom } = (0, usePlaygroundState_js_namespaceObject.usePlaygroundState)(playgroundSDK, effectiveStorage, contextProvider);
90
+ const { handleRun: executeAction, handleStop, canStop } = (0, usePlaygroundExecution_js_namespaceObject.usePlaygroundExecution)(playgroundSDK, effectiveStorage, actionSpace, loading, setLoading, infoList, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef);
82
91
  (0, external_react_namespaceObject.useEffect)(()=>{
83
92
  const completeConfig = {
84
93
  ...config,
@@ -86,7 +95,7 @@ function UniversalPlayground(param) {
86
95
  screenshotIncluded,
87
96
  domIncluded
88
97
  };
89
- if (playgroundSDK.overrideConfig) playgroundSDK.overrideConfig(completeConfig).catch((error)=>{
98
+ if (null == playgroundSDK ? void 0 : playgroundSDK.overrideConfig) playgroundSDK.overrideConfig(completeConfig).catch((error)=>{
90
99
  console.error('Failed to override SDK config:', error);
91
100
  });
92
101
  }, [
@@ -285,7 +294,7 @@ function UniversalPlayground(param) {
285
294
  /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)("div", {
286
295
  className: "bottom-input-section",
287
296
  children: [
288
- !componentConfig.serverMode && /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_env_config_reminder_index_js_namespaceObject.EnvConfigReminder, {}),
297
+ componentConfig.showEnvConfigReminder ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_env_config_reminder_index_js_namespaceObject.EnvConfigReminder, {}) : null,
289
298
  /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_prompt_input_index_js_namespaceObject.PromptInput, {
290
299
  runButtonEnabled: runButtonEnabled,
291
300
  form: form,
@@ -35,6 +35,7 @@ const noReplayAPIs = [
35
35
  ];
36
36
  function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, setLoading, infoList, setInfoList, replayCounter, setReplayCounter, verticalMode, currentRunningIdRef, interruptedFlagRef) {
37
37
  const handleRun = (0, external_react_namespaceObject.useCallback)(async (value)=>{
38
+ if (!playgroundSDK) return void console.warn('PlaygroundSDK is not available');
38
39
  const thisRunningId = Date.now();
39
40
  const actionType = value.type;
40
41
  const displayContent = `${value.type}: ${value.prompt || JSON.stringify(value.params)}`;
@@ -67,18 +68,23 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
67
68
  try {
68
69
  currentRunningIdRef.current = thisRunningId;
69
70
  interruptedFlagRef.current[thisRunningId] = false;
71
+ if (playgroundSDK.onProgressUpdate) playgroundSDK.onProgressUpdate(()=>{});
70
72
  if (playgroundSDK.onProgressUpdate) playgroundSDK.onProgressUpdate((tip)=>{
71
73
  if (interruptedFlagRef.current[thisRunningId]) return;
72
- const progressItem = {
73
- id: `progress-${thisRunningId}-${Date.now()}`,
74
- type: 'progress',
75
- content: tip,
76
- timestamp: new Date()
77
- };
78
- setInfoList((prev)=>[
74
+ setInfoList((prev)=>{
75
+ const lastItem = prev[prev.length - 1];
76
+ if (lastItem && 'progress' === lastItem.type && lastItem.content === tip) return prev;
77
+ const progressItem = {
78
+ id: `progress-${thisRunningId}-${Date.now()}`,
79
+ type: 'progress',
80
+ content: tip,
81
+ timestamp: new Date()
82
+ };
83
+ return [
79
84
  ...prev,
80
85
  progressItem
81
- ]);
86
+ ];
87
+ });
82
88
  });
83
89
  result.result = await playgroundSDK.executeAction(actionType, value, {
84
90
  requestId: thisRunningId.toString()
@@ -156,10 +162,11 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
156
162
  ]);
157
163
  const handleStop = (0, external_react_namespaceObject.useCallback)(async ()=>{
158
164
  const thisRunningId = currentRunningIdRef.current;
159
- if (thisRunningId && playgroundSDK.cancelExecution) try {
165
+ if (thisRunningId && playgroundSDK && playgroundSDK.cancelExecution) try {
160
166
  await playgroundSDK.cancelExecution(thisRunningId.toString());
161
167
  interruptedFlagRef.current[thisRunningId] = true;
162
168
  setLoading(false);
169
+ if (playgroundSDK.onProgressUpdate) playgroundSDK.onProgressUpdate(()=>{});
163
170
  setInfoList((prev)=>prev.map((item)=>item.id === `system-${thisRunningId}` && item.loading ? {
164
171
  ...item,
165
172
  content: 'Operation stopped',
@@ -186,7 +193,7 @@ function usePlaygroundExecution(playgroundSDK, storage, actionSpace, loading, se
186
193
  setLoading,
187
194
  setInfoList
188
195
  ]);
189
- const canStop = loading && !!currentRunningIdRef.current && !!playgroundSDK.cancelExecution;
196
+ const canStop = loading && !!currentRunningIdRef.current && !!playgroundSDK && !!playgroundSDK.cancelExecution;
190
197
  return {
191
198
  handleRun,
192
199
  handleStop,
@@ -29,7 +29,6 @@ __webpack_require__.d(__webpack_exports__, {
29
29
  const external_react_namespaceObject = require("react");
30
30
  const constants_js_namespaceObject = require("../utils/constants.js");
31
31
  function usePlaygroundState(playgroundSDK, storage, contextProvider) {
32
- let enablePersistence = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : true;
33
32
  const [loading, setLoading] = (0, external_react_namespaceObject.useState)(false);
34
33
  const [infoList, setInfoList] = (0, external_react_namespaceObject.useState)([]);
35
34
  const [actionSpace, setActionSpace] = (0, external_react_namespaceObject.useState)([]);
@@ -48,7 +47,7 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
48
47
  id: 'welcome',
49
48
  timestamp: new Date()
50
49
  };
51
- if (enablePersistence && (null == storage ? void 0 : storage.loadMessages)) try {
50
+ if (null == storage ? void 0 : storage.loadMessages) try {
52
51
  const storedMessages = await storage.loadMessages();
53
52
  const hasWelcomeMessage = storedMessages.some((msg)=>'welcome' === msg.id);
54
53
  hasWelcomeMessage ? setInfoList(storedMessages) : setInfoList([
@@ -68,13 +67,12 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
68
67
  if (0 === infoList.length) initializeMessages();
69
68
  }, []);
70
69
  (0, external_react_namespaceObject.useEffect)(()=>{
71
- if (enablePersistence && (null == storage ? void 0 : storage.saveMessages) && infoList.length > 1) storage.saveMessages(infoList).catch((error)=>{
70
+ if ((null == storage ? void 0 : storage.saveMessages) && infoList.length > 1) storage.saveMessages(infoList).catch((error)=>{
72
71
  console.error('Failed to save messages:', error);
73
72
  });
74
73
  }, [
75
74
  infoList,
76
- storage,
77
- enablePersistence
75
+ storage
78
76
  ]);
79
77
  (0, external_react_namespaceObject.useEffect)(()=>{
80
78
  if (!(null == contextProvider ? void 0 : contextProvider.getUIContext) || uiContextPreview) return;
@@ -90,6 +88,7 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
90
88
  setActionSpaceLoading(true);
91
89
  try {
92
90
  var _contextProvider_getUIContext;
91
+ if (!playgroundSDK) return void setActionSpace([]);
93
92
  const context = uiContextPreview || await (null == contextProvider ? void 0 : null == (_contextProvider_getUIContext = contextProvider.getUIContext) ? void 0 : _contextProvider_getUIContext.call(contextProvider));
94
93
  const space = await playgroundSDK.getActionSpace(context);
95
94
  setActionSpace(space || []);
@@ -163,14 +162,13 @@ function usePlaygroundState(playgroundSDK, storage, contextProvider) {
163
162
  setInfoList([
164
163
  welcomeMessage
165
164
  ]);
166
- if (enablePersistence && (null == storage ? void 0 : storage.clearMessages)) try {
165
+ if (null == storage ? void 0 : storage.clearMessages) try {
167
166
  await storage.clearMessages();
168
167
  } catch (error) {
169
168
  console.error('Failed to clear stored messages:', error);
170
169
  }
171
170
  }, [
172
- storage,
173
- enablePersistence
171
+ storage
174
172
  ]);
175
173
  const refreshContext = (0, external_react_namespaceObject.useCallback)(async ()=>{
176
174
  if (null == contextProvider ? void 0 : contextProvider.refreshContext) try {
@@ -3,7 +3,7 @@ import type { FormValue, InfoListItem, PlaygroundSDKLike, StorageProvider } from
3
3
  /**
4
4
  * Hook for handling playground execution logic
5
5
  */
6
- export declare function usePlaygroundExecution(playgroundSDK: PlaygroundSDKLike, storage: StorageProvider | undefined, actionSpace: DeviceAction<unknown>[], loading: boolean, setLoading: (loading: boolean) => void, infoList: InfoListItem[], setInfoList: React.Dispatch<React.SetStateAction<InfoListItem[]>>, replayCounter: number, setReplayCounter: React.Dispatch<React.SetStateAction<number>>, verticalMode: boolean, currentRunningIdRef: React.MutableRefObject<number | null>, interruptedFlagRef: React.MutableRefObject<Record<number, boolean>>): {
6
+ export declare function usePlaygroundExecution(playgroundSDK: PlaygroundSDKLike | null, storage: StorageProvider | undefined, actionSpace: DeviceAction<unknown>[], loading: boolean, setLoading: (loading: boolean) => void, infoList: InfoListItem[], setInfoList: React.Dispatch<React.SetStateAction<InfoListItem[]>>, replayCounter: number, setReplayCounter: React.Dispatch<React.SetStateAction<number>>, verticalMode: boolean, currentRunningIdRef: React.MutableRefObject<number | null>, interruptedFlagRef: React.MutableRefObject<Record<number, boolean>>): {
7
7
  handleRun: (value: FormValue) => Promise<void>;
8
8
  handleStop: () => Promise<void>;
9
9
  canStop: boolean;
@@ -3,7 +3,7 @@ import type { ContextProvider, InfoListItem, PlaygroundSDKLike, StorageProvider
3
3
  /**
4
4
  * Hook for managing playground state
5
5
  */
6
- export declare function usePlaygroundState(playgroundSDK: PlaygroundSDKLike, storage?: StorageProvider, contextProvider?: ContextProvider, enablePersistence?: boolean): {
6
+ export declare function usePlaygroundState(playgroundSDK: PlaygroundSDKLike | null, storage?: StorageProvider, contextProvider?: ContextProvider): {
7
7
  loading: boolean;
8
8
  setLoading: import("react").Dispatch<import("react").SetStateAction<boolean>>;
9
9
  infoList: InfoListItem[];
@@ -117,6 +117,7 @@ export interface PlaygroundSDKLike {
117
117
  cancelExecution?(requestId: string): Promise<void>;
118
118
  overrideConfig?(config: any): Promise<void>;
119
119
  checkStatus?(): Promise<boolean>;
120
+ id?: string;
120
121
  }
121
122
  export interface StorageProvider {
122
123
  saveMessages?(messages: InfoListItem[]): Promise<void>;
@@ -142,11 +143,12 @@ export interface InfoListItem {
142
143
  }
143
144
  export interface UniversalPlaygroundConfig {
144
145
  showContextPreview?: boolean;
145
- enablePersistence?: boolean;
146
+ storageNamespace?: string;
146
147
  layout?: 'vertical' | 'horizontal';
147
148
  showVersionInfo?: boolean;
148
149
  enableScrollToBottom?: boolean;
149
150
  serverMode?: boolean;
151
+ showEnvConfigReminder?: boolean;
150
152
  }
151
153
  export interface PlaygroundBranding {
152
154
  title?: string;
@@ -154,7 +156,7 @@ export interface PlaygroundBranding {
154
156
  version?: string;
155
157
  }
156
158
  export interface UniversalPlaygroundProps {
157
- playgroundSDK: PlaygroundSDKLike;
159
+ playgroundSDK: PlaygroundSDKLike | null;
158
160
  storage?: StorageProvider;
159
161
  contextProvider?: ContextProvider;
160
162
  config?: UniversalPlaygroundConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/visualizer",
3
- "version": "0.28.7-beta-20250912013851.0",
3
+ "version": "0.28.7-beta-20250912113807.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",
@@ -70,10 +70,10 @@
70
70
  "antd": "^5.21.6",
71
71
  "buffer": "6.0.3",
72
72
  "dayjs": "^1.11.11",
73
- "@midscene/core": "0.28.7-beta-20250912013851.0",
74
- "@midscene/playground": "0.28.7-beta-20250912013851.0",
75
- "@midscene/web": "0.28.7-beta-20250912013851.0",
76
- "@midscene/shared": "0.28.7-beta-20250912013851.0"
73
+ "@midscene/core": "0.28.7-beta-20250912113807.0",
74
+ "@midscene/shared": "0.28.7-beta-20250912113807.0",
75
+ "@midscene/web": "0.28.7-beta-20250912113807.0",
76
+ "@midscene/playground": "0.28.7-beta-20250912113807.0"
77
77
  },
78
78
  "license": "MIT",
79
79
  "scripts": {