@godscene/playground-electron 1.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -0
  3. package/dist/es/DeviceInteractionLayer.mjs +383 -0
  4. package/dist/es/PlaygroundApp.css +207 -0
  5. package/dist/es/PlaygroundApp.mjs +776 -0
  6. package/dist/es/PlaygroundPreview.mjs +29 -0
  7. package/dist/es/PlaygroundThemeProvider.mjs +10 -0
  8. package/dist/es/PreviewRenderer.mjs +270 -0
  9. package/dist/es/ScrcpyPanel.mjs +390 -0
  10. package/dist/es/SessionSetupPanel.css +299 -0
  11. package/dist/es/SessionSetupPanel.mjs +169 -0
  12. package/dist/es/components/Header/index.css +19 -0
  13. package/dist/es/components/Header/index.mjs +37 -0
  14. package/dist/es/components/Nav/index.css +15 -0
  15. package/dist/es/components/Nav/index.mjs +93 -0
  16. package/dist/es/components/WinControlTool/index.css +5 -0
  17. package/dist/es/components/WinControlTool/index.mjs +76 -0
  18. package/dist/es/controller/ai-config.mjs +40 -0
  19. package/dist/es/controller/auto-create.mjs +19 -0
  20. package/dist/es/controller/selectors.mjs +78 -0
  21. package/dist/es/controller/single-flight.mjs +13 -0
  22. package/dist/es/controller/types.mjs +0 -0
  23. package/dist/es/controller/usePlaygroundController.mjs +400 -0
  24. package/dist/es/icons/dropdown-chevron.mjs +61 -0
  25. package/dist/es/icons/midscene-logo.mjs +247 -0
  26. package/dist/es/icons/server-offline-background.mjs +102 -0
  27. package/dist/es/icons/server-offline-foreground.mjs +200 -0
  28. package/dist/es/index.mjs +7 -0
  29. package/dist/es/manual-interaction.mjs +56 -0
  30. package/dist/es/panels/PlaygroundConversationPanel.css +20 -0
  31. package/dist/es/panels/PlaygroundConversationPanel.mjs +140 -0
  32. package/dist/es/runtime-info.mjs +128 -0
  33. package/dist/es/scrcpy-preview.mjs +30 -0
  34. package/dist/es/scrcpy-stream.mjs +53 -0
  35. package/dist/es/session-setup.mjs +13 -0
  36. package/dist/es/session-state.mjs +32 -0
  37. package/dist/es/useServerStatus.mjs +120 -0
  38. package/dist/lib/DeviceInteractionLayer.js +434 -0
  39. package/dist/lib/PlaygroundApp.css +207 -0
  40. package/dist/lib/PlaygroundApp.js +821 -0
  41. package/dist/lib/PlaygroundPreview.js +63 -0
  42. package/dist/lib/PlaygroundThemeProvider.js +44 -0
  43. package/dist/lib/PreviewRenderer.js +304 -0
  44. package/dist/lib/ScrcpyPanel.js +424 -0
  45. package/dist/lib/SessionSetupPanel.css +299 -0
  46. package/dist/lib/SessionSetupPanel.js +217 -0
  47. package/dist/lib/components/Header/index.css +19 -0
  48. package/dist/lib/components/Header/index.js +81 -0
  49. package/dist/lib/components/Nav/index.css +15 -0
  50. package/dist/lib/components/Nav/index.js +127 -0
  51. package/dist/lib/components/WinControlTool/index.css +5 -0
  52. package/dist/lib/components/WinControlTool/index.js +110 -0
  53. package/dist/lib/controller/ai-config.js +80 -0
  54. package/dist/lib/controller/auto-create.js +59 -0
  55. package/dist/lib/controller/selectors.js +115 -0
  56. package/dist/lib/controller/single-flight.js +47 -0
  57. package/dist/lib/controller/types.js +18 -0
  58. package/dist/lib/controller/usePlaygroundController.js +434 -0
  59. package/dist/lib/icons/dropdown-chevron.js +95 -0
  60. package/dist/lib/icons/midscene-logo.js +281 -0
  61. package/dist/lib/icons/server-offline-background.js +136 -0
  62. package/dist/lib/icons/server-offline-foreground.js +234 -0
  63. package/dist/lib/index.js +56 -0
  64. package/dist/lib/manual-interaction.js +90 -0
  65. package/dist/lib/panels/PlaygroundConversationPanel.css +20 -0
  66. package/dist/lib/panels/PlaygroundConversationPanel.js +174 -0
  67. package/dist/lib/runtime-info.js +174 -0
  68. package/dist/lib/scrcpy-preview.js +79 -0
  69. package/dist/lib/scrcpy-stream.js +87 -0
  70. package/dist/lib/session-setup.js +47 -0
  71. package/dist/lib/session-state.js +69 -0
  72. package/dist/lib/useServerStatus.js +154 -0
  73. package/dist/types/DeviceInteractionLayer.d.ts +50 -0
  74. package/dist/types/PlaygroundApp.d.ts +14 -0
  75. package/dist/types/PlaygroundPreview.d.ts +22 -0
  76. package/dist/types/PlaygroundThemeProvider.d.ts +2 -0
  77. package/dist/types/PreviewRenderer.d.ts +27 -0
  78. package/dist/types/ScrcpyPanel.d.ts +21 -0
  79. package/dist/types/SessionSetupPanel.d.ts +16 -0
  80. package/dist/types/components/Header/index.d.ts +3 -0
  81. package/dist/types/components/Nav/index.d.ts +3 -0
  82. package/dist/types/components/WinControlTool/index.d.ts +3 -0
  83. package/dist/types/controller/ai-config.d.ts +5 -0
  84. package/dist/types/controller/auto-create.d.ts +15 -0
  85. package/dist/types/controller/selectors.d.ts +5 -0
  86. package/dist/types/controller/single-flight.d.ts +3 -0
  87. package/dist/types/controller/types.d.ts +36 -0
  88. package/dist/types/controller/usePlaygroundController.d.ts +16 -0
  89. package/dist/types/index.d.ts +13 -0
  90. package/dist/types/manual-interaction.d.ts +21 -0
  91. package/dist/types/panels/PlaygroundConversationPanel.d.ts +25 -0
  92. package/dist/types/runtime-info.d.ts +22 -0
  93. package/dist/types/scrcpy-preview.d.ts +11 -0
  94. package/dist/types/scrcpy-stream.d.ts +16 -0
  95. package/dist/types/session-setup.d.ts +2 -0
  96. package/dist/types/session-state.d.ts +9 -0
  97. package/dist/types/useServerStatus.d.ts +12 -0
  98. package/package.json +69 -0
