@flowdrop/flowdrop 1.9.0 → 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 +17 -1
- package/dist/components/PipelineStatus.svelte.d.ts +2 -0
- package/dist/components/WorkflowEditor.svelte +26 -0
- package/dist/components/playground/ChatPanel.svelte +33 -235
- package/dist/components/playground/ExecutionList.svelte +2 -6
- package/dist/components/playground/MessageBubble.svelte +61 -4
- package/dist/components/playground/PipelinePanel.svelte +17 -7
- package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -1
- package/dist/components/playground/Playground.svelte +122 -72
- 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/nodeExecutionService.js +4 -2
- package/dist/services/playgroundService.d.ts +11 -4
- package/dist/services/playgroundService.js +22 -12
- package/dist/stores/playgroundStore.svelte.d.ts +22 -21
- package/dist/stores/playgroundStore.svelte.js +79 -58
- package/dist/types/playground.d.ts +3 -5
- package/package.json +1 -1
|
@@ -6,13 +6,14 @@ interface Props {
|
|
|
6
6
|
workflow: Workflow;
|
|
7
7
|
endpointConfig: EndpointConfig;
|
|
8
8
|
isPinned: boolean;
|
|
9
|
-
runLabel?: string;
|
|
10
9
|
/** All executions for the current session, oldest-first */
|
|
11
10
|
executions?: PlaygroundExecution[];
|
|
12
11
|
/** ID of the most-recent execution */
|
|
13
12
|
latestExecutionId?: string | null;
|
|
14
13
|
/** Called with an execution ID to pin it, or null to follow latest */
|
|
15
14
|
onSelectExecution?: (id: string | null) => void;
|
|
15
|
+
/** Increments when new messages arrive — forwarded to PipelineStatus for immediate refresh */
|
|
16
|
+
refreshTrigger?: number;
|
|
16
17
|
}
|
|
17
18
|
declare const PipelinePanel: import("svelte").Component<Props, {}, "">;
|
|
18
19
|
type PipelinePanel = ReturnType<typeof PipelinePanel>;
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
getError,
|
|
27
27
|
playgroundActions,
|
|
28
28
|
getInputFields,
|
|
29
|
-
|
|
29
|
+
applyServerResponse,
|
|
30
|
+
getCanSendMessage,
|
|
31
|
+
getLatestSequenceNumber,
|
|
30
32
|
getActiveExecutionId,
|
|
31
33
|
getLatestExecutionId,
|
|
32
34
|
getPinnedExecutionId,
|
|
@@ -98,6 +100,9 @@
|
|
|
98
100
|
/** Whether log messages are visible in the chat panel */
|
|
99
101
|
let showLogs = $state(true);
|
|
100
102
|
|
|
103
|
+
/** Whether a manual refresh is in flight */
|
|
104
|
+
let isRefreshing = $state(false);
|
|
105
|
+
|
|
101
106
|
/** Whether the session switcher popover is open (standalone mode) */
|
|
102
107
|
let sessionDropdownOpen = $state(false);
|
|
103
108
|
|
|
@@ -117,41 +122,31 @@
|
|
|
117
122
|
* Initialize the playground on mount
|
|
118
123
|
*/
|
|
119
124
|
onMount(() => {
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const initializePlayground = async (): Promise<void> => {
|
|
132
|
-
try {
|
|
133
|
-
// Load sessions
|
|
134
|
-
await loadSessions();
|
|
135
|
-
|
|
136
|
-
// Resume initial session if provided
|
|
137
|
-
if (initialSessionId) {
|
|
138
|
-
await loadInitialSession(initialSessionId);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Handle auto-run after initialization is complete
|
|
142
|
-
if (config.autoRun && !autoRunTriggered) {
|
|
143
|
-
autoRunTriggered = true;
|
|
144
|
-
const predefinedMessage = config.predefinedMessage ?? 'Run workflow';
|
|
145
|
-
logger.debug('[Playground] Auto-run triggered with message:', predefinedMessage);
|
|
146
|
-
await handleSendMessage(predefinedMessage);
|
|
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));
|
|
147
136
|
}
|
|
148
|
-
} catch (err) {
|
|
149
|
-
logger.error('[Playground] Initialization error:', err);
|
|
150
137
|
}
|
|
151
138
|
};
|
|
139
|
+
document.addEventListener('visibilitychange', handleVisibility);
|
|
140
|
+
|
|
141
|
+
const handleRefreshStatus = () => void refreshFromServer();
|
|
142
|
+
document.addEventListener('flowdrop:refresh-status', handleRefreshStatus);
|
|
152
143
|
|
|
153
|
-
// Execute initialization
|
|
154
144
|
void initializePlayground();
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
document.removeEventListener('visibilitychange', handleVisibility);
|
|
148
|
+
document.removeEventListener('flowdrop:refresh-status', handleRefreshStatus);
|
|
149
|
+
};
|
|
155
150
|
});
|
|
156
151
|
|
|
157
152
|
/**
|
|
@@ -164,8 +159,10 @@
|
|
|
164
159
|
return;
|
|
165
160
|
}
|
|
166
161
|
|
|
167
|
-
// Skip if this session was already loaded
|
|
168
|
-
|
|
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) {
|
|
169
166
|
return;
|
|
170
167
|
}
|
|
171
168
|
|
|
@@ -176,18 +173,42 @@
|
|
|
176
173
|
}
|
|
177
174
|
|
|
178
175
|
// Load the initial session if sessions are available
|
|
179
|
-
if (sessionList.length > 0
|
|
176
|
+
if (sessionList.length > 0) {
|
|
180
177
|
void loadInitialSession(initialSessionId);
|
|
181
178
|
}
|
|
182
179
|
});
|
|
183
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
|
+
|
|
184
203
|
/**
|
|
185
204
|
* Load the initial session with validation and error handling
|
|
186
205
|
*
|
|
187
206
|
* @param sessionId - The session ID to load
|
|
188
207
|
*/
|
|
189
208
|
async function loadInitialSession(sessionId: string): Promise<void> {
|
|
190
|
-
// 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).
|
|
191
212
|
const sessionList = getSessions();
|
|
192
213
|
const sessionExists = sessionList.some((s) => s.id === sessionId);
|
|
193
214
|
|
|
@@ -196,21 +217,19 @@
|
|
|
196
217
|
`[Playground] Initial session "${sessionId}" not found in available sessions. ` +
|
|
197
218
|
`Available sessions: ${sessionList.map((s) => s.id).join(', ') || 'none'}`
|
|
198
219
|
);
|
|
199
|
-
// Don't set error - just log warning and let user pick a session
|
|
200
220
|
initialSessionLoaded = true;
|
|
201
|
-
loadedInitialSessionId = sessionId;
|
|
202
221
|
return;
|
|
203
222
|
}
|
|
204
223
|
|
|
224
|
+
// Set guard BEFORE the first await to prevent concurrent loads.
|
|
225
|
+
loadedInitialSessionId = sessionId;
|
|
226
|
+
|
|
205
227
|
try {
|
|
206
228
|
await loadSession(sessionId);
|
|
207
229
|
initialSessionLoaded = true;
|
|
208
|
-
loadedInitialSessionId = sessionId;
|
|
209
230
|
} catch (err) {
|
|
210
231
|
logger.error('[Playground] Failed to load initial session:', err);
|
|
211
|
-
// Mark as attempted to prevent retry loops
|
|
212
232
|
initialSessionLoaded = true;
|
|
213
|
-
loadedInitialSessionId = sessionId;
|
|
214
233
|
}
|
|
215
234
|
}
|
|
216
235
|
|
|
@@ -265,17 +284,14 @@
|
|
|
265
284
|
playgroundActions.setError(null);
|
|
266
285
|
|
|
267
286
|
try {
|
|
268
|
-
// Get session details
|
|
269
287
|
const session = await playgroundService.getSession(sessionId);
|
|
270
288
|
playgroundActions.setCurrentSession(session);
|
|
271
289
|
|
|
272
|
-
// Get messages
|
|
273
290
|
const response = await playgroundService.getMessages(sessionId);
|
|
274
|
-
|
|
291
|
+
applyServerResponse(response);
|
|
275
292
|
|
|
276
|
-
// Start polling if session is running
|
|
277
293
|
if (session.status === 'running') {
|
|
278
|
-
startPolling(sessionId);
|
|
294
|
+
startPolling(sessionId, true);
|
|
279
295
|
}
|
|
280
296
|
} catch (err) {
|
|
281
297
|
const errorMessage = err instanceof Error ? err.message : 'Failed to load session';
|
|
@@ -326,9 +342,8 @@
|
|
|
326
342
|
return;
|
|
327
343
|
}
|
|
328
344
|
|
|
329
|
-
// Stop polling for current session
|
|
330
345
|
playgroundService.stopPolling();
|
|
331
|
-
|
|
346
|
+
playgroundActions.updateSessionStatus('idle');
|
|
332
347
|
await loadSession(sessionId);
|
|
333
348
|
}
|
|
334
349
|
|
|
@@ -387,9 +402,10 @@
|
|
|
387
402
|
* Send a message
|
|
388
403
|
*/
|
|
389
404
|
async function handleSendMessage(content: string): Promise<void> {
|
|
405
|
+
if (getIsExecuting()) return;
|
|
406
|
+
|
|
390
407
|
const session = getCurrentSession();
|
|
391
408
|
if (!session) {
|
|
392
|
-
// Create a session first if none exists
|
|
393
409
|
await handleCreateSession();
|
|
394
410
|
const newSession = getCurrentSession();
|
|
395
411
|
if (!newSession) {
|
|
@@ -397,23 +413,19 @@
|
|
|
397
413
|
}
|
|
398
414
|
}
|
|
399
415
|
|
|
400
|
-
const sessionId = getCurrentSession()
|
|
401
|
-
if (!sessionId) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
416
|
+
const sessionId = getCurrentSession()!.id;
|
|
404
417
|
|
|
405
|
-
playgroundActions.
|
|
418
|
+
playgroundActions.updateSessionStatus('running');
|
|
419
|
+
playgroundActions.pinExecution(null);
|
|
406
420
|
playgroundActions.setError(null);
|
|
407
421
|
|
|
408
422
|
try {
|
|
409
|
-
// Prepare inputs from the input collector
|
|
410
423
|
const inputs: Record<string, unknown> = {};
|
|
411
424
|
const fields = getInputFields();
|
|
412
425
|
|
|
413
426
|
fields.forEach((field) => {
|
|
414
427
|
const key = `${field.nodeId}:${field.fieldId}`;
|
|
415
428
|
if (inputValues[key] !== undefined) {
|
|
416
|
-
// Map to node ID and field ID for the backend
|
|
417
429
|
if (!inputs[field.nodeId]) {
|
|
418
430
|
inputs[field.nodeId] = {};
|
|
419
431
|
}
|
|
@@ -421,19 +433,13 @@
|
|
|
421
433
|
}
|
|
422
434
|
});
|
|
423
435
|
|
|
424
|
-
// Send message
|
|
425
436
|
const message = await playgroundService.sendMessage(sessionId, content, inputs);
|
|
426
437
|
playgroundActions.addMessage(message);
|
|
427
|
-
|
|
428
|
-
// Update session status
|
|
429
|
-
playgroundActions.updateSessionStatus('running');
|
|
430
|
-
|
|
431
|
-
// Start polling for responses
|
|
432
438
|
startPolling(sessionId);
|
|
433
439
|
} catch (err) {
|
|
434
440
|
const errorMessage = err instanceof Error ? err.message : 'Failed to send message';
|
|
435
441
|
playgroundActions.setError(errorMessage);
|
|
436
|
-
playgroundActions.
|
|
442
|
+
playgroundActions.updateSessionStatus('idle');
|
|
437
443
|
logger.error('Failed to send message:', err);
|
|
438
444
|
}
|
|
439
445
|
}
|
|
@@ -450,33 +456,56 @@
|
|
|
450
456
|
try {
|
|
451
457
|
await playgroundService.stopExecution(sessionId);
|
|
452
458
|
playgroundService.stopPolling();
|
|
453
|
-
playgroundActions.setExecuting(false);
|
|
454
459
|
playgroundActions.updateSessionStatus('idle');
|
|
455
460
|
} catch (err) {
|
|
456
461
|
const errorMessage = err instanceof Error ? err.message : 'Failed to stop execution';
|
|
457
462
|
playgroundActions.setError(errorMessage);
|
|
463
|
+
playgroundService.stopPolling();
|
|
464
|
+
playgroundActions.updateSessionStatus('idle');
|
|
458
465
|
logger.error('Failed to stop execution:', err);
|
|
459
466
|
}
|
|
460
467
|
}
|
|
461
468
|
|
|
462
|
-
/** Shared polling callback created from config lifecycle hooks */
|
|
463
|
-
// svelte-ignore state_referenced_locally — config is static
|
|
464
|
-
const pollingCallback = createPollingCallback(config.isTerminalStatus);
|
|
465
|
-
|
|
466
469
|
/**
|
|
467
470
|
* Start polling for messages
|
|
468
471
|
*/
|
|
469
|
-
function startPolling(sessionId: string): void {
|
|
472
|
+
function startPolling(sessionId: string, seedSequence = false): void {
|
|
470
473
|
const pollingInterval = config.pollingInterval ?? 1500;
|
|
474
|
+
const initialSequenceNumber = seedSequence ? getLatestSequenceNumber() : null;
|
|
471
475
|
|
|
472
476
|
playgroundService.startPolling(
|
|
473
477
|
sessionId,
|
|
474
|
-
|
|
478
|
+
(response) => applyServerResponse(response),
|
|
475
479
|
pollingInterval,
|
|
476
|
-
config.shouldStopPolling
|
|
480
|
+
config.shouldStopPolling,
|
|
481
|
+
initialSequenceNumber
|
|
477
482
|
);
|
|
478
483
|
}
|
|
479
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
|
+
|
|
480
509
|
/**
|
|
481
510
|
* Refresh messages for the current session
|
|
482
511
|
* Called after interrupt resolution when polling has stopped
|
|
@@ -487,9 +516,13 @@
|
|
|
487
516
|
|
|
488
517
|
try {
|
|
489
518
|
const response = await playgroundService.getMessages(sessionId);
|
|
490
|
-
|
|
519
|
+
applyServerResponse(response);
|
|
520
|
+
|
|
521
|
+
if (response.sessionStatus === 'running') {
|
|
522
|
+
startPolling(sessionId, true);
|
|
523
|
+
}
|
|
491
524
|
} catch (err) {
|
|
492
|
-
logger.error('[Playground] Failed to refresh
|
|
525
|
+
logger.error('[Playground] Failed to refresh after interrupt:', err);
|
|
493
526
|
}
|
|
494
527
|
}
|
|
495
528
|
|
|
@@ -771,6 +804,19 @@
|
|
|
771
804
|
Pipeline
|
|
772
805
|
</button>
|
|
773
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}
|
|
774
820
|
<button
|
|
775
821
|
type="button"
|
|
776
822
|
class="playground__log-toggle"
|
|
@@ -1313,6 +1359,10 @@
|
|
|
1313
1359
|
color: var(--fd-primary);
|
|
1314
1360
|
}
|
|
1315
1361
|
|
|
1362
|
+
.playground__refresh--spinning :global(svg) {
|
|
1363
|
+
animation: spin 0.8s linear infinite;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1316
1366
|
/* Session chip (standalone mode) */
|
|
1317
1367
|
.playground__session-chip-wrap {
|
|
1318
1368
|
position: relative;
|