@flowdrop/flowdrop 1.8.1 → 1.10.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.js +5 -1
- package/dist/components/PipelineStatus.svelte +31 -8
- package/dist/components/PipelineStatus.svelte.d.ts +5 -0
- package/dist/components/WorkflowEditor.svelte +26 -0
- package/dist/components/chat/AIChatPanel.svelte +16 -5
- package/dist/components/playground/ChatPanel.svelte +31 -108
- package/dist/components/playground/ChatPanel.svelte.d.ts +3 -1
- package/dist/components/playground/ExecutionList.svelte +138 -0
- package/dist/components/playground/ExecutionList.svelte.d.ts +10 -0
- package/dist/components/playground/MessageBubble.svelte +281 -156
- package/dist/components/playground/PipelinePanel.svelte +382 -0
- package/dist/components/playground/PipelinePanel.svelte.d.ts +20 -0
- package/dist/components/playground/Playground.svelte +707 -174
- package/dist/components/playground/Playground.svelte.d.ts +6 -0
- package/dist/components/playground/PlaygroundStudio.svelte +404 -0
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +30 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/playground/index.d.ts +7 -3
- package/dist/playground/index.js +14 -5
- package/dist/playground/mount.d.ts +7 -0
- package/dist/playground/mount.js +78 -81
- package/dist/services/globalSave.d.ts +7 -0
- package/dist/services/globalSave.js +5 -1
- package/dist/services/nodeExecutionService.js +4 -2
- package/dist/services/playgroundService.d.ts +11 -4
- package/dist/services/playgroundService.js +22 -12
- package/dist/stores/pipelinePanelStore.svelte.d.ts +6 -0
- package/dist/stores/pipelinePanelStore.svelte.js +24 -0
- package/dist/stores/playgroundStore.svelte.d.ts +26 -21
- package/dist/stores/playgroundStore.svelte.js +134 -55
- package/dist/svelte-app.js +25 -2
- package/dist/types/playground.d.ts +15 -5
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { onMount, onDestroy } from 'svelte';
|
|
12
12
|
import Icon from '@iconify/svelte';
|
|
13
13
|
import ChatPanel from './ChatPanel.svelte';
|
|
14
|
+
import ExecutionList from './ExecutionList.svelte';
|
|
14
15
|
import type { Workflow } from '../../types/index.js';
|
|
15
16
|
import type { EndpointConfig } from '../../config/endpoints.js';
|
|
16
17
|
import type { PlaygroundMode, PlaygroundConfig } from '../../types/playground.js';
|
|
@@ -25,7 +26,12 @@
|
|
|
25
26
|
getError,
|
|
26
27
|
playgroundActions,
|
|
27
28
|
getInputFields,
|
|
28
|
-
|
|
29
|
+
applyServerResponse,
|
|
30
|
+
getCanSendMessage,
|
|
31
|
+
getLatestSequenceNumber,
|
|
32
|
+
getActiveExecutionId,
|
|
33
|
+
getLatestExecutionId,
|
|
34
|
+
getPinnedExecutionId,
|
|
29
35
|
} from '../../stores/playgroundStore.svelte.js';
|
|
30
36
|
import { interruptActions } from '../../stores/interruptStore.svelte.js';
|
|
31
37
|
import { logger } from '../../utils/logger.js';
|
|
@@ -49,6 +55,12 @@
|
|
|
49
55
|
config?: PlaygroundConfig;
|
|
50
56
|
/** Callback when playground is closed (for embedded mode) */
|
|
51
57
|
onClose?: () => void;
|
|
58
|
+
/** Callback to toggle the pipeline panel (if undefined, toggle button is hidden) */
|
|
59
|
+
onTogglePanel?: () => void;
|
|
60
|
+
/** Whether the pipeline panel is currently open (for toggle button active state) */
|
|
61
|
+
isPipelinePanelOpen?: boolean;
|
|
62
|
+
/** When provided, session switches and creation navigate to a URL instead of mutating store state */
|
|
63
|
+
onSessionNavigate?: (sessionId: string) => void;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
let {
|
|
@@ -58,7 +70,10 @@
|
|
|
58
70
|
initialSessionId,
|
|
59
71
|
endpointConfig,
|
|
60
72
|
config = {},
|
|
61
|
-
onClose
|
|
73
|
+
onClose,
|
|
74
|
+
onTogglePanel,
|
|
75
|
+
isPipelinePanelOpen = false,
|
|
76
|
+
onSessionNavigate,
|
|
62
77
|
}: Props = $props();
|
|
63
78
|
|
|
64
79
|
/** Current input values from InputCollector */
|
|
@@ -70,6 +85,9 @@
|
|
|
70
85
|
/** Track which session's dropdown menu is open */
|
|
71
86
|
let openMenuId = $state<string | null>(null);
|
|
72
87
|
|
|
88
|
+
/** Whether the runs sub-section is expanded under the active session */
|
|
89
|
+
let runsExpanded = $state(false);
|
|
90
|
+
|
|
73
91
|
/** Track if initial session has been loaded to prevent duplicate loads */
|
|
74
92
|
let initialSessionLoaded = $state(false);
|
|
75
93
|
|
|
@@ -79,45 +97,56 @@
|
|
|
79
97
|
/** Track if auto-run has already been triggered to prevent duplicate executions */
|
|
80
98
|
let autoRunTriggered = $state(false);
|
|
81
99
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
*/
|
|
85
|
-
onMount(() => {
|
|
86
|
-
// Set endpoint config if provided
|
|
87
|
-
if (endpointConfig) {
|
|
88
|
-
setEndpointConfig(endpointConfig);
|
|
89
|
-
}
|
|
100
|
+
/** Whether log messages are visible in the chat panel */
|
|
101
|
+
let showLogs = $state(true);
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
playgroundActions.setWorkflow(workflow);
|
|
94
|
-
}
|
|
103
|
+
/** Whether a manual refresh is in flight */
|
|
104
|
+
let isRefreshing = $state(false);
|
|
95
105
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
// Load sessions
|
|
100
|
-
await loadSessions();
|
|
106
|
+
/** Whether the session switcher popover is open (standalone mode) */
|
|
107
|
+
let sessionDropdownOpen = $state(false);
|
|
101
108
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
// Close session popover on outside click
|
|
110
|
+
$effect(() => {
|
|
111
|
+
if (!sessionDropdownOpen) return;
|
|
112
|
+
function handleOutside(e: MouseEvent) {
|
|
113
|
+
if (!(e.target as HTMLElement).closest('.playground__session-chip-wrap')) {
|
|
114
|
+
sessionDropdownOpen = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
document.addEventListener('click', handleOutside);
|
|
118
|
+
return () => document.removeEventListener('click', handleOutside);
|
|
119
|
+
});
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Initialize the playground on mount
|
|
123
|
+
*/
|
|
124
|
+
onMount(() => {
|
|
125
|
+
if (endpointConfig) setEndpointConfig(endpointConfig);
|
|
126
|
+
if (workflow) playgroundActions.setWorkflow(workflow);
|
|
127
|
+
|
|
128
|
+
const handleVisibility = () => {
|
|
129
|
+
if (document.visibilityState === 'visible' && playgroundService.isPolling()) {
|
|
130
|
+
const sessionId = getCurrentSession()?.id;
|
|
131
|
+
if (sessionId) {
|
|
132
|
+
void playgroundService
|
|
133
|
+
.getMessages(sessionId, playgroundService.getLastSequenceNumber() ?? undefined)
|
|
134
|
+
.then((response) => applyServerResponse(response))
|
|
135
|
+
.catch((err) => logger.error('[Playground] Visibility catchup failed:', err));
|
|
113
136
|
}
|
|
114
|
-
} catch (err) {
|
|
115
|
-
logger.error('[Playground] Initialization error:', err);
|
|
116
137
|
}
|
|
117
138
|
};
|
|
139
|
+
document.addEventListener('visibilitychange', handleVisibility);
|
|
140
|
+
|
|
141
|
+
const handleRefreshStatus = () => void refreshFromServer();
|
|
142
|
+
document.addEventListener('flowdrop:refresh-status', handleRefreshStatus);
|
|
118
143
|
|
|
119
|
-
// Execute initialization
|
|
120
144
|
void initializePlayground();
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
document.removeEventListener('visibilitychange', handleVisibility);
|
|
148
|
+
document.removeEventListener('flowdrop:refresh-status', handleRefreshStatus);
|
|
149
|
+
};
|
|
121
150
|
});
|
|
122
151
|
|
|
123
152
|
/**
|
|
@@ -130,8 +159,10 @@
|
|
|
130
159
|
return;
|
|
131
160
|
}
|
|
132
161
|
|
|
133
|
-
// Skip if this session was already loaded
|
|
134
|
-
|
|
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) {
|
|
135
166
|
return;
|
|
136
167
|
}
|
|
137
168
|
|
|
@@ -142,18 +173,42 @@
|
|
|
142
173
|
}
|
|
143
174
|
|
|
144
175
|
// Load the initial session if sessions are available
|
|
145
|
-
if (sessionList.length > 0
|
|
176
|
+
if (sessionList.length > 0) {
|
|
146
177
|
void loadInitialSession(initialSessionId);
|
|
147
178
|
}
|
|
148
179
|
});
|
|
149
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Initialize the playground: load sessions, load initial session, handle auto-run
|
|
183
|
+
*/
|
|
184
|
+
async function initializePlayground(): Promise<void> {
|
|
185
|
+
try {
|
|
186
|
+
await loadSessions();
|
|
187
|
+
|
|
188
|
+
if (initialSessionId) {
|
|
189
|
+
await loadInitialSession(initialSessionId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (config.autoRun && !autoRunTriggered) {
|
|
193
|
+
autoRunTriggered = true;
|
|
194
|
+
const predefinedMessage = config.predefinedMessage ?? 'Run workflow';
|
|
195
|
+
logger.debug('[Playground] Auto-run triggered with message:', predefinedMessage);
|
|
196
|
+
await handleSendMessage(predefinedMessage);
|
|
197
|
+
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
logger.error('[Playground] Initialization error:', err);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
150
203
|
/**
|
|
151
204
|
* Load the initial session with validation and error handling
|
|
152
205
|
*
|
|
153
206
|
* @param sessionId - The session ID to load
|
|
154
207
|
*/
|
|
155
208
|
async function loadInitialSession(sessionId: string): Promise<void> {
|
|
156
|
-
// Validate session exists in loaded sessions
|
|
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).
|
|
157
212
|
const sessionList = getSessions();
|
|
158
213
|
const sessionExists = sessionList.some((s) => s.id === sessionId);
|
|
159
214
|
|
|
@@ -162,21 +217,19 @@
|
|
|
162
217
|
`[Playground] Initial session "${sessionId}" not found in available sessions. ` +
|
|
163
218
|
`Available sessions: ${sessionList.map((s) => s.id).join(', ') || 'none'}`
|
|
164
219
|
);
|
|
165
|
-
// Don't set error - just log warning and let user pick a session
|
|
166
220
|
initialSessionLoaded = true;
|
|
167
|
-
loadedInitialSessionId = sessionId;
|
|
168
221
|
return;
|
|
169
222
|
}
|
|
170
223
|
|
|
224
|
+
// Set guard BEFORE the first await to prevent concurrent loads.
|
|
225
|
+
loadedInitialSessionId = sessionId;
|
|
226
|
+
|
|
171
227
|
try {
|
|
172
228
|
await loadSession(sessionId);
|
|
173
229
|
initialSessionLoaded = true;
|
|
174
|
-
loadedInitialSessionId = sessionId;
|
|
175
230
|
} catch (err) {
|
|
176
231
|
logger.error('[Playground] Failed to load initial session:', err);
|
|
177
|
-
// Mark as attempted to prevent retry loops
|
|
178
232
|
initialSessionLoaded = true;
|
|
179
|
-
loadedInitialSessionId = sessionId;
|
|
180
233
|
}
|
|
181
234
|
}
|
|
182
235
|
|
|
@@ -231,17 +284,14 @@
|
|
|
231
284
|
playgroundActions.setError(null);
|
|
232
285
|
|
|
233
286
|
try {
|
|
234
|
-
// Get session details
|
|
235
287
|
const session = await playgroundService.getSession(sessionId);
|
|
236
288
|
playgroundActions.setCurrentSession(session);
|
|
237
289
|
|
|
238
|
-
// Get messages
|
|
239
290
|
const response = await playgroundService.getMessages(sessionId);
|
|
240
|
-
|
|
291
|
+
applyServerResponse(response);
|
|
241
292
|
|
|
242
|
-
// Start polling if session is running
|
|
243
293
|
if (session.status === 'running') {
|
|
244
|
-
startPolling(sessionId);
|
|
294
|
+
startPolling(sessionId, true);
|
|
245
295
|
}
|
|
246
296
|
} catch (err) {
|
|
247
297
|
const errorMessage = err instanceof Error ? err.message : 'Failed to load session';
|
|
@@ -262,6 +312,13 @@
|
|
|
262
312
|
try {
|
|
263
313
|
const sessionName = `Session ${getSessions().length + 1}`;
|
|
264
314
|
const session = await playgroundService.createSession(workflowId, sessionName);
|
|
315
|
+
|
|
316
|
+
if (onSessionNavigate) {
|
|
317
|
+
// URL-based routing: navigate to the new session; page remount handles store init
|
|
318
|
+
onSessionNavigate(session.id);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
265
322
|
playgroundActions.addSession(session);
|
|
266
323
|
playgroundActions.setCurrentSession(session);
|
|
267
324
|
playgroundActions.clearMessages();
|
|
@@ -278,14 +335,15 @@
|
|
|
278
335
|
* Select a session
|
|
279
336
|
*/
|
|
280
337
|
async function handleSelectSession(sessionId: string): Promise<void> {
|
|
338
|
+
playgroundActions.pinExecution(null);
|
|
339
|
+
runsExpanded = false;
|
|
281
340
|
const currentSessionId = getCurrentSession()?.id;
|
|
282
341
|
if (currentSessionId === sessionId) {
|
|
283
342
|
return;
|
|
284
343
|
}
|
|
285
344
|
|
|
286
|
-
// Stop polling for current session
|
|
287
345
|
playgroundService.stopPolling();
|
|
288
|
-
|
|
346
|
+
playgroundActions.updateSessionStatus('idle');
|
|
289
347
|
await loadSession(sessionId);
|
|
290
348
|
}
|
|
291
349
|
|
|
@@ -344,9 +402,10 @@
|
|
|
344
402
|
* Send a message
|
|
345
403
|
*/
|
|
346
404
|
async function handleSendMessage(content: string): Promise<void> {
|
|
405
|
+
if (getIsExecuting()) return;
|
|
406
|
+
|
|
347
407
|
const session = getCurrentSession();
|
|
348
408
|
if (!session) {
|
|
349
|
-
// Create a session first if none exists
|
|
350
409
|
await handleCreateSession();
|
|
351
410
|
const newSession = getCurrentSession();
|
|
352
411
|
if (!newSession) {
|
|
@@ -354,23 +413,19 @@
|
|
|
354
413
|
}
|
|
355
414
|
}
|
|
356
415
|
|
|
357
|
-
const sessionId = getCurrentSession()
|
|
358
|
-
if (!sessionId) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
416
|
+
const sessionId = getCurrentSession()!.id;
|
|
361
417
|
|
|
362
|
-
playgroundActions.
|
|
418
|
+
playgroundActions.updateSessionStatus('running');
|
|
419
|
+
playgroundActions.pinExecution(null);
|
|
363
420
|
playgroundActions.setError(null);
|
|
364
421
|
|
|
365
422
|
try {
|
|
366
|
-
// Prepare inputs from the input collector
|
|
367
423
|
const inputs: Record<string, unknown> = {};
|
|
368
424
|
const fields = getInputFields();
|
|
369
425
|
|
|
370
426
|
fields.forEach((field) => {
|
|
371
427
|
const key = `${field.nodeId}:${field.fieldId}`;
|
|
372
428
|
if (inputValues[key] !== undefined) {
|
|
373
|
-
// Map to node ID and field ID for the backend
|
|
374
429
|
if (!inputs[field.nodeId]) {
|
|
375
430
|
inputs[field.nodeId] = {};
|
|
376
431
|
}
|
|
@@ -378,19 +433,13 @@
|
|
|
378
433
|
}
|
|
379
434
|
});
|
|
380
435
|
|
|
381
|
-
// Send message
|
|
382
436
|
const message = await playgroundService.sendMessage(sessionId, content, inputs);
|
|
383
437
|
playgroundActions.addMessage(message);
|
|
384
|
-
|
|
385
|
-
// Update session status
|
|
386
|
-
playgroundActions.updateSessionStatus('running');
|
|
387
|
-
|
|
388
|
-
// Start polling for responses
|
|
389
438
|
startPolling(sessionId);
|
|
390
439
|
} catch (err) {
|
|
391
440
|
const errorMessage = err instanceof Error ? err.message : 'Failed to send message';
|
|
392
441
|
playgroundActions.setError(errorMessage);
|
|
393
|
-
playgroundActions.
|
|
442
|
+
playgroundActions.updateSessionStatus('idle');
|
|
394
443
|
logger.error('Failed to send message:', err);
|
|
395
444
|
}
|
|
396
445
|
}
|
|
@@ -407,33 +456,56 @@
|
|
|
407
456
|
try {
|
|
408
457
|
await playgroundService.stopExecution(sessionId);
|
|
409
458
|
playgroundService.stopPolling();
|
|
410
|
-
playgroundActions.setExecuting(false);
|
|
411
459
|
playgroundActions.updateSessionStatus('idle');
|
|
412
460
|
} catch (err) {
|
|
413
461
|
const errorMessage = err instanceof Error ? err.message : 'Failed to stop execution';
|
|
414
462
|
playgroundActions.setError(errorMessage);
|
|
463
|
+
playgroundService.stopPolling();
|
|
464
|
+
playgroundActions.updateSessionStatus('idle');
|
|
415
465
|
logger.error('Failed to stop execution:', err);
|
|
416
466
|
}
|
|
417
467
|
}
|
|
418
468
|
|
|
419
|
-
/** Shared polling callback created from config lifecycle hooks */
|
|
420
|
-
// svelte-ignore state_referenced_locally — config is static
|
|
421
|
-
const pollingCallback = createPollingCallback(config.isTerminalStatus);
|
|
422
|
-
|
|
423
469
|
/**
|
|
424
470
|
* Start polling for messages
|
|
425
471
|
*/
|
|
426
|
-
function startPolling(sessionId: string): void {
|
|
472
|
+
function startPolling(sessionId: string, seedSequence = false): void {
|
|
427
473
|
const pollingInterval = config.pollingInterval ?? 1500;
|
|
474
|
+
const initialSequenceNumber = seedSequence ? getLatestSequenceNumber() : null;
|
|
428
475
|
|
|
429
476
|
playgroundService.startPolling(
|
|
430
477
|
sessionId,
|
|
431
|
-
|
|
478
|
+
(response) => applyServerResponse(response),
|
|
432
479
|
pollingInterval,
|
|
433
|
-
config.shouldStopPolling
|
|
480
|
+
config.shouldStopPolling,
|
|
481
|
+
initialSequenceNumber
|
|
434
482
|
);
|
|
435
483
|
}
|
|
436
484
|
|
|
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
|
+
async function refreshFromServer(): Promise<void> {
|
|
490
|
+
const sessionId = getCurrentSession()?.id;
|
|
491
|
+
if (!sessionId || isRefreshing) return;
|
|
492
|
+
isRefreshing = true;
|
|
493
|
+
try {
|
|
494
|
+
const response = await playgroundService.getMessages(
|
|
495
|
+
sessionId,
|
|
496
|
+
playgroundService.getLastSequenceNumber() ?? undefined
|
|
497
|
+
);
|
|
498
|
+
applyServerResponse(response);
|
|
499
|
+
if (response.sessionStatus === 'running' && !playgroundService.isPolling()) {
|
|
500
|
+
startPolling(sessionId, true);
|
|
501
|
+
}
|
|
502
|
+
} catch (err) {
|
|
503
|
+
logger.error('[Playground] Status refresh failed:', err);
|
|
504
|
+
} finally {
|
|
505
|
+
isRefreshing = false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
437
509
|
/**
|
|
438
510
|
* Refresh messages for the current session
|
|
439
511
|
* Called after interrupt resolution when polling has stopped
|
|
@@ -444,9 +516,13 @@
|
|
|
444
516
|
|
|
445
517
|
try {
|
|
446
518
|
const response = await playgroundService.getMessages(sessionId);
|
|
447
|
-
|
|
519
|
+
applyServerResponse(response);
|
|
520
|
+
|
|
521
|
+
if (response.sessionStatus === 'running') {
|
|
522
|
+
startPolling(sessionId, true);
|
|
523
|
+
}
|
|
448
524
|
} catch (err) {
|
|
449
|
-
logger.error('[Playground] Failed to refresh
|
|
525
|
+
logger.error('[Playground] Failed to refresh after interrupt:', err);
|
|
450
526
|
}
|
|
451
527
|
}
|
|
452
528
|
|
|
@@ -488,8 +564,8 @@
|
|
|
488
564
|
class:playground--no-sidebar={config.showSidebar === false}
|
|
489
565
|
>
|
|
490
566
|
<div class="playground__container">
|
|
491
|
-
<!-- Sidebar (
|
|
492
|
-
{#if config.showSidebar !== false}
|
|
567
|
+
<!-- Sidebar — hidden in standalone mode (session switcher lives in the header chip instead) -->
|
|
568
|
+
{#if config.showSidebar === true || (config.showSidebar !== false && mode !== 'standalone')}
|
|
493
569
|
<aside
|
|
494
570
|
class="playground__sidebar"
|
|
495
571
|
style={config.sidebarWidth ? `--fd-playground-sidebar-width: ${config.sidebarWidth}` : ''}
|
|
@@ -499,6 +575,13 @@
|
|
|
499
575
|
<div class="playground__sidebar-title">
|
|
500
576
|
<span>Playground</span>
|
|
501
577
|
</div>
|
|
578
|
+
<a
|
|
579
|
+
href="/workflow/{workflowId}/edit"
|
|
580
|
+
class="playground__edit-link"
|
|
581
|
+
title="Edit workflow"
|
|
582
|
+
>
|
|
583
|
+
<Icon icon="mdi:pencil-outline" />
|
|
584
|
+
</a>
|
|
502
585
|
{#if (mode === 'embedded' || mode === 'modal') && onClose}
|
|
503
586
|
<button
|
|
504
587
|
type="button"
|
|
@@ -515,24 +598,24 @@
|
|
|
515
598
|
{/if}
|
|
516
599
|
</div>
|
|
517
600
|
|
|
518
|
-
<!--
|
|
601
|
+
<!-- Sessions Section -->
|
|
519
602
|
<div class="playground__section">
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
class="
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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>
|
|
530
616
|
|
|
531
|
-
<!-- Sessions List
|
|
617
|
+
<!-- Sessions List -->
|
|
532
618
|
<div class="playground__sessions-wrap">
|
|
533
|
-
{#if getSessions().length > 0}
|
|
534
|
-
<p class="playground__sessions-hint">Click a session to load it</p>
|
|
535
|
-
{/if}
|
|
536
619
|
<div class="playground__sessions">
|
|
537
620
|
{#if getSessions().length === 0 && !getIsLoading()}
|
|
538
621
|
<div class="playground__sessions-empty">
|
|
@@ -540,42 +623,75 @@
|
|
|
540
623
|
</div>
|
|
541
624
|
{:else}
|
|
542
625
|
{#each getSessions() as session (session.id)}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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>
|
|
578
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}
|
|
579
695
|
</div>
|
|
580
696
|
{/each}
|
|
581
697
|
{/if}
|
|
@@ -587,18 +703,131 @@
|
|
|
587
703
|
|
|
588
704
|
<!-- Main Content -->
|
|
589
705
|
<main class="playground__main">
|
|
590
|
-
<!-- Session Header
|
|
591
|
-
{#if getCurrentSession() && config.showSessionHeader !== false}
|
|
706
|
+
<!-- Session Header -->
|
|
707
|
+
{#if mode === 'standalone' || (getCurrentSession() && config.showSessionHeader !== false)}
|
|
592
708
|
<header class="playground__header">
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
class="playground__header-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
709
|
+
{#if mode === 'standalone'}
|
|
710
|
+
<!-- Panel icon + label (mirrors PipelinePanel header) -->
|
|
711
|
+
<Icon icon="mdi:message-text-outline" class="playground__header-icon" />
|
|
712
|
+
<span class="playground__header-label">Sessions</span>
|
|
713
|
+
|
|
714
|
+
<!-- Session chip — switches sessions via popover -->
|
|
715
|
+
<div class="playground__session-chip-wrap">
|
|
716
|
+
<button
|
|
717
|
+
type="button"
|
|
718
|
+
class="playground__session-chip"
|
|
719
|
+
class:playground__session-chip--open={sessionDropdownOpen}
|
|
720
|
+
onclick={() => (sessionDropdownOpen = !sessionDropdownOpen)}
|
|
721
|
+
title="Switch session"
|
|
722
|
+
>
|
|
723
|
+
<span class="playground__session-chip-name">
|
|
724
|
+
{getCurrentSession()?.name ?? 'No session'}
|
|
725
|
+
</span>
|
|
726
|
+
<Icon icon={sessionDropdownOpen ? 'mdi:chevron-up' : 'mdi:chevron-down'} class="playground__session-chip-chevron" />
|
|
727
|
+
</button>
|
|
728
|
+
|
|
729
|
+
{#if sessionDropdownOpen}
|
|
730
|
+
<div class="playground__session-popover">
|
|
731
|
+
<button
|
|
732
|
+
type="button"
|
|
733
|
+
class="playground__session-popover-item playground__session-popover-item--new"
|
|
734
|
+
disabled={getIsLoading()}
|
|
735
|
+
onclick={() => { sessionDropdownOpen = false; void handleCreateSession(); }}
|
|
736
|
+
>
|
|
737
|
+
<Icon icon="mdi:plus" />
|
|
738
|
+
<span>New session</span>
|
|
739
|
+
</button>
|
|
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>
|
|
602
831
|
</header>
|
|
603
832
|
{/if}
|
|
604
833
|
|
|
@@ -636,6 +865,7 @@
|
|
|
636
865
|
onSendMessage={handleSendMessage}
|
|
637
866
|
onStopExecution={handleStopExecution}
|
|
638
867
|
onInterruptResolved={handleInterruptResolved}
|
|
868
|
+
bind:showLogs
|
|
639
869
|
/>
|
|
640
870
|
{/if}
|
|
641
871
|
</div>
|
|
@@ -691,6 +921,7 @@
|
|
|
691
921
|
width: var(--fd-playground-sidebar-width);
|
|
692
922
|
background-color: var(--fd-background);
|
|
693
923
|
border-right: 1px solid var(--fd-border);
|
|
924
|
+
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
|
694
925
|
display: flex;
|
|
695
926
|
flex-direction: column;
|
|
696
927
|
}
|
|
@@ -717,6 +948,24 @@
|
|
|
717
948
|
color: var(--fd-foreground);
|
|
718
949
|
}
|
|
719
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);
|
|
961
|
+
flex-shrink: 0;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.playground__edit-link:hover {
|
|
965
|
+
background-color: var(--fd-muted);
|
|
966
|
+
color: var(--fd-foreground);
|
|
967
|
+
}
|
|
968
|
+
|
|
720
969
|
.playground__sidebar-close {
|
|
721
970
|
display: flex;
|
|
722
971
|
align-items: center;
|
|
@@ -742,51 +991,119 @@
|
|
|
742
991
|
display: flex;
|
|
743
992
|
flex-direction: column;
|
|
744
993
|
min-height: 0;
|
|
745
|
-
padding: var(--fd-space-md)
|
|
994
|
+
padding: 0 var(--fd-space-md);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/* Section header: label + add icon */
|
|
998
|
+
.playground__section-header {
|
|
999
|
+
display: flex;
|
|
1000
|
+
align-items: center;
|
|
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;
|
|
746
1011
|
}
|
|
747
1012
|
|
|
748
|
-
|
|
749
|
-
.playground__new-session-btn {
|
|
1013
|
+
.playground__section-add {
|
|
750
1014
|
display: flex;
|
|
751
1015
|
align-items: center;
|
|
752
1016
|
justify-content: center;
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
border: 1px solid var(--fd-border);
|
|
1017
|
+
width: var(--fd-playground-icon-btn-size);
|
|
1018
|
+
height: var(--fd-playground-icon-btn-size);
|
|
1019
|
+
border: none;
|
|
757
1020
|
border-radius: var(--fd-radius-md);
|
|
758
|
-
background
|
|
1021
|
+
background: transparent;
|
|
1022
|
+
color: var(--fd-muted-foreground);
|
|
1023
|
+
cursor: pointer;
|
|
1024
|
+
transition: all var(--fd-transition-fast);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.playground__section-add:hover:not(:disabled) {
|
|
1028
|
+
background-color: var(--fd-muted);
|
|
759
1029
|
color: var(--fd-foreground);
|
|
760
|
-
|
|
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);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
.playground__runs-toggle {
|
|
1048
|
+
display: flex;
|
|
1049
|
+
align-items: center;
|
|
1050
|
+
gap: var(--fd-space-3xs);
|
|
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;
|
|
1055
|
+
border-radius: var(--fd-radius-sm);
|
|
1056
|
+
background: transparent;
|
|
1057
|
+
color: var(--fd-muted-foreground);
|
|
1058
|
+
font-size: var(--fd-text-xs);
|
|
761
1059
|
font-weight: 500;
|
|
762
1060
|
cursor: pointer;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
border-color var(--fd-transition-fast),
|
|
766
|
-
transform 0.1s ease;
|
|
767
|
-
box-sizing: border-box;
|
|
1061
|
+
text-align: left;
|
|
1062
|
+
transition: all var(--fd-transition-fast);
|
|
768
1063
|
}
|
|
769
1064
|
|
|
770
|
-
.
|
|
1065
|
+
.playground__runs-toggle:hover {
|
|
771
1066
|
background-color: var(--fd-muted);
|
|
772
|
-
|
|
773
|
-
transform: translateY(-1px);
|
|
1067
|
+
color: var(--fd-foreground);
|
|
774
1068
|
}
|
|
775
1069
|
|
|
776
|
-
.
|
|
777
|
-
|
|
778
|
-
|
|
1070
|
+
.playground__runs-toggle :global(svg) {
|
|
1071
|
+
width: 0.875rem;
|
|
1072
|
+
height: 0.875rem;
|
|
1073
|
+
flex-shrink: 0;
|
|
779
1074
|
}
|
|
780
1075
|
|
|
781
|
-
.
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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);
|
|
785
1095
|
}
|
|
786
1096
|
|
|
787
|
-
.
|
|
788
|
-
|
|
789
|
-
|
|
1097
|
+
.playground__executions-inline :global(.execution-list__item) {
|
|
1098
|
+
padding: var(--fd-space-xs) var(--fd-space-sm);
|
|
1099
|
+
font-size: var(--fd-text-xs);
|
|
1100
|
+
border-radius: var(--fd-radius-sm);
|
|
1101
|
+
border-left-width: 2px;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.playground__executions-inline :global(.execution-list) {
|
|
1105
|
+
gap: 1px;
|
|
1106
|
+
padding: var(--fd-space-3xs) 0;
|
|
790
1107
|
}
|
|
791
1108
|
|
|
792
1109
|
/* Sessions */
|
|
@@ -797,17 +1114,10 @@
|
|
|
797
1114
|
min-height: 0;
|
|
798
1115
|
}
|
|
799
1116
|
|
|
800
|
-
.playground__sessions-hint {
|
|
801
|
-
font-size: var(--fd-text-2xs);
|
|
802
|
-
color: var(--fd-muted-foreground);
|
|
803
|
-
margin: var(--fd-space-md) 0 var(--fd-space-2xs) var(--fd-space-md);
|
|
804
|
-
line-height: 1.3;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
1117
|
.playground__sessions {
|
|
808
1118
|
flex: 1;
|
|
809
1119
|
overflow-y: auto;
|
|
810
|
-
padding: 0 var(--fd-space-
|
|
1120
|
+
padding: 0 var(--fd-space-3xs) var(--fd-space-xl);
|
|
811
1121
|
min-height: 0;
|
|
812
1122
|
}
|
|
813
1123
|
|
|
@@ -824,7 +1134,6 @@
|
|
|
824
1134
|
align-items: center;
|
|
825
1135
|
justify-content: space-between;
|
|
826
1136
|
padding: var(--fd-space-sm) var(--fd-space-md);
|
|
827
|
-
margin-bottom: var(--fd-space-3xs);
|
|
828
1137
|
border-radius: var(--fd-radius-md);
|
|
829
1138
|
border-left: 3px solid transparent;
|
|
830
1139
|
cursor: pointer;
|
|
@@ -956,15 +1265,34 @@
|
|
|
956
1265
|
.playground__header {
|
|
957
1266
|
display: flex;
|
|
958
1267
|
align-items: center;
|
|
959
|
-
|
|
1268
|
+
gap: var(--fd-space-xs);
|
|
960
1269
|
height: var(--fd-playground-header-height);
|
|
961
|
-
padding: 0 var(--fd-space-
|
|
1270
|
+
padding: 0 var(--fd-space-xl);
|
|
962
1271
|
border-bottom: 1px solid var(--fd-border);
|
|
963
1272
|
background-color: var(--fd-background);
|
|
964
1273
|
box-sizing: border-box;
|
|
965
1274
|
flex-shrink: 0;
|
|
966
1275
|
}
|
|
967
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
|
+
|
|
968
1296
|
.playground__header-title {
|
|
969
1297
|
font-size: var(--fd-text-md);
|
|
970
1298
|
font-weight: 600;
|
|
@@ -992,6 +1320,211 @@
|
|
|
992
1320
|
color: var(--fd-foreground);
|
|
993
1321
|
}
|
|
994
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
|
+
|
|
995
1528
|
/* Error */
|
|
996
1529
|
.playground__error {
|
|
997
1530
|
display: flex;
|