@flowdrop/flowdrop 1.4.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 +351 -12
- package/dist/components/App.svelte.d.ts +3 -0
- 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 +2 -10
- package/dist/components/LogsSidebar.svelte +5 -5
- package/dist/components/NodeSidebar.svelte +15 -49
- 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/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/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/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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Service
|
|
3
|
+
*
|
|
4
|
+
* Handles API interactions for the LLM Chat feature including
|
|
5
|
+
* sending messages, retrieving history, and clearing history.
|
|
6
|
+
*
|
|
7
|
+
* @module services/chatService
|
|
8
|
+
*/
|
|
9
|
+
import { buildEndpointUrl, getEndpointHeaders } from "../config/endpoints.js";
|
|
10
|
+
import { getEndpointConfig } from "./api.js";
|
|
11
|
+
import { logger } from "../utils/logger.js";
|
|
12
|
+
/**
|
|
13
|
+
* Chat Service class
|
|
14
|
+
*
|
|
15
|
+
* Provides methods to interact with the chat API endpoints
|
|
16
|
+
* for LLM-powered workflow building assistance.
|
|
17
|
+
*/
|
|
18
|
+
export class ChatService {
|
|
19
|
+
static instance;
|
|
20
|
+
constructor() { }
|
|
21
|
+
/**
|
|
22
|
+
* Get the singleton instance of ChatService
|
|
23
|
+
*
|
|
24
|
+
* @returns The ChatService singleton instance
|
|
25
|
+
*/
|
|
26
|
+
static getInstance() {
|
|
27
|
+
if (!ChatService.instance) {
|
|
28
|
+
ChatService.instance = new ChatService();
|
|
29
|
+
}
|
|
30
|
+
return ChatService.instance;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the endpoint configuration
|
|
34
|
+
*
|
|
35
|
+
* @throws Error if endpoint configuration is not set
|
|
36
|
+
* @returns The endpoint configuration
|
|
37
|
+
*/
|
|
38
|
+
getConfig() {
|
|
39
|
+
const config = getEndpointConfig();
|
|
40
|
+
if (!config) {
|
|
41
|
+
throw new Error("Endpoint configuration not set. Call setEndpointConfig() first.");
|
|
42
|
+
}
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generic API request helper
|
|
47
|
+
*
|
|
48
|
+
* @param url - The URL to fetch
|
|
49
|
+
* @param options - Fetch options
|
|
50
|
+
* @returns The parsed JSON response
|
|
51
|
+
*/
|
|
52
|
+
async request(url, options = {}) {
|
|
53
|
+
const config = this.getConfig();
|
|
54
|
+
const headers = getEndpointHeaders(config, "chat");
|
|
55
|
+
const response = await fetch(url, {
|
|
56
|
+
...options,
|
|
57
|
+
headers: {
|
|
58
|
+
...headers,
|
|
59
|
+
...options.headers,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errorData = await response.json().catch(() => ({}));
|
|
64
|
+
const errorMessage = errorData.error ||
|
|
65
|
+
errorData.message ||
|
|
66
|
+
`HTTP ${response.status}: ${response.statusText}`;
|
|
67
|
+
throw new Error(errorMessage);
|
|
68
|
+
}
|
|
69
|
+
const json = await response.json();
|
|
70
|
+
// Unwrap the { success, data } envelope used by the Drupal backend.
|
|
71
|
+
if (json && typeof json === "object" && "data" in json) {
|
|
72
|
+
return json.data;
|
|
73
|
+
}
|
|
74
|
+
return json;
|
|
75
|
+
}
|
|
76
|
+
// =========================================================================
|
|
77
|
+
// Chat Operations
|
|
78
|
+
// =========================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Send a message to the chat endpoint
|
|
81
|
+
*
|
|
82
|
+
* @param workflowId - The workflow ID
|
|
83
|
+
* @param request - The chat request payload
|
|
84
|
+
* @returns The chat response from the LLM
|
|
85
|
+
*/
|
|
86
|
+
async sendMessage(workflowId, request) {
|
|
87
|
+
const config = this.getConfig();
|
|
88
|
+
const url = buildEndpointUrl(config, config.endpoints.chat.sendMessage, {
|
|
89
|
+
id: workflowId,
|
|
90
|
+
});
|
|
91
|
+
logger.debug("[ChatService] Sending message to", url);
|
|
92
|
+
return this.request(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
body: JSON.stringify(request),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get conversation history for a workflow
|
|
99
|
+
*
|
|
100
|
+
* @param workflowId - The workflow ID
|
|
101
|
+
* @returns Array of chat history messages
|
|
102
|
+
*/
|
|
103
|
+
async getHistory(workflowId) {
|
|
104
|
+
const config = this.getConfig();
|
|
105
|
+
const url = buildEndpointUrl(config, config.endpoints.chat.getHistory, {
|
|
106
|
+
id: workflowId,
|
|
107
|
+
});
|
|
108
|
+
logger.debug("[ChatService] Getting history from", url);
|
|
109
|
+
return this.request(url);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clear conversation history for a workflow
|
|
113
|
+
*
|
|
114
|
+
* @param workflowId - The workflow ID
|
|
115
|
+
*/
|
|
116
|
+
async clearHistory(workflowId) {
|
|
117
|
+
const config = this.getConfig();
|
|
118
|
+
const url = buildEndpointUrl(config, config.endpoints.chat.clearHistory, {
|
|
119
|
+
id: workflowId,
|
|
120
|
+
});
|
|
121
|
+
logger.debug("[ChatService] Clearing history at", url);
|
|
122
|
+
await fetch(url, {
|
|
123
|
+
method: "DELETE",
|
|
124
|
+
headers: getEndpointHeaders(config, "chat"),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Pre-instantiated ChatService singleton
|
|
130
|
+
*/
|
|
131
|
+
export const chatService = ChatService.getInstance();
|
|
@@ -140,8 +140,10 @@ export declare class HistoryService {
|
|
|
140
140
|
* Cancel the current transaction without committing
|
|
141
141
|
*
|
|
142
142
|
* Discards the transaction without adding to history.
|
|
143
|
+
* Returns the snapshot captured at transaction start so the caller
|
|
144
|
+
* can restore the store to its pre-transaction state.
|
|
143
145
|
*/
|
|
144
|
-
cancelTransaction():
|
|
146
|
+
cancelTransaction(): Workflow | null;
|
|
145
147
|
/**
|
|
146
148
|
* Clear all history
|
|
147
149
|
*
|
|
@@ -189,9 +191,9 @@ export declare class HistoryService {
|
|
|
189
191
|
/**
|
|
190
192
|
* Create a deep clone of a workflow
|
|
191
193
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
194
|
+
* Strips non-serializable values (e.g., onConfigOpen callbacks) before cloning
|
|
195
|
+
* so structuredClone doesn't throw on function references.
|
|
196
|
+
* Falls back to JSON round-trip if structuredClone still fails (e.g., Svelte proxies).
|
|
195
197
|
*/
|
|
196
198
|
private cloneWorkflow;
|
|
197
199
|
/**
|
|
@@ -182,11 +182,15 @@ export class HistoryService {
|
|
|
182
182
|
* Cancel the current transaction without committing
|
|
183
183
|
*
|
|
184
184
|
* Discards the transaction without adding to history.
|
|
185
|
+
* Returns the snapshot captured at transaction start so the caller
|
|
186
|
+
* can restore the store to its pre-transaction state.
|
|
185
187
|
*/
|
|
186
188
|
cancelTransaction() {
|
|
189
|
+
const snapshot = this.transactionSnapshot;
|
|
187
190
|
this.inTransaction = false;
|
|
188
191
|
this.transactionSnapshot = null;
|
|
189
192
|
this.transactionDescription = null;
|
|
193
|
+
return snapshot;
|
|
190
194
|
}
|
|
191
195
|
/**
|
|
192
196
|
* Clear all history
|
|
@@ -292,14 +296,25 @@ export class HistoryService {
|
|
|
292
296
|
/**
|
|
293
297
|
* Create a deep clone of a workflow
|
|
294
298
|
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
299
|
+
* Strips non-serializable values (e.g., onConfigOpen callbacks) before cloning
|
|
300
|
+
* so structuredClone doesn't throw on function references.
|
|
301
|
+
* Falls back to JSON round-trip if structuredClone still fails (e.g., Svelte proxies).
|
|
298
302
|
*/
|
|
299
303
|
cloneWorkflow(workflow) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
const cleaned = {
|
|
305
|
+
...workflow,
|
|
306
|
+
nodes: workflow.nodes.map((n) => ({
|
|
307
|
+
...n,
|
|
308
|
+
data: { ...n.data, onConfigOpen: undefined },
|
|
309
|
+
})),
|
|
310
|
+
};
|
|
311
|
+
try {
|
|
312
|
+
return structuredClone(cleaned);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Fallback for environments where structuredClone can't handle proxied objects
|
|
316
|
+
return JSON.parse(JSON.stringify(cleaned));
|
|
317
|
+
}
|
|
303
318
|
}
|
|
304
319
|
/**
|
|
305
320
|
* Notify all subscribers of state changes
|
|
@@ -56,7 +56,12 @@ export function getPendingInterrupts() {
|
|
|
56
56
|
* Get count of pending interrupts
|
|
57
57
|
*/
|
|
58
58
|
export function getPendingInterruptCount() {
|
|
59
|
-
|
|
59
|
+
let count = 0;
|
|
60
|
+
for (const interrupt of interrupts.values()) {
|
|
61
|
+
if (!isTerminalState(interrupt.machineState))
|
|
62
|
+
count++;
|
|
63
|
+
}
|
|
64
|
+
return count;
|
|
60
65
|
}
|
|
61
66
|
/**
|
|
62
67
|
* Get resolved interrupts array
|
|
@@ -124,7 +124,7 @@ export declare const playgroundActions: {
|
|
|
124
124
|
setMessages: (messageList: PlaygroundMessage[]) => void;
|
|
125
125
|
/**
|
|
126
126
|
* Add a message to the current session
|
|
127
|
-
*
|
|
127
|
+
* Uses binary search insertion for O(log n) instead of full sort.
|
|
128
128
|
*
|
|
129
129
|
* @param message - The message to add
|
|
130
130
|
*/
|
|
@@ -324,12 +324,21 @@ export const playgroundActions = {
|
|
|
324
324
|
},
|
|
325
325
|
/**
|
|
326
326
|
* Add a message to the current session
|
|
327
|
-
*
|
|
327
|
+
* Uses binary search insertion for O(log n) instead of full sort.
|
|
328
328
|
*
|
|
329
329
|
* @param message - The message to add
|
|
330
330
|
*/
|
|
331
331
|
addMessage: (message) => {
|
|
332
|
-
|
|
332
|
+
const seq = message.sequenceNumber ?? 0;
|
|
333
|
+
let lo = 0, hi = _messages.length;
|
|
334
|
+
while (lo < hi) {
|
|
335
|
+
const mid = (lo + hi) >>> 1;
|
|
336
|
+
if ((_messages[mid].sequenceNumber ?? 0) <= seq)
|
|
337
|
+
lo = mid + 1;
|
|
338
|
+
else
|
|
339
|
+
hi = mid;
|
|
340
|
+
}
|
|
341
|
+
_messages = [..._messages.slice(0, lo), message, ..._messages.slice(lo)];
|
|
333
342
|
},
|
|
334
343
|
/**
|
|
335
344
|
* Add multiple messages to the current session
|
|
@@ -36,6 +36,10 @@ export declare function updateNodePortCoordinates(node: WorkflowNodeType, getInt
|
|
|
36
36
|
* @param nodeId - ID of the node to remove
|
|
37
37
|
*/
|
|
38
38
|
export declare function removeNodePortCoordinates(nodeId: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Clear all port coordinates (lifecycle cleanup).
|
|
41
|
+
*/
|
|
42
|
+
export declare function clearPortCoordinates(): void;
|
|
39
43
|
/**
|
|
40
44
|
* Get coordinates for a specific handle.
|
|
41
45
|
*
|
|
@@ -126,27 +126,19 @@ export function updateNodePortCoordinates(node, getInternalNode) {
|
|
|
126
126
|
const internalNode = getInternalNode(node.id);
|
|
127
127
|
if (!internalNode)
|
|
128
128
|
return;
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
}
|
|
134
|
+
if (coord.nodeId !== node.id)
|
|
135
|
+
newMap.set(key, coord);
|
|
139
136
|
}
|
|
140
|
-
return keys;
|
|
141
137
|
});
|
|
142
|
-
for (const
|
|
143
|
-
|
|
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
|
|
158
|
-
|
|
149
|
+
const newMap = new SvelteMap();
|
|
150
|
+
untrack(() => {
|
|
159
151
|
for (const [key, coord] of coordinates) {
|
|
160
|
-
if (coord.nodeId
|
|
161
|
-
|
|
162
|
-
}
|
|
152
|
+
if (coord.nodeId !== nodeId)
|
|
153
|
+
newMap.set(key, coord);
|
|
163
154
|
}
|
|
164
|
-
return keys;
|
|
165
155
|
});
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
*
|
|
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
|
*
|