@flowdrop/flowdrop 1.3.0 → 1.5.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 (114) hide show
  1. package/README.md +68 -24
  2. package/dist/adapters/WorkflowAdapter.js +2 -22
  3. package/dist/adapters/agentspec/autoLayout.d.ts +51 -5
  4. package/dist/adapters/agentspec/autoLayout.js +120 -23
  5. package/dist/chat/commandClassifier.d.ts +19 -0
  6. package/dist/chat/commandClassifier.js +30 -0
  7. package/dist/chat/index.d.ts +27 -0
  8. package/dist/chat/index.js +32 -0
  9. package/dist/chat/responseParser.d.ts +21 -0
  10. package/dist/chat/responseParser.js +87 -0
  11. package/dist/commands/batch.d.ts +18 -0
  12. package/dist/commands/batch.js +56 -0
  13. package/dist/commands/executor.d.ts +37 -0
  14. package/dist/commands/executor.js +1044 -0
  15. package/dist/commands/index.d.ts +14 -0
  16. package/dist/commands/index.js +17 -0
  17. package/dist/commands/parser.d.ts +16 -0
  18. package/dist/commands/parser.js +278 -0
  19. package/dist/commands/positioner.d.ts +19 -0
  20. package/dist/commands/positioner.js +33 -0
  21. package/dist/commands/storeIntegration.svelte.d.ts +16 -0
  22. package/dist/commands/storeIntegration.svelte.js +67 -0
  23. package/dist/commands/types.d.ts +343 -0
  24. package/dist/commands/types.js +45 -0
  25. package/dist/components/App.svelte +431 -17
  26. package/dist/components/App.svelte.d.ts +10 -0
  27. package/dist/components/CanvasBanner.stories.svelte +6 -2
  28. package/dist/components/CanvasController.svelte +38 -0
  29. package/dist/components/CanvasController.svelte.d.ts +32 -0
  30. package/dist/components/ConfigMappingRow.svelte +130 -0
  31. package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
  32. package/dist/components/ConfigPanel.svelte +56 -7
  33. package/dist/components/ConfigPanel.svelte.d.ts +2 -0
  34. package/dist/components/FlowDropEdge.svelte +8 -57
  35. package/dist/components/Logo.svelte +14 -14
  36. package/dist/components/LogsSidebar.svelte +5 -5
  37. package/dist/components/Navbar.svelte +58 -10
  38. package/dist/components/Navbar.svelte.d.ts +7 -0
  39. package/dist/components/NodeSidebar.svelte +238 -362
  40. package/dist/components/NodeSwapPicker.svelte +537 -0
  41. package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
  42. package/dist/components/PortMappingRow.svelte +209 -0
  43. package/dist/components/PortMappingRow.svelte.d.ts +12 -0
  44. package/dist/components/SwapMappingEditor.svelte +550 -0
  45. package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
  46. package/dist/components/WorkflowEditor.svelte +99 -4
  47. package/dist/components/WorkflowEditor.svelte.d.ts +8 -0
  48. package/dist/components/chat/AIChatPanel.svelte +658 -0
  49. package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
  50. package/dist/components/chat/CommandPreview.svelte +184 -0
  51. package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
  52. package/dist/components/console/CommandConsole.stories.svelte +93 -0
  53. package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
  54. package/dist/components/console/CommandConsole.svelte +259 -0
  55. package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
  56. package/dist/components/console/ConsoleAutocomplete.svelte +139 -0
  57. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
  58. package/dist/components/console/ConsoleInput.svelte +712 -0
  59. package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
  60. package/dist/components/console/ConsoleOutput.svelte +121 -0
  61. package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
  62. package/dist/components/console/formatters.d.ts +26 -0
  63. package/dist/components/console/formatters.js +118 -0
  64. package/dist/components/interrupt/index.d.ts +1 -0
  65. package/dist/components/interrupt/index.js +1 -0
  66. package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
  67. package/dist/components/nodes/SimpleNode.svelte +27 -11
  68. package/dist/components/nodes/SquareNode.stories.svelte +45 -0
  69. package/dist/components/nodes/SquareNode.svelte +27 -11
  70. package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
  71. package/dist/config/endpoints.d.ts +8 -0
  72. package/dist/config/endpoints.js +5 -0
  73. package/dist/core/index.d.ts +5 -0
  74. package/dist/core/index.js +9 -0
  75. package/dist/editor/index.d.ts +3 -1
  76. package/dist/editor/index.js +4 -2
  77. package/dist/helpers/proximityConnect.js +8 -1
  78. package/dist/helpers/workflowEditorHelper.d.ts +3 -53
  79. package/dist/helpers/workflowEditorHelper.js +13 -228
  80. package/dist/playground/index.d.ts +1 -1
  81. package/dist/playground/index.js +1 -1
  82. package/dist/schemas/v1/workflow.schema.json +107 -22
  83. package/dist/services/chatService.d.ts +65 -0
  84. package/dist/services/chatService.js +131 -0
  85. package/dist/services/historyService.d.ts +6 -4
  86. package/dist/services/historyService.js +21 -6
  87. package/dist/skins/slate.js +16 -0
  88. package/dist/stores/interruptStore.svelte.js +6 -1
  89. package/dist/stores/playgroundStore.svelte.d.ts +1 -1
  90. package/dist/stores/playgroundStore.svelte.js +11 -2
  91. package/dist/stores/portCoordinateStore.svelte.d.ts +4 -0
  92. package/dist/stores/portCoordinateStore.svelte.js +20 -26
  93. package/dist/stores/workflowStore.svelte.d.ts +31 -2
  94. package/dist/stores/workflowStore.svelte.js +84 -64
  95. package/dist/stories/EdgeDecorator.svelte +4 -4
  96. package/dist/styles/base.css +48 -0
  97. package/dist/svelte-app.d.ts +7 -1
  98. package/dist/svelte-app.js +4 -1
  99. package/dist/types/chat.d.ts +63 -0
  100. package/dist/types/chat.js +9 -0
  101. package/dist/types/events.d.ts +28 -2
  102. package/dist/types/events.js +1 -0
  103. package/dist/types/index.d.ts +8 -0
  104. package/dist/types/settings.d.ts +6 -0
  105. package/dist/types/settings.js +3 -0
  106. package/dist/utils/edgeStyling.d.ts +42 -0
  107. package/dist/utils/edgeStyling.js +176 -0
  108. package/dist/utils/nodeIds.d.ts +31 -0
  109. package/dist/utils/nodeIds.js +42 -0
  110. package/dist/utils/nodeSwap.d.ts +221 -0
  111. package/dist/utils/nodeSwap.js +686 -0
  112. package/package.json +6 -1
  113. package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
  114. package/dist/helpers/nodeLayoutHelper.js +0 -19
