@flowdrop/flowdrop 1.9.0 → 1.11.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.
Files changed (32) hide show
  1. package/dist/api/enhanced-client.js +5 -1
  2. package/dist/components/Navbar.svelte +1 -10
  3. package/dist/components/Navbar.svelte.d.ts +1 -9
  4. package/dist/components/PipelineStatus.svelte +17 -1
  5. package/dist/components/PipelineStatus.svelte.d.ts +2 -0
  6. package/dist/components/WorkflowEditor.svelte +26 -0
  7. package/dist/components/playground/ChatPanel.svelte +33 -235
  8. package/dist/components/playground/ExecutionList.svelte +2 -6
  9. package/dist/components/playground/MessageBubble.svelte +61 -4
  10. package/dist/components/playground/PipelinePanel.svelte +17 -7
  11. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -1
  12. package/dist/components/playground/Playground.svelte +123 -73
  13. package/dist/components/playground/PlaygroundApp.svelte +110 -0
  14. package/dist/components/playground/PlaygroundApp.svelte.d.ts +28 -0
  15. package/dist/components/playground/PlaygroundStudio.svelte +404 -0
  16. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +30 -0
  17. package/dist/editor/index.d.ts +1 -1
  18. package/dist/editor/index.js +1 -1
  19. package/dist/playground/index.d.ts +8 -3
  20. package/dist/playground/index.js +15 -5
  21. package/dist/playground/mount.d.ts +63 -4
  22. package/dist/playground/mount.js +173 -84
  23. package/dist/services/nodeExecutionService.js +4 -2
  24. package/dist/services/playgroundService.d.ts +11 -4
  25. package/dist/services/playgroundService.js +22 -12
  26. package/dist/stores/playgroundStore.svelte.d.ts +22 -21
  27. package/dist/stores/playgroundStore.svelte.js +79 -58
  28. package/dist/svelte-app.d.ts +2 -10
  29. package/dist/types/navbar.d.ts +14 -0
  30. package/dist/types/navbar.js +1 -0
  31. package/dist/types/playground.d.ts +3 -5
  32. package/package.json +1 -1
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module stores/playgroundStore
8
8
  */
9
- import { isChatInputNode, defaultIsTerminalStatus } from '../types/playground.js';
9
+ import { isChatInputNode } from '../types/playground.js';
10
10
  import { logger } from '../utils/logger.js';
11
11
  // =========================================================================
12
12
  // Core State
@@ -23,10 +23,6 @@ let _sessions = $state([]);
23
23
  * Messages in the current session
24
24
  */
25
25
  let _messages = $state([]);
26
- /**
27
- * Whether an execution is currently running
28
- */
29
- let _isExecuting = $state(false);
30
26
  /**
31
27
  * Whether we are currently loading data
32
28
  */
@@ -42,13 +38,19 @@ let _currentWorkflow = $state(null);
42
38
  /**
43
39
  * Last polling timestamp for incremental message fetching
44
40
  */
45
- let _lastPollTimestamp = $state(null);
41
+ let _lastPollSequenceNumber = $state(null);
46
42
  /** Execution ID explicitly pinned by the user (null = follow latest) */
47
43
  let _pinnedExecutionId = $state(null);
44
+ /** Incremented on every message batch that should trigger a pipeline re-fetch */
45
+ let _pipelineRefreshTrigger = $state(0);
48
46
  /** Latest execution ID derived from current session's executions list */
49
47
  const _latestExecutionId = $derived(_currentSession?.executions?.at(-1)?.id ?? null);
50
48
  /** Active execution: pinned if set, otherwise latest */
51
49
  const _activeExecutionId = $derived(_pinnedExecutionId ?? _latestExecutionId);
50
+ // Derived from server status — never manually set.
51
+ // Exception: updateSessionStatus('running') in handleSendMessage is an
52
+ // acknowledged optimistic write, overwritten by the next server response.
53
+ const _isExecuting = $derived(_currentSession?.status === 'running');
52
54
  // =========================================================================
53
55
  // Getter Functions (for reactive access in components)
54
56
  // =========================================================================
@@ -95,10 +97,10 @@ export function getCurrentWorkflow() {
95
97
  return _currentWorkflow;
96
98
  }
97
99
  /**
98
- * Get the last poll timestamp
100
+ * Get the last poll sequence number cursor
99
101
  */
