@midscene/playground-app 1.7.5-beta-20260421030751.0 → 1.7.5

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.
@@ -119,6 +119,7 @@ function PreviewRenderer({ connectingOverlay, onScrcpyStatusChange, renderErrorO
119
119
  description: "This session did not expose a preview capability in runtime metadata."
120
120
  }) : scrcpyAvailable ? /*#__PURE__*/ jsx(ScrcpyPanel, {
121
121
  connectingOverlay: connectingOverlay,
122
+ deviceId: previewConnection.deviceId,
122
123
  onStatusChange: onScrcpyStatusChange,
123
124
  renderErrorOverlay: renderErrorOverlay,
124
125
  serverUrl: previewConnection.scrcpyUrl
@@ -32,8 +32,50 @@ function _async_to_generator(fn) {
32
32
  });
33
33
  };
34
34
  }
35
+ function _define_property(obj, key, value) {
36
+ if (key in obj) Object.defineProperty(obj, key, {
37
+ value: value,
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true
41
+ });
42
+ else obj[key] = value;
43
+ return obj;
44
+ }
45
+ function _object_spread(target) {
46
+ for(var i = 1; i < arguments.length; i++){
47
+ var source = null != arguments[i] ? arguments[i] : {};
48
+ var ownKeys = Object.keys(source);
49
+ if ("function" == typeof Object.getOwnPropertySymbols) ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
50
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
51
+ }));
52
+ ownKeys.forEach(function(key) {
53
+ _define_property(target, key, source[key]);
54
+ });
55
+ }
56
+ return target;
57
+ }
58
+ function ScrcpyPanel_ownKeys(object, enumerableOnly) {
59
+ var keys = Object.keys(object);
60
+ if (Object.getOwnPropertySymbols) {
61
+ var symbols = Object.getOwnPropertySymbols(object);
62
+ if (enumerableOnly) symbols = symbols.filter(function(sym) {
63
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
64
+ });
65
+ keys.push.apply(keys, symbols);
66
+ }
67
+ return keys;
68
+ }
69
+ function _object_spread_props(target, source) {
70
+ source = null != source ? source : {};
71
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
72
+ else ScrcpyPanel_ownKeys(Object(source)).forEach(function(key) {
73
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
74
+ });
75
+ return target;
76
+ }
35
77
  const { Text } = Typography;
36
- function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs = SCRCPY_METADATA_TIMEOUT_MS, reconnectInterval = 3000 }) {
78
+ function ScrcpyPanel({ connectingOverlay, deviceId, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs = SCRCPY_METADATA_TIMEOUT_MS, reconnectInterval = 3000 }) {
37
79
  const canvasStageRef = useRef(null);
38
80
  const socketRef = useRef(null);
39
81
  const decoderRef = useRef(null);
@@ -58,10 +100,11 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
58
100
  setRetryNonce((current)=>current + 1);
59
101
  }, []);
60
102
  useEffect(()=>{
61
- null == onStatusChange || onStatusChange(status);
103
+ null == onStatusChange || onStatusChange(status, statusText);
62
104
  }, [
63
105
  onStatusChange,
64
- status
106
+ status,
107
+ statusText
65
108
  ]);
66
109
  const clearCanvas = ()=>{
67
110
  const stage = canvasStageRef.current;
@@ -92,7 +135,12 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
92
135
  return;
93
136
  }
94
137
  let disposed = false;
138
+ let connectTimer = null;
95
139
  const cleanup = ()=>{
140
+ if (connectTimer) {
141
+ clearTimeout(connectTimer);
142
+ connectTimer = null;
143
+ }
96
144
  clearMetadataTimeout();
97
145
  if (reconnectTimerRef.current) {
98
146
  clearTimeout(reconnectTimerRef.current);
@@ -137,7 +185,10 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
137
185
  const socket = io(serverUrl, {
138
186
  withCredentials: true,
139
187
  reconnection: false,
140
- timeout: 10000
188
+ timeout: 10000,
189
+ transports: [
190
+ 'websocket'
191
+ ]
141
192
  });
142
193
  socketRef.current = socket;
143
194
  const videoStream = createScrcpyVideoStream(socket);
@@ -155,9 +206,11 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
155
206
  socketRef.current = null;
156
207
  scheduleReconnect();
157
208
  }, metadataTimeoutMs);
158
- socket.emit('connect-device', {
209
+ socket.emit('connect-device', _object_spread_props(_object_spread({}, 'string' == typeof deviceId && deviceId.trim() ? {
210
+ deviceId: deviceId.trim()
211
+ } : {}), {
159
212
  maxSize: 1024
160
- });
213
+ }));
161
214
  });
162
215
  socket.on('preview-status', (event)=>{
163
216
  if (disposed || !isScrcpyPreviewStatusEvent(event)) return;
@@ -217,12 +270,16 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
217
270
  scheduleReconnect();
218
271
  });
219
272
  };
220
- connect();
273
+ connectTimer = setTimeout(()=>{
274
+ connectTimer = null;
275
+ connect();
276
+ }, 0);
221
277
  return ()=>{
222
278
  disposed = true;
223
279
  cleanup();
224
280
  };