@@ -126,27 +126,19 @@ export function updateNodePortCoordinates(node, getInternalNode) {
126
126
  const internalNode = getInternalNode(node.id);
127
127
  if (!internalNode)
128
128
  return;
129
- // Remove old entries for this node.
130
- // untrack prevents this read from creating a reactive dependency on `coordinates`
131
- // inside any $effect that calls this function — otherwise the effect would re-run
132
- // every time we mutate `coordinates`, creating an infinite reactive loop during drag.
133
- const keysToDelete = untrack(() => {
134
- const keys = [];
129
+ // Build a new map with all entries except this node's, then add recomputed entries.
130
+ // Single assignment fires one reactive notification instead of N deletes + M sets.
131
+ const newMap = new SvelteMap();
132
+ untrack(() => {
135
133
  for (const [key, coord] of coordinates) {
136
- if (coord.nodeId === node.id) {
137
- keys.push(key);
138
- }
134
+ if (coord.nodeId !== node.id)
135
+ newMap.set(key, coord);
139
136
  }
140
- return keys;
141
137
  });
142
- for (const key of keysToDelete) {
143
- coordinates.delete(key);
144
- }
145
- // Add new entries
146
- const coords = computeNodePortCoordinates(node, internalNode);
147
- for (const coord of coords) {
148
- coordinates.set(coord.handleId, coord);
138
+ for (const coord of computeNodePortCoordinates(node, internalNode)) {
139
+ newMap.set(coord.handleId, coord);
149
140
  }
141
+ coordinates = newMap;
150
142
  }
151
143
  /**
152
144
  * Remove all coordinates for a node (on node delete).
@@ -154,18 +146,20 @@ export function updateNodePortCoordinates(node, getInternalNode) {
154
146
  * @param nodeId - ID of the node to remove
155
147
  */
156
148
  export function removeNodePortCoordinates(nodeId) {
157
- const keysToDelete = untrack(() => {
158
- const keys = [];
149
+ const newMap = new SvelteMap();
150
+ untrack(() => {
159
151
  for (const [key, coord] of coordinates) {
160
- if (coord.nodeId === nodeId) {
161
- keys.push(key);
162
- }
152
+ if (coord.nodeId !== nodeId)
153
+ newMap.set(key, coord);
163
154
  }
164
- return keys;
165
155
  });
166
- for (const key of keysToDelete) {
167
- coordinates.delete(key);
168
- }
156
+ coordinates = newMap;
157
+ }
158
+ /**
159
+ * Clear all port coordinates (lifecycle cleanup).
160
+ */
161
+ export function clearPortCoordinates() {
162
+ coordinates = new SvelteMap();
169
163
  }
170
164
  /**
171
165
  * Get coordinates for a specific handle.
@@ -22,6 +22,10 @@ export declare function getWorkflowStore(): Workflow | null;
22
22
  /**
23
23
  * Get the current dirty state reactively
24
24
  *
25
+ * Reads both _editVersion and _savedVersion, so Svelte tracks them
26
+ * as reactive dependencies — any component using this will re-render
27
+ * when dirty state changes.
28
+ *
25
29
  * @returns true if there are unsaved changes
26
30
  */
27
31
  export declare function getIsDirty(): boolean;
@@ -121,9 +125,10 @@ export declare function setOnDirtyStateChange(callback: ((isDirty: boolean) => v
121
125
  */
122
126
  export declare function setOnWorkflowChange(callback: ((workflow: Workflow, changeType: WorkflowChangeType) => void) | null): void;
123
127
  /**
124
- * Mark the current workflow state as saved
128
+ * Mark the current workflow state as saved.
125
129
  *
126
- * Clears the dirty state by updating the saved snapshot.
130
+ * Captures the current edit version so isDirty becomes false.
131
+ * Call this after a successful backend save.
127
132
  */
128
133
  export declare function markAsSaved(): void;
129
134
  /**
@@ -132,6 +137,19 @@ export declare function markAsSaved(): void;
132
137
  * @returns true if there are unsaved changes
133
138
  */
134
139
  export declare function isDirty(): boolean;
140
+ /**
141
+ * Get the current edit version.
142
+ *
143
+ * Use this for the save verification protocol:
144
+ * 1. Before save: `const v = getEditVersion()`
145
+ * 2. Include `v` in the save request payload
146
+ * 3. Backend echoes `v` in the response
147
+ * 4. If echoed version matches: `markAsSaved()`
148
+ * 5. If not: the save didn't persist the version you submitted — reset from backend response
149
+ *
150
+ * @returns The current monotonic edit version
151
+ */
152
+ export declare function getEditVersion(): number;
135
153
  /**
136
154
  * Enable or disable history recording
137
155
  *
@@ -246,6 +264,17 @@ export declare const workflowActions: {
246
264
  description?: string;
247
265
  metadata?: Partial<Workflow["metadata"]>;
248
266
  }) => void;
267
+ /**
268
+ * Swap a node — atomically replaces nodes and edges with a descriptive history entry.
269
+ *
270
+ * Unlike batchUpdate, this uses `"node_swap"` as the change type and
271
+ * records a meaningful description for the undo history.
272
+ */
273
+ swapNode: (updates: {
274
+ nodes: WorkflowNode[];
275
+ edges: WorkflowEdge[];
276
+ description?: string;
277
+ }) => void;
249
278
  /**
250
279
  * Push current state to history manually
251
280
  *
@@ -34,21 +34,23 @@ function buildMetadata(existing, updates) {
34
34
  /** Global workflow state */
35
35
  let workflowState = $state(null);
36
36
  // =========================================================================
37
- // Dirty State Tracking
37
+ // Dirty State Tracking (Version Counter)
38
38
  // =========================================================================
39
39
  /**
40
- * State for tracking if there are unsaved changes
40
+ * Monotonic edit version bumps on every mutation action.
41
41
  *
42
- * This is set to true whenever the workflow changes after initialization.
43
- * It can be reset to false by calling markAsSaved().
42
+ * Used for two purposes:
43
+ * 1. O(1) dirty detection: isDirty = _editVersion !== _savedVersion
44
+ * 2. Save verification protocol: include the version in the save request,
45
+ * backend echoes it back. If the echoed version doesn't match, the save
46
+ * didn't persist the version the client submitted.
44
47
  */
45
- let isDirtyState = $state(false);
48
+ let _editVersion = $state(0);
46
49
  /**
47
- * Snapshot of the workflow when it was last saved
48
- *
49
- * Used to compare current state with saved state.
50
+ * Edit version captured at the last successful save.
51
+ * When _editVersion === _savedVersion, the workflow is clean.
50
52
  */
51
- let savedSnapshot = null;
53
+ let _savedVersion = $state(0);
52
54
  /**
53
55
  * Callback for dirty state changes
54
56
  *
@@ -87,10 +89,14 @@ export function getWorkflowStore() {
87
89
  /**
88
90
  * Get the current dirty state reactively
89
91
  *
92
+ * Reads both _editVersion and _savedVersion, so Svelte tracks them
93
+ * as reactive dependencies — any component using this will re-render
94
+ * when dirty state changes.
95
+ *
90
96
  * @returns true if there are unsaved changes
91
97
  */
92
98
  export function getIsDirty() {
93
- return isDirtyState;
99
+ return _editVersion !== _savedVersion;
94
100
  }
95
101
  /**
96
102
  * Get the workflow ID reactively
@@ -239,53 +245,21 @@ export function setOnWorkflowChange(callback) {
239
245
  // Internal Helpers
240
246
  // =========================================================================
241
247
  /**
242
- * Create a snapshot of the workflow for comparison
243
- *
244
- * @param workflow - The workflow to snapshot
245
- * @returns A JSON string representation for comparison
246
- */
247
- function createSnapshot(workflow) {
248
- if (!workflow)
249
- return null;
250
- // Only include the parts that matter for "dirty" detection
251
- const toSnapshot = {
252
- name: workflow.name,
253
- description: workflow.description,
254
- nodes: workflow.nodes.map((n) => ({
255
- id: n.id,
256
- position: n.position,
257
- data: {
258
- label: n.data.label,
259
- config: n.data.config,
260
- },
261
- })),
262
- edges: workflow.edges.map((e) => ({
263
- id: e.id,
264
- source: e.source,
265
- target: e.target,
266
- sourceHandle: e.sourceHandle,
267
- targetHandle: e.targetHandle,
268
- })),
269
- };
270
- return JSON.stringify(toSnapshot);
271
- }
272
- /**
273
- * Update dirty state based on current workflow
274
- *
275
- * Compares current workflow with saved snapshot.
248
+ * Bump the edit version and notify dirty state change if needed.
249
+ * Called by every mutation action.
276
250
  */
277
- function updateDirtyState() {
278
- const currentSnapshot = createSnapshot(workflowState);
279
- const newIsDirty = currentSnapshot !== savedSnapshot;
280
- if (newIsDirty !== isDirtyState) {
281
- isDirtyState = newIsDirty;
251
+ function bumpVersion() {
252
+ _editVersion++;
253
+ // Dirty state just flipped from clean → dirty
254
+ if (_editVersion - 1 === _savedVersion) {
282
255
  if (onDirtyStateChangeCallback) {
283
- onDirtyStateChangeCallback(newIsDirty);
256
+ onDirtyStateChangeCallback(true);
284
257
  }
285
258
  }
286
259
  }
287
260
  /**
288
- * Mark the workflow change and update dirty state
261
+ * Notify external listeners of a workflow change.
262
+ * Does NOT bump the version — callers that mutate must call bumpVersion() explicitly.
289
263
  *
290
264
  * @param changeType - The type of change that occurred
291
265
  */
@@ -293,17 +267,17 @@ function notifyWorkflowChange(changeType) {
293
267
  if (workflowState && onWorkflowChangeCallback) {
294
268
  onWorkflowChangeCallback(workflowState, changeType);
295
269
  }
296
- updateDirtyState();
297
270
  }
298
271
  /**
299
- * Mark the current workflow state as saved
272
+ * Mark the current workflow state as saved.
300
273
  *
301
- * Clears the dirty state by updating the saved snapshot.
274
+ * Captures the current edit version so isDirty becomes false.
275
+ * Call this after a successful backend save.
302
276
  */
303
277
  export function markAsSaved() {
304
- savedSnapshot = createSnapshot(workflowState);
305
- isDirtyState = false;
306
- if (onDirtyStateChangeCallback) {
278
+ const wasDirty = _editVersion !== _savedVersion;
279
+ _savedVersion = _editVersion;
280
+ if (wasDirty && onDirtyStateChangeCallback) {
307
281
  onDirtyStateChangeCallback(false);
308
282
  }
309
283
  }
@@ -313,7 +287,22 @@ export function markAsSaved() {
313
287
  * @returns true if there are unsaved changes
314
288
  */
315
289
  export function isDirty() {
316
- return isDirtyState;
290
+ return _editVersion !== _savedVersion;
291
+ }
292
+ /**
293
+ * Get the current edit version.
294
+ *
295
+ * Use this for the save verification protocol:
296
+ * 1. Before save: `const v = getEditVersion()`
297
+ * 2. Include `v` in the save request payload
298
+ * 3. Backend echoes `v` in the response
299
+ * 4. If echoed version matches: `markAsSaved()`
300
+ * 5. If not: the save didn't persist the version you submitted — reset from backend response
301
+ *
302
+ * @returns The current monotonic edit version
303
+ */
304
+ export function getEditVersion() {
305
+ return _editVersion;
317
306
  }
318
307
  /**
319
308
  * Enable or disable history recording
@@ -390,7 +379,7 @@ function hasWorkflowDataChanged(currentWorkflow, newNodes, newEdges) {
390
379
  if (currentNode.position.x !== newNode.position.x ||
391
380
  currentNode.position.y !== newNode.position.y)
392
381
  return true;
393
- if (JSON.stringify(currentNode.data) !== JSON.stringify(newNode.data))
382
+ if (currentNode.data !== newNode.data)
394
383
  return true;
395
384
  }
396
385
  // Check if edges have changed
@@ -426,9 +415,9 @@ export const workflowActions = {
426
415
  */
427
416
  initialize: (workflow) => {
428
417
  workflowState = workflow;
429
- // Set the saved snapshot - workflow is "clean" after initialization
430
- savedSnapshot = createSnapshot(workflow);
431
- isDirtyState = false;
418
+ // Reset version counters workflow is "clean" after initialization
419
+ _editVersion = 0;
420
+ _savedVersion = 0;
432
421
  if (onDirtyStateChangeCallback) {
433
422
  onDirtyStateChangeCallback(false);
434
423
  }
@@ -443,6 +432,7 @@ export const workflowActions = {
443
432
  */
444
433
  updateWorkflow: (workflow) => {
445
434
  workflowState = workflow;
435
+ bumpVersion();
446
436
  notifyWorkflowChange("metadata");
447
437
  },
448
438
  /**
@@ -453,6 +443,7 @@ export const workflowActions = {
453
443
  restoreFromHistory: (workflow) => {
454
444
  isRestoringFromHistory = true;
455
445
  workflowState = workflow;
446
+ bumpVersion();
456
447
  notifyWorkflowChange("metadata");
457
448
  isRestoringFromHistory = false;
458
449
  },
@@ -476,6 +467,7 @@ export const workflowActions = {
476
467
  updateNumber: (workflowState.metadata?.updateNumber ?? 0) + 1,
477
468
  }),
478
469
  };
470
+ bumpVersion();
479
471
  notifyWorkflowChange("node_move");
480
472
  },
481
473
  /**
@@ -498,6 +490,7 @@ export const workflowActions = {
498
490
  updateNumber: (workflowState.metadata?.updateNumber ?? 0) + 1,
499
491
  }),
500
492
  };
493
+ bumpVersion();
501
494
  notifyWorkflowChange("edge_add");
502
495
  },
503
496
  /**
@@ -511,6 +504,7 @@ export const workflowActions = {
511
504
  name,
512
505
  metadata: buildMetadata(workflowState.metadata),
513
506
  };
507
+ bumpVersion();
514
508
  notifyWorkflowChange("name");
515
509
  },
516
510
  /**
@@ -525,6 +519,7 @@ export const workflowActions = {
525
519
  nodes: [...workflowState.nodes, node],
526
520
  metadata: buildMetadata(workflowState.metadata),
527
521
  };
522
+ bumpVersion();
528
523
  notifyWorkflowChange("node_add");
529
524
  },
530
525
  /**
@@ -543,6 +538,7 @@ export const workflowActions = {
543
538
  edges: workflowState.edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId),
544
539
  metadata: buildMetadata(workflowState.metadata),
545
540
  };
541
+ bumpVersion();
546
542
  notifyWorkflowChange("node_remove");
547
543
  },
548
544
  /**
@@ -557,6 +553,7 @@ export const workflowActions = {
557
553
  edges: [...workflowState.edges, edge],
558
554
  metadata: buildMetadata(workflowState.metadata),
559
555
  };
556
+ bumpVersion();
560
557
  notifyWorkflowChange("edge_add");
561
558
  },
562
559
  /**
@@ -571,6 +568,7 @@ export const workflowActions = {
571
568
  edges: workflowState.edges.filter((edge) => edge.id !== edgeId),
572
569
  metadata: buildMetadata(workflowState.metadata),
573
570
  };
571
+ bumpVersion();
574
572
  notifyWorkflowChange("edge_remove");
575
573
  },
576
574
  /**
@@ -587,6 +585,7 @@ export const workflowActions = {
587
585
  nodes: workflowState.nodes.map((node) => node.id === nodeId ? { ...node, ...updates } : node),
588
586
  metadata: buildMetadata(workflowState.metadata),
589
587
  };
588
+ bumpVersion();
590
589
  notifyWorkflowChange("node_config");
591
590
  },
592
591
  /**
@@ -596,8 +595,8 @@ export const workflowActions = {
596
595
  */
597
596
  clear: () => {
598
597
  workflowState = null;
599
- savedSnapshot = null;
600
- isDirtyState = false;
598
+ _editVersion = 0;
599
+ _savedVersion = 0;
601
600
  historyService.clear();
602
601
  if (onDirtyStateChangeCallback) {
603
602
  onDirtyStateChangeCallback(false);
@@ -613,6 +612,7 @@ export const workflowActions = {
613
612
  ...workflowState,
614
613
  metadata: buildMetadata(workflowState.metadata, metadata),
615
614
  };
615
+ bumpVersion();
616
616
  notifyWorkflowChange("metadata");
617
617
  },
618
618
  /**
@@ -635,8 +635,28 @@ export const workflowActions = {
635
635
  }),
636
636
  metadata: buildMetadata(workflowState.metadata, updates.metadata ?? undefined),
637
637
  };
638
+ bumpVersion();
638
639
  notifyWorkflowChange("metadata");
639
640
  },
641
+ /**
642
+ * Swap a node — atomically replaces nodes and edges with a descriptive history entry.
643
+ *
644
+ * Unlike batchUpdate, this uses `"node_swap"` as the change type and
645
+ * records a meaningful description for the undo history.
646
+ */
647
+ swapNode: (updates) => {
648
+ pushToHistory(updates.description ?? "Swap node");
649
+ if (!workflowState)
650
+ return;
651
+ workflowState = {
652
+ ...workflowState,
653
+ nodes: updates.nodes,
654
+ edges: updates.edges,
655
+ metadata: buildMetadata(workflowState.metadata),
656
+ };
657
+ bumpVersion();
658
+ notifyWorkflowChange("node_swap");
659
+ },
640
660
  /**
641
661
  * Push current state to history manually
642
662
  *
@@ -46,7 +46,7 @@
46
46
  const SOURCE_ID = "source-node";
47
47
  const TARGET_ID = "target-node";
48
48
 
49
- let nodes = $state<Node[]>([
49
+ let nodes = $derived<Node[]>([
50
50
  {
51
51
  id: SOURCE_ID,
52
52
  type: "universalNode",
@@ -62,7 +62,7 @@
62
62
  ]);
63
63
 
64
64
  // Handle IDs follow the format: {nodeId}-{input|output}-{portId}
65
- let edges = $state<Edge[]>([
65
+ let edges = $derived<Edge[]>([
66
66
  {
67
67
  id: "edge-1",
68
68
  source: SOURCE_ID,
@@ -100,8 +100,8 @@
100
100
 
101
101
  <div class="edge-decorator-wrapper">
102
102
  <SvelteFlow
103
- bind:nodes
104
- bind:edges
103
+ {nodes}
104
+ {edges}
105
105
  {nodeTypes}
106
106
  {edgeTypes}
107
107
  fitView
@@ -30,6 +30,54 @@
30
30
  box-shadow: var(--fd-shadow-md);
31
31
  }
32
32
 
33
+ /* xyflow Controls & MiniMap: wire up to skin tokens so themes can restyle them */
34
+ .svelte-flow {
35
+ --xy-controls-button-background-color: var(
36
+ --fd-controls-button-bg,
37
+ var(--fd-card)
38
+ );
39
+ --xy-controls-button-background-color-hover: var(
40
+ --fd-controls-button-bg-hover,
41
+ var(--fd-muted)
42
+ );
43
+ --xy-controls-button-color: var(
44
+ --fd-controls-button-color,
45
+ var(--fd-foreground)
46
+ );
47
+ --xy-controls-button-color-hover: var(
48
+ --fd-controls-button-color-hover,
49
+ var(--fd-foreground)
50
+ );
51
+ --xy-controls-button-border-color: var(
52
+ --fd-controls-button-border,
53
+ var(--fd-border)
54
+ );
55
+ --xy-controls-box-shadow: var(
56
+ --fd-controls-box-shadow,
57
+ 0 0 2px 1px rgba(0, 0, 0, 0.08)
58
+ );
59
+
60
+ --xy-minimap-background-color: var(--fd-minimap-bg, var(--fd-card));
61
+ --xy-minimap-mask-background-color: var(
62
+ --fd-minimap-mask-bg,
63
+ var(--fd-backdrop)
64
+ );
65
+ --xy-minimap-mask-stroke-color: var(
66
+ --fd-minimap-mask-stroke,
67
+ var(--fd-border)
68
+ );
69
+ --xy-minimap-mask-stroke-width: var(--fd-minimap-mask-stroke-width, 1);
70
+ --xy-minimap-node-background-color: var(
71
+ --fd-minimap-node-bg,
72
+ var(--fd-muted)
73
+ );
74
+ --xy-minimap-node-stroke-color: var(
75
+ --fd-minimap-node-stroke,
76
+ var(--fd-border-muted)
77
+ );
78
+ --xy-minimap-node-stroke-width: var(--fd-minimap-node-stroke-width, 2);
79
+ }
80
+
33
81
  /* Flow node handles: 20px connection area, 12px visible circle (::before)
34
82
  Override xyflow's default background so port color (--fd-handle-fill from inline style) shows.
35
83
  Use `.svelte-flow` parent for higher specificity (0-2-0) to beat xyflow defaults (0-1-0). */
@@ -13,7 +13,7 @@ import type { FlowDropEventHandlers, FlowDropFeatures } from "./types/events.js"
13
13
  import type { FlowDropTheme, FlowDropThemeName } from "./types/theme.js";
14
14
  import type { WorkflowFormatAdapter } from "./registry/workflowFormatRegistry.js";
15
15
  import "./registry/builtinFormats.js";
16
- import type { PartialSettings } from "./types/settings.js";
16
+ import type { PartialSettings, SettingsCategory } from "./types/settings.js";
17
17
  /**
18
18
  * Navbar action configuration
19
19
  */
@@ -74,6 +74,12 @@ export interface FlowDropMountOptions {
74
74
  formatAdapters?: WorkflowFormatAdapter[];
75
75
  /** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
76
76
  theme?: FlowDropTheme | FlowDropThemeName;
77
+ /** Which settings tabs to show in the modal (defaults to all) */
78
+ settingsCategories?: SettingsCategory[];
79
+ /** Show the "Sync to Cloud" button in the settings modal */
80
+ showSettingsSyncButton?: boolean;
81
+ /** Show the reset buttons in the settings modal */
82
+ showSettingsResetButton?: boolean;
77
83
  }
78
84
  /**
79
85
  * Return type for mounted FlowDrop app
@@ -47,7 +47,7 @@ import { globalSaveWorkflow, globalExportWorkflow, } from "./services/globalSave
47
47
  * ```
48
48
  */
49
49
  export async function mountFlowDropApp(container, options = {}) {
50
- const { workflow, nodes, endpointConfig, portConfig, categories, height = "100vh", width = "100%", showNavbar = false, disableSidebar, lockWorkflow, readOnly, nodeStatuses, pipelineId, navbarTitle, navbarActions, showSettings, authProvider, eventHandlers, features: userFeatures, settings: initialSettings, draftStorageKey: customDraftKey, formatAdapters, theme, } = options;
50
+ const { workflow, nodes, endpointConfig, portConfig, categories, height = "100vh", width = "100%", showNavbar = false, disableSidebar, lockWorkflow, readOnly, nodeStatuses, pipelineId, navbarTitle, navbarActions, showSettings, authProvider, eventHandlers, features: userFeatures, settings: initialSettings, draftStorageKey: customDraftKey, formatAdapters, theme, settingsCategories, showSettingsSyncButton, showSettingsResetButton, } = options;
51
51
  // Register custom format adapters before mounting
52
52
  if (formatAdapters) {
53
53
  for (const adapter of formatAdapters) {
@@ -137,6 +137,9 @@ export async function mountFlowDropApp(container, options = {}) {
137
137
  eventHandlers,
138
138
  features,
139
139
  theme,
140
+ settingsCategories,
141
+ showSettingsSyncButton,
142
+ showSettingsResetButton,
140
143
  },
141
144
  });
142
145
  // Set up draft auto-save manager
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Chat Types for FlowDrop LLM Chat Interface
3
+ *
4
+ * Provides type definitions for the chat panel's communication
5
+ * with backend LLM integrations.
6
+ *
7
+ * @module types/chat
8
+ */
9
+ /**
10
+ * Role for chat history messages
11
+ */
12
+ export type ChatMessageRole = "user" | "assistant";
13
+ /**
14
+ * A single message in the chat history
15
+ */
16
+ export interface ChatHistoryMessage {
17
+ role: ChatMessageRole;
18
+ content: string;
19
+ }
20
+ /**
21
+ * Request payload sent to the chat endpoint
22
+ */
23
+ export interface ChatRequest {
24
+ /** The user's natural language message */
25
+ message: string;
26
+ /** Serialized current workflow state (nodes + edges) */
27
+ workflowState: unknown;
28
+ /** Optional conversation history for context */
29
+ history?: ChatHistoryMessage[];
30
+ }
31
+ /**
32
+ * Response payload from the chat endpoint
33
+ */
34
+ export interface ChatResponse {
35
+ /** The LLM's response content (may contain markdown and code blocks) */
36
+ content: string;
37
+ /** Optional conversation ID for backend session tracking */
38
+ conversationId?: string;
39
+ }
40
+ /**
41
+ * Result of parsing an LLM response to extract DSL commands
42
+ */
43
+ export interface ExtractedCommands {
44
+ /** The full explanation text (content outside code blocks) */
45
+ explanation: string;
46
+ /** Extracted DSL command strings */
47
+ commands: string[];
48
+ }
49
+ /**
50
+ * Status of a single command in the preview
51
+ */
52
+ export type CommandExecutionStatus = "pending" | "executing" | "success" | "error";
53
+ /**
54
+ * A single command shown in the command preview UI
55
+ */
56
+ export interface CommandPreviewItem {
57
+ /** The raw DSL command string */
58
+ raw: string;
59
+ /** Current execution status */
60
+ status: CommandExecutionStatus;
61
+ /** Optional result or error message after execution */
62
+ result?: string;
63
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Chat Types for FlowDrop LLM Chat Interface
3
+ *
4
+ * Provides type definitions for the chat panel's communication
5
+ * with backend LLM integrations.
6
+ *
7
+ * @module types/chat
8
+ */
9
+ export {};