100
- export function getLastPollTimestamp() {
101
- return _lastPollTimestamp;
102
+ export function getLastPollSequenceNumber() {
103
+ return _lastPollSequenceNumber;
102
104
  }
103
105
  // =========================================================================
104
106
  // Derived Getters
@@ -109,6 +111,14 @@ export function getLastPollTimestamp() {
109
111
  export function getSessionStatus() {
110
112
  return _currentSession?.status ?? 'idle';
111
113
  }
114
+ /**
115
+ * Whether the user can currently send a message.
116
+ * False when executing, when awaiting input, or when no session exists.
117
+ */
118
+ export function getCanSendMessage() {
119
+ const status = _currentSession?.status ?? 'idle';
120
+ return _currentSession !== null && !_isExecuting && status !== 'awaiting_input';
121
+ }
112
122
  /**
113
123
  * Get message count
114
124
  */
@@ -219,6 +229,14 @@ export function getLatestExecutionId() {
219
229
  export function getActiveExecutionId() {
220
230
  return _activeExecutionId;
221
231
  }
232
+ /**
233
+ * Counter that increments whenever new messages arrive and the pipeline display
234
+ * should re-fetch — i.e. when following latest or pinned to the latest execution.
235
+ * Pass to PipelinePanel's refreshTrigger prop.
236
+ */
237
+ export function getPipelineRefreshTrigger() {
238
+ return _pipelineRefreshTrigger;
239
+ }
222
240
  // =========================================================================
223
241
  // Helper Functions
224
242
  // =========================================================================
@@ -277,6 +295,8 @@ function syncExecutionsFromMessages(messages) {
277
295
  ..._currentSession,
278
296
  executions: [...(_currentSession.executions ?? []), ...newExecutions]
279
297
  };
298
+ // Clear any manual pin so the panel automatically follows the new run.
299
+ _pinnedExecutionId = null;
280
300
  }
281
301
  }
282
302
  // =========================================================================
@@ -323,11 +343,14 @@ export const playgroundActions = {
323
343
  // Update the latest execution status when the session reaches a terminal state.
324
344
  // Only the last execution can be running at any time (sessions are single-pipeline),
325
345
  // so we only need to check and update the tail entry.
326
- if ((status === 'completed' || status === 'failed') && _currentSession?.executions?.length) {
346
+ // 'idle' means the run finished normally (server returns 'idle' post-completion,
347
+ // not 'completed'), so map it to 'completed' for the execution entry.
348
+ const terminalExecutionStatus = status === 'failed' ? 'failed' : status === 'completed' || status === 'idle' ? 'completed' : null;
349
+ if (terminalExecutionStatus && _currentSession?.executions?.length) {
327
350
  const execs = [..._currentSession.executions];
328
351
  const last = execs[execs.length - 1];
329
352
  if (last.status === 'running') {
330
- execs[execs.length - 1] = { ...last, status };
353
+ execs[execs.length - 1] = { ...last, status: terminalExecutionStatus };
331
354
  _currentSession = { ..._currentSession, executions: execs };
332
355
  }
333
356
  }
@@ -402,27 +425,27 @@ export const playgroundActions = {
402
425
  addMessages: (newMessages) => {
403
426
  if (newMessages.length === 0)
404
427
  return;
405
- // Deduplicate by message ID
428
+ // Deduplicate against existing messages AND within the incoming batch itself.
429
+ // The latter matters when the backend returns the same page twice (e.g. broken
430
+ // offset pagination), which would otherwise create duplicate IDs in _messages
431
+ // and trigger Svelte's each_key_duplicate error.
406
432
  const existingIds = new Set(_messages.map((m) => m.id));
407
- const uniqueNewMessages = newMessages.filter((m) => !existingIds.has(m.id));
408
- // Sort the combined messages chronologically
433
+ const seenInBatch = new Set();
434
+ const uniqueNewMessages = newMessages.filter((m) => {
435
+ if (existingIds.has(m.id) || seenInBatch.has(m.id))
436
+ return false;
437
+ seenInBatch.add(m.id);
438
+ return true;
439
+ });
409
440
  _messages = sortMessagesChronologically([..._messages, ...uniqueNewMessages]);
410
- syncExecutionsFromMessages(newMessages);
441
+ syncExecutionsFromMessages(uniqueNewMessages);
411
442
  },
412
443
  /**
413
444
  * Clear all messages
414
445
  */
415
446
  clearMessages: () => {
416
447
  _messages = [];
417
- _lastPollTimestamp = null;
418
- },
419
- /**
420
- * Set the executing state
421
- *
422
- * @param executing - Whether execution is in progress
423
- */
424
- setExecuting: (executing) => {
425
- _isExecuting = executing;
448
+ _lastPollSequenceNumber = null;
426
449
  },
427
450
  /**
428
451
  * Set the loading state
@@ -445,8 +468,8 @@ export const playgroundActions = {
445
468
  *
446
469
  * @param timestamp - ISO 8601 timestamp
447
470
  */
448
- updateLastPollTimestamp: (timestamp) => {
449
- _lastPollTimestamp = timestamp;
471
+ updateLastPollSequenceNumber: (seq) => {
472
+ _lastPollSequenceNumber = seq;
450
473
  },
451
474
  /**
452
475
  * Reset all playground state
@@ -455,11 +478,11 @@ export const playgroundActions = {
455
478
  _currentSession = null;
456
479
  _sessions = [];
457
480
  _messages = [];
458
- _isExecuting = false;
459
481
  _isLoading = false;
460
482
  _error = null;
461
483
  _currentWorkflow = null;
462
- _lastPollTimestamp = null;
484
+ _lastPollSequenceNumber = null;
485
+ _pipelineRefreshTrigger = 0;
463
486
  },
464
487
  /**
465
488
  * Switch to a different session
@@ -472,7 +495,7 @@ export const playgroundActions = {
472
495
  if (session) {
473
496
  _currentSession = session;
474
497
  _messages = [];
475
- _lastPollTimestamp = null;
498
+ _lastPollSequenceNumber = null;
476
499
  }
477
500
  },
478
501
  pinExecution(executionId) {
@@ -480,28 +503,25 @@ export const playgroundActions = {
480
503
  }
481
504
  };
482
505
  // =========================================================================
483
- // Polling Callback Factory
506
+ // Server Response Application
484
507
  // =========================================================================
485
508
  /**
486
- * Create a polling callback that processes poll responses.
487
- * This is the single source of truth for how poll responses update stores.
488
- * Used by mount.ts, Playground.svelte, and refreshSessionMessages.
489
- *
490
- * @param isTerminalStatus - Function to determine if a status clears isExecuting (default: defaultIsTerminalStatus)
491
- * @returns A callback suitable for playgroundService.startPolling() or pushMessages()
492
- */
493
- export function createPollingCallback(isTerminalStatus = defaultIsTerminalStatus) {
494
- return (response) => {
495
- if (response.data && response.data.length > 0) {
496
- playgroundActions.addMessages(response.data);
509
+ * Apply a server response to the store. All message and status updates from
510
+ * the server flow through here polling callback, manual fetches, interrupt
511
+ * resolution. Nothing updates messages or session status except this function.
512
+ */
513
+ export function applyServerResponse(response) {
514
+ if (response.data && response.data.length > 0) {
515
+ playgroundActions.addMessages(response.data);
516
+ // Refresh pipeline when following latest or pinned to the latest execution.
517
+ // Skip only when the user is viewing a historical run.
518
+ if (_pinnedExecutionId === null || _pinnedExecutionId === _latestExecutionId) {
519
+ _pipelineRefreshTrigger++;
497
520
  }
498
- if (response.sessionStatus) {
499
- playgroundActions.updateSessionStatus(response.sessionStatus);
500
- if (isTerminalStatus(response.sessionStatus)) {
501
- playgroundActions.setExecuting(false);
502
- }
503
- }
504
- };
521
+ }
522
+ if (response.sessionStatus) {
523
+ playgroundActions.updateSessionStatus(response.sessionStatus);
524
+ }
505
525
  }
506
526
  // =========================================================================
507
527
  // Utilities
@@ -532,14 +552,17 @@ export function getMessagesSnapshot() {
532
552
  return _messages;
533
553
  }
534
554
  /**
535
- * Get the latest message timestamp for polling
555
+ * Get the sequence number of the latest message, used to seed incremental polling.
536
556
  *
537
- * @returns ISO 8601 timestamp of the latest message, or null
557
+ * @returns Sequence number of the last message, or null
538
558
  */
539
- export function getLatestMessageTimestamp() {
540
- if (_messages.length === 0)
541
- return null;
542
- return _messages[_messages.length - 1].timestamp;
559
+ export function getLatestSequenceNumber() {
560
+ for (let i = _messages.length - 1; i >= 0; i--) {
561
+ if (_messages[i].sequenceNumber !== undefined) {
562
+ return _messages[i].sequenceNumber;
563
+ }
564
+ }
565
+ return null;
543
566
  }
544
567
  /**
545
568
  * Subscribe to session status changes using $effect.root.
@@ -568,17 +591,15 @@ export function subscribeToSessionStatus(callback) {
568
591
  * has stopped but new messages may exist on the server.
569
592
  *
570
593
  * @param fetchMessages - Async function to fetch messages from the API
571
- * @param isTerminalStatus - Optional override for terminal status check
572
594
  * @returns Promise that resolves when messages are refreshed
573
595
  */
574
- export async function refreshSessionMessages(fetchMessages, isTerminalStatus) {
596
+ export async function refreshSessionMessages(fetchMessages) {
575
597
  const session = _currentSession;
576
598
  if (!session)
577
599
  return;
578
600
  try {
579
601
  const response = await fetchMessages(session.id);
580
- const callback = createPollingCallback(isTerminalStatus);
581
- callback(response);
602
+ applyServerResponse(response);
582
603
  }
583
604
  catch (err) {
584
605
  logger.error('[playgroundStore] Failed to refresh messages:', err);
@@ -14,16 +14,8 @@ import type { FlowDropTheme, FlowDropThemeName } from './types/theme.js';
14
14
  import type { WorkflowFormatAdapter } from './registry/workflowFormatRegistry.js';
15
15
  import './registry/builtinFormats.js';
16
16
  import type { PartialSettings, SettingsCategory } from './types/settings.js';
17
- /**
18
- * Navbar action configuration
19
- */
20
- export interface NavbarAction {
21
- label: string;
22
- href: string;
23
- icon?: string;
24
- variant?: 'primary' | 'secondary' | 'outline';
25
- onclick?: (event: Event) => void;
26
- }
17
+ import type { NavbarAction } from './types/navbar.js';
18
+ export type { NavbarAction };
27
19
  /**
28
20
  * Mount options for FlowDrop App
29
21
  */
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Navbar action button configuration.
3
+ *
4
+ * Rendered as a link by `<Navbar>` and the mount functions that wrap it.
5
+ */
6
+ export interface NavbarAction {
7
+ label: string;
8
+ href: string;
9
+ icon?: string;
10
+ variant?: 'primary' | 'secondary' | 'outline';
11
+ onclick?: (event: Event) => void;
12
+ /** If true, opens link in new tab with `rel="noopener noreferrer"`. */
13
+ external?: boolean;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -110,6 +110,8 @@ export interface PlaygroundMessageMetadata {
110
110
  outputs?: Record<string, unknown>;
111
111
  /** User's display name for user-role messages (from backend) */
112
112
  userName?: string;
113
+ /** Subsystem that produced this message (e.g. 'pipeline', 'job', 'queue', 'cron') */
114
+ source?: string;
113
115
  /** Allow additional properties */
114
116
  [key: string]: unknown;
115
117
  }
@@ -146,11 +148,7 @@ export interface PlaygroundMessage {
146
148
  timestamp: string;
147
149
  /** Message status */
148
150
  status?: PlaygroundMessageStatus;
149
- /**
150
- * Sequence number for ordering messages
151
- * - User messages: incrementing numbers (1, 2, 3, ...)
152
- * - Assistant/system responses: 0 (sorted after parent via parentMessageId)
153
- */
151
+ /** Incrementing sequence number for chronological ordering. All message roles receive unique incrementing numbers (1, 2, 3, ...). Primary sort key. */
154
152
  sequenceNumber?: number;
155
153
  /** Parent message ID (for assistant responses linked to user messages) */
156
154
  parentMessageId?: string;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration.",
4
4
  "license": "MIT",
5
5
  "private": false,
6
- "version": "1.9.0",
6
+ "version": "1.11.0",
7
7
  "author": "Shibin Das (D34dMan)",
8
8
  "bugs": {
9
9
  "url": "https://github.com/flowdrop-io/flowdrop/issues"