225
281
  }, [
282
+ deviceId,
226
283
  metadataTimeoutMs,
227
284
  reconnectInterval,
228
285
  retryNonce,
@@ -27,6 +27,9 @@ function _async_to_generator(fn) {
27
27
  function hasPlaygroundAiConfig(config) {
28
28
  return Object.keys(config).length > 0;
29
29
  }
30
+ function serializePlaygroundAiConfig(config) {
31
+ return JSON.stringify(Object.entries(config).sort(([leftKey], [rightKey])=>leftKey.localeCompare(rightKey)));
32
+ }
30
33
  function applyPlaygroundAiConfig(playgroundSDK, config) {
31
34
  return _async_to_generator(function*() {
32
35
  if (!hasPlaygroundAiConfig(config)) return false;
@@ -34,4 +37,4 @@ function applyPlaygroundAiConfig(playgroundSDK, config) {
34
37
  return true;
35
38
  })();
36
39
  }
37
- export { applyPlaygroundAiConfig, hasPlaygroundAiConfig };
40
+ export { applyPlaygroundAiConfig, hasPlaygroundAiConfig, serializePlaygroundAiConfig };
@@ -41,19 +41,33 @@ function _object_spread_props(target, source) {
41
41
  return target;
42
42
  }
43
43
  function buildConversationConfig(state, playgroundConfig) {
44
- return _object_spread({
44
+ const defaultConfig = {
45
45
  showContextPreview: false,
46
46
  layout: 'vertical',
47
- showVersionInfo: true,
48
- enableScrollToBottom: true,
47
+ showVersionInfo: false,
48
+ enableScrollToBottom: false,
49
49
  serverMode: true,
50
- showEnvConfigReminder: true,
50
+ showEnvConfigReminder: false,
51
+ showClearButton: false,
52
+ showSystemMessageHeader: false,
53
+ promptInputChrome: {
54
+ variant: 'minimal',
55
+ placeholder: 'Type a message',
56
+ primaryActionLabel: 'Action'
57
+ },
58
+ executionFlow: {
59
+ collapsible: true
60
+ },
51
61
  deviceType: state.deviceType,
52
62
  executionUx: {
53
63
  hints: state.executionUxHints,
54
64
  countdownSeconds: state.countdownSeconds
55
65
  }
56
- }, playgroundConfig);
66
+ };
67
+ return _object_spread_props(_object_spread({}, defaultConfig, playgroundConfig), {
68
+ executionFlow: _object_spread({}, defaultConfig.executionFlow, null == playgroundConfig ? void 0 : playgroundConfig.executionFlow),
69
+ executionUx: _object_spread({}, defaultConfig.executionUx, null == playgroundConfig ? void 0 : playgroundConfig.executionUx)
70
+ });
57
71
  }
58
72
  function buildConversationBranding(runtimeInfo, title, appVersion, deviceType, branding) {
59
73
  var _runtimeInfo_title, _runtimeInfo_platformId, _ref, _ref1;
@@ -0,0 +1,13 @@
1
+ function runSingleFlight(pendingRef, task) {
2
+ if (pendingRef.current) return pendingRef.current;
3
+ const taskPromise = task();
4
+ const pendingState = {
5
+ promise: null
6
+ };
7
+ pendingState.promise = taskPromise.finally(()=>{
8
+ if (pendingRef.current === pendingState.promise) pendingRef.current = null;
9
+ });
10
+ pendingRef.current = pendingState.promise;
11
+ return pendingState.promise;
12
+ }
13
+ export { runSingleFlight };
@@ -1,12 +1,13 @@
1
1
  import { PlaygroundSDK } from "@midscene/playground";
2
2
  import { useEnvConfig } from "@midscene/visualizer";
3
3
  import { Form, message } from "antd";
4
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
5
  import { resolveAutoCreateSessionInput } from "../session-setup.mjs";
6
6
  import { buildSessionInitialValues, resolveSessionViewState } from "../session-state.mjs";
7
7
  import { useServerStatus } from "../useServerStatus.mjs";
8
- import { applyPlaygroundAiConfig, hasPlaygroundAiConfig } from "./ai-config.mjs";
8
+ import { applyPlaygroundAiConfig, hasPlaygroundAiConfig, serializePlaygroundAiConfig } from "./ai-config.mjs";
9
9
  import { resolveAutoCreateDecision, serializeAutoCreateInput, shouldResetAutoCreateBlock } from "./auto-create.mjs";
10
+ import { runSingleFlight } from "./single-flight.mjs";
10
11
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
11
12
  try {
12
13
  var info = gen[key](arg);
@@ -79,8 +80,18 @@ function getPlatformSelectorFieldKey(setup) {
79
80
  var _setup_platformSelector;
80
81
  return null == setup ? void 0 : null == (_setup_platformSelector = setup.platformSelector) ? void 0 : _setup_platformSelector.fieldKey;
81
82
  }
82
- function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollIntervalMs = 5000, countdownSeconds = 3 }) {
83
+ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollIntervalMs = 5000, countdownSeconds = 3, initialFormValues }) {
83
84
  const [form] = Form.useForm();
85
+ const initialFormValuesRef = useRef(initialFormValues);
86
+ useLayoutEffect(()=>{
87
+ const seed = initialFormValuesRef.current;
88
+ if (!seed) return;
89
+ for (const [key, value] of Object.entries(seed))if (void 0 === form.getFieldValue(key)) form.setFieldsValue({
90
+ [key]: value
91
+ });
92
+ }, [
93
+ form
94
+ ]);
84
95
  var _Form_useWatch;
85
96
  const formValues = null != (_Form_useWatch = Form.useWatch([], form)) ? _Form_useWatch : {};
86
97
  const [countdown, setCountdown] = useState(null);
@@ -89,6 +100,9 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
89
100
  const [sessionLoading, setSessionLoading] = useState(false);
90
101
  const [sessionMutating, setSessionMutating] = useState(false);
91
102
  const aiConfig = useEnvConfig((state)=>state.config);
103
+ const aiConfigSignature = useMemo(()=>serializePlaygroundAiConfig(aiConfig), [
104
+ aiConfig
105
+ ]);
92
106
  const platformSelectorFieldKey = getPlatformSelectorFieldKey(sessionSetup);
93
107
  const selectedPlatformId = 'string' == typeof platformSelectorFieldKey ? formValues[platformSelectorFieldKey] : void 0;
94
108
  const playgroundSDK = useMemo(()=>new PlaygroundSDK({
@@ -101,26 +115,50 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
101
115
  const sessionViewState = useMemo(()=>resolveSessionViewState(runtimeInfo), [
102
116
  runtimeInfo
103
117
  ]);
118
+ const countdownTimerRef = useRef(null);
119
+ const countdownResolveRef = useRef(null);
120
+ const mountedRef = useRef(true);
121
+ const lastSetupPlatformIdRef = useRef(void 0);
122
+ const autoCreateSignatureRef = useRef(null);
123
+ const autoCreateBlockedSignatureRef = useRef(null);
124
+ const sessionMutatingRef = useRef(false);
125
+ const appliedAiConfigSignatureRef = useRef(null);
126
+ const pendingCreateSessionRef = useRef(null);
127
+ const pendingAiConfigApplicationRef = useRef(null);
104
128
  const applyAiConfig = useCallback(()=>_async_to_generator(function*() {
105
- if (!hasPlaygroundAiConfig(aiConfig)) return true;
106
- try {
107
- yield applyPlaygroundAiConfig(playgroundSDK, aiConfig);
129
+ if (!hasPlaygroundAiConfig(aiConfig)) {
130
+ appliedAiConfigSignatureRef.current = null;
131
+ pendingAiConfigApplicationRef.current = null;
108
132
  return true;
109
- } catch (error) {
110
- const errorMessage = error instanceof Error ? error.message : 'Failed to apply AI configuration';
111
- message.error(errorMessage);
112
- return false;
113
133
  }
134
+ if (appliedAiConfigSignatureRef.current === aiConfigSignature) return true;
135
+ const pendingApplication = pendingAiConfigApplicationRef.current;
136
+ if ((null == pendingApplication ? void 0 : pendingApplication.signature) === aiConfigSignature) return pendingApplication.promise;
137
+ const pendingApplicationState = {
138
+ promise: Promise.resolve(true),
139
+ signature: aiConfigSignature
140
+ };
141
+ const applyPromise = (()=>_async_to_generator(function*() {
142
+ try {
143
+ yield applyPlaygroundAiConfig(playgroundSDK, aiConfig);
144
+ appliedAiConfigSignatureRef.current = aiConfigSignature;
145
+ return true;
146
+ } catch (error) {
147
+ const errorMessage = error instanceof Error ? error.message : 'Failed to apply AI configuration';
148
+ message.error(errorMessage);
149
+ return false;
150
+ } finally{
151
+ if (pendingAiConfigApplicationRef.current === pendingApplicationState) pendingAiConfigApplicationRef.current = null;
152
+ }
153
+ })())();
154
+ pendingApplicationState.promise = applyPromise;
155
+ pendingAiConfigApplicationRef.current = pendingApplicationState;
156
+ return applyPromise;
114
157
  })(), [
115
158
  aiConfig,
159
+ aiConfigSignature,
116
160
  playgroundSDK
117
161
  ]);
118
- const countdownTimerRef = useRef(null);
119
- const countdownResolveRef = useRef(null);
120
- const mountedRef = useRef(true);
121
- const lastSetupPlatformIdRef = useRef(void 0);
122
- const autoCreateSignatureRef = useRef(null);
123
- const autoCreateBlockedSignatureRef = useRef(null);
124
162
  const finishCountdown = useCallback(()=>{
125
163
  if (null !== countdownTimerRef.current) {
126
164
  window.clearInterval(countdownTimerRef.current);
@@ -198,23 +236,27 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
198
236
  playgroundSDK
199
237
  ]);
200
238
  const createSession = useCallback((input, options)=>_async_to_generator(function*() {
201
- try {
202
- if (!(yield applyAiConfig())) return false;
203
- const values = null != input ? input : yield form.validateFields();
204
- setSessionMutating(true);
205
- yield playgroundSDK.createSession(values);
206
- if (shouldResetAutoCreateBlock(options)) autoCreateBlockedSignatureRef.current = null;
207
- if (!(null == options ? void 0 : options.silent)) message.success('Agent created');
208
- yield refreshServerState();
209
- return true;
210
- } catch (error) {
211
- if (error.errorFields) return false;
212
- const errorMessage = error instanceof Error ? error.message : 'Failed to create Agent';
213
- message.error(errorMessage);
214
- return false;
215
- } finally{
216
- setSessionMutating(false);
217
- }
239
+ return runSingleFlight(pendingCreateSessionRef, ()=>_async_to_generator(function*() {
240
+ try {
241
+ sessionMutatingRef.current = true;
242
+ setSessionMutating(true);
243
+ if (!(yield applyAiConfig())) return false;
244
+ const values = null != input ? input : yield form.validateFields();
245
+ yield playgroundSDK.createSession(values);
246
+ if (shouldResetAutoCreateBlock(options)) autoCreateBlockedSignatureRef.current = null;
247
+ if (!(null == options ? void 0 : options.silent)) message.success('Agent created');
248
+ yield refreshServerState();
249
+ return true;
250
+ } catch (error) {
251
+ if (error.errorFields) return false;
252
+ const errorMessage = error instanceof Error ? error.message : 'Failed to create Agent';
253
+ message.error(errorMessage);
254
+ return false;
255
+ } finally{
256
+ sessionMutatingRef.current = false;
257
+ setSessionMutating(false);
258
+ }
259
+ })());
218
260
  })(), [
219
261
  applyAiConfig,
220
262
  form,
@@ -224,6 +266,7 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
224
266
  const destroySession = useCallback(()=>_async_to_generator(function*() {
225
267
  try {
226
268
  autoCreateBlockedSignatureRef.current = serializeAutoCreateInput(resolveAutoCreateSessionInput(sessionSetup, form.getFieldsValue(true)));
269
+ sessionMutatingRef.current = true;
227
270
  setSessionMutating(true);
228
271
  yield playgroundSDK.destroySession();
229
272
  message.success('Session disconnected');
@@ -233,6 +276,7 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
233
276
  const errorMessage = error instanceof Error ? error.message : 'Failed to disconnect session';
234
277
  message.error(errorMessage);
235
278
  } finally{
279
+ sessionMutatingRef.current = false;
236
280
  setSessionMutating(false);
237
281
  }
238
282
  })(), [
@@ -298,7 +342,7 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
298
342
  autoCreateSignatureRef.current = null;
299
343
  return;
300
344
  }
301
- if (!serverOnline || sessionLoading || sessionMutating || sessionSetupError) return;
345
+ if (!serverOnline || sessionLoading || sessionMutating || sessionMutatingRef.current || sessionSetupError) return;
302
346
  const autoCreateInput = resolveAutoCreateSessionInput(sessionSetup, form.getFieldsValue(true));
303
347
  const { signature, shouldCreate } = resolveAutoCreateDecision({
304
348
  autoCreateInput,
@@ -30,7 +30,8 @@ function _async_to_generator(fn) {
30
30
  });
31
31
  };
32
32
  }
33
- function PlaygroundConversationPanel({ controller, appVersion, title = 'Playground', branding, playgroundConfig, header, className, notConnectedFallback }) {
33
+ const PLAYGROUND_CONVERSATION_SKIN_CLASS = 'playground-conversation-skin';
34
+ function PlaygroundConversationPanel({ controller, appVersion, title = 'Playground', branding, playgroundConfig, header, className, playgroundClassName, notConnectedFallback }) {
34
35
  const { state, actions } = controller;
35
36
  const mergedConfig = buildConversationConfig(state, playgroundConfig);
36
37
  const mergedBranding = buildConversationBranding(state.runtimeInfo, title, appVersion, state.deviceType, branding);
@@ -105,7 +106,11 @@ function PlaygroundConversationPanel({ controller, appVersion, title = 'Playgrou
105
106
  playgroundSDK: state.playgroundSDK,
106
107
  config: mergedConfig,
107
108
  branding: mergedBranding,
108
- className: "playground-container"
109
+ className: [
110
+ 'playground-container',
111
+ PLAYGROUND_CONVERSATION_SKIN_CLASS,
112
+ playgroundClassName
113
+ ].filter(Boolean).join(' ')
109
114
  }) : void 0 !== notConnectedFallback ? /*#__PURE__*/ jsx(Fragment, {
110
115
  children: notConnectedFallback
111
116
  }) : /*#__PURE__*/ jsx(SessionSetupPanel, {
@@ -100,7 +100,8 @@ function resolvePreviewConnectionInfo(runtimeInfo, serverUrl) {
100
100
  }
101
101
  if ('scrcpy' === preview.kind) {
102
102
  var _runtimeInfo_metadata, _preview_custom;
103
- if (isRemoteAndroidDeviceId(null == runtimeInfo ? void 0 : null == (_runtimeInfo_metadata = runtimeInfo.metadata) ? void 0 : _runtimeInfo_metadata.deviceId)) return {
103
+ const runtimeDeviceId = 'string' == typeof (null == runtimeInfo ? void 0 : null == (_runtimeInfo_metadata = runtimeInfo.metadata) ? void 0 : _runtimeInfo_metadata.deviceId) ? runtimeInfo.metadata.deviceId.trim() : void 0;
104
+ if (isRemoteAndroidDeviceId(runtimeDeviceId)) return {
104
105
  type: 'screenshot'
105
106
  };
106
107
  const scrcpyPort = Number(null == (_preview_custom = preview.custom) ? void 0 : _preview_custom.scrcpyPort);
@@ -114,6 +115,7 @@ function resolvePreviewConnectionInfo(runtimeInfo, serverUrl) {
114
115
  return url.toString();
115
116
  })() : void 0;
116
117
  return {
118
+ deviceId: runtimeDeviceId,
117
119
  type: 'scrcpy',
118
120
  scrcpyPort: resolvedScrcpyPort,
119
121
  scrcpyUrl
@@ -147,6 +147,7 @@ function PreviewRenderer({ connectingOverlay, onScrcpyStatusChange, renderErrorO
147
147
  description: "This session did not expose a preview capability in runtime metadata."
148
148
  }) : scrcpyAvailable ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_ScrcpyPanel_js_namespaceObject.ScrcpyPanel, {
149
149
  connectingOverlay: connectingOverlay,
150
+ deviceId: previewConnection.deviceId,
150
151
  onStatusChange: onScrcpyStatusChange,
151
152
  renderErrorOverlay: renderErrorOverlay,
152
153
  serverUrl: previewConnection.scrcpyUrl
@@ -60,8 +60,50 @@ function _async_to_generator(fn) {
60
60
  });
61
61
  };
62
62
  }
63
+ function _define_property(obj, key, value) {
64
+ if (key in obj) Object.defineProperty(obj, key, {
65
+ value: value,
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true
69
+ });
70
+ else obj[key] = value;
71
+ return obj;
72
+ }
73
+ function _object_spread(target) {
74
+ for(var i = 1; i < arguments.length; i++){
75
+ var source = null != arguments[i] ? arguments[i] : {};
76
+ var ownKeys = Object.keys(source);
77
+ if ("function" == typeof Object.getOwnPropertySymbols) ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
78
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
79
+ }));
80
+ ownKeys.forEach(function(key) {
81
+ _define_property(target, key, source[key]);
82
+ });
83
+ }
84
+ return target;
85
+ }
86
+ function ScrcpyPanel_ownKeys(object, enumerableOnly) {
87
+ var keys = Object.keys(object);
88
+ if (Object.getOwnPropertySymbols) {
89
+ var symbols = Object.getOwnPropertySymbols(object);
90
+ if (enumerableOnly) symbols = symbols.filter(function(sym) {
91
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
92
+ });
93
+ keys.push.apply(keys, symbols);
94
+ }
95
+ return keys;
96
+ }
97
+ function _object_spread_props(target, source) {
98
+ source = null != source ? source : {};
99
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
100
+ else ScrcpyPanel_ownKeys(Object(source)).forEach(function(key) {
101
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
102
+ });
103
+ return target;
104
+ }
63
105
  const { Text } = external_antd_namespaceObject.Typography;
64
- function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs = external_scrcpy_preview_js_namespaceObject.SCRCPY_METADATA_TIMEOUT_MS, reconnectInterval = 3000 }) {
106
+ function ScrcpyPanel({ connectingOverlay, deviceId, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs = external_scrcpy_preview_js_namespaceObject.SCRCPY_METADATA_TIMEOUT_MS, reconnectInterval = 3000 }) {
65
107
  const canvasStageRef = (0, external_react_namespaceObject.useRef)(null);
66
108
  const socketRef = (0, external_react_namespaceObject.useRef)(null);
67
109
  const decoderRef = (0, external_react_namespaceObject.useRef)(null);
@@ -86,10 +128,11 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
86
128
  setRetryNonce((current)=>current + 1);
87
129
  }, []);
88
130
  (0, external_react_namespaceObject.useEffect)(()=>{
89
- null == onStatusChange || onStatusChange(status);
131
+ null == onStatusChange || onStatusChange(status, statusText);
90
132
  }, [
91
133
  onStatusChange,
92
- status
134
+ status,
135
+ statusText
93
136
  ]);
94
137
  const clearCanvas = ()=>{
95
138
  const stage = canvasStageRef.current;
@@ -120,7 +163,12 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
120
163
  return;
121
164
  }
122
165
  let disposed = false;
166
+ let connectTimer = null;
123
167
  const cleanup = ()=>{
168
+ if (connectTimer) {
169
+ clearTimeout(connectTimer);
170
+ connectTimer = null;
171
+ }
124
172
  clearMetadataTimeout();
125
173
  if (reconnectTimerRef.current) {
126
174
  clearTimeout(reconnectTimerRef.current);
@@ -165,7 +213,10 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
165
213
  const socket = (0, external_socket_io_client_namespaceObject.io)(serverUrl, {
166
214
  withCredentials: true,
167
215
  reconnection: false,
168
- timeout: 10000
216
+ timeout: 10000,
217
+ transports: [
218
+ 'websocket'
219
+ ]
169
220
  });
170
221
  socketRef.current = socket;
171
222
  const videoStream = (0, external_scrcpy_stream_js_namespaceObject.createScrcpyVideoStream)(socket);
@@ -183,9 +234,11 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
183
234
  socketRef.current = null;
184
235
  scheduleReconnect();
185
236
  }, metadataTimeoutMs);
186
- socket.emit('connect-device', {
237
+ socket.emit('connect-device', _object_spread_props(_object_spread({}, 'string' == typeof deviceId && deviceId.trim() ? {
238
+ deviceId: deviceId.trim()
239
+ } : {}), {
187
240
  maxSize: 1024
188
- });
241
+ }));
189
242
  });
190
243
  socket.on('preview-status', (event)=>{
191
244
  if (disposed || !(0, external_scrcpy_preview_js_namespaceObject.isScrcpyPreviewStatusEvent)(event)) return;
@@ -245,12 +298,16 @@ function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, se
245
298
  scheduleReconnect();
246
299
  });
247
300
  };
248
- connect();
301
+ connectTimer = setTimeout(()=>{
302
+ connectTimer = null;
303
+ connect();
304
+ }, 0);
249
305
  return ()=>{
250
306
  disposed = true;
251
307
  cleanup();
252
308
  };
253
309
  }, [
310
+ deviceId,
254
311
  metadataTimeoutMs,
255
312
  reconnectInterval,
256
313
  retryNonce,
@@ -25,7 +25,8 @@ var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  applyPlaygroundAiConfig: ()=>applyPlaygroundAiConfig,
28
- hasPlaygroundAiConfig: ()=>hasPlaygroundAiConfig
28
+ hasPlaygroundAiConfig: ()=>hasPlaygroundAiConfig,
29
+ serializePlaygroundAiConfig: ()=>serializePlaygroundAiConfig
29
30
  });
30
31
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
31
32
  try {
@@ -56,6 +57,9 @@ function _async_to_generator(fn) {
56
57
  function hasPlaygroundAiConfig(config) {
57
58
  return Object.keys(config).length > 0;
58
59
  }
60
+ function serializePlaygroundAiConfig(config) {
61
+ return JSON.stringify(Object.entries(config).sort(([leftKey], [rightKey])=>leftKey.localeCompare(rightKey)));
62
+ }
59
63
  function applyPlaygroundAiConfig(playgroundSDK, config) {
60
64
  return _async_to_generator(function*() {
61
65
  if (!hasPlaygroundAiConfig(config)) return false;
@@ -65,9 +69,11 @@ function applyPlaygroundAiConfig(playgroundSDK, config) {
65
69
  }
66
70
  exports.applyPlaygroundAiConfig = __webpack_exports__.applyPlaygroundAiConfig;
67
71
  exports.hasPlaygroundAiConfig = __webpack_exports__.hasPlaygroundAiConfig;
72
+ exports.serializePlaygroundAiConfig = __webpack_exports__.serializePlaygroundAiConfig;
68
73
  for(var __rspack_i in __webpack_exports__)if (-1 === [
69
74
  "applyPlaygroundAiConfig",
70
- "hasPlaygroundAiConfig"
75
+ "hasPlaygroundAiConfig",
76
+ "serializePlaygroundAiConfig"
71
77
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
72
78
  Object.defineProperty(exports, '__esModule', {
73
79
  value: true
@@ -70,19 +70,33 @@ function _object_spread_props(target, source) {
70
70
  return target;
71
71
  }
72
72
  function buildConversationConfig(state, playgroundConfig) {
73
- return _object_spread({
73
+ const defaultConfig = {
74
74
  showContextPreview: false,
75
75
  layout: 'vertical',
76
- showVersionInfo: true,
77
- enableScrollToBottom: true,
76
+ showVersionInfo: false,
77
+ enableScrollToBottom: false,
78
78
  serverMode: true,
79
- showEnvConfigReminder: true,
79
+ showEnvConfigReminder: false,
80
+ showClearButton: false,
81
+ showSystemMessageHeader: false,
82
+ promptInputChrome: {
83
+ variant: 'minimal',
84
+ placeholder: 'Type a message',
85
+ primaryActionLabel: 'Action'
86
+ },
87
+ executionFlow: {
88
+ collapsible: true
89
+ },
80
90
  deviceType: state.deviceType,
81
91
  executionUx: {
82
92
  hints: state.executionUxHints,
83
93
  countdownSeconds: state.countdownSeconds
84
94
  }
85
- }, playgroundConfig);
95
+ };
96
+ return _object_spread_props(_object_spread({}, defaultConfig, playgroundConfig), {
97
+ executionFlow: _object_spread({}, defaultConfig.executionFlow, null == playgroundConfig ? void 0 : playgroundConfig.executionFlow),
98
+ executionUx: _object_spread({}, defaultConfig.executionUx, null == playgroundConfig ? void 0 : playgroundConfig.executionUx)
99
+ });
86
100
  }
87
101
  function buildConversationBranding(runtimeInfo, title, appVersion, deviceType, branding) {
88
102
  var _runtimeInfo_title, _runtimeInfo_platformId, _ref, _ref1;
@@ -0,0 +1,47 @@
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
+ runSingleFlight: ()=>runSingleFlight
28
+ });
29
+ function runSingleFlight(pendingRef, task) {
30
+ if (pendingRef.current) return pendingRef.current;
31
+ const taskPromise = task();
32
+ const pendingState = {
33
+ promise: null
34
+ };
35
+ pendingState.promise = taskPromise.finally(()=>{
36
+ if (pendingRef.current === pendingState.promise) pendingRef.current = null;
37
+ });
38
+ pendingRef.current = pendingState.promise;
39
+ return pendingState.promise;
40
+ }
41
+ exports.runSingleFlight = __webpack_exports__.runSingleFlight;
42
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
43
+ "runSingleFlight"
44
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
45
+ Object.defineProperty(exports, '__esModule', {
46
+ value: true
47
+ });
@@ -35,6 +35,7 @@ const external_session_state_js_namespaceObject = require("../session-state.js")
35
35
  const external_useServerStatus_js_namespaceObject = require("../useServerStatus.js");
36
36
  const external_ai_config_js_namespaceObject = require("./ai-config.js");
37
37
  const external_auto_create_js_namespaceObject = require("./auto-create.js");
38
+ const external_single_flight_js_namespaceObject = require("./single-flight.js");
38
39
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
39
40
  try {
40
41
  var info = gen[key](arg);
@@ -107,8 +108,18 @@ function getPlatformSelectorFieldKey(setup) {
107
108
  var _setup_platformSelector;
108
109
  return null == setup ? void 0 : null == (_setup_platformSelector = setup.platformSelector) ? void 0 : _setup_platformSelector.fieldKey;
109
110
  }
110
- function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollIntervalMs = 5000, countdownSeconds = 3 }) {
111
+ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollIntervalMs = 5000, countdownSeconds = 3, initialFormValues }) {
111
112
  const [form] = external_antd_namespaceObject.Form.useForm();
113
+ const initialFormValuesRef = (0, external_react_namespaceObject.useRef)(initialFormValues);
114
+ (0, external_react_namespaceObject.useLayoutEffect)(()=>{
115
+ const seed = initialFormValuesRef.current;
116
+ if (!seed) return;
117
+ for (const [key, value] of Object.entries(seed))if (void 0 === form.getFieldValue(key)) form.setFieldsValue({
118
+ [key]: value
119
+ });
120
+ }, [
121
+ form
122
+ ]);
112
123
  var _Form_useWatch;
113
124
  const formValues = null != (_Form_useWatch = external_antd_namespaceObject.Form.useWatch([], form)) ? _Form_useWatch : {};
114
125
  const [countdown, setCountdown] = (0, external_react_namespaceObject.useState)(null);
@@ -117,6 +128,9 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
117
128
  const [sessionLoading, setSessionLoading] = (0, external_react_namespaceObject.useState)(false);
118
129
  const [sessionMutating, setSessionMutating] = (0, external_react_namespaceObject.useState)(false);
119
130
  const aiConfig = (0, visualizer_namespaceObject.useEnvConfig)((state)=>state.config);
131
+ const aiConfigSignature = (0, external_react_namespaceObject.useMemo)(()=>(0, external_ai_config_js_namespaceObject.serializePlaygroundAiConfig)(aiConfig), [
132
+ aiConfig
133
+ ]);
120
134
  const platformSelectorFieldKey = getPlatformSelectorFieldKey(sessionSetup);
121
135
  const selectedPlatformId = 'string' == typeof platformSelectorFieldKey ? formValues[platformSelectorFieldKey] : void 0;
122
136
  const playgroundSDK = (0, external_react_namespaceObject.useMemo)(()=>new playground_namespaceObject.PlaygroundSDK({
@@ -129,26 +143,50 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
129
143
  const sessionViewState = (0, external_react_namespaceObject.useMemo)(()=>(0, external_session_state_js_namespaceObject.resolveSessionViewState)(runtimeInfo), [
130
144
  runtimeInfo
131
145
  ]);
146
+ const countdownTimerRef = (0, external_react_namespaceObject.useRef)(null);
147
+ const countdownResolveRef = (0, external_react_namespaceObject.useRef)(null);
148
+ const mountedRef = (0, external_react_namespaceObject.useRef)(true);
149
+ const lastSetupPlatformIdRef = (0, external_react_namespaceObject.useRef)(void 0);
150
+ const autoCreateSignatureRef = (0, external_react_namespaceObject.useRef)(null);
151
+ const autoCreateBlockedSignatureRef = (0, external_react_namespaceObject.useRef)(null);
152
+ const sessionMutatingRef = (0, external_react_namespaceObject.useRef)(false);
153
+ const appliedAiConfigSignatureRef = (0, external_react_namespaceObject.useRef)(null);
154
+ const pendingCreateSessionRef = (0, external_react_namespaceObject.useRef)(null);
155
+ const pendingAiConfigApplicationRef = (0, external_react_namespaceObject.useRef)(null);
132
156
  const applyAiConfig = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
133
- if (!(0, external_ai_config_js_namespaceObject.hasPlaygroundAiConfig)(aiConfig)) return true;
134
- try {
135
- yield (0, external_ai_config_js_namespaceObject.applyPlaygroundAiConfig)(playgroundSDK, aiConfig);
157
+ if (!(0, external_ai_config_js_namespaceObject.hasPlaygroundAiConfig)(aiConfig)) {
158
+ appliedAiConfigSignatureRef.current = null;
159
+ pendingAiConfigApplicationRef.current = null;
136
160
  return true;
137
- } catch (error) {
138
- const errorMessage = error instanceof Error ? error.message : 'Failed to apply AI configuration';
139
- external_antd_namespaceObject.message.error(errorMessage);
140
- return false;
141
161
  }
162
+ if (appliedAiConfigSignatureRef.current === aiConfigSignature) return true;
163
+ const pendingApplication = pendingAiConfigApplicationRef.current;
164
+ if ((null == pendingApplication ? void 0 : pendingApplication.signature) === aiConfigSignature) return pendingApplication.promise;
165
+ const pendingApplicationState = {
166
+ promise: Promise.resolve(true),
167
+ signature: aiConfigSignature
168
+ };
169
+ const applyPromise = (()=>_async_to_generator(function*() {
170
+ try {
171
+ yield (0, external_ai_config_js_namespaceObject.applyPlaygroundAiConfig)(playgroundSDK, aiConfig);
172
+ appliedAiConfigSignatureRef.current = aiConfigSignature;
173
+ return true;
174
+ } catch (error) {
175
+ const errorMessage = error instanceof Error ? error.message : 'Failed to apply AI configuration';
176
+ external_antd_namespaceObject.message.error(errorMessage);
177
+ return false;
178
+ } finally{
179
+ if (pendingAiConfigApplicationRef.current === pendingApplicationState) pendingAiConfigApplicationRef.current = null;
180
+ }
181
+ })())();
182
+ pendingApplicationState.promise = applyPromise;
183
+ pendingAiConfigApplicationRef.current = pendingApplicationState;
184
+ return applyPromise;
142
185
  })(), [
143
186
  aiConfig,
187
+ aiConfigSignature,
144
188
  playgroundSDK
145
189
  ]);
146
- const countdownTimerRef = (0, external_react_namespaceObject.useRef)(null);
147
- const countdownResolveRef = (0, external_react_namespaceObject.useRef)(null);
148
- const mountedRef = (0, external_react_namespaceObject.useRef)(true);
149
- const lastSetupPlatformIdRef = (0, external_react_namespaceObject.useRef)(void 0);
150
- const autoCreateSignatureRef = (0, external_react_namespaceObject.useRef)(null);
151
- const autoCreateBlockedSignatureRef = (0, external_react_namespaceObject.useRef)(null);
152
190
  const finishCountdown = (0, external_react_namespaceObject.useCallback)(()=>{
153
191
  if (null !== countdownTimerRef.current) {
154
192
  window.clearInterval(countdownTimerRef.current);
@@ -226,23 +264,27 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
226
264
  playgroundSDK
227
265
  ]);
228
266
  const createSession = (0, external_react_namespaceObject.useCallback)((input, options)=>_async_to_generator(function*() {
229
- try {
230
- if (!(yield applyAiConfig())) return false;
231
- const values = null != input ? input : yield form.validateFields();
232
- setSessionMutating(true);
233
- yield playgroundSDK.createSession(values);
234
- if ((0, external_auto_create_js_namespaceObject.shouldResetAutoCreateBlock)(options)) autoCreateBlockedSignatureRef.current = null;
235
- if (!(null == options ? void 0 : options.silent)) external_antd_namespaceObject.message.success('Agent created');
236
- yield refreshServerState();
237
- return true;
238
- } catch (error) {
239
- if (error.errorFields) return false;
240
- const errorMessage = error instanceof Error ? error.message : 'Failed to create Agent';
241
- external_antd_namespaceObject.message.error(errorMessage);
242
- return false;
243
- } finally{
244
- setSessionMutating(false);
245
- }
267
+ return (0, external_single_flight_js_namespaceObject.runSingleFlight)(pendingCreateSessionRef, ()=>_async_to_generator(function*() {
268
+ try {
269
+ sessionMutatingRef.current = true;
270
+ setSessionMutating(true);
271
+ if (!(yield applyAiConfig())) return false;
272
+ const values = null != input ? input : yield form.validateFields();
273
+ yield playgroundSDK.createSession(values);
274
+ if ((0, external_auto_create_js_namespaceObject.shouldResetAutoCreateBlock)(options)) autoCreateBlockedSignatureRef.current = null;
275
+ if (!(null == options ? void 0 : options.silent)) external_antd_namespaceObject.message.success('Agent created');
276
+ yield refreshServerState();
277
+ return true;
278
+ } catch (error) {
279
+ if (error.errorFields) return false;
280
+ const errorMessage = error instanceof Error ? error.message : 'Failed to create Agent';
281
+ external_antd_namespaceObject.message.error(errorMessage);
282
+ return false;
283
+ } finally{
284
+ sessionMutatingRef.current = false;
285
+ setSessionMutating(false);
286
+ }
287
+ })());
246
288
  })(), [
247
289
  applyAiConfig,
248
290
  form,
@@ -252,6 +294,7 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
252
294
  const destroySession = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
253
295
  try {
254
296
  autoCreateBlockedSignatureRef.current = (0, external_auto_create_js_namespaceObject.serializeAutoCreateInput)((0, external_session_setup_js_namespaceObject.resolveAutoCreateSessionInput)(sessionSetup, form.getFieldsValue(true)));
297
+ sessionMutatingRef.current = true;
255
298
  setSessionMutating(true);
256
299
  yield playgroundSDK.destroySession();
257
300
  external_antd_namespaceObject.message.success('Session disconnected');
@@ -261,6 +304,7 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
261
304
  const errorMessage = error instanceof Error ? error.message : 'Failed to disconnect session';
262
305
  external_antd_namespaceObject.message.error(errorMessage);
263
306
  } finally{
307
+ sessionMutatingRef.current = false;
264
308
  setSessionMutating(false);
265
309
  }
266
310
  })(), [
@@ -326,7 +370,7 @@ function usePlaygroundController({ serverUrl, defaultDeviceType = 'web', pollInt
326
370
  autoCreateSignatureRef.current = null;
327
371
  return;
328
372
  }
329
- if (!serverOnline || sessionLoading || sessionMutating || sessionSetupError) return;
373
+ if (!serverOnline || sessionLoading || sessionMutating || sessionMutatingRef.current || sessionSetupError) return;
330
374
  const autoCreateInput = (0, external_session_setup_js_namespaceObject.resolveAutoCreateSessionInput)(sessionSetup, form.getFieldsValue(true));
331
375
  const { signature, shouldCreate } = (0, external_auto_create_js_namespaceObject.resolveAutoCreateDecision)({
332
376
  autoCreateInput,
@@ -58,7 +58,8 @@ function _async_to_generator(fn) {
58
58
  });
59
59
  };
60
60
  }
61
- function PlaygroundConversationPanel({ controller, appVersion, title = 'Playground', branding, playgroundConfig, header, className, notConnectedFallback }) {
61
+ const PLAYGROUND_CONVERSATION_SKIN_CLASS = 'playground-conversation-skin';
62
+ function PlaygroundConversationPanel({ controller, appVersion, title = 'Playground', branding, playgroundConfig, header, className, playgroundClassName, notConnectedFallback }) {
62
63
  const { state, actions } = controller;
63
64
  const mergedConfig = (0, selectors_js_namespaceObject.buildConversationConfig)(state, playgroundConfig);
64
65
  const mergedBranding = (0, selectors_js_namespaceObject.buildConversationBranding)(state.runtimeInfo, title, appVersion, state.deviceType, branding);
@@ -133,7 +134,11 @@ function PlaygroundConversationPanel({ controller, appVersion, title = 'Playgrou
133
134
  playgroundSDK: state.playgroundSDK,
134
135
  config: mergedConfig,
135
136
  branding: mergedBranding,
136
- className: "playground-container"
137
+ className: [
138
+ 'playground-container',
139
+ PLAYGROUND_CONVERSATION_SKIN_CLASS,
140
+ playgroundClassName
141
+ ].filter(Boolean).join(' ')
137
142
  }) : void 0 !== notConnectedFallback ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(jsx_runtime_namespaceObject.Fragment, {
138
143
  children: notConnectedFallback
139
144
  }) : /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_SessionSetupPanel_js_namespaceObject.SessionSetupPanel, {
@@ -132,7 +132,8 @@ function resolvePreviewConnectionInfo(runtimeInfo, serverUrl) {
132
132
  }
133
133
  if ('scrcpy' === preview.kind) {
134
134
  var _runtimeInfo_metadata, _preview_custom;
135
- if (isRemoteAndroidDeviceId(null == runtimeInfo ? void 0 : null == (_runtimeInfo_metadata = runtimeInfo.metadata) ? void 0 : _runtimeInfo_metadata.deviceId)) return {
135
+ const runtimeDeviceId = 'string' == typeof (null == runtimeInfo ? void 0 : null == (_runtimeInfo_metadata = runtimeInfo.metadata) ? void 0 : _runtimeInfo_metadata.deviceId) ? runtimeInfo.metadata.deviceId.trim() : void 0;
136
+ if (isRemoteAndroidDeviceId(runtimeDeviceId)) return {
136
137
  type: 'screenshot'
137
138
  };
138
139
  const scrcpyPort = Number(null == (_preview_custom = preview.custom) ? void 0 : _preview_custom.scrcpyPort);
@@ -146,6 +147,7 @@ function resolvePreviewConnectionInfo(runtimeInfo, serverUrl) {
146
147
  return url.toString();
147
148
  })() : void 0;
148
149
  return {
150
+ deviceId: runtimeDeviceId,
149
151
  type: 'scrcpy',
150
152
  scrcpyPort: resolvedScrcpyPort,
151
153
  scrcpyUrl
@@ -4,7 +4,7 @@ import type { ScrcpyErrorOverlayRenderer } from './ScrcpyPanel';
4
4
  import type { ScrcpyPreviewStatus } from './scrcpy-preview';
5
5
  export interface PlaygroundPreviewProps {
6
6
  connectingOverlay?: ReactNode;
7
- onScrcpyStatusChange?: (status: ScrcpyPreviewStatus) => void;
7
+ onScrcpyStatusChange?: (status: ScrcpyPreviewStatus, statusText: string) => void;
8
8
  renderErrorOverlay?: ScrcpyErrorOverlayRenderer;
9
9
  playgroundSDK: PlaygroundSDK;
10
10
  runtimeInfo: PlaygroundRuntimeInfo | null;
@@ -4,7 +4,7 @@ import { type ScrcpyErrorOverlayRenderer } from './ScrcpyPanel';
4
4
  import type { ScrcpyPreviewStatus } from './scrcpy-preview';
5
5
  interface PreviewRendererProps {
6
6
  connectingOverlay?: ReactNode;
7
- onScrcpyStatusChange?: (status: ScrcpyPreviewStatus) => void;
7
+ onScrcpyStatusChange?: (status: ScrcpyPreviewStatus, statusText: string) => void;
8
8
  renderErrorOverlay?: ScrcpyErrorOverlayRenderer;
9
9
  playgroundSDK: PlaygroundSDK;
10
10
  runtimeInfo: PlaygroundRuntimeInfo | null;
@@ -9,11 +9,12 @@ export interface ScrcpyErrorOverlayContext {
9
9
  export type ScrcpyErrorOverlayRenderer = (context: ScrcpyErrorOverlayContext) => ReactNode;
10
10
  interface ScrcpyPanelProps {
11
11
  connectingOverlay?: ReactNode;
12
- onStatusChange?: (status: ScrcpyPreviewStatus) => void;
12
+ deviceId?: string;
13
+ onStatusChange?: (status: ScrcpyPreviewStatus, statusText: string) => void;
13
14
  renderErrorOverlay?: ScrcpyErrorOverlayRenderer;
14
15
  serverUrl?: string;
15
16
  metadataTimeoutMs?: number;
16
17
  reconnectInterval?: number;
17
18
  }
18
- export declare function ScrcpyPanel({ connectingOverlay, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs, reconnectInterval, }: ScrcpyPanelProps): React.JSX.Element;
19
+ export declare function ScrcpyPanel({ connectingOverlay, deviceId, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs, reconnectInterval, }: ScrcpyPanelProps): React.JSX.Element;
19
20
  export {};
@@ -1,4 +1,5 @@
1
1
  import type { PlaygroundSDK } from '@midscene/playground';
2
2
  export type PlaygroundAiConfig = Record<string, string>;
3
3
  export declare function hasPlaygroundAiConfig(config: PlaygroundAiConfig): boolean;
4
+ export declare function serializePlaygroundAiConfig(config: PlaygroundAiConfig): string;
4
5
  export declare function applyPlaygroundAiConfig(playgroundSDK: Pick<PlaygroundSDK, 'overrideConfig'>, config: PlaygroundAiConfig): Promise<boolean>;
@@ -0,0 +1,3 @@
1
+ export declare function runSingleFlight<T>(pendingRef: {
2
+ current: Promise<T> | null;
3
+ }, task: () => Promise<T>): Promise<T>;
@@ -5,5 +5,12 @@ export interface UsePlaygroundControllerOptions {
5
5
  defaultDeviceType?: DeviceType;
6
6
  pollIntervalMs?: number;
7
7
  countdownSeconds?: number;
8
+ /**
9
+ * Seed values written into the session-setup form on the first render.
10
+ * Useful for pre-selecting a default platform so the initial
11
+ * `refreshSessionSetup` poll already has a `platformId`, instead of
12
+ * returning a generic "Choose a platform" setup.
13
+ */
14
+ initialFormValues?: Record<string, unknown>;
8
15
  }
9
- export declare function usePlaygroundController({ serverUrl, defaultDeviceType, pollIntervalMs, countdownSeconds, }: UsePlaygroundControllerOptions): PlaygroundControllerResult;
16
+ export declare function usePlaygroundController({ serverUrl, defaultDeviceType, pollIntervalMs, countdownSeconds, initialFormValues, }: UsePlaygroundControllerOptions): PlaygroundControllerResult;
@@ -10,6 +10,10 @@ export interface PlaygroundConversationPanelProps {
10
10
  playgroundConfig?: Partial<UniversalPlaygroundConfig>;
11
11
  header?: ReactNode;
12
12
  className?: string;
13
+ /**
14
+ * Extra class appended to the inner `UniversalPlayground` root.
15
+ */
16
+ playgroundClassName?: string;
13
17
  /**
14
18
  * Custom content shown while the session is not yet connected.
15
19
  * When supplied, replaces the built-in `SessionSetupPanel`, letting hosts
@@ -17,4 +21,4 @@ export interface PlaygroundConversationPanelProps {
17
21
  */
18
22
  notConnectedFallback?: ReactNode;
19
23
  }
20
- export declare function PlaygroundConversationPanel({ controller, appVersion, title, branding, playgroundConfig, header, className, notConnectedFallback, }: PlaygroundConversationPanelProps): import("react").JSX.Element;
24
+ export declare function PlaygroundConversationPanel({ controller, appVersion, title, branding, playgroundConfig, header, className, playgroundClassName, notConnectedFallback, }: PlaygroundConversationPanelProps): import("react").JSX.Element;
@@ -2,6 +2,7 @@ import type { PlaygroundRuntimeInfo } from '@midscene/playground';
2
2
  import type { DeviceType, ExecutionUxHint } from '@midscene/visualizer';
3
3
  export interface PreviewConnectionInfo {
4
4
  type: 'none' | 'screenshot' | 'mjpeg' | 'scrcpy';
5
+ deviceId?: string;
5
6
  mjpegUrl?: string;
6
7
  scrcpyUrl?: string;
7
8
  scrcpyPort?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/playground-app",
3
- "version": "1.7.5-beta-20260421030751.0",
3
+ "version": "1.7.5",
4
4
  "description": "Reusable React shell for Midscene playground applications",
5
5
  "repository": "https://github.com/web-infra-dev/midscene",
6
6
  "homepage": "https://midscenejs.com/",
@@ -28,9 +28,9 @@
28
28
  "antd": "^5.21.6",
29
29
  "react-resizable-panels": "2.0.22",
30
30
  "socket.io-client": "4.8.1",
31
- "@midscene/playground": "1.7.5-beta-20260421030751.0",
32
- "@midscene/shared": "1.7.5-beta-20260421030751.0",
33
- "@midscene/visualizer": "1.7.5-beta-20260421030751.0"
31
+ "@midscene/playground": "1.7.5",
32
+ "@midscene/shared": "1.7.5",
33
+ "@midscene/visualizer": "1.7.5"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@rsbuild/plugin-less": "^1.5.0",