@flowdrop/flowdrop 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/enhanced-client.d.ts +29 -16
- package/dist/api/enhanced-client.js +0 -14
- package/dist/components/PipelineStatus.svelte +9 -12
- package/dist/components/WorkflowEditor.svelte +3 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +24 -5
- package/dist/components/interrupt/ConfirmationPrompt.svelte +5 -0
- package/dist/components/interrupt/InterruptBubble.svelte +12 -0
- package/dist/components/interrupt/ReviewPrompt.svelte +20 -0
- package/dist/components/interrupt/TextInputPrompt.svelte +5 -0
- package/dist/components/nodes/GatewayNode.svelte +2 -6
- package/dist/components/nodes/WorkflowNode.svelte +2 -6
- package/dist/components/playground/ChatInput.svelte +359 -0
- package/dist/components/playground/ChatInput.svelte.d.ts +14 -0
- package/dist/components/playground/ChatPanel.svelte +100 -724
- package/dist/components/playground/ChatPanel.svelte.d.ts +9 -26
- package/dist/components/playground/ControlPanel.svelte +496 -0
- package/dist/components/playground/ControlPanel.svelte.d.ts +20 -0
- package/dist/components/playground/ExecutionConsole.svelte +163 -0
- package/dist/components/playground/ExecutionConsole.svelte.d.ts +14 -0
- package/dist/components/playground/MessageStream.svelte +283 -0
- package/dist/components/playground/MessageStream.svelte.d.ts +27 -0
- package/dist/components/playground/PipelineKanbanView.svelte +284 -0
- package/dist/components/playground/PipelineKanbanView.svelte.d.ts +11 -0
- package/dist/components/playground/PipelinePanel.svelte +204 -65
- package/dist/components/playground/PipelinePanel.svelte.d.ts +3 -1
- package/dist/components/playground/PipelineTableView.svelte +376 -0
- package/dist/components/playground/PipelineTableView.svelte.d.ts +11 -0
- package/dist/components/playground/Playground.svelte +262 -1200
- package/dist/components/playground/Playground.svelte.d.ts +0 -13
- package/dist/components/playground/PlaygroundStudio.svelte +35 -61
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -1
- package/dist/components/playground/pipelineViewUtils.svelte.d.ts +22 -0
- package/dist/components/playground/pipelineViewUtils.svelte.js +77 -0
- package/dist/messages/defaults.d.ts +24 -0
- package/dist/messages/defaults.js +24 -0
- package/dist/playground/index.d.ts +6 -1
- package/dist/playground/index.js +6 -0
- package/dist/playground/mount.d.ts +3 -0
- package/dist/playground/mount.js +3 -2
- package/dist/stores/playgroundStore.svelte.d.ts +6 -0
- package/dist/stores/playgroundStore.svelte.js +21 -1
- package/dist/types/index.d.ts +28 -2
- package/dist/types/playground.d.ts +5 -2
- package/dist/types/playground.js +5 -7
- package/dist/utils/nodeStatus.js +15 -5
- package/package.json +1 -1
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Playground Component
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
|
|
4
|
+
Three-pane interactive workflow runtime. Hosts session and execution logic
|
|
5
|
+
for the playground feature; delegates rendering to ExecutionConsole (top
|
|
6
|
+
right) and ControlPanel (bottom right) with a draggable vertical resizer
|
|
7
|
+
between them.
|
|
8
|
+
|
|
9
|
+
Used by PlaygroundStudio (standalone), PlaygroundModal (modal), and the
|
|
10
|
+
/workflow/[id]/playground/[sessionId] route.
|
|
8
11
|
-->
|
|
9
12
|
|
|
10
13
|
<script lang="ts">
|
|
11
|
-
import { onMount, onDestroy } from 'svelte';
|
|
14
|
+
import { onMount, onDestroy, untrack } from 'svelte';
|
|
12
15
|
import Icon from '@iconify/svelte';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
16
|
+
import ExecutionConsole from './ExecutionConsole.svelte';
|
|
17
|
+
import ControlPanel from './ControlPanel.svelte';
|
|
15
18
|
import type { Workflow } from '../../types/index.js';
|
|
16
19
|
import type { EndpointConfig } from '../../config/endpoints.js';
|
|
17
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
PlaygroundMode,
|
|
22
|
+
PlaygroundConfig,
|
|
23
|
+
PlaygroundSessionStatus
|
|
24
|
+
} from '../../types/playground.js';
|
|
18
25
|
import { playgroundService } from '../../services/playgroundService.js';
|
|
19
26
|
import { interruptService } from '../../services/interruptService.js';
|
|
20
27
|
import { setEndpointConfig } from '../../services/api.js';
|
|
@@ -25,41 +32,22 @@
|
|
|
25
32
|
getIsLoading,
|
|
26
33
|
getError,
|
|
27
34
|
playgroundActions,
|
|
28
|
-
getInputFields,
|
|
29
35
|
applyServerResponse,
|
|
30
|
-
|
|
31
|
-
getLatestSequenceNumber,
|
|
32
|
-
getActiveExecutionId,
|
|
33
|
-
getLatestExecutionId,
|
|
34
|
-
getPinnedExecutionId,
|
|
36
|
+
getLatestSequenceNumber
|
|
35
37
|
} from '../../stores/playgroundStore.svelte.js';
|
|
36
38
|
import { interruptActions } from '../../stores/interruptStore.svelte.js';
|
|
37
39
|
import { logger } from '../../utils/logger.js';
|
|
38
|
-
import { m } from '../../messages/index.js';
|
|
39
40
|
|
|
40
|
-
/**
|
|
41
|
-
* Component props
|
|
42
|
-
*/
|
|
43
41
|
interface Props {
|
|
44
|
-
/** Target workflow ID */
|
|
45
42
|
workflowId: string;
|
|
46
|
-
/** Pre-loaded workflow (optional, will be fetched if not provided) */
|
|
47
43
|
workflow?: Workflow;
|
|
48
|
-
/** Display mode: embedded (panel) or standalone (page) */
|
|
49
44
|
mode?: PlaygroundMode;
|
|
50
|
-
/** Resume a specific session */
|
|
51
45
|
initialSessionId?: string;
|
|
52
|
-
/** API endpoint configuration */
|
|
53
46
|
endpointConfig?: EndpointConfig;
|
|
54
|
-
/** Playground configuration options */
|
|
55
47
|
config?: PlaygroundConfig;
|
|
56
|
-
/** Callback when playground is closed (for embedded mode) */
|
|
57
48
|
onClose?: () => void;
|
|
58
|
-
/** Callback to toggle the pipeline panel (if undefined, toggle button is hidden) */
|
|
59
49
|
onTogglePanel?: () => void;
|
|
60
|
-
/** Whether the pipeline panel is currently open (for toggle button active state) */
|
|
61
50
|
isPipelinePanelOpen?: boolean;
|
|
62
|
-
/** When provided, session switches and creation navigate to a URL instead of mutating store state */
|
|
63
51
|
onSessionNavigate?: (sessionId: string) => void;
|
|
64
52
|
}
|
|
65
53
|
|
|
@@ -73,54 +61,69 @@
|
|
|
73
61
|
onClose,
|
|
74
62
|
onTogglePanel,
|
|
75
63
|
isPipelinePanelOpen = false,
|
|
76
|
-
onSessionNavigate
|
|
64
|
+
onSessionNavigate
|
|
77
65
|
}: Props = $props();
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
/** Track session being edited for rename */
|
|
83
|
-
let editingSessionId = $state<string | null>(null);
|
|
67
|
+
let loadedInitialSessionId = $state<string | undefined>(undefined);
|
|
68
|
+
let autoRunTriggered = $state(false);
|
|
69
|
+
let isRefreshing = $state(false);
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
let
|
|
71
|
+
// Vertical resizer state for the ExecutionConsole ↔ ControlPanel split.
|
|
72
|
+
let playgroundContentEl = $state<HTMLElement | null>(null);
|
|
73
|
+
let controlPanelHeight = $state(280);
|
|
74
|
+
let isVerticalResizing = $state(false);
|
|
75
|
+
let containerHeight = $state(0);
|
|
76
|
+
let dragContainerBottom = 0;
|
|
87
77
|
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
$effect(() => {
|
|
79
|
+
if (!playgroundContentEl) return;
|
|
80
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
81
|
+
containerHeight = entry.contentRect.height;
|
|
82
|
+
});
|
|
83
|
+
observer.observe(playgroundContentEl);
|
|
84
|
+
return () => observer.disconnect();
|
|
85
|
+
});
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
$effect(() => {
|
|
88
|
+
if (containerHeight > 0) {
|
|
89
|
+
controlPanelHeight = clampControlPanelHeight(untrack(() => controlPanelHeight));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
const maxControlPanelHeight = $derived(
|
|
94
|
+
containerHeight ? Math.round(containerHeight * 0.6) : 600
|
|
95
|
+
);
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
function clampControlPanelHeight(h: number): number {
|
|
98
|
+
return Math.min(Math.max(h, 140), maxControlPanelHeight);
|
|
99
|
+
}
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
function handleVerticalResizerPointerDown(e: PointerEvent) {
|
|
102
|
+
if (playgroundContentEl) dragContainerBottom = playgroundContentEl.getBoundingClientRect().bottom;
|
|
103
|
+
isVerticalResizing = true;
|
|
104
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
105
|
+
}
|
|
102
106
|
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
function handleVerticalResizerPointerMove(e: PointerEvent) {
|
|
108
|
+
if (!isVerticalResizing) return;
|
|
109
|
+
controlPanelHeight = clampControlPanelHeight(dragContainerBottom - e.clientY);
|
|
110
|
+
}
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
function handleVerticalResizerPointerUp() {
|
|
113
|
+
isVerticalResizing = false;
|
|
114
|
+
}
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
function handleVerticalResizerKeyDown(e: KeyboardEvent) {
|
|
117
|
+
const step = e.shiftKey ? 50 : 20;
|
|
118
|
+
if (e.key === 'ArrowUp') {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
controlPanelHeight = clampControlPanelHeight(controlPanelHeight + step);
|
|
121
|
+
} else if (e.key === 'ArrowDown') {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
controlPanelHeight = clampControlPanelHeight(controlPanelHeight - step);
|
|
116
124
|
}
|
|
117
|
-
|
|
118
|
-
return () => document.removeEventListener('click', handleOutside);
|
|
119
|
-
});
|
|
125
|
+
}
|
|
120
126
|
|
|
121
|
-
/**
|
|
122
|
-
* Initialize the playground on mount
|
|
123
|
-
*/
|
|
124
127
|
onMount(() => {
|
|
125
128
|
if (endpointConfig) setEndpointConfig(endpointConfig);
|
|
126
129
|
if (workflow) playgroundActions.setWorkflow(workflow);
|
|
@@ -151,36 +154,17 @@
|
|
|
151
154
|
|
|
152
155
|
/**
|
|
153
156
|
* Handle reactive changes to initialSessionId prop
|
|
154
|
-
* This allows the initial session to be set after mount
|
|
155
157
|
*/
|
|
156
158
|
$effect(() => {
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Skip if this session was already loaded or is currently loading.
|
|
163
|
-
// loadedInitialSessionId is set synchronously at the start of loadInitialSession,
|
|
164
|
-
// so this prevents the effect from spawning concurrent loads when isLoading changes.
|
|
165
|
-
if (loadedInitialSessionId === initialSessionId) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
159
|
+
if (!initialSessionId) return;
|
|
160
|
+
if (loadedInitialSessionId === initialSessionId) return;
|
|
168
161
|
|
|
169
|
-
// Skip if sessions haven't been loaded yet (will be handled by onMount)
|
|
170
162
|
const sessionList = getSessions();
|
|
171
|
-
if (sessionList.length === 0
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
163
|
+
if (sessionList.length === 0) return;
|
|
174
164
|
|
|
175
|
-
|
|
176
|
-
if (sessionList.length > 0) {
|
|
177
|
-
void loadInitialSession(initialSessionId);
|
|
178
|
-
}
|
|
165
|
+
void loadInitialSession(initialSessionId);
|
|
179
166
|
});
|
|
180
167
|
|
|
181
|
-
/**
|
|
182
|
-
* Initialize the playground: load sessions, load initial session, handle auto-run
|
|
183
|
-
*/
|
|
184
168
|
async function initializePlayground(): Promise<void> {
|
|
185
169
|
try {
|
|
186
170
|
await loadSessions();
|
|
@@ -200,42 +184,27 @@
|
|
|
200
184
|
}
|
|
201
185
|
}
|
|
202
186
|
|
|
203
|
-
/**
|
|
204
|
-
* Load the initial session with validation and error handling
|
|
205
|
-
*
|
|
206
|
-
* @param sessionId - The session ID to load
|
|
207
|
-
*/
|
|
208
187
|
async function loadInitialSession(sessionId: string): Promise<void> {
|
|
209
|
-
// Validate session exists in loaded sessions before setting the guard.
|
|
210
|
-
// If not found yet, we skip setting loadedInitialSessionId so the $effect
|
|
211
|
-
// can retry when _sessions updates (e.g. after a new session is created).
|
|
212
188
|
const sessionList = getSessions();
|
|
213
189
|
const sessionExists = sessionList.some((s) => s.id === sessionId);
|
|
214
190
|
|
|
215
191
|
if (!sessionExists) {
|
|
216
192
|
logger.warn(
|
|
217
|
-
`[Playground] Initial session "${sessionId}" not found
|
|
218
|
-
`Available
|
|
193
|
+
`[Playground] Initial session "${sessionId}" not found. ` +
|
|
194
|
+
`Available: ${sessionList.map((s) => s.id).join(', ') || 'none'}`
|
|
219
195
|
);
|
|
220
|
-
initialSessionLoaded = true;
|
|
221
196
|
return;
|
|
222
197
|
}
|
|
223
198
|
|
|
224
|
-
// Set guard BEFORE the first await to prevent concurrent loads.
|
|
225
199
|
loadedInitialSessionId = sessionId;
|
|
226
200
|
|
|
227
201
|
try {
|
|
228
202
|
await loadSession(sessionId);
|
|
229
|
-
initialSessionLoaded = true;
|
|
230
203
|
} catch (err) {
|
|
231
204
|
logger.error('[Playground] Failed to load initial session:', err);
|
|
232
|
-
initialSessionLoaded = true;
|
|
233
205
|
}
|
|
234
206
|
}
|
|
235
207
|
|
|
236
|
-
/**
|
|
237
|
-
* Cleanup on destroy
|
|
238
|
-
*/
|
|
239
208
|
onDestroy(() => {
|
|
240
209
|
playgroundService.stopPolling();
|
|
241
210
|
interruptService.stopPolling();
|
|
@@ -243,23 +212,6 @@
|
|
|
243
212
|
interruptActions.reset();
|
|
244
213
|
});
|
|
245
214
|
|
|
246
|
-
/**
|
|
247
|
-
* Close dropdown menu when clicking outside
|
|
248
|
-
*/
|
|
249
|
-
$effect(() => {
|
|
250
|
-
if (!openMenuId) return;
|
|
251
|
-
|
|
252
|
-
function onDocumentClick() {
|
|
253
|
-
openMenuId = null;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
document.addEventListener('click', onDocumentClick);
|
|
257
|
-
return () => document.removeEventListener('click', onDocumentClick);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Load sessions for the workflow
|
|
262
|
-
*/
|
|
263
215
|
async function loadSessions(): Promise<void> {
|
|
264
216
|
playgroundActions.setLoading(true);
|
|
265
217
|
playgroundActions.setError(null);
|
|
@@ -276,9 +228,6 @@
|
|
|
276
228
|
}
|
|
277
229
|
}
|
|
278
230
|
|
|
279
|
-
/**
|
|
280
|
-
* Load a specific session and its messages
|
|
281
|
-
*/
|
|
282
231
|
async function loadSession(sessionId: string): Promise<void> {
|
|
283
232
|
playgroundActions.setLoading(true);
|
|
284
233
|
playgroundActions.setError(null);
|
|
@@ -290,7 +239,7 @@
|
|
|
290
239
|
const response = await playgroundService.getMessages(sessionId);
|
|
291
240
|
applyServerResponse(response);
|
|
292
241
|
|
|
293
|
-
if (session.status
|
|
242
|
+
if (session.status !== 'idle') {
|
|
294
243
|
startPolling(sessionId, true);
|
|
295
244
|
}
|
|
296
245
|
} catch (err) {
|
|
@@ -302,9 +251,6 @@
|
|
|
302
251
|
}
|
|
303
252
|
}
|
|
304
253
|
|
|
305
|
-
/**
|
|
306
|
-
* Create a new session
|
|
307
|
-
*/
|
|
308
254
|
async function handleCreateSession(): Promise<void> {
|
|
309
255
|
playgroundActions.setLoading(true);
|
|
310
256
|
playgroundActions.setError(null);
|
|
@@ -314,7 +260,6 @@
|
|
|
314
260
|
const session = await playgroundService.createSession(workflowId, sessionName);
|
|
315
261
|
|
|
316
262
|
if (onSessionNavigate) {
|
|
317
|
-
// URL-based routing: navigate to the new session; page remount handles store init
|
|
318
263
|
onSessionNavigate(session.id);
|
|
319
264
|
return;
|
|
320
265
|
}
|
|
@@ -331,31 +276,21 @@
|
|
|
331
276
|
}
|
|
332
277
|
}
|
|
333
278
|
|
|
334
|
-
/**
|
|
335
|
-
* Select a session
|
|
336
|
-
*/
|
|
337
279
|
async function handleSelectSession(sessionId: string): Promise<void> {
|
|
338
280
|
playgroundActions.pinExecution(null);
|
|
339
|
-
runsExpanded = false;
|
|
340
281
|
const currentSessionId = getCurrentSession()?.id;
|
|
341
|
-
if (currentSessionId === sessionId)
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
282
|
+
if (currentSessionId === sessionId) return;
|
|
344
283
|
|
|
345
284
|
playgroundService.stopPolling();
|
|
346
285
|
playgroundActions.updateSessionStatus('idle');
|
|
347
286
|
await loadSession(sessionId);
|
|
348
287
|
}
|
|
349
288
|
|
|
350
|
-
/**
|
|
351
|
-
* Delete a session
|
|
352
|
-
*/
|
|
353
289
|
async function handleDeleteSession(sessionId: string): Promise<void> {
|
|
354
290
|
try {
|
|
355
291
|
await playgroundService.deleteSession(sessionId);
|
|
356
292
|
playgroundActions.removeSession(sessionId);
|
|
357
293
|
|
|
358
|
-
// If we deleted the current session, clear it
|
|
359
294
|
if (getCurrentSession()?.id === sessionId) {
|
|
360
295
|
playgroundService.stopPolling();
|
|
361
296
|
}
|
|
@@ -366,51 +301,12 @@
|
|
|
366
301
|
}
|
|
367
302
|
}
|
|
368
303
|
|
|
369
|
-
/**
|
|
370
|
-
* Toggle session dropdown menu
|
|
371
|
-
*/
|
|
372
|
-
function handleMenuToggle(event: Event, sessionId: string): void {
|
|
373
|
-
event.stopPropagation();
|
|
374
|
-
openMenuId = openMenuId === sessionId ? null : sessionId;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Handle delete from dropdown menu
|
|
379
|
-
*/
|
|
380
|
-
function handleMenuDelete(event: Event, sessionId: string): void {
|
|
381
|
-
event.stopPropagation();
|
|
382
|
-
openMenuId = null;
|
|
383
|
-
void handleDeleteSession(sessionId);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Close current session (go back to welcome)
|
|
388
|
-
*/
|
|
389
|
-
function handleCloseSession(): void {
|
|
390
|
-
playgroundService.stopPolling();
|
|
391
|
-
interruptService.stopPolling();
|
|
392
|
-
playgroundActions.setCurrentSession(null);
|
|
393
|
-
playgroundActions.clearMessages();
|
|
394
|
-
// Clear interrupts for this session
|
|
395
|
-
const sessionId = getCurrentSession()?.id;
|
|
396
|
-
if (sessionId) {
|
|
397
|
-
interruptActions.clearSessionInterrupts(sessionId);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Send a message
|
|
403
|
-
*/
|
|
404
304
|
async function handleSendMessage(content: string): Promise<void> {
|
|
405
305
|
if (getIsExecuting()) return;
|
|
406
306
|
|
|
407
|
-
|
|
408
|
-
if (!session) {
|
|
307
|
+
if (!getCurrentSession()) {
|
|
409
308
|
await handleCreateSession();
|
|
410
|
-
|
|
411
|
-
if (!newSession) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
309
|
+
if (!getCurrentSession()) return;
|
|
414
310
|
}
|
|
415
311
|
|
|
416
312
|
const sessionId = getCurrentSession()!.id;
|
|
@@ -420,22 +316,13 @@
|
|
|
420
316
|
playgroundActions.setError(null);
|
|
421
317
|
|
|
422
318
|
try {
|
|
423
|
-
const
|
|
424
|
-
const fields = getInputFields();
|
|
425
|
-
|
|
426
|
-
fields.forEach((field) => {
|
|
427
|
-
const key = `${field.nodeId}:${field.fieldId}`;
|
|
428
|
-
if (inputValues[key] !== undefined) {
|
|
429
|
-
if (!inputs[field.nodeId]) {
|
|
430
|
-
inputs[field.nodeId] = {};
|
|
431
|
-
}
|
|
432
|
-
(inputs[field.nodeId] as Record<string, unknown>)[field.fieldId] = inputValues[key];
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const message = await playgroundService.sendMessage(sessionId, content, inputs);
|
|
319
|
+
const message = await playgroundService.sendMessage(sessionId, content, {});
|
|
437
320
|
playgroundActions.addMessage(message);
|
|
438
|
-
|
|
321
|
+
// Only start polling if not already active — avoids resetting the cursor
|
|
322
|
+
// mid-session and re-fetching messages that are already in the store.
|
|
323
|
+
if (!playgroundService.isPolling()) {
|
|
324
|
+
startPolling(sessionId);
|
|
325
|
+
}
|
|
439
326
|
} catch (err) {
|
|
440
327
|
const errorMessage = err instanceof Error ? err.message : 'Failed to send message';
|
|
441
328
|
playgroundActions.setError(errorMessage);
|
|
@@ -444,14 +331,9 @@
|
|
|
444
331
|
}
|
|
445
332
|
}
|
|
446
333
|
|
|
447
|
-
/**
|
|
448
|
-
* Stop execution
|
|
449
|
-
*/
|
|
450
334
|
async function handleStopExecution(): Promise<void> {
|
|
451
335
|
const sessionId = getCurrentSession()?.id;
|
|
452
|
-
if (!sessionId)
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
336
|
+
if (!sessionId) return;
|
|
455
337
|
|
|
456
338
|
try {
|
|
457
339
|
await playgroundService.stopExecution(sessionId);
|
|
@@ -466,10 +348,11 @@
|
|
|
466
348
|
}
|
|
467
349
|
}
|
|
468
350
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
351
|
+
function startPolling(
|
|
352
|
+
sessionId: string,
|
|
353
|
+
seedSequence = false,
|
|
354
|
+
overrideShouldStopPolling?: (status: PlaygroundSessionStatus) => boolean
|
|
355
|
+
): void {
|
|
473
356
|
const pollingInterval = config.pollingInterval ?? 1500;
|
|
474
357
|
const initialSequenceNumber = seedSequence ? getLatestSequenceNumber() : null;
|
|
475
358
|
|
|
@@ -477,15 +360,11 @@
|
|
|
477
360
|
sessionId,
|
|
478
361
|
(response) => applyServerResponse(response),
|
|
479
362
|
pollingInterval,
|
|
480
|
-
config.shouldStopPolling,
|
|
363
|
+
overrideShouldStopPolling ?? config.shouldStopPolling,
|
|
481
364
|
initialSequenceNumber
|
|
482
365
|
);
|
|
483
366
|
}
|
|
484
367
|
|
|
485
|
-
/**
|
|
486
|
-
* Fetch the latest messages and session status from the server.
|
|
487
|
-
* Resumes polling if the session is running but polling had stopped.
|
|
488
|
-
*/
|
|
489
368
|
async function refreshFromServer(): Promise<void> {
|
|
490
369
|
const sessionId = getCurrentSession()?.id;
|
|
491
370
|
if (!sessionId || isRefreshing) return;
|
|
@@ -506,53 +385,27 @@
|
|
|
506
385
|
}
|
|
507
386
|
}
|
|
508
387
|
|
|
509
|
-
/**
|
|
510
|
-
* Refresh messages for the current session
|
|
511
|
-
* Called after interrupt resolution when polling has stopped
|
|
512
|
-
*/
|
|
513
388
|
async function handleInterruptResolved(): Promise<void> {
|
|
514
389
|
const sessionId = getCurrentSession()?.id;
|
|
515
390
|
if (!sessionId) return;
|
|
516
391
|
|
|
517
392
|
try {
|
|
518
|
-
|
|
393
|
+
// Catch up immediately rather than waiting for the next poll interval.
|
|
394
|
+
// Use the service's sequence cursor so we only fetch new messages.
|
|
395
|
+
const response = await playgroundService.getMessages(
|
|
396
|
+
sessionId,
|
|
397
|
+
playgroundService.getLastSequenceNumber() ?? undefined
|
|
398
|
+
);
|
|
519
399
|
applyServerResponse(response);
|
|
520
|
-
|
|
521
|
-
if (response.sessionStatus === 'running') {
|
|
522
|
-
startPolling(sessionId, true);
|
|
523
|
-
}
|
|
524
400
|
} catch (err) {
|
|
525
401
|
logger.error('[Playground] Failed to refresh after interrupt:', err);
|
|
526
402
|
}
|
|
527
|
-
}
|
|
528
403
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const date = new Date(dateString);
|
|
534
|
-
const now = new Date();
|
|
535
|
-
const diffMs = now.getTime() - date.getTime();
|
|
536
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
537
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
538
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
539
|
-
|
|
540
|
-
if (diffMins < 1) {
|
|
541
|
-
return 'Just now';
|
|
542
|
-
}
|
|
543
|
-
if (diffMins < 60) {
|
|
544
|
-
return `${diffMins}m ago`;
|
|
545
|
-
}
|
|
546
|
-
if (diffHours < 24) {
|
|
547
|
-
return `${diffHours}h ago`;
|
|
404
|
+
// Polling continues through awaiting_input now, but restart defensively
|
|
405
|
+
// in case it stopped for any reason (e.g. component re-mount).
|
|
406
|
+
if (!playgroundService.isPolling()) {
|
|
407
|
+
startPolling(sessionId, true);
|
|
548
408
|
}
|
|
549
|
-
if (diffDays < 7) {
|
|
550
|
-
return `${diffDays}d ago`;
|
|
551
|
-
}
|
|
552
|
-
return date.toLocaleDateString('en-US', {
|
|
553
|
-
month: 'short',
|
|
554
|
-
day: 'numeric'
|
|
555
|
-
});
|
|
556
409
|
}
|
|
557
410
|
</script>
|
|
558
411
|
|
|
@@ -561,324 +414,100 @@
|
|
|
561
414
|
class:playground--embedded={mode === 'embedded'}
|
|
562
415
|
class:playground--standalone={mode === 'standalone'}
|
|
563
416
|
class:playground--modal={mode === 'modal'}
|
|
564
|
-
class:playground--no-sidebar={config.showSidebar === false}
|
|
565
417
|
>
|
|
566
|
-
<
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
href="/workflow/{workflowId}/edit"
|
|
580
|
-
class="playground__edit-link"
|
|
581
|
-
title="Edit workflow"
|
|
582
|
-
>
|
|
583
|
-
<Icon icon="mdi:pencil-outline" />
|
|
584
|
-
</a>
|
|
585
|
-
{#if (mode === 'embedded' || mode === 'modal') && onClose}
|
|
586
|
-
<button
|
|
587
|
-
type="button"
|
|
588
|
-
class="playground__sidebar-close"
|
|
589
|
-
onclick={onClose}
|
|
590
|
-
title="Close playground"
|
|
591
|
-
>
|
|
592
|
-
{#if mode === 'modal'}
|
|
593
|
-
<Icon icon="mdi:close" />
|
|
594
|
-
{:else}
|
|
595
|
-
<Icon icon="mdi:dock-right" />
|
|
596
|
-
{/if}
|
|
597
|
-
</button>
|
|
598
|
-
{/if}
|
|
599
|
-
</div>
|
|
600
|
-
|
|
601
|
-
<!-- Sessions Section -->
|
|
602
|
-
<div class="playground__section">
|
|
603
|
-
<!-- Section header with inline add button -->
|
|
604
|
-
<div class="playground__section-header">
|
|
605
|
-
<span class="playground__section-label">Sessions</span>
|
|
606
|
-
<button
|
|
607
|
-
type="button"
|
|
608
|
-
class="playground__section-add"
|
|
609
|
-
onclick={handleCreateSession}
|
|
610
|
-
disabled={getIsLoading()}
|
|
611
|
-
title="New session"
|
|
612
|
-
>
|
|
613
|
-
<Icon icon="mdi:plus" />
|
|
614
|
-
</button>
|
|
615
|
-
</div>
|
|
616
|
-
|
|
617
|
-
<!-- Sessions List -->
|
|
618
|
-
<div class="playground__sessions-wrap">
|
|
619
|
-
<div class="playground__sessions">
|
|
620
|
-
{#if getSessions().length === 0 && !getIsLoading()}
|
|
621
|
-
<div class="playground__sessions-empty">
|
|
622
|
-
<span>No sessions yet</span>
|
|
623
|
-
</div>
|
|
624
|
-
{:else}
|
|
625
|
-
{#each getSessions() as session (session.id)}
|
|
626
|
-
{@const isActive = getCurrentSession()?.id === session.id}
|
|
627
|
-
<div class="playground__session-group">
|
|
628
|
-
<div
|
|
629
|
-
class="playground__session"
|
|
630
|
-
class:playground__session--active={isActive}
|
|
631
|
-
role="button"
|
|
632
|
-
tabindex="0"
|
|
633
|
-
title="Click to load this session"
|
|
634
|
-
aria-label={m().layout.loadSession({ name: session.name })}
|
|
635
|
-
onclick={() => handleSelectSession(session.id)}
|
|
636
|
-
onkeydown={(e) => e.key === 'Enter' && handleSelectSession(session.id)}
|
|
637
|
-
>
|
|
638
|
-
<span class="playground__session-name" title={session.name}>
|
|
639
|
-
{session.name}
|
|
640
|
-
</span>
|
|
641
|
-
<div class="playground__session-actions">
|
|
642
|
-
<button
|
|
643
|
-
type="button"
|
|
644
|
-
class="playground__session-menu"
|
|
645
|
-
class:playground__session-menu--open={openMenuId === session.id}
|
|
646
|
-
onclick={(e) => handleMenuToggle(e, session.id)}
|
|
647
|
-
title="Session options"
|
|
648
|
-
>
|
|
649
|
-
<Icon icon="mdi:dots-vertical" />
|
|
650
|
-
</button>
|
|
651
|
-
{#if openMenuId === session.id}
|
|
652
|
-
<div class="playground__session-dropdown">
|
|
653
|
-
<button
|
|
654
|
-
type="button"
|
|
655
|
-
class="playground__session-dropdown-item playground__session-dropdown-item--danger"
|
|
656
|
-
onclick={(e) => handleMenuDelete(e, session.id)}
|
|
657
|
-
>
|
|
658
|
-
<Icon icon="mdi:delete-outline" />
|
|
659
|
-
<span>Delete</span>
|
|
660
|
-
</button>
|
|
661
|
-
</div>
|
|
662
|
-
{/if}
|
|
663
|
-
</div>
|
|
664
|
-
</div>
|
|
665
|
-
<!-- Collapsible runs sub-section under active session -->
|
|
666
|
-
{#if isActive && getCurrentSession()?.executions?.length}
|
|
667
|
-
<div class="playground__runs-section">
|
|
668
|
-
<button
|
|
669
|
-
type="button"
|
|
670
|
-
class="playground__runs-toggle"
|
|
671
|
-
onclick={() => (runsExpanded = !runsExpanded)}
|
|
672
|
-
>
|
|
673
|
-
<Icon icon={runsExpanded ? 'mdi:chevron-down' : 'mdi:chevron-right'} />
|
|
674
|
-
<span>Runs</span>
|
|
675
|
-
<span class="playground__runs-count">{getCurrentSession()!.executions!.length}</span>
|
|
676
|
-
</button>
|
|
677
|
-
{#if runsExpanded}
|
|
678
|
-
<div class="playground__executions-inline">
|
|
679
|
-
<ExecutionList
|
|
680
|
-
executions={getCurrentSession()!.executions!}
|
|
681
|
-
activeExecutionId={getActiveExecutionId()}
|
|
682
|
-
latestExecutionId={getLatestExecutionId()}
|
|
683
|
-
onSelect={(id) => {
|
|
684
|
-
if (id === getLatestExecutionId()) {
|
|
685
|
-
playgroundActions.pinExecution(null);
|
|
686
|
-
} else {
|
|
687
|
-
playgroundActions.pinExecution(id);
|
|
688
|
-
}
|
|
689
|
-
}}
|
|
690
|
-
/>
|
|
691
|
-
</div>
|
|
692
|
-
{/if}
|
|
693
|
-
</div>
|
|
694
|
-
{/if}
|
|
695
|
-
</div>
|
|
696
|
-
{/each}
|
|
697
|
-
{/if}
|
|
698
|
-
</div>
|
|
699
|
-
</div>
|
|
700
|
-
</div>
|
|
701
|
-
</aside>
|
|
418
|
+
<main class="playground__main">
|
|
419
|
+
{#if getError()}
|
|
420
|
+
<div class="playground__error">
|
|
421
|
+
<Icon icon="mdi:alert-circle" />
|
|
422
|
+
<span>{getError()}</span>
|
|
423
|
+
<button
|
|
424
|
+
type="button"
|
|
425
|
+
class="playground__error-dismiss"
|
|
426
|
+
onclick={() => playgroundActions.setError(null)}
|
|
427
|
+
>
|
|
428
|
+
<Icon icon="mdi:close" />
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
702
431
|
{/if}
|
|
703
432
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
{#if getSessions().length > 0}
|
|
741
|
-
<div class="playground__session-popover-divider"></div>
|
|
742
|
-
{#each getSessions() as session (session.id)}
|
|
743
|
-
{@const isActive = getCurrentSession()?.id === session.id}
|
|
744
|
-
<div class="playground__session-popover-row">
|
|
745
|
-
<button
|
|
746
|
-
type="button"
|
|
747
|
-
class="playground__session-popover-item"
|
|
748
|
-
class:playground__session-popover-item--active={isActive}
|
|
749
|
-
onclick={() => {
|
|
750
|
-
sessionDropdownOpen = false;
|
|
751
|
-
if (onSessionNavigate) {
|
|
752
|
-
onSessionNavigate(session.id);
|
|
753
|
-
} else {
|
|
754
|
-
void handleSelectSession(session.id);
|
|
755
|
-
}
|
|
756
|
-
}}
|
|
757
|
-
>
|
|
758
|
-
{#if isActive}
|
|
759
|
-
<Icon icon="mdi:check" class="playground__session-popover-check" />
|
|
760
|
-
{:else}
|
|
761
|
-
<Icon icon="mdi:message-outline" />
|
|
762
|
-
{/if}
|
|
763
|
-
<span>{session.name}</span>
|
|
764
|
-
</button>
|
|
765
|
-
<button
|
|
766
|
-
type="button"
|
|
767
|
-
class="playground__session-popover-delete"
|
|
768
|
-
onclick={(e) => { handleMenuDelete(e, session.id); sessionDropdownOpen = false; }}
|
|
769
|
-
title="Delete session"
|
|
770
|
-
>
|
|
771
|
-
<Icon icon="mdi:delete-outline" />
|
|
772
|
-
</button>
|
|
773
|
-
</div>
|
|
774
|
-
{/each}
|
|
775
|
-
{/if}
|
|
776
|
-
</div>
|
|
777
|
-
{/if}
|
|
778
|
-
</div>
|
|
779
|
-
{:else}
|
|
780
|
-
<!-- Embedded / modal: original title + close -->
|
|
781
|
-
<div class="playground__header-group">
|
|
782
|
-
<h2 class="playground__header-title">{getCurrentSession()?.name}</h2>
|
|
783
|
-
<button
|
|
784
|
-
type="button"
|
|
785
|
-
class="playground__header-close"
|
|
786
|
-
onclick={handleCloseSession}
|
|
787
|
-
title="Close session"
|
|
788
|
-
>
|
|
789
|
-
<Icon icon="mdi:close" />
|
|
790
|
-
</button>
|
|
791
|
-
</div>
|
|
792
|
-
{/if}
|
|
793
|
-
|
|
794
|
-
<div class="playground__header-actions">
|
|
795
|
-
{#if mode === 'standalone' && onTogglePanel}
|
|
796
|
-
<button
|
|
797
|
-
type="button"
|
|
798
|
-
class="playground__log-toggle"
|
|
799
|
-
class:playground__log-toggle--active={isPipelinePanelOpen}
|
|
800
|
-
onclick={onTogglePanel}
|
|
801
|
-
title={isPipelinePanelOpen ? 'Hide pipeline' : 'Show pipeline'}
|
|
802
|
-
>
|
|
803
|
-
<Icon icon="mdi:source-branch" />
|
|
804
|
-
Pipeline
|
|
805
|
-
</button>
|
|
806
|
-
{/if}
|
|
807
|
-
{#if getCurrentSession()}
|
|
808
|
-
<button
|
|
809
|
-
type="button"
|
|
810
|
-
class="playground__log-toggle"
|
|
811
|
-
class:playground__refresh--spinning={isRefreshing}
|
|
812
|
-
onclick={() => void refreshFromServer()}
|
|
813
|
-
disabled={isRefreshing}
|
|
814
|
-
title="Refresh status"
|
|
815
|
-
>
|
|
816
|
-
<Icon icon="mdi:refresh" />
|
|
817
|
-
Refresh
|
|
818
|
-
</button>
|
|
819
|
-
{/if}
|
|
820
|
-
<button
|
|
821
|
-
type="button"
|
|
822
|
-
class="playground__log-toggle"
|
|
823
|
-
class:playground__log-toggle--active={showLogs}
|
|
824
|
-
onclick={() => (showLogs = !showLogs)}
|
|
825
|
-
title={showLogs ? 'Hide log messages' : 'Show log messages'}
|
|
826
|
-
>
|
|
827
|
-
<Icon icon="mdi:console" />
|
|
828
|
-
Logs
|
|
829
|
-
</button>
|
|
830
|
-
</div>
|
|
831
|
-
</header>
|
|
832
|
-
{/if}
|
|
833
|
-
|
|
834
|
-
<!-- Error Banner -->
|
|
835
|
-
{#if getError()}
|
|
836
|
-
<div class="playground__error">
|
|
837
|
-
<Icon icon="mdi:alert-circle" />
|
|
838
|
-
<span>{getError()}</span>
|
|
839
|
-
<button
|
|
840
|
-
type="button"
|
|
841
|
-
class="playground__error-dismiss"
|
|
842
|
-
onclick={() => playgroundActions.setError(null)}
|
|
843
|
-
>
|
|
844
|
-
<Icon icon="mdi:close" />
|
|
845
|
-
</button>
|
|
433
|
+
<div
|
|
434
|
+
class="playground__content"
|
|
435
|
+
bind:this={playgroundContentEl}
|
|
436
|
+
>
|
|
437
|
+
{#if getIsLoading() && !getCurrentSession()}
|
|
438
|
+
<div class="playground__loading">
|
|
439
|
+
<Icon icon="mdi:loading" class="playground__loading-icon" />
|
|
440
|
+
<span>Loading...</span>
|
|
441
|
+
</div>
|
|
442
|
+
{:else}
|
|
443
|
+
<ExecutionConsole
|
|
444
|
+
showTimestamps={config.showTimestamps ?? true}
|
|
445
|
+
autoScroll={config.autoScroll ?? true}
|
|
446
|
+
enableMarkdown={config.enableMarkdown ?? true}
|
|
447
|
+
showLogsInline={config.logDisplayMode === 'inline'}
|
|
448
|
+
onInterruptResolved={handleInterruptResolved}
|
|
449
|
+
onCreateSession={getSessions().length === 0 ? handleCreateSession : undefined}
|
|
450
|
+
/>
|
|
451
|
+
|
|
452
|
+
<div
|
|
453
|
+
class="playground__vertical-resizer"
|
|
454
|
+
class:playground__vertical-resizer--active={isVerticalResizing}
|
|
455
|
+
role="separator"
|
|
456
|
+
aria-orientation="horizontal"
|
|
457
|
+
aria-valuenow={Math.round(controlPanelHeight)}
|
|
458
|
+
aria-valuemin={140}
|
|
459
|
+
aria-valuemax={maxControlPanelHeight}
|
|
460
|
+
aria-label="Resize execution console"
|
|
461
|
+
tabindex="0"
|
|
462
|
+
onpointerdown={handleVerticalResizerPointerDown}
|
|
463
|
+
onpointermove={handleVerticalResizerPointerMove}
|
|
464
|
+
onpointerup={handleVerticalResizerPointerUp}
|
|
465
|
+
onpointercancel={handleVerticalResizerPointerUp}
|
|
466
|
+
onkeydown={handleVerticalResizerKeyDown}
|
|
467
|
+
>
|
|
468
|
+
<div class="playground__vertical-resizer-handle"></div>
|
|
846
469
|
</div>
|
|
847
|
-
{/if}
|
|
848
470
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
471
|
+
<ControlPanel
|
|
472
|
+
style="height: {controlPanelHeight}px; flex-shrink: 0;"
|
|
473
|
+
{isPipelinePanelOpen}
|
|
474
|
+
{onTogglePanel}
|
|
475
|
+
{isRefreshing}
|
|
476
|
+
{onSessionNavigate}
|
|
477
|
+
onCreateSession={handleCreateSession}
|
|
478
|
+
onSelectSession={handleSelectSession}
|
|
479
|
+
onDeleteSession={handleDeleteSession}
|
|
480
|
+
onSendMessage={handleSendMessage}
|
|
481
|
+
onStopExecution={handleStopExecution}
|
|
482
|
+
onRefresh={refreshFromServer}
|
|
483
|
+
showChatInput={config.showChatInput ?? true}
|
|
484
|
+
showRunButton={config.showRunButton ?? true}
|
|
485
|
+
predefinedMessage={config.predefinedMessage}
|
|
486
|
+
/>
|
|
487
|
+
{/if}
|
|
488
|
+
</div>
|
|
489
|
+
</main>
|
|
490
|
+
|
|
491
|
+
{#if (mode === 'embedded' || mode === 'modal') && onClose}
|
|
492
|
+
<button
|
|
493
|
+
type="button"
|
|
494
|
+
class="playground__floating-close"
|
|
495
|
+
onclick={onClose}
|
|
496
|
+
title="Close playground"
|
|
497
|
+
aria-label="Close playground"
|
|
498
|
+
>
|
|
499
|
+
<Icon icon={mode === 'modal' ? 'mdi:close' : 'mdi:dock-right'} />
|
|
500
|
+
</button>
|
|
501
|
+
{/if}
|
|
874
502
|
</div>
|
|
875
503
|
|
|
876
504
|
<style>
|
|
877
505
|
.playground {
|
|
506
|
+
position: relative;
|
|
878
507
|
display: flex;
|
|
879
508
|
flex-direction: column;
|
|
880
509
|
height: 100%;
|
|
881
|
-
overflow:
|
|
510
|
+
overflow: clip; /* clip avoids the BFC that overflow:hidden creates, which breaks position:sticky inside */
|
|
882
511
|
background-color: var(--fd-muted);
|
|
883
512
|
font-family:
|
|
884
513
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
@@ -890,720 +519,153 @@
|
|
|
890
519
|
}
|
|
891
520
|
|
|
892
521
|
.playground--standalone {
|
|
893
|
-
height: 100%;
|
|
894
522
|
background: var(--fd-layout-background, var(--fd-muted));
|
|
895
523
|
}
|
|
896
524
|
|
|
897
|
-
/* Dark mode override for standalone */
|
|
898
525
|
:global([data-theme='dark']) .playground--standalone {
|
|
899
526
|
background: linear-gradient(135deg, #141418 0%, #1a1a2e 50%, #16162a 100%);
|
|
900
527
|
}
|
|
901
528
|
|
|
902
529
|
.playground--modal {
|
|
903
|
-
height: 100%;
|
|
904
530
|
width: 100%;
|
|
905
531
|
}
|
|
906
532
|
|
|
907
|
-
|
|
908
|
-
.playground--no-sidebar .playground__main {
|
|
909
|
-
border-left: none;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
/* Container */
|
|
913
|
-
.playground__container {
|
|
914
|
-
display: flex;
|
|
533
|
+
.playground__main {
|
|
915
534
|
flex: 1;
|
|
916
|
-
min-height: 0;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
/* Sidebar */
|
|
920
|
-
.playground__sidebar {
|
|
921
|
-
width: var(--fd-playground-sidebar-width);
|
|
922
|
-
background-color: var(--fd-background);
|
|
923
|
-
border-right: 1px solid var(--fd-border);
|
|
924
|
-
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
|
925
535
|
display: flex;
|
|
926
536
|
flex-direction: column;
|
|
537
|
+
min-width: 0;
|
|
538
|
+
min-height: 0;
|
|
539
|
+
overflow: clip; /* clip avoids the BFC that overflow:hidden creates, which breaks position:sticky inside */
|
|
540
|
+
background-color: var(--fd-background);
|
|
927
541
|
}
|
|
928
542
|
|
|
929
|
-
|
|
930
|
-
.playground__sidebar-header {
|
|
931
|
-
display: flex;
|
|
932
|
-
align-items: center;
|
|
933
|
-
justify-content: space-between;
|
|
934
|
-
height: var(--fd-playground-header-height);
|
|
935
|
-
padding: 0 var(--fd-space-xl);
|
|
936
|
-
border-bottom: 1px solid var(--fd-border);
|
|
937
|
-
box-sizing: border-box;
|
|
938
|
-
flex-shrink: 0;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
.playground__sidebar-title {
|
|
543
|
+
.playground__error {
|
|
942
544
|
display: flex;
|
|
943
545
|
align-items: center;
|
|
944
546
|
gap: var(--fd-space-xs);
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
color: var(--fd-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
.playground__edit-link {
|
|
952
|
-
display: flex;
|
|
953
|
-
align-items: center;
|
|
954
|
-
justify-content: center;
|
|
955
|
-
width: var(--fd-playground-icon-btn-size);
|
|
956
|
-
height: var(--fd-playground-icon-btn-size);
|
|
957
|
-
border-radius: var(--fd-radius-md);
|
|
958
|
-
color: var(--fd-muted-foreground);
|
|
959
|
-
text-decoration: none;
|
|
960
|
-
transition: all var(--fd-transition-fast);
|
|
547
|
+
padding: var(--fd-space-md) var(--fd-space-xl);
|
|
548
|
+
background-color: var(--fd-error-muted);
|
|
549
|
+
border-bottom: 1px solid var(--fd-error);
|
|
550
|
+
color: var(--fd-error);
|
|
551
|
+
font-size: var(--fd-text-sm);
|
|
961
552
|
flex-shrink: 0;
|
|
962
553
|
}
|
|
963
554
|
|
|
964
|
-
.
|
|
965
|
-
|
|
966
|
-
color: var(--fd-foreground);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
.playground__sidebar-close {
|
|
555
|
+
.playground__error-dismiss {
|
|
556
|
+
margin-left: auto;
|
|
970
557
|
display: flex;
|
|
971
558
|
align-items: center;
|
|
972
559
|
justify-content: center;
|
|
973
|
-
width: var(--fd-
|
|
974
|
-
height: var(--fd-
|
|
560
|
+
width: var(--fd-space-3xl);
|
|
561
|
+
height: var(--fd-space-3xl);
|
|
975
562
|
border: none;
|
|
976
|
-
border-radius: var(--fd-radius-
|
|
563
|
+
border-radius: var(--fd-radius-sm);
|
|
977
564
|
background: transparent;
|
|
978
|
-
color: var(--fd-
|
|
565
|
+
color: var(--fd-error);
|
|
979
566
|
cursor: pointer;
|
|
980
|
-
transition:
|
|
567
|
+
transition: background-color var(--fd-transition-fast);
|
|
981
568
|
}
|
|
982
569
|
|
|
983
|
-
.
|
|
984
|
-
background-color: var(--fd-muted);
|
|
985
|
-
color: var(--fd-foreground);
|
|
570
|
+
.playground__error-dismiss:hover {
|
|
571
|
+
background-color: var(--fd-error-muted);
|
|
986
572
|
}
|
|
987
573
|
|
|
988
|
-
|
|
989
|
-
.playground__section {
|
|
574
|
+
.playground__content {
|
|
990
575
|
flex: 1;
|
|
991
|
-
display: flex;
|
|
992
|
-
flex-direction: column;
|
|
993
576
|
min-height: 0;
|
|
994
|
-
padding: 0 var(--fd-space-md);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
/* Section header: label + add icon */
|
|
998
|
-
.playground__section-header {
|
|
999
577
|
display: flex;
|
|
1000
|
-
|
|
1001
|
-
justify-content: space-between;
|
|
1002
|
-
padding: var(--fd-space-md) var(--fd-space-xs) var(--fd-space-xs);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
.playground__section-label {
|
|
1006
|
-
font-size: var(--fd-text-xs);
|
|
1007
|
-
font-weight: 600;
|
|
1008
|
-
color: var(--fd-muted-foreground);
|
|
1009
|
-
text-transform: uppercase;
|
|
1010
|
-
letter-spacing: 0.06em;
|
|
578
|
+
flex-direction: column;
|
|
1011
579
|
}
|
|
1012
580
|
|
|
1013
|
-
.
|
|
581
|
+
.playground__vertical-resizer {
|
|
582
|
+
height: 8px;
|
|
583
|
+
flex-shrink: 0;
|
|
584
|
+
cursor: row-resize;
|
|
585
|
+
background-color: var(--fd-background);
|
|
586
|
+
border-top: 1px solid var(--fd-border);
|
|
587
|
+
border-bottom: 1px solid var(--fd-border);
|
|
1014
588
|
display: flex;
|
|
1015
589
|
align-items: center;
|
|
1016
590
|
justify-content: center;
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
border-radius: var(--fd-radius-md);
|
|
1021
|
-
background: transparent;
|
|
1022
|
-
color: var(--fd-muted-foreground);
|
|
1023
|
-
cursor: pointer;
|
|
1024
|
-
transition: all var(--fd-transition-fast);
|
|
591
|
+
touch-action: none;
|
|
592
|
+
z-index: 1;
|
|
593
|
+
transition: background-color var(--fd-transition-normal);
|
|
1025
594
|
}
|
|
1026
595
|
|
|
1027
|
-
.
|
|
1028
|
-
|
|
1029
|
-
color: var(--fd-
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
.playground__section-add:disabled {
|
|
1033
|
-
opacity: 0.4;
|
|
1034
|
-
cursor: not-allowed;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
/* Session group wraps session row + its inline runs */
|
|
1038
|
-
.playground__session-group {
|
|
1039
|
-
margin-bottom: var(--fd-space-3xs);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
/* Collapsible runs sub-section under active session */
|
|
1043
|
-
.playground__runs-section {
|
|
1044
|
-
margin-bottom: var(--fd-space-3xs);
|
|
596
|
+
.playground__vertical-resizer:hover,
|
|
597
|
+
.playground__vertical-resizer--active {
|
|
598
|
+
background-color: var(--fd-primary-muted);
|
|
1045
599
|
}
|
|
1046
600
|
|
|
1047
|
-
.
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
width: 100%;
|
|
1052
|
-
padding: var(--fd-space-3xs) var(--fd-space-sm);
|
|
1053
|
-
padding-left: calc(var(--fd-space-md) + var(--fd-space-3xs));
|
|
1054
|
-
border: none;
|
|
601
|
+
.playground__vertical-resizer-handle {
|
|
602
|
+
width: 48px;
|
|
603
|
+
height: 4px;
|
|
604
|
+
background-color: var(--fd-border-strong);
|
|
1055
605
|
border-radius: var(--fd-radius-sm);
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
font-weight: 500;
|
|
1060
|
-
cursor: pointer;
|
|
1061
|
-
text-align: left;
|
|
1062
|
-
transition: all var(--fd-transition-fast);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
.playground__runs-toggle:hover {
|
|
1066
|
-
background-color: var(--fd-muted);
|
|
1067
|
-
color: var(--fd-foreground);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
.playground__runs-toggle :global(svg) {
|
|
1071
|
-
width: 0.875rem;
|
|
1072
|
-
height: 0.875rem;
|
|
1073
|
-
flex-shrink: 0;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
.playground__runs-count {
|
|
1077
|
-
margin-left: auto;
|
|
1078
|
-
font-size: var(--fd-text-2xs);
|
|
1079
|
-
font-weight: 600;
|
|
1080
|
-
color: var(--fd-muted-foreground);
|
|
1081
|
-
background: var(--fd-muted);
|
|
1082
|
-
border-radius: 999px;
|
|
1083
|
-
padding: 1px var(--fd-space-xs);
|
|
1084
|
-
min-width: 1.4em;
|
|
1085
|
-
text-align: center;
|
|
1086
|
-
line-height: 1.4;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
/* Inline runs tree under active session */
|
|
1090
|
-
.playground__executions-inline {
|
|
1091
|
-
margin-left: calc(var(--fd-space-md) + var(--fd-space-xs));
|
|
1092
|
-
margin-bottom: var(--fd-space-xs);
|
|
1093
|
-
border-left: 2px solid var(--fd-border);
|
|
1094
|
-
padding-left: var(--fd-space-xs);
|
|
606
|
+
transition:
|
|
607
|
+
background-color var(--fd-transition-normal),
|
|
608
|
+
transform var(--fd-transition-normal);
|
|
1095
609
|
}
|
|
1096
610
|
|
|
1097
|
-
.
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
border-radius: var(--fd-radius-sm);
|
|
1101
|
-
border-left-width: 2px;
|
|
611
|
+
.playground__vertical-resizer:hover .playground__vertical-resizer-handle {
|
|
612
|
+
background-color: var(--fd-primary);
|
|
613
|
+
transform: scaleX(1.1);
|
|
1102
614
|
}
|
|
1103
615
|
|
|
1104
|
-
.
|
|
1105
|
-
|
|
1106
|
-
|
|
616
|
+
.playground__vertical-resizer--active .playground__vertical-resizer-handle {
|
|
617
|
+
background-color: var(--fd-primary-hover);
|
|
618
|
+
transform: scaleX(1.2);
|
|
1107
619
|
}
|
|
1108
620
|
|
|
1109
|
-
|
|
1110
|
-
.playground__sessions-wrap {
|
|
1111
|
-
flex: 1;
|
|
621
|
+
.playground__loading {
|
|
1112
622
|
display: flex;
|
|
1113
623
|
flex-direction: column;
|
|
1114
|
-
|
|
624
|
+
align-items: center;
|
|
625
|
+
justify-content: center;
|
|
626
|
+
flex: 1;
|
|
627
|
+
gap: var(--fd-space-xl);
|
|
628
|
+
color: var(--fd-muted-foreground);
|
|
1115
629
|
}
|
|
1116
630
|
|
|
1117
|
-
.
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
padding: 0 var(--fd-space-3xs) var(--fd-space-xl);
|
|
1121
|
-
min-height: 0;
|
|
631
|
+
:global(.playground__loading-icon) {
|
|
632
|
+
font-size: var(--fd-text-2xl);
|
|
633
|
+
animation: playground-spin 1s linear infinite;
|
|
1122
634
|
}
|
|
1123
635
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
636
|
+
@keyframes playground-spin {
|
|
637
|
+
from {
|
|
638
|
+
transform: rotate(0deg);
|
|
639
|
+
}
|
|
640
|
+
to {
|
|
641
|
+
transform: rotate(360deg);
|
|
642
|
+
}
|
|
1129
643
|
}
|
|
1130
644
|
|
|
1131
|
-
|
|
1132
|
-
|
|
645
|
+
.playground__floating-close {
|
|
646
|
+
position: absolute;
|
|
647
|
+
top: var(--fd-space-md);
|
|
648
|
+
right: var(--fd-space-md);
|
|
649
|
+
z-index: 10;
|
|
1133
650
|
display: flex;
|
|
1134
651
|
align-items: center;
|
|
1135
|
-
justify-content:
|
|
1136
|
-
|
|
652
|
+
justify-content: center;
|
|
653
|
+
width: var(--fd-playground-icon-btn-size, 2rem);
|
|
654
|
+
height: var(--fd-playground-icon-btn-size, 2rem);
|
|
655
|
+
border: 1px solid var(--fd-border);
|
|
1137
656
|
border-radius: var(--fd-radius-md);
|
|
1138
|
-
|
|
657
|
+
background-color: var(--fd-background);
|
|
658
|
+
color: var(--fd-muted-foreground);
|
|
1139
659
|
cursor: pointer;
|
|
1140
|
-
transition:
|
|
1141
|
-
background-color var(--fd-transition-fast),
|
|
1142
|
-
border-left-color var(--fd-transition-fast);
|
|
660
|
+
transition: all var(--fd-transition-fast);
|
|
1143
661
|
}
|
|
1144
662
|
|
|
1145
|
-
.
|
|
663
|
+
.playground__floating-close:hover {
|
|
1146
664
|
background-color: var(--fd-muted);
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
.playground__session--active {
|
|
1151
|
-
background-color: var(--fd-primary-muted);
|
|
1152
|
-
border-left-color: var(--fd-primary);
|
|
665
|
+
color: var(--fd-foreground);
|
|
1153
666
|
}
|
|
1154
667
|
|
|
1155
|
-
.
|
|
1156
|
-
|
|
1157
|
-
border-left-color: var(--fd-primary);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
.playground__session-name {
|
|
1161
|
-
flex: 1;
|
|
1162
|
-
font-size: var(--fd-text-sm);
|
|
1163
|
-
color: var(--fd-foreground);
|
|
1164
|
-
white-space: nowrap;
|
|
1165
|
-
overflow: hidden;
|
|
1166
|
-
text-overflow: ellipsis;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
.playground__session--active .playground__session-name {
|
|
1170
|
-
color: var(--fd-primary);
|
|
1171
|
-
font-weight: 500;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
.playground__session-menu {
|
|
1175
|
-
display: flex;
|
|
1176
|
-
align-items: center;
|
|
1177
|
-
justify-content: center;
|
|
1178
|
-
width: var(--fd-space-3xl);
|
|
1179
|
-
height: var(--fd-space-3xl);
|
|
1180
|
-
border: none;
|
|
1181
|
-
border-radius: var(--fd-radius-sm);
|
|
1182
|
-
background: transparent;
|
|
1183
|
-
color: var(--fd-muted-foreground);
|
|
1184
|
-
cursor: pointer;
|
|
1185
|
-
opacity: 0;
|
|
1186
|
-
transition: all var(--fd-transition-fast);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
.playground__session:hover .playground__session-menu {
|
|
1190
|
-
opacity: 1;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
.playground__session-menu:hover {
|
|
1194
|
-
background-color: var(--fd-muted);
|
|
1195
|
-
color: var(--fd-foreground);
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
.playground__session-menu--open {
|
|
1199
|
-
opacity: 1;
|
|
1200
|
-
background-color: var(--fd-muted);
|
|
1201
|
-
color: var(--fd-foreground);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
.playground__session-actions {
|
|
1205
|
-
position: relative;
|
|
1206
|
-
display: flex;
|
|
1207
|
-
align-items: center;
|
|
1208
|
-
flex-shrink: 0;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
.playground__session-dropdown {
|
|
1212
|
-
position: absolute;
|
|
1213
|
-
top: 100%;
|
|
1214
|
-
right: 0;
|
|
1215
|
-
z-index: 50;
|
|
1216
|
-
min-width: 140px;
|
|
1217
|
-
padding: var(--fd-space-xs);
|
|
1218
|
-
background-color: var(--fd-background);
|
|
1219
|
-
border: 1px solid var(--fd-border);
|
|
1220
|
-
border-radius: var(--fd-radius-md);
|
|
1221
|
-
box-shadow: var(--fd-shadow-lg);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
.playground__session-dropdown-item {
|
|
1225
|
-
display: flex;
|
|
1226
|
-
align-items: center;
|
|
1227
|
-
gap: var(--fd-space-sm);
|
|
1228
|
-
width: 100%;
|
|
1229
|
-
padding: var(--fd-space-sm) var(--fd-space-md);
|
|
1230
|
-
border: none;
|
|
1231
|
-
border-radius: var(--fd-radius-sm);
|
|
1232
|
-
background: transparent;
|
|
1233
|
-
color: var(--fd-foreground);
|
|
1234
|
-
font-size: var(--fd-text-sm);
|
|
1235
|
-
cursor: pointer;
|
|
1236
|
-
transition: all var(--fd-transition-fast);
|
|
1237
|
-
white-space: nowrap;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
.playground__session-dropdown-item:hover {
|
|
1241
|
-
background-color: var(--fd-muted);
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
.playground__session-dropdown-item--danger {
|
|
1245
|
-
color: var(--fd-error);
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
.playground__session-dropdown-item--danger:hover {
|
|
1249
|
-
background-color: var(--fd-error-muted);
|
|
1250
|
-
color: var(--fd-error);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/* Main Content */
|
|
1254
|
-
.playground__main {
|
|
1255
|
-
flex: 1;
|
|
1256
|
-
display: flex;
|
|
1257
|
-
flex-direction: column;
|
|
1258
|
-
min-width: 0;
|
|
1259
|
-
min-height: 0; /* Allow proper flex shrinking */
|
|
1260
|
-
overflow: hidden; /* Prevent scrolling - ChatPanel handles it */
|
|
1261
|
-
background-color: var(--fd-background);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
/* Header - exact same height as playground__sidebar-header for alignment */
|
|
1265
|
-
.playground__header {
|
|
1266
|
-
display: flex;
|
|
1267
|
-
align-items: center;
|
|
1268
|
-
gap: var(--fd-space-xs);
|
|
1269
|
-
height: var(--fd-playground-header-height);
|
|
1270
|
-
padding: 0 var(--fd-space-xl);
|
|
1271
|
-
border-bottom: 1px solid var(--fd-border);
|
|
1272
|
-
background-color: var(--fd-background);
|
|
1273
|
-
box-sizing: border-box;
|
|
1274
|
-
flex-shrink: 0;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
:global(.playground__header-icon) {
|
|
1278
|
-
font-size: var(--fd-text-base);
|
|
1279
|
-
color: var(--fd-muted-foreground);
|
|
1280
|
-
flex-shrink: 0;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
.playground__header-label {
|
|
1284
|
-
font-size: var(--fd-text-sm);
|
|
1285
|
-
font-weight: 600;
|
|
1286
|
-
color: var(--fd-foreground);
|
|
1287
|
-
flex-shrink: 0;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
.playground__header-group {
|
|
1291
|
-
display: flex;
|
|
1292
|
-
align-items: center;
|
|
1293
|
-
gap: var(--fd-space-xs);
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
.playground__header-title {
|
|
1297
|
-
font-size: var(--fd-text-md);
|
|
1298
|
-
font-weight: 600;
|
|
1299
|
-
line-height: 1.25;
|
|
1300
|
-
color: var(--fd-foreground);
|
|
1301
|
-
margin: 0;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
.playground__header-close {
|
|
1305
|
-
display: flex;
|
|
1306
|
-
align-items: center;
|
|
1307
|
-
justify-content: center;
|
|
1308
|
-
width: var(--fd-playground-icon-btn-size);
|
|
1309
|
-
height: var(--fd-playground-icon-btn-size);
|
|
1310
|
-
border: none;
|
|
1311
|
-
border-radius: var(--fd-radius-md);
|
|
1312
|
-
background: transparent;
|
|
1313
|
-
color: var(--fd-muted-foreground);
|
|
1314
|
-
cursor: pointer;
|
|
1315
|
-
transition: all var(--fd-transition-fast);
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
.playground__header-close:hover {
|
|
1319
|
-
background-color: var(--fd-muted);
|
|
1320
|
-
color: var(--fd-foreground);
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
.playground__header-actions {
|
|
1324
|
-
display: flex;
|
|
1325
|
-
align-items: center;
|
|
1326
|
-
gap: var(--fd-space-xs);
|
|
1327
|
-
margin-left: auto;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
.playground__log-toggle {
|
|
1331
|
-
display: inline-flex;
|
|
1332
|
-
align-items: center;
|
|
1333
|
-
gap: var(--fd-space-3xs);
|
|
1334
|
-
padding: var(--fd-space-3xs) var(--fd-space-sm);
|
|
1335
|
-
border: 1px solid var(--fd-border);
|
|
1336
|
-
border-radius: var(--fd-radius-md);
|
|
1337
|
-
background: transparent;
|
|
1338
|
-
color: var(--fd-muted-foreground);
|
|
1339
|
-
font-size: var(--fd-text-xs);
|
|
1340
|
-
font-weight: 500;
|
|
1341
|
-
cursor: pointer;
|
|
1342
|
-
transition: all var(--fd-transition-fast);
|
|
1343
|
-
line-height: 1;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
.playground__log-toggle :global(svg) {
|
|
1347
|
-
font-size: var(--fd-text-xs);
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
.playground__log-toggle:hover {
|
|
1351
|
-
background-color: var(--fd-muted);
|
|
1352
|
-
color: var(--fd-foreground);
|
|
1353
|
-
border-color: var(--fd-border-strong);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
.playground__log-toggle--active {
|
|
1357
|
-
background-color: var(--fd-primary-muted);
|
|
1358
|
-
border-color: var(--fd-primary);
|
|
1359
|
-
color: var(--fd-primary);
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
.playground__refresh--spinning :global(svg) {
|
|
1363
|
-
animation: spin 0.8s linear infinite;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
/* Session chip (standalone mode) */
|
|
1367
|
-
.playground__session-chip-wrap {
|
|
1368
|
-
position: relative;
|
|
1369
|
-
flex-shrink: 0;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
.playground__session-chip {
|
|
1373
|
-
display: inline-flex;
|
|
1374
|
-
align-items: center;
|
|
1375
|
-
gap: var(--fd-space-xs);
|
|
1376
|
-
padding: var(--fd-space-3xs) var(--fd-space-sm) var(--fd-space-3xs) var(--fd-space-xs);
|
|
1377
|
-
border: 1px solid var(--fd-border);
|
|
1378
|
-
border-radius: var(--fd-radius-md);
|
|
1379
|
-
background: var(--fd-background);
|
|
1380
|
-
color: var(--fd-foreground);
|
|
1381
|
-
font-size: var(--fd-text-sm);
|
|
1382
|
-
font-weight: 500;
|
|
1383
|
-
cursor: pointer;
|
|
1384
|
-
transition: all var(--fd-transition-fast);
|
|
1385
|
-
max-width: 220px;
|
|
1386
|
-
line-height: 1;
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
.playground__session-chip :global(svg) {
|
|
1390
|
-
flex-shrink: 0;
|
|
1391
|
-
font-size: var(--fd-text-sm);
|
|
1392
|
-
color: var(--fd-muted-foreground);
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
.playground__session-chip:hover {
|
|
1396
|
-
background-color: var(--fd-muted);
|
|
1397
|
-
border-color: var(--fd-border-strong);
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
.playground__session-chip--open {
|
|
1401
|
-
background-color: var(--fd-muted);
|
|
1402
|
-
border-color: var(--fd-border-strong);
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
.playground__session-chip-name {
|
|
1406
|
-
flex: 1;
|
|
1407
|
-
white-space: nowrap;
|
|
1408
|
-
overflow: hidden;
|
|
1409
|
-
text-overflow: ellipsis;
|
|
1410
|
-
min-width: 0;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
:global(.playground__session-chip-chevron) {
|
|
1414
|
-
color: var(--fd-muted-foreground);
|
|
1415
|
-
flex-shrink: 0;
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
/* Session switcher popover */
|
|
1419
|
-
.playground__session-popover {
|
|
1420
|
-
position: absolute;
|
|
1421
|
-
top: calc(100% + var(--fd-space-xs));
|
|
1422
|
-
left: 0;
|
|
1423
|
-
z-index: 50;
|
|
1424
|
-
min-width: 220px;
|
|
1425
|
-
max-width: 300px;
|
|
1426
|
-
padding: var(--fd-space-xs);
|
|
1427
|
-
background-color: var(--fd-background);
|
|
1428
|
-
border: 1px solid var(--fd-border);
|
|
1429
|
-
border-radius: var(--fd-radius-lg);
|
|
1430
|
-
box-shadow: var(--fd-shadow-lg);
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
.playground__session-popover-divider {
|
|
1434
|
-
height: 1px;
|
|
1435
|
-
background-color: var(--fd-border-muted);
|
|
1436
|
-
margin: var(--fd-space-xs) 0;
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
.playground__session-popover-row {
|
|
1440
|
-
display: flex;
|
|
1441
|
-
align-items: center;
|
|
1442
|
-
gap: 2px;
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
.playground__session-popover-item {
|
|
1446
|
-
display: flex;
|
|
1447
|
-
align-items: center;
|
|
1448
|
-
gap: var(--fd-space-sm);
|
|
1449
|
-
flex: 1;
|
|
1450
|
-
min-width: 0;
|
|
1451
|
-
padding: var(--fd-space-sm) var(--fd-space-sm);
|
|
1452
|
-
border: none;
|
|
1453
|
-
border-radius: var(--fd-radius-sm);
|
|
1454
|
-
background: transparent;
|
|
1455
|
-
color: var(--fd-foreground);
|
|
1456
|
-
font-size: var(--fd-text-sm);
|
|
1457
|
-
text-align: left;
|
|
1458
|
-
cursor: pointer;
|
|
1459
|
-
transition: background-color var(--fd-transition-fast);
|
|
1460
|
-
white-space: nowrap;
|
|
1461
|
-
overflow: hidden;
|
|
1462
|
-
text-overflow: ellipsis;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
.playground__session-popover-item :global(svg) {
|
|
1466
|
-
flex-shrink: 0;
|
|
1467
|
-
color: var(--fd-muted-foreground);
|
|
1468
|
-
font-size: var(--fd-text-sm);
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
.playground__session-popover-item span {
|
|
1472
|
-
overflow: hidden;
|
|
1473
|
-
text-overflow: ellipsis;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
.playground__session-popover-item:hover {
|
|
1477
|
-
background-color: var(--fd-muted);
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
.playground__session-popover-item:disabled {
|
|
1481
|
-
opacity: 0.4;
|
|
1482
|
-
cursor: not-allowed;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
.playground__session-popover-item--new {
|
|
1486
|
-
color: var(--fd-primary);
|
|
1487
|
-
font-weight: 500;
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
.playground__session-popover-item--new :global(svg) {
|
|
1491
|
-
color: var(--fd-primary);
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
.playground__session-popover-item--active {
|
|
1495
|
-
font-weight: 500;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
:global(.playground__session-popover-check) {
|
|
1499
|
-
color: var(--fd-primary) !important;
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
.playground__session-popover-delete {
|
|
1503
|
-
display: flex;
|
|
1504
|
-
align-items: center;
|
|
1505
|
-
justify-content: center;
|
|
1506
|
-
flex-shrink: 0;
|
|
1507
|
-
width: var(--fd-size-icon-btn);
|
|
1508
|
-
height: var(--fd-size-icon-btn);
|
|
1509
|
-
border: none;
|
|
1510
|
-
border-radius: var(--fd-radius-sm);
|
|
1511
|
-
background: transparent;
|
|
1512
|
-
color: var(--fd-muted-foreground);
|
|
1513
|
-
cursor: pointer;
|
|
1514
|
-
opacity: 0;
|
|
1515
|
-
transition: all var(--fd-transition-fast);
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
.playground__session-popover-row:hover .playground__session-popover-delete {
|
|
1519
|
-
opacity: 1;
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
.playground__session-popover-delete:hover {
|
|
1523
|
-
background-color: var(--fd-error-muted);
|
|
1524
|
-
color: var(--fd-error);
|
|
1525
|
-
opacity: 1;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
/* Error */
|
|
1529
|
-
.playground__error {
|
|
1530
|
-
display: flex;
|
|
1531
|
-
align-items: center;
|
|
1532
|
-
gap: var(--fd-space-xs);
|
|
1533
|
-
padding: var(--fd-space-md) var(--fd-space-xl);
|
|
1534
|
-
background-color: var(--fd-error-muted);
|
|
1535
|
-
border-bottom: 1px solid var(--fd-error);
|
|
1536
|
-
color: var(--fd-error);
|
|
1537
|
-
font-size: var(--fd-text-sm);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
.playground__error-dismiss {
|
|
1541
|
-
margin-left: auto;
|
|
1542
|
-
display: flex;
|
|
1543
|
-
align-items: center;
|
|
1544
|
-
justify-content: center;
|
|
1545
|
-
width: var(--fd-space-3xl);
|
|
1546
|
-
height: var(--fd-space-3xl);
|
|
1547
|
-
border: none;
|
|
1548
|
-
border-radius: var(--fd-radius-sm);
|
|
1549
|
-
background: transparent;
|
|
1550
|
-
color: var(--fd-error);
|
|
1551
|
-
cursor: pointer;
|
|
1552
|
-
transition: background-color var(--fd-transition-fast);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
.playground__error-dismiss:hover {
|
|
1556
|
-
background-color: var(--fd-error-muted);
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
/* Content */
|
|
1560
|
-
.playground__content {
|
|
1561
|
-
flex: 1;
|
|
1562
|
-
min-height: 0;
|
|
1563
|
-
display: flex;
|
|
1564
|
-
flex-direction: column;
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
/* Loading */
|
|
1568
|
-
.playground__loading {
|
|
1569
|
-
display: flex;
|
|
1570
|
-
flex-direction: column;
|
|
1571
|
-
align-items: center;
|
|
1572
|
-
justify-content: center;
|
|
1573
|
-
flex: 1;
|
|
1574
|
-
gap: var(--fd-space-xl);
|
|
1575
|
-
color: var(--fd-muted-foreground);
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
:global(.playground__loading-icon) {
|
|
1579
|
-
font-size: var(--fd-text-2xl);
|
|
1580
|
-
animation: spin 1s linear infinite;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
@keyframes spin {
|
|
1584
|
-
from {
|
|
1585
|
-
transform: rotate(0deg);
|
|
1586
|
-
}
|
|
1587
|
-
to {
|
|
1588
|
-
transform: rotate(360deg);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
/* Responsive */
|
|
1593
|
-
@media (max-width: 768px) {
|
|
1594
|
-
.playground__sidebar {
|
|
1595
|
-
width: 180px;
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
@media (max-width: 640px) {
|
|
1600
|
-
.playground__sidebar {
|
|
1601
|
-
position: absolute;
|
|
1602
|
-
left: 0;
|
|
1603
|
-
top: 0;
|
|
1604
|
-
bottom: 0;
|
|
1605
|
-
z-index: 20;
|
|
1606
|
-
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.1);
|
|
1607
|
-
}
|
|
668
|
+
.playground--modal .playground__floating-close {
|
|
669
|
+
display: none;
|
|
1608
670
|
}
|
|
1609
671
|
</style>
|