@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.
- package/README.md +68 -24
- package/dist/adapters/WorkflowAdapter.js +2 -22
- package/dist/adapters/agentspec/autoLayout.d.ts +51 -5
- package/dist/adapters/agentspec/autoLayout.js +120 -23
- package/dist/chat/commandClassifier.d.ts +19 -0
- package/dist/chat/commandClassifier.js +30 -0
- package/dist/chat/index.d.ts +27 -0
- package/dist/chat/index.js +32 -0
- package/dist/chat/responseParser.d.ts +21 -0
- package/dist/chat/responseParser.js +87 -0
- package/dist/commands/batch.d.ts +18 -0
- package/dist/commands/batch.js +56 -0
- package/dist/commands/executor.d.ts +37 -0
- package/dist/commands/executor.js +1044 -0
- package/dist/commands/index.d.ts +14 -0
- package/dist/commands/index.js +17 -0
- package/dist/commands/parser.d.ts +16 -0
- package/dist/commands/parser.js +278 -0
- package/dist/commands/positioner.d.ts +19 -0
- package/dist/commands/positioner.js +33 -0
- package/dist/commands/storeIntegration.svelte.d.ts +16 -0
- package/dist/commands/storeIntegration.svelte.js +67 -0
- package/dist/commands/types.d.ts +343 -0
- package/dist/commands/types.js +45 -0
- package/dist/components/App.svelte +431 -17
- package/dist/components/App.svelte.d.ts +10 -0
- package/dist/components/CanvasBanner.stories.svelte +6 -2
- package/dist/components/CanvasController.svelte +38 -0
- package/dist/components/CanvasController.svelte.d.ts +32 -0
- package/dist/components/ConfigMappingRow.svelte +130 -0
- package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
- package/dist/components/ConfigPanel.svelte +56 -7
- package/dist/components/ConfigPanel.svelte.d.ts +2 -0
- package/dist/components/FlowDropEdge.svelte +8 -57
- package/dist/components/Logo.svelte +14 -14
- package/dist/components/LogsSidebar.svelte +5 -5
- package/dist/components/Navbar.svelte +58 -10
- package/dist/components/Navbar.svelte.d.ts +7 -0
- package/dist/components/NodeSidebar.svelte +238 -362
- package/dist/components/NodeSwapPicker.svelte +537 -0
- package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
- package/dist/components/PortMappingRow.svelte +209 -0
- package/dist/components/PortMappingRow.svelte.d.ts +12 -0
- package/dist/components/SwapMappingEditor.svelte +550 -0
- package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
- package/dist/components/WorkflowEditor.svelte +99 -4
- package/dist/components/WorkflowEditor.svelte.d.ts +8 -0
- package/dist/components/chat/AIChatPanel.svelte +658 -0
- package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
- package/dist/components/chat/CommandPreview.svelte +184 -0
- package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
- package/dist/components/console/CommandConsole.stories.svelte +93 -0
- package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
- package/dist/components/console/CommandConsole.svelte +259 -0
- package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
- package/dist/components/console/ConsoleAutocomplete.svelte +139 -0
- package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
- package/dist/components/console/ConsoleInput.svelte +712 -0
- package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
- package/dist/components/console/ConsoleOutput.svelte +121 -0
- package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
- package/dist/components/console/formatters.d.ts +26 -0
- package/dist/components/console/formatters.js +118 -0
- package/dist/components/interrupt/index.d.ts +1 -0
- package/dist/components/interrupt/index.js +1 -0
- package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
- package/dist/components/nodes/SimpleNode.svelte +27 -11
- package/dist/components/nodes/SquareNode.stories.svelte +45 -0
- package/dist/components/nodes/SquareNode.svelte +27 -11
- package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
- package/dist/config/endpoints.d.ts +8 -0
- package/dist/config/endpoints.js +5 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +9 -0
- package/dist/editor/index.d.ts +3 -1
- package/dist/editor/index.js +4 -2
- package/dist/helpers/proximityConnect.js +8 -1
- package/dist/helpers/workflowEditorHelper.d.ts +3 -53
- package/dist/helpers/workflowEditorHelper.js +13 -228
- package/dist/playground/index.d.ts +1 -1
- package/dist/playground/index.js +1 -1
- package/dist/schemas/v1/workflow.schema.json +107 -22
- package/dist/services/chatService.d.ts +65 -0
- package/dist/services/chatService.js +131 -0
- package/dist/services/historyService.d.ts +6 -4
- package/dist/services/historyService.js +21 -6
- package/dist/skins/slate.js +16 -0
- package/dist/stores/interruptStore.svelte.js +6 -1
- package/dist/stores/playgroundStore.svelte.d.ts +1 -1
- package/dist/stores/playgroundStore.svelte.js +11 -2
- package/dist/stores/portCoordinateStore.svelte.d.ts +4 -0
- package/dist/stores/portCoordinateStore.svelte.js +20 -26
- package/dist/stores/workflowStore.svelte.d.ts +31 -2
- package/dist/stores/workflowStore.svelte.js +84 -64
- package/dist/stories/EdgeDecorator.svelte +4 -4
- package/dist/styles/base.css +48 -0
- package/dist/svelte-app.d.ts +7 -1
- package/dist/svelte-app.js +4 -1
- package/dist/types/chat.d.ts +63 -0
- package/dist/types/chat.js +9 -0
- package/dist/types/events.d.ts +28 -2
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/settings.d.ts +6 -0
- package/dist/types/settings.js +3 -0
- package/dist/utils/edgeStyling.d.ts +42 -0
- package/dist/utils/edgeStyling.js +176 -0
- package/dist/utils/nodeIds.d.ts +31 -0
- package/dist/utils/nodeIds.js +42 -0
- package/dist/utils/nodeSwap.d.ts +221 -0
- package/dist/utils/nodeSwap.js +686 -0
- package/package.json +6 -1
- package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
- package/dist/helpers/nodeLayoutHelper.js +0 -19
package/dist/types/events.d.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module types/events
|
|
8
8
|
*/
|
|
9
|
-
import type { Workflow, NodeExecutionInfo } from "./index.js";
|
|
9
|
+
import type { Workflow, NodeExecutionInfo, WorkflowNode } from "./index.js";
|
|
10
|
+
import type { SwapEventContext, SwapResult } from "../utils/nodeSwap.js";
|
|
10
11
|
/**
|
|
11
12
|
* Types of workflow changes
|
|
12
13
|
*
|
|
13
14
|
* Used to identify what kind of change triggered the onWorkflowChange event.
|
|
14
15
|
*/
|
|
15
|
-
export type WorkflowChangeType = "node_add" | "node_remove" | "node_move" | "node_config" | "edge_add" | "edge_remove" | "metadata" | "name" | "description";
|
|
16
|
+
export type WorkflowChangeType = "node_add" | "node_remove" | "node_move" | "node_config" | "node_swap" | "edge_add" | "edge_remove" | "metadata" | "name" | "description";
|
|
16
17
|
/**
|
|
17
18
|
* High-level event handlers for enterprise integration
|
|
18
19
|
*
|
|
@@ -113,6 +114,22 @@ export interface FlowDropEventHandlers {
|
|
|
113
114
|
* @returns true to suppress default error handling, false/void to show default toast
|
|
114
115
|
*/
|
|
115
116
|
onApiError?: (error: Error, operation: string) => boolean | void;
|
|
117
|
+
/**
|
|
118
|
+
* Called before a node swap is executed.
|
|
119
|
+
* Return false to cancel the swap.
|
|
120
|
+
*
|
|
121
|
+
* @param context - Data-only swap context (oldNode, newMetadata, preview, overrides)
|
|
122
|
+
* @returns false to cancel, true/void to proceed
|
|
123
|
+
*/
|
|
124
|
+
onBeforeSwap?: (context: SwapEventContext) => boolean | void | Promise<boolean | void>;
|
|
125
|
+
/**
|
|
126
|
+
* Called after a node swap is successfully executed.
|
|
127
|
+
*
|
|
128
|
+
* @param result - The swap result with updated nodes/edges
|
|
129
|
+
* @param oldNode - The node that was replaced
|
|
130
|
+
* @param newNodeId - The ID of the newly created node
|
|
131
|
+
*/
|
|
132
|
+
onAfterSwap?: (result: SwapResult, oldNode: WorkflowNode, newNodeId: string) => void;
|
|
116
133
|
/**
|
|
117
134
|
* Called when an Agent Spec execution starts
|
|
118
135
|
*
|
|
@@ -174,6 +191,15 @@ export interface FlowDropFeatures {
|
|
|
174
191
|
* @default true
|
|
175
192
|
*/
|
|
176
193
|
showToasts?: boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Enable node swap functionality
|
|
196
|
+
*
|
|
197
|
+
* When enabled, users can swap a node for a different type
|
|
198
|
+
* while preserving compatible connections and configuration.
|
|
199
|
+
*
|
|
200
|
+
* @default true
|
|
201
|
+
*/
|
|
202
|
+
enableNodeSwap?: boolean;
|
|
177
203
|
}
|
|
178
204
|
/**
|
|
179
205
|
* Default feature values
|
package/dist/types/events.js
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -535,6 +535,14 @@ export interface NodeExtensions {
|
|
|
535
535
|
* Allows overriding the node type's configEdit settings for specific instances
|
|
536
536
|
*/
|
|
537
537
|
configEdit?: ConfigEditOptions;
|
|
538
|
+
/**
|
|
539
|
+
* Set when this node was created by swapping another node.
|
|
540
|
+
* Stores the immediate predecessor only — not a full chain.
|
|
541
|
+
* Sequential swaps (A→B→C) each store only their direct predecessor.
|
|
542
|
+
*/
|
|
543
|
+
swap?: {
|
|
544
|
+
previousNodeId: string;
|
|
545
|
+
};
|
|
538
546
|
/**
|
|
539
547
|
* Namespaced extension data from 3rd party integrations
|
|
540
548
|
* Use your package/organization name as the key (e.g., "myapp", "acme:analyzer")
|
package/dist/types/settings.d.ts
CHANGED
|
@@ -57,6 +57,12 @@ export interface UISettings {
|
|
|
57
57
|
compactMode: boolean;
|
|
58
58
|
/** Active theme name — overridden by the theme prop when explicitly provided */
|
|
59
59
|
theme: "default" | "minimal";
|
|
60
|
+
/** Whether the command console panel is open */
|
|
61
|
+
consoleOpen: boolean;
|
|
62
|
+
/** Height of the command console panel in pixels */
|
|
63
|
+
consoleHeight: number;
|
|
64
|
+
/** Active tab in the bottom panel */
|
|
65
|
+
bottomPanelTab: "console" | "chat";
|
|
60
66
|
}
|
|
61
67
|
/**
|
|
62
68
|
* Application behavior and automation settings
|
package/dist/types/settings.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge Styling Utility
|
|
3
|
+
*
|
|
4
|
+
* Standalone functions for determining edge categories and applying
|
|
5
|
+
* visual styling to workflow edges based on source port data types.
|
|
6
|
+
* Used by both the visual editor and the command DSL system.
|
|
7
|
+
*/
|
|
8
|
+
import type { WorkflowNode as WorkflowNodeType, WorkflowEdge, EdgeCategory } from "../types/index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Check if a port ID matches a dynamic branch in a Gateway node.
|
|
11
|
+
* Gateway nodes store branches in config.branches array.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isGatewayBranch(node: WorkflowNodeType, portId: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Get the data type of a port from a node's metadata.
|
|
16
|
+
* Also handles dynamic ports like Gateway branches.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getPortDataType(node: WorkflowNodeType, portId: string, portType: "input" | "output"): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Determine the edge category based on source port data type.
|
|
21
|
+
* Note: This does not check for loopback edges — use getEdgeCategoryWithLoopback() for that.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getEdgeCategory(sourcePortDataType: string | null): EdgeCategory;
|
|
24
|
+
/**
|
|
25
|
+
* Determine the full edge category including loopback detection.
|
|
26
|
+
* Loopback edges take precedence over source port data type.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getEdgeCategoryWithLoopback(edge: WorkflowEdge, sourcePortDataType: string | null): EdgeCategory;
|
|
29
|
+
/**
|
|
30
|
+
* Apply custom styling to a connection edge based on its source/target nodes.
|
|
31
|
+
*
|
|
32
|
+
* Sets:
|
|
33
|
+
* - edge.data.metadata.edgeType (trigger/tool/loopback/data)
|
|
34
|
+
* - edge.style, edge.class, edge.markerEnd based on category
|
|
35
|
+
* - edge.data.targetNodeType and edge.data.targetCategory
|
|
36
|
+
*/
|
|
37
|
+
export declare function applyConnectionStyling(edge: WorkflowEdge, sourceNode: WorkflowNodeType, targetNode: WorkflowNodeType): void;
|
|
38
|
+
/**
|
|
39
|
+
* Update existing edges with custom styling rules.
|
|
40
|
+
* Batch operation that applies styling to all edges using a node map for O(1) lookup.
|
|
41
|
+
*/
|
|
42
|
+
export declare function updateEdgeStyles(edges: WorkflowEdge[], nodes: WorkflowNodeType[]): WorkflowEdge[];
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge Styling Utility
|
|
3
|
+
*
|
|
4
|
+
* Standalone functions for determining edge categories and applying
|
|
5
|
+
* visual styling to workflow edges based on source port data types.
|
|
6
|
+
* Used by both the visual editor and the command DSL system.
|
|
7
|
+
*/
|
|
8
|
+
import { MarkerType } from "@xyflow/svelte";
|
|
9
|
+
import { extractPortId } from "../utils/handleIds.js";
|
|
10
|
+
import { isLoopbackEdge } from "../utils/connections.js";
|
|
11
|
+
import { EDGE_MARKER_SIZES } from "../config/constants.js";
|
|
12
|
+
/**
|
|
13
|
+
* Check if a port ID matches a dynamic branch in a Gateway node.
|
|
14
|
+
* Gateway nodes store branches in config.branches array.
|
|
15
|
+
*/
|
|
16
|
+
export function isGatewayBranch(node, portId) {
|
|
17
|
+
const nodeType = node.data?.metadata?.type || node.type;
|
|
18
|
+
if (nodeType !== "gateway") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const branches = node.data?.config?.branches;
|
|
22
|
+
if (!branches || !Array.isArray(branches)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return branches.some((branch) => branch.name === portId);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the data type of a port from a node's metadata.
|
|
29
|
+
* Also handles dynamic ports like Gateway branches.
|
|
30
|
+
*/
|
|
31
|
+
export function getPortDataType(node, portId, portType) {
|
|
32
|
+
// First, check static ports in metadata
|
|
33
|
+
const ports = portType === "output"
|
|
34
|
+
? node.data?.metadata?.outputs
|
|
35
|
+
: node.data?.metadata?.inputs;
|
|
36
|
+
if (ports && Array.isArray(ports)) {
|
|
37
|
+
const port = ports.find((p) => p.id === portId);
|
|
38
|
+
if (port?.dataType) {
|
|
39
|
+
return port.dataType;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Check dynamic ports from config (dynamicInputs/dynamicOutputs)
|
|
43
|
+
const dynamicKey = portType === "output" ? "dynamicOutputs" : "dynamicInputs";
|
|
44
|
+
const dynamicPorts = node.data?.config?.[dynamicKey];
|
|
45
|
+
if (dynamicPorts && Array.isArray(dynamicPorts)) {
|
|
46
|
+
const dynamicPort = dynamicPorts.find((p) => p.name === portId);
|
|
47
|
+
if (dynamicPort?.dataType) {
|
|
48
|
+
return dynamicPort.dataType;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// For output ports, also check dynamic Gateway branches
|
|
52
|
+
// Gateway branches are always trigger type (control flow)
|
|
53
|
+
if (portType === "output" && isGatewayBranch(node, portId)) {
|
|
54
|
+
return "trigger";
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Determine the edge category based on source port data type.
|
|
60
|
+
* Note: This does not check for loopback edges — use getEdgeCategoryWithLoopback() for that.
|
|
61
|
+
*/
|
|
62
|
+
export function getEdgeCategory(sourcePortDataType) {
|
|
63
|
+
if (sourcePortDataType === "trigger") {
|
|
64
|
+
return "trigger";
|
|
65
|
+
}
|
|
66
|
+
if (sourcePortDataType === "tool") {
|
|
67
|
+
return "tool";
|
|
68
|
+
}
|
|
69
|
+
return "data";
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Determine the full edge category including loopback detection.
|
|
73
|
+
* Loopback edges take precedence over source port data type.
|
|
74
|
+
*/
|
|
75
|
+
export function getEdgeCategoryWithLoopback(edge, sourcePortDataType) {
|
|
76
|
+
if (isLoopbackEdge(edge)) {
|
|
77
|
+
return "loopback";
|
|
78
|
+
}
|
|
79
|
+
return getEdgeCategory(sourcePortDataType);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Apply custom styling to a connection edge based on its source/target nodes.
|
|
83
|
+
*
|
|
84
|
+
* Sets:
|
|
85
|
+
* - edge.data.metadata.edgeType (trigger/tool/loopback/data)
|
|
86
|
+
* - edge.style, edge.class, edge.markerEnd based on category
|
|
87
|
+
* - edge.data.targetNodeType and edge.data.targetCategory
|
|
88
|
+
*/
|
|
89
|
+
export function applyConnectionStyling(edge, sourceNode, targetNode) {
|
|
90
|
+
// Extract port ID from sourceHandle
|
|
91
|
+
const sourcePortId = extractPortId(edge.sourceHandle);
|
|
92
|
+
// Get the source port's data type
|
|
93
|
+
const sourcePortDataType = sourcePortId
|
|
94
|
+
? getPortDataType(sourceNode, sourcePortId, "output")
|
|
95
|
+
: null;
|
|
96
|
+
// Determine edge category (loopback takes precedence)
|
|
97
|
+
const edgeCategory = getEdgeCategoryWithLoopback(edge, sourcePortDataType);
|
|
98
|
+
// Apply styling based on edge category
|
|
99
|
+
// Marker colors use CSS custom properties so they respond to theme changes automatically
|
|
100
|
+
switch (edgeCategory) {
|
|
101
|
+
case "loopback":
|
|
102
|
+
edge.style =
|
|
103
|
+
"stroke: var(--fd-edge-loopback); stroke-dasharray: var(--fd-edge-loopback-dasharray); stroke-width: var(--fd-edge-loopback-width); opacity: var(--fd-edge-loopback-opacity);";
|
|
104
|
+
edge.class = "flowdrop--edge--loopback";
|
|
105
|
+
edge.markerEnd = {
|
|
106
|
+
type: MarkerType.ArrowClosed,
|
|
107
|
+
...EDGE_MARKER_SIZES.loopback,
|
|
108
|
+
color: "var(--fd-edge-loopback)",
|
|
109
|
+
};
|
|
110
|
+
break;
|
|
111
|
+
case "trigger":
|
|
112
|
+
edge.style =
|
|
113
|
+
"stroke: var(--fd-edge-trigger); stroke-width: var(--fd-edge-trigger-width);";
|
|
114
|
+
edge.class = "flowdrop--edge--trigger";
|
|
115
|
+
edge.markerEnd = {
|
|
116
|
+
type: MarkerType.ArrowClosed,
|
|
117
|
+
...EDGE_MARKER_SIZES.trigger,
|
|
118
|
+
color: "var(--fd-edge-trigger)",
|
|
119
|
+
};
|
|
120
|
+
break;
|
|
121
|
+
case "tool":
|
|
122
|
+
edge.style = "stroke: var(--fd-edge-tool); stroke-dasharray: 5 3;";
|
|
123
|
+
edge.class = "flowdrop--edge--tool";
|
|
124
|
+
edge.markerEnd = {
|
|
125
|
+
type: MarkerType.ArrowClosed,
|
|
126
|
+
...EDGE_MARKER_SIZES.tool,
|
|
127
|
+
color: "var(--fd-edge-tool)",
|
|
128
|
+
};
|
|
129
|
+
break;
|
|
130
|
+
case "data":
|
|
131
|
+
default:
|
|
132
|
+
edge.style = "stroke: var(--fd-edge-data);";
|
|
133
|
+
edge.class = "flowdrop--edge--data";
|
|
134
|
+
edge.markerEnd = {
|
|
135
|
+
type: MarkerType.ArrowClosed,
|
|
136
|
+
...EDGE_MARKER_SIZES.data,
|
|
137
|
+
color: "var(--fd-edge-data)",
|
|
138
|
+
};
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
// Store metadata in edge data for API and persistence
|
|
142
|
+
edge.data = {
|
|
143
|
+
...edge.data,
|
|
144
|
+
metadata: {
|
|
145
|
+
...(edge.data?.metadata || {}),
|
|
146
|
+
edgeType: edgeCategory,
|
|
147
|
+
sourcePortDataType: sourcePortDataType ?? undefined,
|
|
148
|
+
},
|
|
149
|
+
targetNodeType: targetNode.type,
|
|
150
|
+
targetCategory: targetNode.data.metadata.category,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Update existing edges with custom styling rules.
|
|
155
|
+
* Batch operation that applies styling to all edges using a node map for O(1) lookup.
|
|
156
|
+
*/
|
|
157
|
+
export function updateEdgeStyles(edges, nodes) {
|
|
158
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
159
|
+
return edges.map((edge) => {
|
|
160
|
+
const sourceNode = nodeMap.get(edge.source);
|
|
161
|
+
const targetNode = nodeMap.get(edge.target);
|
|
162
|
+
const updatedEdge = { ...edge };
|
|
163
|
+
if (!sourceNode || !targetNode) {
|
|
164
|
+
updatedEdge.data = {
|
|
165
|
+
...updatedEdge.data,
|
|
166
|
+
metadata: {
|
|
167
|
+
...(updatedEdge.data?.metadata || {}),
|
|
168
|
+
edgeType: "data",
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
return updatedEdge;
|
|
172
|
+
}
|
|
173
|
+
applyConnectionStyling(updatedEdge, sourceNode, targetNode);
|
|
174
|
+
return updatedEdge;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared node ID generation and config defaults utilities.
|
|
3
|
+
* Used by both the visual editor and the workflow adapter to ensure
|
|
4
|
+
* consistent behavior across all code paths.
|
|
5
|
+
*/
|
|
6
|
+
import type { ConfigSchema } from "../types/index.js";
|
|
7
|
+
/**
|
|
8
|
+
* Minimal node shape required for ID generation.
|
|
9
|
+
* Both WorkflowNode and StandardNode satisfy this interface.
|
|
10
|
+
*/
|
|
11
|
+
interface NodeWithMetadata {
|
|
12
|
+
id: string;
|
|
13
|
+
data?: {
|
|
14
|
+
metadata?: {
|
|
15
|
+
id?: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate a unique node ID based on node type and existing nodes.
|
|
21
|
+
* Format: <node_type>.<number>
|
|
22
|
+
* Example: boolean_gateway.1, calculator.2
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateNodeId(nodeTypeId: string, existingNodes: NodeWithMetadata[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* Extract default config values from a node's configSchema.
|
|
27
|
+
* Iterates configSchema.properties and returns an object with each property's
|
|
28
|
+
* default value (if defined).
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractConfigDefaults(configSchema?: ConfigSchema): Record<string, unknown>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared node ID generation and config defaults utilities.
|
|
3
|
+
* Used by both the visual editor and the workflow adapter to ensure
|
|
4
|
+
* consistent behavior across all code paths.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unique node ID based on node type and existing nodes.
|
|
8
|
+
* Format: <node_type>.<number>
|
|
9
|
+
* Example: boolean_gateway.1, calculator.2
|
|
10
|
+
*/
|
|
11
|
+
export function generateNodeId(nodeTypeId, existingNodes) {
|
|
12
|
+
// Count how many nodes of this type already exist
|
|
13
|
+
const existingNodeIds = existingNodes
|
|
14
|
+
.filter((node) => node.data?.metadata?.id === nodeTypeId)
|
|
15
|
+
.map((node) => node.id);
|
|
16
|
+
// Extract the numbers from existing IDs with the same prefix
|
|
17
|
+
const existingNumbers = existingNodeIds
|
|
18
|
+
.map((id) => {
|
|
19
|
+
const match = id.match(new RegExp(`^${nodeTypeId}\\.(\\d+)$`));
|
|
20
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
21
|
+
})
|
|
22
|
+
.filter((num) => num > 0);
|
|
23
|
+
// Find the next available number (highest + 1)
|
|
24
|
+
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
|
|
25
|
+
return `${nodeTypeId}.${nextNumber}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract default config values from a node's configSchema.
|
|
29
|
+
* Iterates configSchema.properties and returns an object with each property's
|
|
30
|
+
* default value (if defined).
|
|
31
|
+
*/
|
|
32
|
+
export function extractConfigDefaults(configSchema) {
|
|
33
|
+
const config = {};
|
|
34
|
+
if (!configSchema?.properties)
|
|
35
|
+
return config;
|
|
36
|
+
for (const [key, prop] of Object.entries(configSchema.properties)) {
|
|
37
|
+
if (prop && typeof prop === "object" && "default" in prop) {
|
|
38
|
+
config[key] = prop.default;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Swap utilities for FlowDrop
|
|
3
|
+
*
|
|
4
|
+
* Provides logic for swapping a workflow node with a different node type
|
|
5
|
+
* while intelligently remapping compatible port connections.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/nodeSwap
|
|
8
|
+
*/
|
|
9
|
+
import type { WorkflowNode, WorkflowEdge, NodeMetadata, NodePort, ConfigSchema, ConfigValues } from "../types/index.js";
|
|
10
|
+
import type { WorkflowValidationResult } from "./validation.js";
|
|
11
|
+
import type { PortCompatibilityChecker } from "./connections.js";
|
|
12
|
+
/**
|
|
13
|
+
* Describes how a single port was remapped during a swap.
|
|
14
|
+
*/
|
|
15
|
+
export interface PortMapping {
|
|
16
|
+
oldHandleId: string;
|
|
17
|
+
newHandleId: string;
|
|
18
|
+
oldPortId: string;
|
|
19
|
+
newPortId: string;
|
|
20
|
+
direction: "input" | "output";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* An edge that could not be remapped and will be dropped.
|
|
24
|
+
*/
|
|
25
|
+
export interface DroppedEdge {
|
|
26
|
+
edge: WorkflowEdge;
|
|
27
|
+
reason: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Preview of what a node swap will do before it is executed.
|
|
31
|
+
*/
|
|
32
|
+
export interface SwapPreview {
|
|
33
|
+
/** Edges that will be preserved with their rewritten versions */
|
|
34
|
+
keptEdges: Array<{
|
|
35
|
+
edge: WorkflowEdge;
|
|
36
|
+
newEdge: WorkflowEdge;
|
|
37
|
+
}>;
|
|
38
|
+
/** Edges that will be removed */
|
|
39
|
+
droppedEdges: DroppedEdge[];
|
|
40
|
+
/** True if any connected edges will be lost */
|
|
41
|
+
hasDataLoss: boolean;
|
|
42
|
+
/** The new node ID that will be generated */
|
|
43
|
+
newNodeId: string;
|
|
44
|
+
/** Config keys carried over from the old node */
|
|
45
|
+
configCarriedOver: string[];
|
|
46
|
+
/** Config keys reset to defaults on the new node */
|
|
47
|
+
configReset: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Result of executing a node swap.
|
|
51
|
+
*/
|
|
52
|
+
export interface SwapResult {
|
|
53
|
+
updatedNodes: WorkflowNode[];
|
|
54
|
+
updatedEdges: WorkflowEdge[];
|
|
55
|
+
}
|
|
56
|
+
/** Quality annotation for how a port was matched. */
|
|
57
|
+
export type MatchQuality = "id" | "name" | "type" | "manual" | "unmapped";
|
|
58
|
+
/** Manual override for a single port mapping. */
|
|
59
|
+
export interface PortMappingOverride {
|
|
60
|
+
oldPortId: string;
|
|
61
|
+
newPortId: string | null;
|
|
62
|
+
direction: "input" | "output";
|
|
63
|
+
}
|
|
64
|
+
/** Manual override for a single config mapping. */
|
|
65
|
+
export interface ConfigMappingOverride {
|
|
66
|
+
key: string;
|
|
67
|
+
action: "carry" | "reset" | "set";
|
|
68
|
+
value?: unknown;
|
|
69
|
+
}
|
|
70
|
+
/** Options bag for advanced swap functions. */
|
|
71
|
+
export interface SwapOptions {
|
|
72
|
+
checker?: PortCompatibilityChecker | null;
|
|
73
|
+
portOverrides?: PortMappingOverride[];
|
|
74
|
+
configOverrides?: ConfigMappingOverride[];
|
|
75
|
+
strategies?: SwapStrategy[];
|
|
76
|
+
}
|
|
77
|
+
/** Pluggable strategy passed per-call, not registered globally. */
|
|
78
|
+
export interface SwapStrategy {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
readonly name: string;
|
|
81
|
+
canHandle(ctx: SwapStrategyContext): boolean;
|
|
82
|
+
mapPorts?(ctx: SwapStrategyContext): Record<string, string | null> | undefined;
|
|
83
|
+
mapConfig?(ctx: SwapStrategyContext): Record<string, {
|
|
84
|
+
action: "carry" | "reset" | "set";
|
|
85
|
+
value?: unknown;
|
|
86
|
+
}> | undefined;
|
|
87
|
+
}
|
|
88
|
+
/** Context passed to swap strategies. */
|
|
89
|
+
export interface SwapStrategyContext {
|
|
90
|
+
oldNode: WorkflowNode;
|
|
91
|
+
newMetadata: NodeMetadata;
|
|
92
|
+
edges: WorkflowEdge[];
|
|
93
|
+
allNodes: WorkflowNode[];
|
|
94
|
+
checker: PortCompatibilityChecker | null;
|
|
95
|
+
}
|
|
96
|
+
/** Stable, data-only event context for swap hooks. */
|
|
97
|
+
export interface SwapEventContext {
|
|
98
|
+
oldNode: WorkflowNode;
|
|
99
|
+
newMetadata: NodeMetadata;
|
|
100
|
+
preview: SwapPreview;
|
|
101
|
+
portOverrides: PortMappingOverride[];
|
|
102
|
+
configOverrides: ConfigMappingOverride[];
|
|
103
|
+
}
|
|
104
|
+
/** Error class for swap validation failures. */
|
|
105
|
+
export declare class SwapValidationError extends Error {
|
|
106
|
+
constructor(message: string);
|
|
107
|
+
}
|
|
108
|
+
/** Editable port mapping for the interactive mapping editor. */
|
|
109
|
+
export interface EditablePortMapping {
|
|
110
|
+
oldPort: NodePort;
|
|
111
|
+
edge: WorkflowEdge;
|
|
112
|
+
direction: "input" | "output";
|
|
113
|
+
selectedNewPortId: string | null;
|
|
114
|
+
matchQuality: MatchQuality;
|
|
115
|
+
autoSuggestedPortId: string | null;
|
|
116
|
+
isOverridden: boolean;
|
|
117
|
+
}
|
|
118
|
+
/** Editable config mapping for the interactive mapping editor. */
|
|
119
|
+
export interface EditableConfigMapping {
|
|
120
|
+
key: string;
|
|
121
|
+
title: string;
|
|
122
|
+
oldValue: unknown;
|
|
123
|
+
newDefault: unknown;
|
|
124
|
+
carryOver: boolean;
|
|
125
|
+
autoCarryOver: boolean;
|
|
126
|
+
/** false for nested objects/arrays — shown as read-only, always reset */
|
|
127
|
+
isFlat: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** Full interactive swap state for the mapping editor. */
|
|
130
|
+
export interface InteractiveSwapState {
|
|
131
|
+
oldNode: WorkflowNode;
|
|
132
|
+
newMetadata: NodeMetadata;
|
|
133
|
+
newNodeId: string;
|
|
134
|
+
portMappings: EditablePortMapping[];
|
|
135
|
+
configMappings: EditableConfigMapping[];
|
|
136
|
+
availableNewInputs: NodePort[];
|
|
137
|
+
availableNewOutputs: NodePort[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Compare two semver-like version strings.
|
|
141
|
+
* Returns positive if a > b, negative if a < b, 0 if equal.
|
|
142
|
+
*
|
|
143
|
+
* Handles pre-release tags: "2.0.0-beta" < "2.0.0"
|
|
144
|
+
*/
|
|
145
|
+
export declare function compareSemver(a: string, b: string): number;
|
|
146
|
+
/**
|
|
147
|
+
* Map config values from an old node to a new node's schema.
|
|
148
|
+
*
|
|
149
|
+
* - Keys present in both old config and new schema: carry over the old value
|
|
150
|
+
* - Keys only in the new schema: use the schema default or newDefaults
|
|
151
|
+
* - Keys only in the old config: discarded
|
|
152
|
+
* - Dynamic port keys (dynamicInputs, dynamicOutputs, branches): never carried over
|
|
153
|
+
*/
|
|
154
|
+
export declare function mapConfig(oldConfig: ConfigValues, newConfigSchema: ConfigSchema | undefined, newDefaults?: Record<string, unknown>): {
|
|
155
|
+
config: ConfigValues;
|
|
156
|
+
carriedOver: string[];
|
|
157
|
+
reset: string[];
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Compute a preview of what will happen when swapping oldNode with newMetadata.
|
|
161
|
+
*
|
|
162
|
+
* This does NOT mutate anything — it returns a preview that can be displayed
|
|
163
|
+
* to the user for confirmation before executing the swap.
|
|
164
|
+
*/
|
|
165
|
+
export declare function computeSwapPreview(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], checker?: PortCompatibilityChecker | null): SwapPreview;
|
|
166
|
+
/**
|
|
167
|
+
* Execute a node swap using a previously computed preview.
|
|
168
|
+
*
|
|
169
|
+
* Returns new nodes and edges arrays ready for `workflowActions.batchUpdate()`.
|
|
170
|
+
*/
|
|
171
|
+
export declare function executeSwap(oldNode: WorkflowNode, newMetadata: NodeMetadata, preview: SwapPreview, allNodes: WorkflowNode[], allEdges: WorkflowEdge[]): SwapResult;
|
|
172
|
+
/**
|
|
173
|
+
* Check if a newer version of the same node type is available.
|
|
174
|
+
*
|
|
175
|
+
* Compares the node's embedded metadata.version against the same-ID entry
|
|
176
|
+
* in the available nodes list (API returns only the latest version).
|
|
177
|
+
*
|
|
178
|
+
* @returns The newer NodeMetadata if an upgrade is available, null otherwise
|
|
179
|
+
*/
|
|
180
|
+
export declare function getVersionUpgrade(currentMetadata: NodeMetadata, allNodeTypes: NodeMetadata[]): NodeMetadata | null;
|
|
181
|
+
/**
|
|
182
|
+
* Compute a swap preview with full options support (strategies, overrides).
|
|
183
|
+
*
|
|
184
|
+
* Resolution order:
|
|
185
|
+
* 1. Check strategies — first canHandle() match wins for mapPorts()/mapConfig()
|
|
186
|
+
* 2. Fall through to built-in 3-pass for ports not covered by strategy
|
|
187
|
+
* 3. Apply portOverrides on top (highest priority — user's manual overrides)
|
|
188
|
+
* 4. Same cascade for config
|
|
189
|
+
*/
|
|
190
|
+
export declare function computeSwapPreviewWithOptions(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], options: SwapOptions): SwapPreview;
|
|
191
|
+
/**
|
|
192
|
+
* Compute interactive state for the mapping editor UI.
|
|
193
|
+
*
|
|
194
|
+
* Returns EditablePortMapping and EditableConfigMapping entries
|
|
195
|
+
* with match quality annotations and isFlat flags.
|
|
196
|
+
*/
|
|
197
|
+
export declare function computeInteractiveState(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], options?: SwapOptions): InteractiveSwapState;
|
|
198
|
+
/**
|
|
199
|
+
* Convert user-edited InteractiveSwapState back into a SwapPreview
|
|
200
|
+
* for executeSwap(). Pure function, no side effects.
|
|
201
|
+
*/
|
|
202
|
+
export declare function buildSwapPreviewFromState(state: InteractiveSwapState, allEdges: WorkflowEdge[]): SwapPreview;
|
|
203
|
+
/**
|
|
204
|
+
* Headless one-shot swap with full validation.
|
|
205
|
+
*
|
|
206
|
+
* Guardrails:
|
|
207
|
+
* - Validates oldNode.id exists in allNodes
|
|
208
|
+
* - Validates format compatibility if newMetadata.formats is set
|
|
209
|
+
* - Computes preview → executes → validates → returns result
|
|
210
|
+
* - Throws SwapValidationError on invalid input
|
|
211
|
+
*/
|
|
212
|
+
export declare function performSwap(oldNode: WorkflowNode, newMetadata: NodeMetadata, allNodes: WorkflowNode[], allEdges: WorkflowEdge[], options?: SwapOptions): SwapResult;
|
|
213
|
+
/**
|
|
214
|
+
* Validate a swap result for structural integrity.
|
|
215
|
+
*
|
|
216
|
+
* Checks:
|
|
217
|
+
* - No dangling edge references (every edge source/target exists in nodes)
|
|
218
|
+
* - No duplicate node IDs
|
|
219
|
+
* - No duplicate edge IDs
|
|
220
|
+
*/
|
|
221
|
+
export declare function validateSwapResult(result: SwapResult): WorkflowValidationResult;
|