@d34dman/flowdrop 0.0.29 → 0.0.31
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/components/App.svelte +54 -6
- package/dist/components/NodeSidebar.svelte +17 -9
- package/dist/components/SchemaForm.svelte +14 -14
- package/dist/components/SchemaForm.svelte.d.ts +1 -1
- package/dist/components/WorkflowEditor.svelte +4 -0
- package/dist/components/form/FormFieldLight.svelte +66 -66
- package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
- package/dist/components/form/types.d.ts +1 -1
- package/dist/components/nodes/ToolNode.svelte +23 -8
- package/dist/components/playground/ChatPanel.svelte +523 -0
- package/dist/components/playground/ChatPanel.svelte.d.ts +20 -0
- package/dist/components/playground/ExecutionLogs.svelte +486 -0
- package/dist/components/playground/ExecutionLogs.svelte.d.ts +14 -0
- package/dist/components/playground/InputCollector.svelte +444 -0
- package/dist/components/playground/InputCollector.svelte.d.ts +16 -0
- package/dist/components/playground/MessageBubble.svelte +398 -0
- package/dist/components/playground/MessageBubble.svelte.d.ts +15 -0
- package/dist/components/playground/Playground.svelte +851 -0
- package/dist/components/playground/Playground.svelte.d.ts +25 -0
- package/dist/components/playground/SessionManager.svelte +537 -0
- package/dist/components/playground/SessionManager.svelte.d.ts +20 -0
- package/dist/config/endpoints.d.ts +16 -0
- package/dist/config/endpoints.js +9 -0
- package/dist/core/index.d.ts +25 -23
- package/dist/core/index.js +13 -12
- package/dist/display/index.d.ts +2 -2
- package/dist/display/index.js +2 -2
- package/dist/editor/index.d.ts +57 -49
- package/dist/editor/index.js +52 -42
- package/dist/form/code.d.ts +4 -4
- package/dist/form/code.js +11 -11
- package/dist/form/fieldRegistry.d.ts +2 -2
- package/dist/form/fieldRegistry.js +8 -10
- package/dist/form/full.d.ts +5 -5
- package/dist/form/full.js +7 -7
- package/dist/form/index.d.ts +16 -16
- package/dist/form/index.js +14 -14
- package/dist/form/markdown.d.ts +3 -3
- package/dist/form/markdown.js +6 -6
- package/dist/index.d.ts +6 -4
- package/dist/index.js +9 -4
- package/dist/playground/index.d.ts +92 -0
- package/dist/playground/index.js +114 -0
- package/dist/playground/mount.d.ts +183 -0
- package/dist/playground/mount.js +178 -0
- package/dist/services/playgroundService.d.ts +129 -0
- package/dist/services/playgroundService.js +317 -0
- package/dist/stores/playgroundStore.d.ts +199 -0
- package/dist/stores/playgroundStore.js +350 -0
- package/dist/styles/base.css +5 -0
- package/dist/types/playground.d.ts +230 -0
- package/dist/types/playground.js +28 -0
- package/dist/utils/colors.d.ts +42 -0
- package/dist/utils/colors.js +77 -0
- package/package.json +6 -1
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Playground Component
|
|
3
|
+
|
|
4
|
+
Main component for the Playground feature.
|
|
5
|
+
Clean, conversational interface similar to Langflow.
|
|
6
|
+
Supports both embedded (panel) and standalone (page) modes.
|
|
7
|
+
Styled with BEM syntax.
|
|
8
|
+
-->
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { onMount, onDestroy } from 'svelte';
|
|
12
|
+
import Icon from '@iconify/svelte';
|
|
13
|
+
import ChatPanel from './ChatPanel.svelte';
|
|
14
|
+
import type { Workflow } from '../../types/index.js';
|
|
15
|
+
import type { EndpointConfig } from '../../config/endpoints.js';
|
|
16
|
+
import type {
|
|
17
|
+
PlaygroundMode,
|
|
18
|
+
PlaygroundConfig,
|
|
19
|
+
PlaygroundMessagesApiResponse
|
|
20
|
+
} from '../../types/playground.js';
|
|
21
|
+
import { playgroundService } from '../../services/playgroundService.js';
|
|
22
|
+
import { setEndpointConfig } from '../../services/api.js';
|
|
23
|
+
import {
|
|
24
|
+
currentSession,
|
|
25
|
+
sessions,
|
|
26
|
+
messages,
|
|
27
|
+
isExecuting,
|
|
28
|
+
isLoading,
|
|
29
|
+
error,
|
|
30
|
+
playgroundActions,
|
|
31
|
+
inputFields
|
|
32
|
+
} from '../../stores/playgroundStore.js';
|
|
33
|
+
import { get } from 'svelte/store';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Component props
|
|
37
|
+
*/
|
|
38
|
+
interface Props {
|
|
39
|
+
/** Target workflow ID */
|
|
40
|
+
workflowId: string;
|
|
41
|
+
/** Pre-loaded workflow (optional, will be fetched if not provided) */
|
|
42
|
+
workflow?: Workflow;
|
|
43
|
+
/** Display mode: embedded (panel) or standalone (page) */
|
|
44
|
+
mode?: PlaygroundMode;
|
|
45
|
+
/** Resume a specific session */
|
|
46
|
+
initialSessionId?: string;
|
|
47
|
+
/** API endpoint configuration */
|
|
48
|
+
endpointConfig?: EndpointConfig;
|
|
49
|
+
/** Playground configuration options */
|
|
50
|
+
config?: PlaygroundConfig;
|
|
51
|
+
/** Callback when playground is closed (for embedded mode) */
|
|
52
|
+
onClose?: () => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let {
|
|
56
|
+
workflowId,
|
|
57
|
+
workflow,
|
|
58
|
+
mode = 'standalone',
|
|
59
|
+
initialSessionId,
|
|
60
|
+
endpointConfig,
|
|
61
|
+
config = {},
|
|
62
|
+
onClose
|
|
63
|
+
}: Props = $props();
|
|
64
|
+
|
|
65
|
+
/** Current input values from InputCollector */
|
|
66
|
+
let inputValues = $state<Record<string, unknown>>({});
|
|
67
|
+
|
|
68
|
+
/** Track session being edited for rename */
|
|
69
|
+
let editingSessionId = $state<string | null>(null);
|
|
70
|
+
|
|
71
|
+
/** Track session pending delete */
|
|
72
|
+
let pendingDeleteId = $state<string | null>(null);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Initialize the playground
|
|
76
|
+
*/
|
|
77
|
+
onMount(() => {
|
|
78
|
+
// Set endpoint config if provided
|
|
79
|
+
if (endpointConfig) {
|
|
80
|
+
setEndpointConfig(endpointConfig);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Set workflow in store
|
|
84
|
+
if (workflow) {
|
|
85
|
+
playgroundActions.setWorkflow(workflow);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Async initialization
|
|
89
|
+
const initializePlayground = async (): Promise<void> => {
|
|
90
|
+
try {
|
|
91
|
+
// Load sessions
|
|
92
|
+
await loadSessions();
|
|
93
|
+
|
|
94
|
+
// Resume initial session if provided
|
|
95
|
+
if (initialSessionId) {
|
|
96
|
+
await loadSession(initialSessionId);
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error('[Playground] Initialization error:', err);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Execute initialization
|
|
104
|
+
void initializePlayground();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Cleanup on destroy
|
|
109
|
+
*/
|
|
110
|
+
onDestroy(() => {
|
|
111
|
+
playgroundService.stopPolling();
|
|
112
|
+
playgroundActions.reset();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Load sessions for the workflow
|
|
117
|
+
*/
|
|
118
|
+
async function loadSessions(): Promise<void> {
|
|
119
|
+
playgroundActions.setLoading(true);
|
|
120
|
+
playgroundActions.setError(null);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const sessionList = await playgroundService.listSessions(workflowId);
|
|
124
|
+
playgroundActions.setSessions(sessionList);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to load sessions';
|
|
127
|
+
playgroundActions.setError(errorMessage);
|
|
128
|
+
console.error('Failed to load sessions:', err);
|
|
129
|
+
} finally {
|
|
130
|
+
playgroundActions.setLoading(false);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Load a specific session and its messages
|
|
136
|
+
*/
|
|
137
|
+
async function loadSession(sessionId: string): Promise<void> {
|
|
138
|
+
playgroundActions.setLoading(true);
|
|
139
|
+
playgroundActions.setError(null);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Get session details
|
|
143
|
+
const session = await playgroundService.getSession(sessionId);
|
|
144
|
+
playgroundActions.setCurrentSession(session);
|
|
145
|
+
|
|
146
|
+
// Get messages
|
|
147
|
+
const response = await playgroundService.getMessages(sessionId);
|
|
148
|
+
playgroundActions.setMessages(response.data ?? []);
|
|
149
|
+
|
|
150
|
+
// Start polling if session is running
|
|
151
|
+
if (session.status === 'running') {
|
|
152
|
+
startPolling(sessionId);
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to load session';
|
|
156
|
+
playgroundActions.setError(errorMessage);
|
|
157
|
+
console.error('Failed to load session:', err);
|
|
158
|
+
} finally {
|
|
159
|
+
playgroundActions.setLoading(false);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create a new session
|
|
165
|
+
*/
|
|
166
|
+
async function handleCreateSession(): Promise<void> {
|
|
167
|
+
playgroundActions.setLoading(true);
|
|
168
|
+
playgroundActions.setError(null);
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const sessionName = `Session ${get(sessions).length + 1}`;
|
|
172
|
+
const session = await playgroundService.createSession(workflowId, sessionName);
|
|
173
|
+
playgroundActions.addSession(session);
|
|
174
|
+
playgroundActions.setCurrentSession(session);
|
|
175
|
+
playgroundActions.clearMessages();
|
|
176
|
+
} catch (err) {
|
|
177
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to create session';
|
|
178
|
+
playgroundActions.setError(errorMessage);
|
|
179
|
+
console.error('Failed to create session:', err);
|
|
180
|
+
} finally {
|
|
181
|
+
playgroundActions.setLoading(false);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Select a session
|
|
187
|
+
*/
|
|
188
|
+
async function handleSelectSession(sessionId: string): Promise<void> {
|
|
189
|
+
const currentSessionId = get(currentSession)?.id;
|
|
190
|
+
if (currentSessionId === sessionId) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Stop polling for current session
|
|
195
|
+
playgroundService.stopPolling();
|
|
196
|
+
|
|
197
|
+
await loadSession(sessionId);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Delete a session
|
|
202
|
+
*/
|
|
203
|
+
async function handleDeleteSession(sessionId: string): Promise<void> {
|
|
204
|
+
try {
|
|
205
|
+
await playgroundService.deleteSession(sessionId);
|
|
206
|
+
playgroundActions.removeSession(sessionId);
|
|
207
|
+
|
|
208
|
+
// If we deleted the current session, clear it
|
|
209
|
+
if (get(currentSession)?.id === sessionId) {
|
|
210
|
+
playgroundService.stopPolling();
|
|
211
|
+
}
|
|
212
|
+
pendingDeleteId = null;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to delete session';
|
|
215
|
+
playgroundActions.setError(errorMessage);
|
|
216
|
+
console.error('Failed to delete session:', err);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle delete click - show confirmation or execute
|
|
222
|
+
*/
|
|
223
|
+
function handleDeleteClick(event: Event, sessionId: string): void {
|
|
224
|
+
event.stopPropagation();
|
|
225
|
+
if (pendingDeleteId === sessionId) {
|
|
226
|
+
// Confirm deletion
|
|
227
|
+
void handleDeleteSession(sessionId);
|
|
228
|
+
} else {
|
|
229
|
+
pendingDeleteId = sessionId;
|
|
230
|
+
// Auto-reset after 3 seconds
|
|
231
|
+
setTimeout(() => {
|
|
232
|
+
if (pendingDeleteId === sessionId) {
|
|
233
|
+
pendingDeleteId = null;
|
|
234
|
+
}
|
|
235
|
+
}, 3000);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Close current session (go back to welcome)
|
|
241
|
+
*/
|
|
242
|
+
function handleCloseSession(): void {
|
|
243
|
+
playgroundService.stopPolling();
|
|
244
|
+
playgroundActions.setCurrentSession(null);
|
|
245
|
+
playgroundActions.clearMessages();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Send a message
|
|
250
|
+
*/
|
|
251
|
+
async function handleSendMessage(content: string): Promise<void> {
|
|
252
|
+
const session = get(currentSession);
|
|
253
|
+
if (!session) {
|
|
254
|
+
// Create a session first if none exists
|
|
255
|
+
await handleCreateSession();
|
|
256
|
+
const newSession = get(currentSession);
|
|
257
|
+
if (!newSession) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const sessionId = get(currentSession)?.id;
|
|
263
|
+
if (!sessionId) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
playgroundActions.setExecuting(true);
|
|
268
|
+
playgroundActions.setError(null);
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
// Prepare inputs from the input collector
|
|
272
|
+
const inputs: Record<string, unknown> = {};
|
|
273
|
+
const fields = get(inputFields);
|
|
274
|
+
|
|
275
|
+
fields.forEach((field) => {
|
|
276
|
+
const key = `${field.nodeId}:${field.fieldId}`;
|
|
277
|
+
if (inputValues[key] !== undefined) {
|
|
278
|
+
// Map to node ID and field ID for the backend
|
|
279
|
+
if (!inputs[field.nodeId]) {
|
|
280
|
+
inputs[field.nodeId] = {};
|
|
281
|
+
}
|
|
282
|
+
(inputs[field.nodeId] as Record<string, unknown>)[field.fieldId] = inputValues[key];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Send message
|
|
287
|
+
const message = await playgroundService.sendMessage(sessionId, content, inputs);
|
|
288
|
+
playgroundActions.addMessage(message);
|
|
289
|
+
|
|
290
|
+
// Update session status
|
|
291
|
+
playgroundActions.updateSessionStatus('running');
|
|
292
|
+
|
|
293
|
+
// Start polling for responses
|
|
294
|
+
startPolling(sessionId);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send message';
|
|
297
|
+
playgroundActions.setError(errorMessage);
|
|
298
|
+
playgroundActions.setExecuting(false);
|
|
299
|
+
console.error('Failed to send message:', err);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Stop execution
|
|
305
|
+
*/
|
|
306
|
+
async function handleStopExecution(): Promise<void> {
|
|
307
|
+
const sessionId = get(currentSession)?.id;
|
|
308
|
+
if (!sessionId) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
await playgroundService.stopExecution(sessionId);
|
|
314
|
+
playgroundService.stopPolling();
|
|
315
|
+
playgroundActions.setExecuting(false);
|
|
316
|
+
playgroundActions.updateSessionStatus('idle');
|
|
317
|
+
} catch (err) {
|
|
318
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to stop execution';
|
|
319
|
+
playgroundActions.setError(errorMessage);
|
|
320
|
+
console.error('Failed to stop execution:', err);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Start polling for messages
|
|
326
|
+
*/
|
|
327
|
+
function startPolling(sessionId: string): void {
|
|
328
|
+
const pollingInterval = config.pollingInterval ?? 1500;
|
|
329
|
+
|
|
330
|
+
playgroundService.startPolling(
|
|
331
|
+
sessionId,
|
|
332
|
+
(response: PlaygroundMessagesApiResponse) => {
|
|
333
|
+
// Add new messages
|
|
334
|
+
if (response.data && response.data.length > 0) {
|
|
335
|
+
playgroundActions.addMessages(response.data);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Update session status
|
|
339
|
+
if (response.sessionStatus) {
|
|
340
|
+
playgroundActions.updateSessionStatus(response.sessionStatus);
|
|
341
|
+
|
|
342
|
+
// Stop executing if completed or failed
|
|
343
|
+
if (response.sessionStatus === 'completed' || response.sessionStatus === 'failed') {
|
|
344
|
+
playgroundActions.setExecuting(false);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
pollingInterval
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Format date for display
|
|
354
|
+
*/
|
|
355
|
+
function formatDate(dateString: string): string {
|
|
356
|
+
const date = new Date(dateString);
|
|
357
|
+
const now = new Date();
|
|
358
|
+
const diffMs = now.getTime() - date.getTime();
|
|
359
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
360
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
361
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
362
|
+
|
|
363
|
+
if (diffMins < 1) {
|
|
364
|
+
return 'Just now';
|
|
365
|
+
}
|
|
366
|
+
if (diffMins < 60) {
|
|
367
|
+
return `${diffMins}m ago`;
|
|
368
|
+
}
|
|
369
|
+
if (diffHours < 24) {
|
|
370
|
+
return `${diffHours}h ago`;
|
|
371
|
+
}
|
|
372
|
+
if (diffDays < 7) {
|
|
373
|
+
return `${diffDays}d ago`;
|
|
374
|
+
}
|
|
375
|
+
return date.toLocaleDateString('en-US', {
|
|
376
|
+
month: 'short',
|
|
377
|
+
day: 'numeric'
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
</script>
|
|
381
|
+
|
|
382
|
+
<div
|
|
383
|
+
class="playground"
|
|
384
|
+
class:playground--embedded={mode === 'embedded'}
|
|
385
|
+
class:playground--standalone={mode === 'standalone'}
|
|
386
|
+
>
|
|
387
|
+
<div class="playground__container">
|
|
388
|
+
<!-- Sidebar -->
|
|
389
|
+
<aside class="playground__sidebar">
|
|
390
|
+
<!-- Sidebar Header -->
|
|
391
|
+
<div class="playground__sidebar-header">
|
|
392
|
+
<div class="playground__sidebar-title">
|
|
393
|
+
<Icon icon="mdi:play-circle-outline" />
|
|
394
|
+
<span>Playground</span>
|
|
395
|
+
</div>
|
|
396
|
+
{#if mode === 'embedded' && onClose}
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
class="playground__sidebar-close"
|
|
400
|
+
onclick={onClose}
|
|
401
|
+
title="Close playground"
|
|
402
|
+
>
|
|
403
|
+
<Icon icon="mdi:dock-right" />
|
|
404
|
+
</button>
|
|
405
|
+
{/if}
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<!-- Chat Section -->
|
|
409
|
+
<div class="playground__section">
|
|
410
|
+
<div class="playground__section-header">
|
|
411
|
+
<div class="playground__section-title">
|
|
412
|
+
<Icon icon="mdi:chat-outline" />
|
|
413
|
+
<span>Chat</span>
|
|
414
|
+
</div>
|
|
415
|
+
<button
|
|
416
|
+
type="button"
|
|
417
|
+
class="playground__add-btn"
|
|
418
|
+
onclick={handleCreateSession}
|
|
419
|
+
disabled={$isLoading}
|
|
420
|
+
title="New chat session"
|
|
421
|
+
>
|
|
422
|
+
<Icon icon="mdi:plus" />
|
|
423
|
+
</button>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<!-- Sessions List -->
|
|
427
|
+
<div class="playground__sessions">
|
|
428
|
+
{#if $sessions.length === 0 && !$isLoading}
|
|
429
|
+
<div class="playground__sessions-empty">
|
|
430
|
+
<span>No sessions yet</span>
|
|
431
|
+
</div>
|
|
432
|
+
{:else}
|
|
433
|
+
{#each $sessions as session (session.id)}
|
|
434
|
+
<div
|
|
435
|
+
class="playground__session"
|
|
436
|
+
class:playground__session--active={$currentSession?.id === session.id}
|
|
437
|
+
role="button"
|
|
438
|
+
tabindex="0"
|
|
439
|
+
onclick={() => handleSelectSession(session.id)}
|
|
440
|
+
onkeydown={(e) => e.key === 'Enter' && handleSelectSession(session.id)}
|
|
441
|
+
>
|
|
442
|
+
<span class="playground__session-name" title={session.name}>
|
|
443
|
+
{session.name}
|
|
444
|
+
</span>
|
|
445
|
+
<button
|
|
446
|
+
type="button"
|
|
447
|
+
class="playground__session-menu"
|
|
448
|
+
class:playground__session-menu--delete={pendingDeleteId === session.id}
|
|
449
|
+
onclick={(e) => handleDeleteClick(e, session.id)}
|
|
450
|
+
title={pendingDeleteId === session.id
|
|
451
|
+
? 'Click to confirm delete'
|
|
452
|
+
: 'Delete session'}
|
|
453
|
+
>
|
|
454
|
+
{#if pendingDeleteId === session.id}
|
|
455
|
+
<Icon icon="mdi:check" />
|
|
456
|
+
{:else}
|
|
457
|
+
<Icon icon="mdi:dots-horizontal" />
|
|
458
|
+
{/if}
|
|
459
|
+
</button>
|
|
460
|
+
</div>
|
|
461
|
+
{/each}
|
|
462
|
+
{/if}
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
</aside>
|
|
466
|
+
|
|
467
|
+
<!-- Main Content -->
|
|
468
|
+
<main class="playground__main">
|
|
469
|
+
<!-- Session Header -->
|
|
470
|
+
{#if $currentSession}
|
|
471
|
+
<header class="playground__header">
|
|
472
|
+
<h2 class="playground__header-title">{$currentSession.name}</h2>
|
|
473
|
+
<button
|
|
474
|
+
type="button"
|
|
475
|
+
class="playground__header-close"
|
|
476
|
+
onclick={handleCloseSession}
|
|
477
|
+
title="Close session"
|
|
478
|
+
>
|
|
479
|
+
<Icon icon="mdi:close" />
|
|
480
|
+
</button>
|
|
481
|
+
</header>
|
|
482
|
+
{/if}
|
|
483
|
+
|
|
484
|
+
<!-- Error Banner -->
|
|
485
|
+
{#if $error}
|
|
486
|
+
<div class="playground__error">
|
|
487
|
+
<Icon icon="mdi:alert-circle" />
|
|
488
|
+
<span>{$error}</span>
|
|
489
|
+
<button
|
|
490
|
+
type="button"
|
|
491
|
+
class="playground__error-dismiss"
|
|
492
|
+
onclick={() => playgroundActions.setError(null)}
|
|
493
|
+
>
|
|
494
|
+
<Icon icon="mdi:close" />
|
|
495
|
+
</button>
|
|
496
|
+
</div>
|
|
497
|
+
{/if}
|
|
498
|
+
|
|
499
|
+
<!-- Chat Content -->
|
|
500
|
+
<div class="playground__content">
|
|
501
|
+
{#if $isLoading && !$currentSession}
|
|
502
|
+
<div class="playground__loading">
|
|
503
|
+
<Icon icon="mdi:loading" class="playground__loading-icon" />
|
|
504
|
+
<span>Loading...</span>
|
|
505
|
+
</div>
|
|
506
|
+
{:else}
|
|
507
|
+
<ChatPanel
|
|
508
|
+
showTimestamps={config.showTimestamps ?? true}
|
|
509
|
+
autoScroll={config.autoScroll ?? true}
|
|
510
|
+
showLogsInline={config.logDisplayMode === 'inline'}
|
|
511
|
+
onSendMessage={handleSendMessage}
|
|
512
|
+
onStopExecution={handleStopExecution}
|
|
513
|
+
/>
|
|
514
|
+
{/if}
|
|
515
|
+
</div>
|
|
516
|
+
</main>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<style>
|
|
521
|
+
.playground {
|
|
522
|
+
display: flex;
|
|
523
|
+
flex-direction: column;
|
|
524
|
+
height: 100%;
|
|
525
|
+
background-color: #f8fafc;
|
|
526
|
+
font-family:
|
|
527
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.playground--embedded {
|
|
531
|
+
border-left: 1px solid #e2e8f0;
|
|
532
|
+
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.08);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.playground--standalone {
|
|
536
|
+
height: 100vh;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* Container */
|
|
540
|
+
.playground__container {
|
|
541
|
+
display: flex;
|
|
542
|
+
flex: 1;
|
|
543
|
+
min-height: 0;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* Sidebar */
|
|
547
|
+
.playground__sidebar {
|
|
548
|
+
width: 220px;
|
|
549
|
+
background-color: #fafbfc;
|
|
550
|
+
border-right: 1px solid #e5e7eb;
|
|
551
|
+
display: flex;
|
|
552
|
+
flex-direction: column;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.playground__sidebar-header {
|
|
556
|
+
display: flex;
|
|
557
|
+
align-items: center;
|
|
558
|
+
justify-content: space-between;
|
|
559
|
+
padding: 1rem;
|
|
560
|
+
border-bottom: 1px solid #e5e7eb;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.playground__sidebar-title {
|
|
564
|
+
display: flex;
|
|
565
|
+
align-items: center;
|
|
566
|
+
gap: 0.5rem;
|
|
567
|
+
font-size: 0.9375rem;
|
|
568
|
+
font-weight: 600;
|
|
569
|
+
color: #1f2937;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.playground__sidebar-close {
|
|
573
|
+
display: flex;
|
|
574
|
+
align-items: center;
|
|
575
|
+
justify-content: center;
|
|
576
|
+
width: 1.75rem;
|
|
577
|
+
height: 1.75rem;
|
|
578
|
+
border: none;
|
|
579
|
+
border-radius: 0.375rem;
|
|
580
|
+
background: transparent;
|
|
581
|
+
color: #6b7280;
|
|
582
|
+
cursor: pointer;
|
|
583
|
+
transition: all 0.15s ease;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.playground__sidebar-close:hover {
|
|
587
|
+
background-color: #f3f4f6;
|
|
588
|
+
color: #374151;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/* Section */
|
|
592
|
+
.playground__section {
|
|
593
|
+
flex: 1;
|
|
594
|
+
display: flex;
|
|
595
|
+
flex-direction: column;
|
|
596
|
+
min-height: 0;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.playground__section-header {
|
|
600
|
+
display: flex;
|
|
601
|
+
align-items: center;
|
|
602
|
+
justify-content: space-between;
|
|
603
|
+
padding: 0.75rem 1rem;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.playground__section-title {
|
|
607
|
+
display: flex;
|
|
608
|
+
align-items: center;
|
|
609
|
+
gap: 0.5rem;
|
|
610
|
+
font-size: 0.8125rem;
|
|
611
|
+
font-weight: 500;
|
|
612
|
+
color: #6b7280;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.playground__add-btn {
|
|
616
|
+
display: flex;
|
|
617
|
+
align-items: center;
|
|
618
|
+
justify-content: center;
|
|
619
|
+
width: 1.5rem;
|
|
620
|
+
height: 1.5rem;
|
|
621
|
+
border: none;
|
|
622
|
+
border-radius: 0.375rem;
|
|
623
|
+
background: transparent;
|
|
624
|
+
color: #6b7280;
|
|
625
|
+
cursor: pointer;
|
|
626
|
+
transition: all 0.15s ease;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.playground__add-btn:hover:not(:disabled) {
|
|
630
|
+
background-color: #e5e7eb;
|
|
631
|
+
color: #374151;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.playground__add-btn:disabled {
|
|
635
|
+
opacity: 0.5;
|
|
636
|
+
cursor: not-allowed;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* Sessions */
|
|
640
|
+
.playground__sessions {
|
|
641
|
+
flex: 1;
|
|
642
|
+
overflow-y: auto;
|
|
643
|
+
padding: 0 0.5rem 1rem;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.playground__sessions-empty {
|
|
647
|
+
padding: 1rem;
|
|
648
|
+
text-align: center;
|
|
649
|
+
font-size: 0.8125rem;
|
|
650
|
+
color: #9ca3af;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.playground__session {
|
|
654
|
+
display: flex;
|
|
655
|
+
align-items: center;
|
|
656
|
+
justify-content: space-between;
|
|
657
|
+
padding: 0.625rem 0.75rem;
|
|
658
|
+
margin-bottom: 0.25rem;
|
|
659
|
+
border-radius: 0.5rem;
|
|
660
|
+
cursor: pointer;
|
|
661
|
+
transition: all 0.15s ease;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.playground__session:hover {
|
|
665
|
+
background-color: #f3f4f6;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.playground__session--active {
|
|
669
|
+
background-color: #e0e7ff;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.playground__session--active:hover {
|
|
673
|
+
background-color: #c7d2fe;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.playground__session-name {
|
|
677
|
+
flex: 1;
|
|
678
|
+
font-size: 0.875rem;
|
|
679
|
+
color: #374151;
|
|
680
|
+
white-space: nowrap;
|
|
681
|
+
overflow: hidden;
|
|
682
|
+
text-overflow: ellipsis;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.playground__session--active .playground__session-name {
|
|
686
|
+
color: #4338ca;
|
|
687
|
+
font-weight: 500;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.playground__session-menu {
|
|
691
|
+
display: flex;
|
|
692
|
+
align-items: center;
|
|
693
|
+
justify-content: center;
|
|
694
|
+
width: 1.5rem;
|
|
695
|
+
height: 1.5rem;
|
|
696
|
+
border: none;
|
|
697
|
+
border-radius: 0.25rem;
|
|
698
|
+
background: transparent;
|
|
699
|
+
color: #9ca3af;
|
|
700
|
+
cursor: pointer;
|
|
701
|
+
opacity: 0;
|
|
702
|
+
transition: all 0.15s ease;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.playground__session:hover .playground__session-menu {
|
|
706
|
+
opacity: 1;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.playground__session-menu:hover {
|
|
710
|
+
background-color: #fecaca;
|
|
711
|
+
color: #dc2626;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.playground__session-menu--delete {
|
|
715
|
+
opacity: 1;
|
|
716
|
+
background-color: #dcfce7;
|
|
717
|
+
color: #16a34a;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.playground__session-menu--delete:hover {
|
|
721
|
+
background-color: #bbf7d0;
|
|
722
|
+
color: #15803d;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Main Content */
|
|
726
|
+
.playground__main {
|
|
727
|
+
flex: 1;
|
|
728
|
+
display: flex;
|
|
729
|
+
flex-direction: column;
|
|
730
|
+
min-width: 0;
|
|
731
|
+
background-color: #ffffff;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/* Header */
|
|
735
|
+
.playground__header {
|
|
736
|
+
display: flex;
|
|
737
|
+
align-items: center;
|
|
738
|
+
justify-content: space-between;
|
|
739
|
+
padding: 0.875rem 1.25rem;
|
|
740
|
+
border-bottom: 1px solid #e5e7eb;
|
|
741
|
+
background-color: #fafbfc;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.playground__header-title {
|
|
745
|
+
font-size: 0.9375rem;
|
|
746
|
+
font-weight: 600;
|
|
747
|
+
color: #1f2937;
|
|
748
|
+
margin: 0;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.playground__header-close {
|
|
752
|
+
display: flex;
|
|
753
|
+
align-items: center;
|
|
754
|
+
justify-content: center;
|
|
755
|
+
width: 1.75rem;
|
|
756
|
+
height: 1.75rem;
|
|
757
|
+
border: none;
|
|
758
|
+
border-radius: 0.375rem;
|
|
759
|
+
background: transparent;
|
|
760
|
+
color: #6b7280;
|
|
761
|
+
cursor: pointer;
|
|
762
|
+
transition: all 0.15s ease;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.playground__header-close:hover {
|
|
766
|
+
background-color: #f3f4f6;
|
|
767
|
+
color: #374151;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* Error */
|
|
771
|
+
.playground__error {
|
|
772
|
+
display: flex;
|
|
773
|
+
align-items: center;
|
|
774
|
+
gap: 0.5rem;
|
|
775
|
+
padding: 0.75rem 1rem;
|
|
776
|
+
background-color: #fef2f2;
|
|
777
|
+
border-bottom: 1px solid #fecaca;
|
|
778
|
+
color: #dc2626;
|
|
779
|
+
font-size: 0.875rem;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.playground__error-dismiss {
|
|
783
|
+
margin-left: auto;
|
|
784
|
+
display: flex;
|
|
785
|
+
align-items: center;
|
|
786
|
+
justify-content: center;
|
|
787
|
+
width: 1.5rem;
|
|
788
|
+
height: 1.5rem;
|
|
789
|
+
border: none;
|
|
790
|
+
border-radius: 0.25rem;
|
|
791
|
+
background: transparent;
|
|
792
|
+
color: #dc2626;
|
|
793
|
+
cursor: pointer;
|
|
794
|
+
transition: background-color 0.15s ease;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.playground__error-dismiss:hover {
|
|
798
|
+
background-color: #fee2e2;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/* Content */
|
|
802
|
+
.playground__content {
|
|
803
|
+
flex: 1;
|
|
804
|
+
min-height: 0;
|
|
805
|
+
display: flex;
|
|
806
|
+
flex-direction: column;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/* Loading */
|
|
810
|
+
.playground__loading {
|
|
811
|
+
display: flex;
|
|
812
|
+
flex-direction: column;
|
|
813
|
+
align-items: center;
|
|
814
|
+
justify-content: center;
|
|
815
|
+
flex: 1;
|
|
816
|
+
gap: 1rem;
|
|
817
|
+
color: #6b7280;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
:global(.playground__loading-icon) {
|
|
821
|
+
font-size: 2rem;
|
|
822
|
+
animation: spin 1s linear infinite;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
@keyframes spin {
|
|
826
|
+
from {
|
|
827
|
+
transform: rotate(0deg);
|
|
828
|
+
}
|
|
829
|
+
to {
|
|
830
|
+
transform: rotate(360deg);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* Responsive */
|
|
835
|
+
@media (max-width: 768px) {
|
|
836
|
+
.playground__sidebar {
|
|
837
|
+
width: 180px;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@media (max-width: 640px) {
|
|
842
|
+
.playground__sidebar {
|
|
843
|
+
position: absolute;
|
|
844
|
+
left: 0;
|
|
845
|
+
top: 0;
|
|
846
|
+
bottom: 0;
|
|
847
|
+
z-index: 20;
|
|
848
|
+
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.1);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
</style>
|