@@ -0,0 +1,390 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { ScrcpyVideoCodecId } from "@yume-chan/scrcpy";
3
+ import { BitmapVideoFrameRenderer, WebCodecsVideoDecoder, WebGLVideoFrameRenderer } from "@yume-chan/scrcpy-decoder-webcodecs";
4
+ import { Alert, Spin, Typography } from "antd";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { io } from "socket.io-client";
7
+ import { SCRCPY_METADATA_TIMEOUT_MS, getDefaultScrcpyWaitingStatusText, getScrcpyDecoderStatusText, getScrcpyMetadataTimeoutMessage, getScrcpyPreviewStatusText, isScrcpyPreviewStatusEvent } from "./scrcpy-preview.mjs";
8
+ import { createScrcpyVideoStream } from "./scrcpy-stream.mjs";
9
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
10
+ try {
11
+ var info = gen[key](arg);
12
+ var value = info.value;
13
+ } catch (error) {
14
+ reject(error);
15
+ return;
16
+ }
17
+ if (info.done) resolve(value);
18
+ else Promise.resolve(value).then(_next, _throw);
19
+ }
20
+ function _async_to_generator(fn) {
21
+ return function() {
22
+ var self = this, args = arguments;
23
+ return new Promise(function(resolve, reject) {
24
+ var gen = fn.apply(self, args);
25
+ function _next(value) {
26
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
27
+ }
28
+ function _throw(err) {
29
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
30
+ }
31
+ _next(void 0);
32
+ });
33
+ };
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
+ }
77
+ const { Text } = Typography;
78
+ function ScrcpyPanel({ connectingOverlay, deviceId, onStatusChange, renderErrorOverlay, serverUrl, metadataTimeoutMs = SCRCPY_METADATA_TIMEOUT_MS, reconnectInterval = 3000, viewportStyle }) {
79
+ const canvasStageRef = useRef(null);
80
+ const socketRef = useRef(null);
81
+ const decoderRef = useRef(null);
82
+ const metadataTimeoutRef = useRef(null);
83
+ const reconnectTimerRef = useRef(null);
84
+ const ignoreDisconnectRef = useRef(false);
85
+ const [status, setStatus] = useState('connecting');
86
+ const [errorMessage, setErrorMessage] = useState(null);
87
+ const [waitingStatusMessage, setWaitingStatusMessage] = useState(()=>getDefaultScrcpyWaitingStatusText());
88
+ const [webCodecsSupported, setWebCodecsSupported] = useState(true);
89
+ const [retryNonce, setRetryNonce] = useState(0);
90
+ const statusText = useMemo(()=>getScrcpyPreviewStatusText(status, waitingStatusMessage), [
91
+ status,
92
+ waitingStatusMessage
93
+ ]);
94
+ const showCustomErrorOverlay = ('error' === status || 'disconnected' === status) && Boolean(renderErrorOverlay);
95
+ const renderResolvedErrorOverlay = renderErrorOverlay;
96
+ const requestRetry = useCallback(()=>{
97
+ setStatus('connecting');
98
+ setErrorMessage(null);
99
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
100
+ setRetryNonce((current)=>current + 1);
101
+ }, []);
102
+ useEffect(()=>{
103
+ null == onStatusChange || onStatusChange(status, statusText);
104
+ }, [
105
+ onStatusChange,
106
+ status,
107
+ statusText
108
+ ]);
109
+ const clearCanvas = ()=>{
110
+ const stage = canvasStageRef.current;
111
+ if (!stage) return;
112
+ stage.replaceChildren();
113
+ };
114
+ const disposeDecoder = ()=>{
115
+ if (!decoderRef.current) return;
116
+ decoderRef.current.dispose();
117
+ decoderRef.current = null;
118
+ };
119
+ const clearMetadataTimeout = ()=>{
120
+ if (metadataTimeoutRef.current) {
121
+ clearTimeout(metadataTimeoutRef.current);
122
+ metadataTimeoutRef.current = null;
123
+ }
124
+ };
125
+ useEffect(()=>{
126
+ if (!serverUrl) {
127
+ setStatus('error');
128
+ setErrorMessage('scrcpy preview metadata is missing a server URL.');
129
+ return;
130
+ }
131
+ if (!WebCodecsVideoDecoder.isSupported) {
132
+ setWebCodecsSupported(false);
133
+ setStatus('error');
134
+ setErrorMessage('Current browser does not support WebCodecs, so live scrcpy preview is unavailable.');
135
+ return;
136
+ }
137
+ let disposed = false;
138
+ let connectTimer = null;
139
+ const cleanup = ()=>{
140
+ if (connectTimer) {
141
+ clearTimeout(connectTimer);
142
+ connectTimer = null;
143
+ }
144
+ clearMetadataTimeout();
145
+ if (reconnectTimerRef.current) {
146
+ clearTimeout(reconnectTimerRef.current);
147
+ reconnectTimerRef.current = null;
148
+ }
149
+ if (socketRef.current) {
150
+ socketRef.current.disconnect();
151
+ socketRef.current = null;
152
+ }
153
+ disposeDecoder();
154
+ clearCanvas();
155
+ };
156
+ const scheduleReconnect = ()=>{
157
+ if (disposed || reconnectTimerRef.current) return;
158
+ reconnectTimerRef.current = setTimeout(()=>{
159
+ reconnectTimerRef.current = null;
160
+ cleanup();
161
+ connect();
162
+ }, reconnectInterval);
163
+ };
164
+ const createDecoder = (codecId)=>_async_to_generator(function*() {
165
+ var _canvasStageRef_current;
166
+ const renderer = WebGLVideoFrameRenderer.isSupported ? new WebGLVideoFrameRenderer() : new BitmapVideoFrameRenderer();
167
+ const canvas = renderer.canvas;
168
+ canvas.style.width = '100%';
169
+ canvas.style.height = '100%';
170
+ canvas.style.objectFit = 'contain';
171
+ clearCanvas();
172
+ null == (_canvasStageRef_current = canvasStageRef.current) || _canvasStageRef_current.appendChild(canvas);
173
+ const decoder = new WebCodecsVideoDecoder({
174
+ codec: codecId,
175
+ renderer
176
+ });
177
+ return decoder;
178
+ })();
179
+ const connect = ()=>{
180
+ if (disposed) return;
181
+ ignoreDisconnectRef.current = false;
182
+ setStatus('connecting');
183
+ setErrorMessage(null);
184
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
185
+ const socket = io(serverUrl, {
186
+ withCredentials: true,
187
+ reconnection: false,
188
+ timeout: 10000,
189
+ transports: [
190
+ 'websocket'
191
+ ]
192
+ });
193
+ socketRef.current = socket;
194
+ const videoStream = createScrcpyVideoStream(socket);
195
+ socket.on('connect', ()=>{
196
+ setStatus('waiting-for-stream');
197
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
198
+ clearMetadataTimeout();
199
+ metadataTimeoutRef.current = setTimeout(()=>{
200
+ if (disposed) return;
201
+ ignoreDisconnectRef.current = true;
202
+ setStatus('error');
203
+ setErrorMessage(getScrcpyMetadataTimeoutMessage(metadataTimeoutMs));
204
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
205
+ socket.disconnect();
206
+ socketRef.current = null;
207
+ scheduleReconnect();
208
+ }, metadataTimeoutMs);
209
+ socket.emit('connect-device', _object_spread_props(_object_spread({}, 'string' == typeof deviceId && deviceId.trim() ? {
210
+ deviceId: deviceId.trim()
211
+ } : {}), {
212
+ maxSize: 1024
213
+ }));
214
+ });
215
+ socket.on('preview-status', (event)=>{
216
+ if (disposed || !isScrcpyPreviewStatusEvent(event)) return;
217
+ setStatus('waiting-for-stream');
218
+ setWaitingStatusMessage(event.message);
219
+ });
220
+ socket.on('video-metadata', (metadata)=>_async_to_generator(function*() {
221
+ try {
222
+ clearMetadataTimeout();
223
+ disposeDecoder();
224
+ setWaitingStatusMessage(getScrcpyDecoderStatusText());
225
+ const codecId = metadata.codec ? metadata.codec : ScrcpyVideoCodecId.H264;
226
+ const decoder = yield createDecoder(codecId);
227
+ decoderRef.current = decoder;
228
+ videoStream.pipeTo(decoder.writable).catch((error)=>{
229
+ if (disposed) return;
230
+ setStatus('error');
231
+ setErrorMessage(error.message);
232
+ scheduleReconnect();
233
+ });
234
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
235
+ setStatus('connected');
236
+ } catch (error) {
237
+ if (disposed) return;
238
+ setStatus('error');
239
+ setErrorMessage(error instanceof Error ? error.message : 'Failed to start decoder.');
240
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
241
+ scheduleReconnect();
242
+ }
243
+ })());
244
+ socket.on('disconnect', ()=>{
245
+ clearMetadataTimeout();
246
+ if (disposed) return;
247
+ if (ignoreDisconnectRef.current) {
248
+ ignoreDisconnectRef.current = false;
249
+ return;
250
+ }
251
+ setStatus('disconnected');
252
+ setErrorMessage(null);
253
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
254
+ scheduleReconnect();
255
+ });
256
+ socket.on('connect_error', (error)=>{
257
+ clearMetadataTimeout();
258
+ if (disposed) return;
259
+ setStatus('error');
260
+ setErrorMessage(error.message);
261
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
262
+ scheduleReconnect();
263
+ });
264
+ socket.on('error', (error)=>{
265
+ clearMetadataTimeout();
266
+ if (disposed) return;
267
+ setStatus('error');
268
+ setErrorMessage(error.message);
269
+ setWaitingStatusMessage(getDefaultScrcpyWaitingStatusText());
270
+ scheduleReconnect();
271
+ });
272
+ };
273
+ connectTimer = setTimeout(()=>{
274
+ connectTimer = null;
275
+ connect();
276
+ }, 0);
277
+ return ()=>{
278
+ disposed = true;
279
+ cleanup();
280
+ };
281
+ }, [
282
+ deviceId,
283
+ metadataTimeoutMs,
284
+ reconnectInterval,
285
+ retryNonce,
286
+ serverUrl
287
+ ]);
288
+ return /*#__PURE__*/ jsxs("div", {
289
+ style: {
290
+ height: '100%',
291
+ display: 'flex',
292
+ flexDirection: 'column'
293
+ },
294
+ children: [
295
+ errorMessage && !showCustomErrorOverlay ? /*#__PURE__*/ jsx(Alert, {
296
+ type: "warning",
297
+ showIcon: true,
298
+ style: {
299
+ marginBottom: 12
300
+ },
301
+ message: statusText,
302
+ description: errorMessage
303
+ }) : null,
304
+ /*#__PURE__*/ jsxs("div", {
305
+ style: _object_spread({
306
+ position: 'relative',
307
+ flex: 1,
308
+ minHeight: 0,
309
+ display: 'flex',
310
+ alignItems: 'center',
311
+ justifyContent: 'center',
312
+ background: '#111827',
313
+ borderRadius: 8,
314
+ overflow: 'hidden'
315
+ }, viewportStyle),
316
+ children: [
317
+ /*#__PURE__*/ jsx("div", {
318
+ ref: canvasStageRef,
319
+ style: {
320
+ position: 'absolute',
321
+ inset: 0,
322
+ display: 'flex',
323
+ alignItems: 'center',
324
+ justifyContent: 'center'
325
+ }
326
+ }),
327
+ 'connected' !== status ? showCustomErrorOverlay && renderResolvedErrorOverlay ? /*#__PURE__*/ jsx("div", {
328
+ style: {
329
+ position: 'absolute',
330
+ inset: 0,
331
+ zIndex: 1
332
+ },
333
+ children: renderResolvedErrorOverlay({
334
+ errorMessage,
335
+ retry: requestRetry,
336
+ status,
337
+ statusText
338
+ })
339
+ }) : 'error' !== status && 'disconnected' !== status && connectingOverlay ? /*#__PURE__*/ jsx("div", {
340
+ style: {
341
+ position: 'absolute',
342
+ inset: 0,
343
+ zIndex: 1
344
+ },
345
+ children: connectingOverlay
346
+ }) : /*#__PURE__*/ jsxs("div", {
347
+ style: {
348
+ position: 'absolute',
349
+ inset: 0,
350
+ display: 'flex',
351
+ flexDirection: 'column',
352
+ alignItems: 'center',
353
+ justifyContent: 'center',
354
+ gap: 12,
355
+ color: '#fff',
356
+ background: 'rgba(17, 24, 39, 0.78)',
357
+ textAlign: 'center',
358
+ padding: 24,
359
+ zIndex: 1
360
+ },
361
+ children: [
362
+ 'error' === status ? null : /*#__PURE__*/ jsx(Spin, {
363
+ spinning: true
364
+ }),
365
+ /*#__PURE__*/ jsx(Text, {
366
+ style: {
367
+ color: '#fff'
368
+ },
369
+ children: statusText
370
+ }),
371
+ 'error' === status ? /*#__PURE__*/ jsx(Text, {
372
+ style: {
373
+ color: '#d1d5db'
374
+ },
375
+ children: "Scrcpy preview will retry automatically."
376
+ }) : null,
377
+ !webCodecsSupported && /*#__PURE__*/ jsx(Text, {
378
+ style: {
379
+ color: '#d1d5db'
380
+ },
381
+ children: "Please use a modern Chromium browser to view the stream."
382
+ })
383
+ ]
384
+ }) : null
385
+ ]
386
+ })
387
+ ]
388
+ });
389
+ }
390
+ export { ScrcpyPanel };
@@ -0,0 +1,299 @@
1
+ .session-setup-panel {
2
+ overscroll-behavior: contain;
3
+ scrollbar-gutter: stable;
4
+ background: var(--midscene-surface, #fff);
5
+ flex-direction: column;
6
+ flex: 1;
7
+ justify-content: flex-start;
8
+ align-items: center;
9
+ min-height: 0;
10
+ padding: 0 56px 32px;
11
+ display: flex;
12
+ overflow-x: hidden;
13
+ overflow-y: auto;
14
+ }
15
+
16
+ .session-setup-card {
17
+ flex-direction: column;
18
+ flex-shrink: 0;
19
+ align-items: center;
20
+ width: 100%;
21
+ max-width: 288px;
22
+ margin-top: max(32px, min(10vh, 96px));
23
+ padding-bottom: 32px;
24
+ display: flex;
25
+ }
26
+
27
+ .session-setup-logo {
28
+ object-fit: contain;
29
+ flex-shrink: 0;
30
+ width: 51px;
31
+ height: 48px;
32
+ }
33
+
34
+ .session-setup-title {
35
+ width: 240px;
36
+ color: var(--midscene-text-primary, #000);
37
+ text-align: center;
38
+ white-space: pre-line;
39
+ margin: 16px 0 0;
40
+ font-family: Roboto, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;
41
+ font-size: 18px;
42
+ font-weight: 600;
43
+ line-height: 22px;
44
+ }
45
+
46
+ .session-setup-description {
47
+ width: 276px;
48
+ color: var(--midscene-text-secondary, rgba(0, 0, 0, .7));
49
+ text-align: center;
50
+ margin: 14.7px 0 0;
51
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;
52
+ font-size: 12px;
53
+ font-weight: 400;
54
+ line-height: 20px;
55
+ }
56
+
57
+ .session-setup-alert.ant-alert {
58
+ width: 100%;
59
+ margin-top: 16px;
60
+ }
61
+
62
+ .session-setup-form {
63
+ width: 100%;
64
+ margin-top: 24px;
65
+ }
66
+
67
+ .session-setup-form .ant-form-item {
68
+ margin-bottom: 16px;
69
+ }
70
+
71
+ .session-setup-form .ant-form-item-label {
72
+ padding-bottom: 4px;
73
+ }
74
+
75
+ .session-setup-form .ant-form-item-label > label {
76
+ height: 15px;
77
+ color: var(--midscene-text-tertiary, rgba(0, 0, 0, .5));
78
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;
79
+ font-size: 12px;
80
+ font-weight: 500;
81
+ line-height: 14.5px;
82
+ }
83
+
84
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:before {
85
+ color: var(--midscene-text-tertiary, rgba(0, 0, 0, .5));
86
+ }
87
+
88
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))):before {
89
+ margin-right: 2px;
90
+ }
91
+
92
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:not(:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))):before {
93
+ margin-right: 2px;
94
+ }
95
+
96
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))):before {
97
+ margin-right: 2px;
98
+ }
99
+
100
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)):before {
101
+ margin-left: 2px;
102
+ }
103
+
104
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)):before {
105
+ margin-left: 2px;
106
+ }
107
+
108
+ .session-setup-form .ant-form-item-label > label.ant-form-item-required:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)):before {
109
+ margin-left: 2px;
110
+ }
111
+
112
+ .session-setup-form .ant-select-single .ant-select-selector {
113
+ background: var(--midscene-surface-muted, #f2f4f7);
114
+ height: 36px;
115
+ box-shadow: none;
116
+ border: none;
117
+ border-radius: 8px;
118
+ align-items: center;
119
+ padding: 0 12px;
120
+ display: flex;
121
+ }
122
+
123
+ .session-setup-form .ant-select-single .ant-select-selector .ant-select-selection-item, .session-setup-form .ant-select-single .ant-select-selector .ant-select-selection-placeholder {
124
+ color: var(--midscene-text-primary, #000);
125
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;
126
+ font-size: 14px;
127
+ font-weight: 400;
128
+ line-height: 36px;
129
+ }
130
+
131
+ .session-setup-form .ant-select-single .ant-select-selector .ant-select-selection-placeholder {
132
+ color: var(--midscene-text-placeholder, rgba(0, 0, 0, .4));
133
+ }
134
+
135
+ .session-setup-form .ant-select-arrow:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {
136
+ right: 12px;
137
+ }
138
+
139
+ .session-setup-form .ant-select-arrow:not(:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {
140
+ right: 12px;
141
+ }
142
+
143
+ .session-setup-form .ant-select-arrow:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {
144
+ right: 12px;
145
+ }
146
+
147
+ .session-setup-form .ant-select-arrow:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {
148
+ left: 12px;
149
+ }
150
+
151
+ .session-setup-form .ant-select-arrow:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {
152
+ left: 12px;
153
+ }
154
+
155
+ .session-setup-form .ant-select-arrow:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {
156
+ left: 12px;
157
+ }
158
+
159
+ .session-setup-form .ant-input, .session-setup-form .ant-input-number {
160
+ background: var(--midscene-surface-muted, #f2f4f7);
161
+ height: 36px;
162
+ box-shadow: none;
163
+ color: var(--midscene-text-primary, #000);
164
+ border: none;
165
+ border-radius: 8px;
166
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;
167
+ font-size: 14px;
168
+ }
169
+
170
+ .session-setup-form .ant-input-number-input {
171
+ height: 36px;
172
+ }
173
+
174
+ .session-setup-select-icon {
175
+ object-fit: contain;
176
+ pointer-events: none;
177
+ width: 16px;
178
+ height: 16px;
179
+ }
180
+
181
+ .session-setup-submit {
182
+ background: var(--midscene-brand, #1979ff);
183
+ color: #f2f4f7;
184
+ cursor: pointer;
185
+ border: 0;
186
+ border-radius: 8px;
187
+ width: 100%;
188
+ height: 32px;
189
+ margin-top: 7px;
190
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif;
191
+ font-size: 14px;
192
+ font-weight: 500;
193
+ line-height: 22px;
194
+ transition: opacity .2s;
195
+ }
196
+
197
+ .session-setup-submit:hover:not(:disabled) {
198
+ opacity: .9;
199
+ }
200
+
201
+ .session-setup-submit:disabled {
202
+ cursor: not-allowed;
203
+ opacity: .6;
204
+ }
205
+
206
+ .platform-selector-group {
207
+ grid-template-columns: repeat(2, minmax(0, 1fr));
208
+ gap: 12px;
209
+ width: 100%;
210
+ display: grid;
211
+ }
212
+
213
+ .platform-selector-group .ant-radio-button-wrapper {
214
+ white-space: normal;
215
+ background: #fff;
216
+ border-radius: 14px;
217
+ justify-content: flex-start;
218
+ align-items: flex-start;
219
+ height: auto;
220
+ min-height: 92px;
221
+ padding: 14px 16px;
222
+ transition: border-color .2s, box-shadow .2s, transform .2s;
223
+ display: flex;
224
+ }
225
+
226
+ .platform-selector-group .ant-radio-button-wrapper:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {
227
+ border-left-width: 1px;
228
+ }
229
+
230
+ .platform-selector-group .ant-radio-button-wrapper:not(:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {
231
+ border-left-width: 1px;
232
+ }
233
+
234
+ .platform-selector-group .ant-radio-button-wrapper:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) {
235
+ border-left-width: 1px;
236
+ }
237
+
238
+ .platform-selector-group .ant-radio-button-wrapper:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {
239
+ border-right-width: 1px;
240
+ }
241
+
242
+ .platform-selector-group .ant-radio-button-wrapper:-moz-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {
243
+ border-right-width: 1px;
244
+ }
245
+
246
+ .platform-selector-group .ant-radio-button-wrapper:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) {
247
+ border-right-width: 1px;
248
+ }
249
+
250
+ .platform-selector-group .ant-radio-button-wrapper:before {
251
+ display: none;
252
+ }
253
+
254
+ .platform-selector-group .ant-radio-button-wrapper:hover {
255
+ border-color: #1677ff;
256
+ transform: translateY(-1px);
257
+ }
258
+
259
+ .platform-selector-group .ant-radio-button-wrapper-checked {
260
+ border-color: #1677ff;
261
+ box-shadow: 0 10px 24px rgba(22, 119, 255, .12);
262
+ }
263
+
264
+ .platform-selector-card .platform-selector-title {
265
+ color: rgba(0, 0, 0, .88);
266
+ font-size: 15px;
267
+ font-weight: 600;
268
+ line-height: 1.4;
269
+ }
270
+
271
+ .platform-selector-card .platform-selector-description {
272
+ color: rgba(0, 0, 0, .5);
273
+ margin-top: 6px;
274
+ font-size: 12px;
275
+ line-height: 1.5;
276
+ }
277
+
278
+ .session-select-option {
279
+ flex-direction: column;
280
+ gap: 2px;
281
+ line-height: 1.4;
282
+ display: flex;
283
+ }
284
+
285
+ .session-select-option-label {
286
+ color: rgba(0, 0, 0, .88);
287
+ }
288
+
289
+ .session-select-option-description {
290
+ color: rgba(0, 0, 0, .45);
291
+ font-size: 12px;
292
+ }
293
+
294
+ @media (max-width: 640px) {
295
+ .platform-selector-group {
296
+ grid-template-columns: 1fr;
297
+ }
298
+ }
299
+