@d34dman/flowdrop 0.0.4 → 0.0.6
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/README.md +107 -104
- package/dist/components/App.svelte +67 -64
- package/dist/components/App.svelte.d.ts +1 -0
- package/dist/components/Navbar.svelte +1 -11
- package/dist/components/NodeSidebar.svelte +2 -1
- package/dist/components/NodeStatusOverlay.svelte +0 -1
- package/dist/components/NodeStatusOverlay.svelte.d.ts +0 -1
- package/dist/components/PipelineStatus.svelte +0 -37
- package/dist/components/SimpleNode.svelte +0 -7
- package/dist/components/SquareNode.svelte +0 -7
- package/dist/components/UniversalNode.svelte +0 -8
- package/dist/components/WorkflowEditor.svelte +63 -456
- package/dist/components/WorkflowNode.svelte +1 -8
- package/dist/config/apiConfig.d.ts +2 -1
- package/dist/config/apiConfig.js +3 -2
- package/dist/helpers/workflowEditorHelper.d.ts +87 -0
- package/dist/helpers/workflowEditorHelper.js +365 -0
- package/dist/index.d.ts +31 -1
- package/dist/index.js +30 -1
- package/dist/mocks/app-navigation.d.ts +4 -4
- package/dist/mocks/app-navigation.js +4 -4
- package/dist/services/api.js +13 -18
- package/dist/services/globalSave.js +12 -12
- package/dist/svelte-app.js +2 -3
- package/dist/utils/config.d.ts +5 -1
- package/dist/utils/config.js +9 -17
- package/package.json +2 -1
|
@@ -93,13 +93,6 @@
|
|
|
93
93
|
props.data.metadata?.outputs?.find((port) => port.dataType !== 'trigger')
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
// Use trigger port if present, otherwise use first data port
|
|
97
|
-
let firstInputPort = $derived(triggerInputPort || firstDataInputPort);
|
|
98
|
-
let firstOutputPort = $derived(triggerOutputPort || firstDataOutputPort);
|
|
99
|
-
|
|
100
|
-
let hasInput = $derived(!!firstInputPort);
|
|
101
|
-
let hasOutput = $derived(!!firstOutputPort);
|
|
102
|
-
|
|
103
96
|
// Check if we need to show both trigger and data ports
|
|
104
97
|
let hasBothInputTypes = $derived(!!triggerInputPort && !!firstDataInputPort);
|
|
105
98
|
let hasBothOutputTypes = $derived(!!triggerOutputPort && !!firstDataOutputPort);
|
|
@@ -20,14 +20,6 @@
|
|
|
20
20
|
} from '../utils/nodeWrapper.js';
|
|
21
21
|
import { resolveComponentName } from '../utils/nodeTypes.js';
|
|
22
22
|
|
|
23
|
-
interface Props {
|
|
24
|
-
data: WorkflowNode['data'] & {
|
|
25
|
-
nodeId?: string;
|
|
26
|
-
onConfigOpen?: (node: { id: string; type: string; data: WorkflowNode['data'] }) => void;
|
|
27
|
-
};
|
|
28
|
-
selected?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
let {
|
|
32
24
|
data,
|
|
33
25
|
selected = false
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import {
|
|
9
9
|
SvelteFlow,
|
|
10
10
|
ConnectionLineType,
|
|
11
|
-
MarkerType,
|
|
12
11
|
Controls,
|
|
13
12
|
Background,
|
|
14
13
|
BackgroundVariant,
|
|
@@ -16,29 +15,25 @@
|
|
|
16
15
|
SvelteFlowProvider
|
|
17
16
|
} from '@xyflow/svelte';
|
|
18
17
|
import '@xyflow/svelte/dist/style.css';
|
|
19
|
-
import WorkflowNode from './WorkflowNode.svelte';
|
|
20
|
-
import NotesNode from './NotesNode.svelte';
|
|
21
|
-
import SimpleNode from './SimpleNode.svelte';
|
|
22
|
-
import SquareNode from './SquareNode.svelte';
|
|
23
|
-
import ToolNode from './ToolNode.svelte';
|
|
24
18
|
import type {
|
|
25
19
|
WorkflowNode as WorkflowNodeType,
|
|
26
20
|
NodeMetadata,
|
|
27
21
|
Workflow,
|
|
28
22
|
WorkflowEdge
|
|
29
23
|
} from '../types/index.js';
|
|
30
|
-
import { hasCycles } from '../utils/connections.js';
|
|
31
24
|
import CanvasBanner from './CanvasBanner.svelte';
|
|
32
|
-
import { workflowApi, nodeApi, setApiBaseUrl, setEndpointConfig } from '../services/api.js';
|
|
33
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
34
25
|
import { tick } from 'svelte';
|
|
35
26
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
36
27
|
import ConnectionLine from './ConnectionLine.svelte';
|
|
37
|
-
import { resolveComponentName } from '../utils/nodeTypes.js';
|
|
38
28
|
import { workflowStore, workflowActions } from '../stores/workflowStore.js';
|
|
39
|
-
import { nodeExecutionService } from '../services/nodeExecutionService.js';
|
|
40
|
-
import type { NodeExecutionInfo } from '../types/index.js';
|
|
41
29
|
import UniversalNode from './UniversalNode.svelte';
|
|
30
|
+
import {
|
|
31
|
+
EdgeStylingHelper,
|
|
32
|
+
NodeOperationsHelper,
|
|
33
|
+
WorkflowOperationsHelper,
|
|
34
|
+
ConfigurationHelper
|
|
35
|
+
} from '../helpers/workflowEditorHelper.js';
|
|
36
|
+
import type { NodeExecutionInfo } from '../types/index.js';
|
|
42
37
|
|
|
43
38
|
interface Props {
|
|
44
39
|
nodes?: NodeMetadata[];
|
|
@@ -70,9 +65,6 @@
|
|
|
70
65
|
});
|
|
71
66
|
});
|
|
72
67
|
|
|
73
|
-
// Initialize from props only once, not on every re-render
|
|
74
|
-
let availableNodes = $state<NodeMetadata[]>([]);
|
|
75
|
-
|
|
76
68
|
// Create a local currentWorkflow variable that we can control directly
|
|
77
69
|
let currentWorkflow = $state<Workflow | null>(null);
|
|
78
70
|
|
|
@@ -136,59 +128,47 @@
|
|
|
136
128
|
async function loadNodeExecutionInfo(): Promise<void> {
|
|
137
129
|
if (!currentWorkflow?.nodes) return;
|
|
138
130
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
...node.data,
|
|
154
|
-
executionInfo: executionInfo[node.id] || {
|
|
155
|
-
status: 'idle',
|
|
131
|
+
const executionInfo = await NodeOperationsHelper.loadNodeExecutionInfo(
|
|
132
|
+
currentWorkflow,
|
|
133
|
+
props.pipelineId
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Update nodes with execution information without triggering reactive updates
|
|
137
|
+
const updatedNodes = currentWorkflow.nodes.map((node) => ({
|
|
138
|
+
...node,
|
|
139
|
+
data: {
|
|
140
|
+
...node.data,
|
|
141
|
+
executionInfo:
|
|
142
|
+
executionInfo[node.id] ||
|
|
143
|
+
({
|
|
144
|
+
status: 'idle' as const,
|
|
156
145
|
executionCount: 0,
|
|
157
146
|
isExecuting: false
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
147
|
+
} as NodeExecutionInfo)
|
|
148
|
+
}
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
// Update the flow nodes to reflect the changes
|
|
152
|
+
flowNodes = updatedNodes.map((node) => ({
|
|
153
|
+
...node,
|
|
154
|
+
data: {
|
|
155
|
+
...node.data,
|
|
156
|
+
onConfigOpen: props.openConfigSidebar
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
170
159
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.error('Failed to load node execution info:', error);
|
|
175
|
-
}
|
|
160
|
+
// Update currentWorkflow without triggering reactive effects
|
|
161
|
+
currentWorkflow.nodes = updatedNodes;
|
|
176
162
|
}
|
|
177
163
|
|
|
178
164
|
// Function to update currentWorkflow when SvelteFlow changes nodes/edges
|
|
179
165
|
function updateCurrentWorkflowFromSvelteFlow(): void {
|
|
180
166
|
if (currentWorkflow) {
|
|
181
|
-
currentWorkflow =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
...currentWorkflow.metadata,
|
|
187
|
-
updatedAt: new Date().toISOString(),
|
|
188
|
-
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
189
|
-
updateNumber: (currentWorkflow.metadata?.updateNumber || 0) + 1
|
|
190
|
-
}
|
|
191
|
-
};
|
|
167
|
+
currentWorkflow = WorkflowOperationsHelper.updateWorkflow(
|
|
168
|
+
currentWorkflow,
|
|
169
|
+
flowNodes,
|
|
170
|
+
flowEdges
|
|
171
|
+
);
|
|
192
172
|
|
|
193
173
|
// Update the global store
|
|
194
174
|
updateGlobalStore();
|
|
@@ -261,49 +241,6 @@
|
|
|
261
241
|
}
|
|
262
242
|
}
|
|
263
243
|
|
|
264
|
-
/**
|
|
265
|
-
* Apply custom styling to connection edges based on rules:
|
|
266
|
-
* - Dashed lines for connections to tool nodes
|
|
267
|
-
* - Arrow markers pointing towards input ports
|
|
268
|
-
*/
|
|
269
|
-
function applyConnectionStyling(
|
|
270
|
-
edge: WorkflowEdge,
|
|
271
|
-
sourceNode: WorkflowNodeType,
|
|
272
|
-
targetNode: WorkflowNodeType
|
|
273
|
-
): void {
|
|
274
|
-
// Rule 1: Dashed lines for tool nodes
|
|
275
|
-
// A node is a tool node when it uses the ToolNode component,
|
|
276
|
-
// which happens when sourceNode.type === 'tool'
|
|
277
|
-
const isToolNode = sourceNode.type === 'tool';
|
|
278
|
-
|
|
279
|
-
// Use inline styles for dashed lines (more reliable than CSS classes)
|
|
280
|
-
if (isToolNode) {
|
|
281
|
-
edge.style = 'stroke-dasharray: 0 4 0; stroke: amber !important;';
|
|
282
|
-
edge.class = 'flowdrop--edge--tool';
|
|
283
|
-
} else {
|
|
284
|
-
edge.style = 'stroke: grey;';
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Store metadata in edge data for debugging
|
|
288
|
-
edge.data = {
|
|
289
|
-
...edge.data,
|
|
290
|
-
isToolConnection: isToolNode,
|
|
291
|
-
targetNodeType: targetNode.type,
|
|
292
|
-
targetCategory: targetNode.data.metadata.category
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
// Rule 2: Always add arrow pointing towards input port
|
|
296
|
-
// This replaces the default arrows we removed
|
|
297
|
-
if (!isToolNode) {
|
|
298
|
-
edge.markerEnd = {
|
|
299
|
-
type: MarkerType.ArrowClosed,
|
|
300
|
-
width: 16,
|
|
301
|
-
height: 16,
|
|
302
|
-
color: 'grey'
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
244
|
/**
|
|
308
245
|
* Update existing edges with our custom styling rules
|
|
309
246
|
* This ensures all edges (including existing ones) follow our rules
|
|
@@ -312,35 +249,15 @@
|
|
|
312
249
|
// Wait for any pending DOM updates
|
|
313
250
|
await tick();
|
|
314
251
|
|
|
315
|
-
const updatedEdges =
|
|
316
|
-
// Find source and target nodes
|
|
317
|
-
const sourceNode = flowNodes.find((node) => node.id === edge.source);
|
|
318
|
-
const targetNode = flowNodes.find((node) => node.id === edge.target);
|
|
319
|
-
|
|
320
|
-
if (!sourceNode || !targetNode) {
|
|
321
|
-
console.warn('Could not find nodes for edge:', edge.id);
|
|
322
|
-
return edge;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Create a copy of the edge and apply styling
|
|
326
|
-
const updatedEdge = { ...edge };
|
|
327
|
-
applyConnectionStyling(updatedEdge, sourceNode, targetNode);
|
|
328
|
-
|
|
329
|
-
return updatedEdge;
|
|
330
|
-
});
|
|
252
|
+
const updatedEdges = EdgeStylingHelper.updateEdgeStyles(flowEdges, flowNodes);
|
|
331
253
|
|
|
332
254
|
// Update currentWorkflow with the styled edges
|
|
333
255
|
if (currentWorkflow) {
|
|
334
|
-
currentWorkflow =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
updatedAt: new Date().toISOString(),
|
|
340
|
-
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
341
|
-
updateNumber: (currentWorkflow.metadata?.updateNumber || 0) + 1
|
|
342
|
-
}
|
|
343
|
-
};
|
|
256
|
+
currentWorkflow = WorkflowOperationsHelper.updateWorkflow(
|
|
257
|
+
currentWorkflow,
|
|
258
|
+
flowNodes,
|
|
259
|
+
updatedEdges
|
|
260
|
+
);
|
|
344
261
|
|
|
345
262
|
// Update the global store
|
|
346
263
|
updateGlobalStore();
|
|
@@ -349,259 +266,18 @@
|
|
|
349
266
|
|
|
350
267
|
// Edge styling will be handled when edges are first created or manually updated
|
|
351
268
|
|
|
352
|
-
// Configure endpoints
|
|
269
|
+
// Configure endpoints when props change
|
|
353
270
|
$effect(() => {
|
|
354
271
|
if (props.endpointConfig) {
|
|
355
|
-
|
|
356
|
-
// Load nodes after setting endpoint config
|
|
357
|
-
loadNodesFromApi();
|
|
358
|
-
} else if (props.nodes) {
|
|
359
|
-
// If we have nodes prop, use them directly
|
|
360
|
-
availableNodes = props.nodes;
|
|
272
|
+
ConfigurationHelper.configureEndpoints(props.endpointConfig);
|
|
361
273
|
}
|
|
362
274
|
});
|
|
363
275
|
|
|
364
|
-
/**
|
|
365
|
-
* Load nodes from API if not provided
|
|
366
|
-
*/
|
|
367
|
-
async function loadNodesFromApi(): Promise<void> {
|
|
368
|
-
// If nodes are provided via props, use them
|
|
369
|
-
if (props.nodes && props.nodes.length > 0) {
|
|
370
|
-
availableNodes = props.nodes;
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Otherwise, load from API
|
|
375
|
-
try {
|
|
376
|
-
const fetchedNodes = await nodeApi.getNodes();
|
|
377
|
-
|
|
378
|
-
availableNodes = fetchedNodes;
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error('❌ Failed to load nodes from API:', error);
|
|
381
|
-
|
|
382
|
-
// Use fallback sample nodes
|
|
383
|
-
availableNodes = [
|
|
384
|
-
{
|
|
385
|
-
id: 'text-input',
|
|
386
|
-
name: 'Text Input',
|
|
387
|
-
category: 'inputs',
|
|
388
|
-
description: 'Simple text input field',
|
|
389
|
-
version: '1.0.0',
|
|
390
|
-
icon: 'mdi:text-box',
|
|
391
|
-
inputs: [],
|
|
392
|
-
outputs: [{ id: 'text', name: 'text', type: 'output', dataType: 'string' }]
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
id: 'text-output',
|
|
396
|
-
name: 'Text Output',
|
|
397
|
-
category: 'outputs',
|
|
398
|
-
description: 'Display text output',
|
|
399
|
-
version: '1.0.0',
|
|
400
|
-
icon: 'mdi:text-box-outline',
|
|
401
|
-
inputs: [{ id: 'text', name: 'text', type: 'input', dataType: 'string' }],
|
|
402
|
-
outputs: []
|
|
403
|
-
}
|
|
404
|
-
];
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Clear workflow
|
|
410
|
-
*/
|
|
411
|
-
function clearWorkflow(): void {
|
|
412
|
-
if (currentWorkflow) {
|
|
413
|
-
currentWorkflow = {
|
|
414
|
-
...currentWorkflow,
|
|
415
|
-
nodes: [],
|
|
416
|
-
edges: [],
|
|
417
|
-
metadata: {
|
|
418
|
-
...currentWorkflow.metadata,
|
|
419
|
-
updatedAt: new Date().toISOString(),
|
|
420
|
-
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
421
|
-
updateNumber: (currentWorkflow.metadata?.updateNumber || 0) + 1
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
// Update the global store
|
|
426
|
-
updateGlobalStore();
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// ConfigSidebar functions are now handled by the parent App component
|
|
431
|
-
|
|
432
|
-
async function handleConfigSave(newConfig: Record<string, unknown>): Promise<void> {
|
|
433
|
-
console.log('🔧 WorkflowEditor: handleConfigSave called with:', newConfig);
|
|
434
|
-
|
|
435
|
-
if (props.selectedNodeForConfig) {
|
|
436
|
-
console.log('🔧 WorkflowEditor: Updating config for node:', props.selectedNodeForConfig.id);
|
|
437
|
-
|
|
438
|
-
// Wait for any pending DOM updates
|
|
439
|
-
await tick();
|
|
440
|
-
|
|
441
|
-
// Update the node's config
|
|
442
|
-
props.selectedNodeForConfig.data.config = { ...newConfig };
|
|
443
|
-
|
|
444
|
-
// Update the node in currentWorkflow
|
|
445
|
-
// NOTE: We do NOT change the node's type field anymore
|
|
446
|
-
// All nodes use 'universalNode' and UniversalNode handles internal switching
|
|
447
|
-
if (currentWorkflow) {
|
|
448
|
-
currentWorkflow = {
|
|
449
|
-
...currentWorkflow,
|
|
450
|
-
nodes: currentWorkflow.nodes.map((node) =>
|
|
451
|
-
node.id === props.selectedNodeForConfig.id
|
|
452
|
-
? {
|
|
453
|
-
...node,
|
|
454
|
-
data: { ...node.data, config: { ...newConfig } }
|
|
455
|
-
}
|
|
456
|
-
: node
|
|
457
|
-
),
|
|
458
|
-
metadata: {
|
|
459
|
-
...currentWorkflow.metadata,
|
|
460
|
-
updatedAt: new Date().toISOString(),
|
|
461
|
-
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
462
|
-
updateNumber: (currentWorkflow.metadata?.updateNumber || 0) + 1
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
console.log('🔧 WorkflowEditor: Updated currentWorkflow, calling updateGlobalStore');
|
|
467
|
-
// Update the global store
|
|
468
|
-
updateGlobalStore();
|
|
469
|
-
} else {
|
|
470
|
-
console.warn('⚠️ WorkflowEditor: No currentWorkflow available for config update');
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
console.warn('⚠️ WorkflowEditor: No selectedNodeForConfig available for config update');
|
|
474
|
-
}
|
|
475
|
-
props.closeConfigSidebar?.();
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Save workflow
|
|
480
|
-
*/
|
|
481
|
-
async function saveWorkflow(): Promise<void> {
|
|
482
|
-
try {
|
|
483
|
-
// Wait for any pending DOM updates before saving
|
|
484
|
-
await tick();
|
|
485
|
-
|
|
486
|
-
// Use current workflow from local variable
|
|
487
|
-
if (!currentWorkflow) {
|
|
488
|
-
console.warn('⚠️ No workflow data available to save');
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Determine the workflow ID based on whether we have an existing workflow
|
|
493
|
-
let workflowId: string;
|
|
494
|
-
if (currentWorkflow.id) {
|
|
495
|
-
// Use the existing workflow ID
|
|
496
|
-
workflowId = currentWorkflow.id;
|
|
497
|
-
} else {
|
|
498
|
-
// Generate a new UUID for a new workflow
|
|
499
|
-
workflowId = uuidv4();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const workflow: Workflow = {
|
|
503
|
-
id: workflowId,
|
|
504
|
-
name: currentWorkflow.name || 'Untitled Workflow',
|
|
505
|
-
nodes: currentWorkflow.nodes || [],
|
|
506
|
-
edges: currentWorkflow.edges || [],
|
|
507
|
-
metadata: {
|
|
508
|
-
version: '1.0.0',
|
|
509
|
-
createdAt: currentWorkflow.metadata?.createdAt || new Date().toISOString(),
|
|
510
|
-
updatedAt: new Date().toISOString()
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
console.log('💾 WorkflowEditor: Saving workflow to backend:');
|
|
515
|
-
console.log(' - ID:', workflow.id);
|
|
516
|
-
console.log(' - Name:', workflow.name);
|
|
517
|
-
console.log(' - Nodes count:', workflow.nodes.length);
|
|
518
|
-
console.log(' - Edges count:', workflow.edges.length);
|
|
519
|
-
console.log(' - Full workflow object:', JSON.stringify(workflow, null, 2));
|
|
520
|
-
|
|
521
|
-
const savedWorkflow = await workflowApi.saveWorkflow(workflow);
|
|
522
|
-
|
|
523
|
-
console.log('✅ WorkflowEditor: Received workflow from backend:');
|
|
524
|
-
console.log(' - ID:', savedWorkflow.id);
|
|
525
|
-
console.log(' - Name:', savedWorkflow.name);
|
|
526
|
-
console.log(' - Nodes count:', savedWorkflow.nodes?.length || 0);
|
|
527
|
-
console.log(' - Edges count:', savedWorkflow.edges?.length || 0);
|
|
528
|
-
|
|
529
|
-
// Update the workflow ID if it changed (new workflow)
|
|
530
|
-
// Keep our current workflow state, only update ID and metadata from backend
|
|
531
|
-
if (savedWorkflow.id && savedWorkflow.id !== workflow.id) {
|
|
532
|
-
console.log('🔄 Updating workflow ID from', workflow.id, 'to', savedWorkflow.id);
|
|
533
|
-
workflowActions.batchUpdate({
|
|
534
|
-
nodes: workflow.nodes,
|
|
535
|
-
edges: workflow.edges,
|
|
536
|
-
name: workflow.name,
|
|
537
|
-
metadata: {
|
|
538
|
-
...workflow.metadata,
|
|
539
|
-
...savedWorkflow.metadata
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
console.log('🔍 WorkflowEditor: Workflow store after save:', $workflowStore);
|
|
545
|
-
|
|
546
|
-
// Note: Notes node configurations (content, noteType) are automatically
|
|
547
|
-
// saved as part of the node.data.config object and will be restored
|
|
548
|
-
// when the workflow is loaded.
|
|
549
|
-
|
|
550
|
-
// Update the workflow ID if it was a new workflow
|
|
551
|
-
if (!currentWorkflow.id) {
|
|
552
|
-
console.log('🆕 New workflow created with ID:', savedWorkflow.id);
|
|
553
|
-
} else {
|
|
554
|
-
console.log('🔄 Existing workflow updated with ID:', savedWorkflow.id);
|
|
555
|
-
}
|
|
556
|
-
} catch (error) {
|
|
557
|
-
console.error('❌ Failed to save workflow:', error);
|
|
558
|
-
// Here you would typically show a user-friendly error message
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Export workflow
|
|
564
|
-
*/
|
|
565
|
-
async function exportWorkflow(): Promise<void> {
|
|
566
|
-
// Wait for any pending DOM updates before exporting
|
|
567
|
-
await tick();
|
|
568
|
-
|
|
569
|
-
// Use current workflow from local variable
|
|
570
|
-
if (!currentWorkflow) {
|
|
571
|
-
console.warn('⚠️ No workflow data available to export');
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Use the same ID logic as saveWorkflow
|
|
576
|
-
const workflowId = currentWorkflow.id || uuidv4();
|
|
577
|
-
|
|
578
|
-
const workflow: Workflow = {
|
|
579
|
-
id: workflowId,
|
|
580
|
-
name: currentWorkflow.name || 'Untitled Workflow',
|
|
581
|
-
nodes: currentWorkflow.nodes || [],
|
|
582
|
-
edges: currentWorkflow.edges || [],
|
|
583
|
-
metadata: {
|
|
584
|
-
version: '1.0.0',
|
|
585
|
-
createdAt: currentWorkflow.metadata?.createdAt || new Date().toISOString(),
|
|
586
|
-
updatedAt: new Date().toISOString()
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
const dataStr = JSON.stringify(workflow, null, 2);
|
|
591
|
-
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
592
|
-
const url = URL.createObjectURL(dataBlob);
|
|
593
|
-
const link = document.createElement('a');
|
|
594
|
-
link.href = url;
|
|
595
|
-
link.download = `${workflow.name}.json`;
|
|
596
|
-
link.click();
|
|
597
|
-
URL.revokeObjectURL(url);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
276
|
/**
|
|
601
277
|
* Check if workflow has cycles
|
|
602
278
|
*/
|
|
603
279
|
function checkWorkflowCycles(): boolean {
|
|
604
|
-
return
|
|
280
|
+
return WorkflowOperationsHelper.checkWorkflowCycles(flowNodes, flowEdges);
|
|
605
281
|
}
|
|
606
282
|
</script>
|
|
607
283
|
|
|
@@ -631,92 +307,23 @@
|
|
|
631
307
|
y: e.clientY - rect.top
|
|
632
308
|
};
|
|
633
309
|
|
|
634
|
-
// Create the node
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (parsedData.type === 'node') {
|
|
647
|
-
// Old format from sidebar
|
|
648
|
-
nodeType = parsedData.nodeData.metadata;
|
|
649
|
-
nodeData = parsedData.nodeData;
|
|
650
|
-
} else {
|
|
651
|
-
// New format (direct NodeMetadata)
|
|
652
|
-
nodeType = parsedData;
|
|
653
|
-
|
|
654
|
-
// Extract initial config from configSchema
|
|
655
|
-
let initialConfig = {};
|
|
656
|
-
if (nodeType.configSchema && typeof nodeType.configSchema === 'object') {
|
|
657
|
-
// If configSchema is a JSON Schema, extract default values
|
|
658
|
-
if (nodeType.configSchema.properties) {
|
|
659
|
-
// JSON Schema format - extract defaults
|
|
660
|
-
Object.entries(nodeType.configSchema.properties).forEach(([key, prop]) => {
|
|
661
|
-
if (prop && typeof prop === 'object' && 'default' in prop) {
|
|
662
|
-
initialConfig[key] = prop.default;
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
} else {
|
|
666
|
-
// Simple object format - use as is
|
|
667
|
-
initialConfig = { ...nodeType.configSchema };
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
nodeData = {
|
|
672
|
-
label: nodeType.name,
|
|
673
|
-
config: initialConfig,
|
|
674
|
-
metadata: nodeType
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const newNodeId = uuidv4();
|
|
679
|
-
|
|
680
|
-
// All nodes use 'universalNode' type
|
|
681
|
-
// UniversalNode component handles internal switching based on metadata and config
|
|
682
|
-
const newNode: WorkflowNodeType = {
|
|
683
|
-
id: newNodeId,
|
|
684
|
-
type: 'universalNode',
|
|
685
|
-
position, // Use the position calculated from the drop event
|
|
686
|
-
deletable: true,
|
|
687
|
-
data: {
|
|
688
|
-
...nodeData,
|
|
689
|
-
nodeId: newNodeId // Use the same ID
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
// Add node to currentWorkflow
|
|
694
|
-
if (currentWorkflow) {
|
|
695
|
-
console.log('🔧 WorkflowEditor: Adding new node to currentWorkflow:', newNode.id);
|
|
696
|
-
currentWorkflow = {
|
|
697
|
-
...currentWorkflow,
|
|
698
|
-
nodes: [...currentWorkflow.nodes, newNode],
|
|
699
|
-
metadata: {
|
|
700
|
-
...currentWorkflow.metadata,
|
|
701
|
-
updatedAt: new Date().toISOString(),
|
|
702
|
-
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
703
|
-
updateNumber: (currentWorkflow.metadata?.updateNumber || 0) + 1
|
|
704
|
-
}
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
console.log(
|
|
708
|
-
'🔧 WorkflowEditor: Updated currentWorkflow with new node, calling updateGlobalStore'
|
|
709
|
-
);
|
|
710
|
-
// Update the global store
|
|
711
|
-
updateGlobalStore();
|
|
712
|
-
} else {
|
|
713
|
-
console.warn('⚠️ WorkflowEditor: No currentWorkflow available for new node');
|
|
714
|
-
}
|
|
310
|
+
// Create the node using the helper
|
|
311
|
+
const newNode = NodeOperationsHelper.createNodeFromDrop(nodeTypeData, position);
|
|
312
|
+
|
|
313
|
+
if (newNode && currentWorkflow) {
|
|
314
|
+
console.log('🔧 WorkflowEditor: Adding new node to currentWorkflow:', newNode.id);
|
|
315
|
+
currentWorkflow = WorkflowOperationsHelper.addNode(currentWorkflow, newNode);
|
|
316
|
+
|
|
317
|
+
console.log(
|
|
318
|
+
'🔧 WorkflowEditor: Updated currentWorkflow with new node, calling updateGlobalStore'
|
|
319
|
+
);
|
|
320
|
+
// Update the global store
|
|
321
|
+
updateGlobalStore();
|
|
715
322
|
|
|
716
323
|
// Wait for DOM update to ensure SvelteFlow updates
|
|
717
324
|
await tick();
|
|
718
|
-
}
|
|
719
|
-
console.
|
|
325
|
+
} else if (!currentWorkflow) {
|
|
326
|
+
console.warn('⚠️ WorkflowEditor: No currentWorkflow available for new node');
|
|
720
327
|
}
|
|
721
328
|
}
|
|
722
329
|
}}
|
|
@@ -7,17 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
<script lang="ts">
|
|
9
9
|
import { Position, Handle } from '@xyflow/svelte';
|
|
10
|
-
import type { WorkflowNode
|
|
10
|
+
import type { WorkflowNode } from '../types/index.js';
|
|
11
11
|
import Icon from '@iconify/svelte';
|
|
12
12
|
import { getNodeIcon } from '../utils/icons.js';
|
|
13
13
|
import { getDataTypeColorToken, getCategoryColorToken } from '../utils/colors.js';
|
|
14
|
-
import {
|
|
15
|
-
getStatusColor,
|
|
16
|
-
getStatusIcon,
|
|
17
|
-
getStatusLabel,
|
|
18
|
-
formatExecutionDuration,
|
|
19
|
-
formatLastExecuted
|
|
20
|
-
} from '../utils/nodeStatus.js';
|
|
21
14
|
|
|
22
15
|
interface Props {
|
|
23
16
|
data: WorkflowNode['data'] & {
|
|
@@ -24,7 +24,8 @@ export interface ApiConfig {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Default API configuration
|
|
27
|
-
*
|
|
27
|
+
* For library usage, configuration should be provided at runtime
|
|
28
|
+
* This provides sensible defaults that can be overridden
|
|
28
29
|
*/
|
|
29
30
|
export declare const defaultApiConfig: ApiConfig;
|
|
30
31
|
/**
|
package/dist/config/apiConfig.js
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Default API configuration
|
|
7
|
-
*
|
|
7
|
+
* For library usage, configuration should be provided at runtime
|
|
8
|
+
* This provides sensible defaults that can be overridden
|
|
8
9
|
*/
|
|
9
10
|
export const defaultApiConfig = {
|
|
10
|
-
baseUrl:
|
|
11
|
+
baseUrl: '/api/flowdrop',
|
|
11
12
|
endpoints: {
|
|
12
13
|
workflows: {
|
|
13
14
|
list: '/workflows',
|