@d34dman/flowdrop 0.0.61 → 0.0.62
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 +6 -0
- package/dist/adapters/WorkflowAdapter.d.ts +1 -1
- package/dist/adapters/agentspec/AgentSpecAdapter.js +3 -1
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.js +6 -1
- package/dist/api/enhanced-client.js +7 -6
- package/dist/components/App.svelte +143 -219
- package/dist/components/CanvasBanner.stories.svelte +25 -0
- package/dist/components/CanvasBanner.stories.svelte.d.ts +27 -0
- package/dist/components/CanvasBanner.svelte +2 -2
- package/dist/components/ConfigForm.svelte +37 -36
- package/dist/components/ConfigPanel.stories.svelte +38 -0
- package/dist/components/ConfigPanel.stories.svelte.d.ts +27 -0
- package/dist/components/ConfigPanel.svelte +2 -2
- package/dist/components/ConnectionLine.svelte +2 -2
- package/dist/components/FlowDropZone.svelte +18 -2
- package/dist/components/FlowDropZone.svelte.d.ts +2 -0
- package/dist/components/LoadingSpinner.stories.svelte +30 -0
- package/dist/components/LoadingSpinner.stories.svelte.d.ts +27 -0
- package/dist/components/Logo.stories.svelte +22 -0
- package/dist/components/Logo.stories.svelte.d.ts +27 -0
- package/dist/components/Logo.svelte +33 -13
- package/dist/components/Logo.svelte.d.ts +1 -1
- package/dist/components/MarkdownDisplay.stories.svelte +21 -0
- package/dist/components/MarkdownDisplay.stories.svelte.d.ts +27 -0
- package/dist/components/MarkdownDisplay.svelte +4 -3
- package/dist/components/Navbar.stories.svelte +41 -0
- package/dist/components/Navbar.stories.svelte.d.ts +27 -0
- package/dist/components/Navbar.svelte +4 -4
- package/dist/components/NodeSidebar.svelte +12 -12
- package/dist/components/NodeStatusOverlay.stories.svelte +74 -0
- package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +27 -0
- package/dist/components/PipelineStatus.svelte +11 -4
- package/dist/components/PortCoordinateTracker.svelte +1 -1
- package/dist/components/SchemaForm.stories.svelte +101 -0
- package/dist/components/SchemaForm.stories.svelte.d.ts +27 -0
- package/dist/components/SchemaForm.svelte +17 -12
- package/dist/components/SettingsModal.svelte +3 -3
- package/dist/components/SettingsPanel.svelte +23 -22
- package/dist/components/StatusIcon.stories.svelte +60 -0
- package/dist/components/StatusIcon.stories.svelte.d.ts +27 -0
- package/dist/components/StatusIcon.svelte +7 -0
- package/dist/components/StatusLabel.stories.svelte +17 -0
- package/dist/components/StatusLabel.stories.svelte.d.ts +27 -0
- package/dist/components/ThemeToggle.stories.svelte +25 -0
- package/dist/components/ThemeToggle.stories.svelte.d.ts +27 -0
- package/dist/components/ThemeToggle.svelte +8 -8
- package/dist/components/UniversalNode.svelte +1 -1
- package/dist/components/WorkflowEditor.svelte +298 -294
- package/dist/components/form/FormAutocomplete.svelte +20 -19
- package/dist/components/form/FormCheckboxGroup.stories.svelte +28 -0
- package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormField.svelte +3 -3
- package/dist/components/form/FormFieldLight.svelte +2 -2
- package/dist/components/form/FormFieldWrapper.stories.svelte +31 -0
- package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormFieldset.svelte +7 -7
- package/dist/components/form/FormNumberField.stories.svelte +33 -0
- package/dist/components/form/FormNumberField.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormRangeField.stories.svelte +31 -0
- package/dist/components/form/FormRangeField.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormSelect.stories.svelte +50 -0
- package/dist/components/form/FormSelect.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormTemplateEditor.svelte +2 -1
- package/dist/components/form/FormTextField.stories.svelte +30 -0
- package/dist/components/form/FormTextField.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormTextarea.stories.svelte +31 -0
- package/dist/components/form/FormTextarea.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormToggle.stories.svelte +30 -0
- package/dist/components/form/FormToggle.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormUISchemaRenderer.svelte +1 -1
- package/dist/components/form/types.d.ts +15 -47
- package/dist/components/interrupt/ChoicePrompt.stories.svelte +43 -0
- package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +24 -24
- package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +49 -0
- package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/ConfirmationPrompt.svelte +19 -19
- package/dist/components/interrupt/FormPrompt.svelte +15 -15
- package/dist/components/interrupt/InterruptBubble.svelte +202 -236
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +1 -1
- package/dist/components/interrupt/ReviewPrompt.stories.svelte +46 -0
- package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/ReviewPrompt.svelte +842 -0
- package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +23 -0
- package/dist/components/interrupt/TextInputPrompt.stories.svelte +34 -0
- package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/TextInputPrompt.svelte +21 -21
- package/dist/components/nodes/GatewayNode.stories.svelte +76 -0
- package/dist/components/nodes/GatewayNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/GatewayNode.svelte +19 -17
- package/dist/components/nodes/IdeaNode.stories.svelte +48 -0
- package/dist/components/nodes/IdeaNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/IdeaNode.svelte +10 -26
- package/dist/components/nodes/NotesNode.stories.svelte +69 -0
- package/dist/components/nodes/NotesNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/NotesNode.svelte +8 -8
- package/dist/components/nodes/SimpleNode.stories.svelte +101 -0
- package/dist/components/nodes/SimpleNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/SimpleNode.svelte +16 -24
- package/dist/components/nodes/SquareNode.stories.svelte +56 -0
- package/dist/components/nodes/SquareNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/SquareNode.svelte +13 -21
- package/dist/components/nodes/TerminalNode.stories.svelte +25 -0
- package/dist/components/nodes/TerminalNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/TerminalNode.svelte +6 -6
- package/dist/components/nodes/ToolNode.stories.svelte +71 -0
- package/dist/components/nodes/ToolNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/ToolNode.svelte +7 -15
- package/dist/components/nodes/WorkflowNode.stories.svelte +50 -0
- package/dist/components/nodes/WorkflowNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/WorkflowNode.svelte +13 -13
- package/dist/components/playground/ChatPanel.svelte +48 -48
- package/dist/components/playground/ExecutionLogs.svelte +23 -23
- package/dist/components/playground/InputCollector.svelte +24 -24
- package/dist/components/playground/MessageBubble.stories.svelte +49 -0
- package/dist/components/playground/MessageBubble.stories.svelte.d.ts +27 -0
- package/dist/components/playground/MessageBubble.svelte +49 -46
- package/dist/components/playground/Playground.svelte +194 -129
- package/dist/components/playground/PlaygroundModal.svelte +5 -5
- package/dist/components/playground/SessionManager.svelte +26 -26
- package/dist/config/constants.d.ts +22 -0
- package/dist/config/constants.js +22 -0
- package/dist/config/endpoints.d.ts +19 -0
- package/dist/config/runtimeConfig.js +2 -1
- package/dist/core/index.d.ts +5 -2
- package/dist/core/index.js +9 -1
- package/dist/editor/index.d.ts +13 -9
- package/dist/editor/index.js +15 -11
- package/dist/form/code.d.ts +2 -1
- package/dist/form/code.js +1 -3
- package/dist/form/markdown.d.ts +2 -1
- package/dist/form/markdown.js +1 -3
- package/dist/helpers/workflowEditorHelper.js +13 -9
- package/dist/mocks/app-forms.js +1 -0
- package/dist/mocks/app-navigation.js +3 -1
- package/dist/mocks/app-stores.d.ts +4 -4
- package/dist/playground/index.d.ts +4 -3
- package/dist/playground/index.js +12 -10
- package/dist/playground/mount.js +6 -13
- package/dist/services/agentSpecExecutionService.js +2 -1
- package/dist/services/api.js +10 -18
- package/dist/services/apiVariableService.js +2 -1
- package/dist/services/autoSaveService.d.ts +3 -3
- package/dist/services/autoSaveService.js +21 -17
- package/dist/services/categoriesApi.js +13 -5
- package/dist/services/draftStorage.js +5 -4
- package/dist/services/dynamicSchemaService.js +4 -4
- package/dist/services/globalSave.d.ts +60 -11
- package/dist/services/globalSave.js +160 -83
- package/dist/services/historyService.d.ts +2 -1
- package/dist/services/historyService.js +7 -3
- package/dist/services/interruptService.js +9 -8
- package/dist/services/nodeExecutionService.js +14 -6
- package/dist/services/playgroundService.js +2 -1
- package/dist/services/portConfigApi.js +11 -7
- package/dist/services/toastService.d.ts +1 -1
- package/dist/services/toastService.js +6 -5
- package/dist/services/variableService.js +3 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js +1 -1
- package/dist/stores/{categoriesStore.d.ts → categoriesStore.svelte.d.ts} +3 -3
- package/dist/stores/{categoriesStore.js → categoriesStore.svelte.js} +15 -18
- package/dist/stores/editorStateMachine.svelte.d.ts +42 -0
- package/dist/stores/editorStateMachine.svelte.js +132 -0
- package/dist/stores/{historyStore.d.ts → historyStore.svelte.d.ts} +18 -15
- package/dist/stores/{historyStore.js → historyStore.svelte.js} +40 -21
- package/dist/stores/{interruptStore.d.ts → interruptStore.svelte.d.ts} +16 -15
- package/dist/stores/{interruptStore.js → interruptStore.svelte.js} +85 -94
- package/dist/stores/{playgroundStore.d.ts → playgroundStore.svelte.d.ts} +41 -33
- package/dist/stores/{playgroundStore.js → playgroundStore.svelte.js} +164 -84
- package/dist/stores/{portCoordinateStore.d.ts → portCoordinateStore.svelte.d.ts} +10 -4
- package/dist/stores/{portCoordinateStore.js → portCoordinateStore.svelte.js} +38 -35
- package/dist/stores/{settingsStore.d.ts → settingsStore.svelte.d.ts} +45 -28
- package/dist/stores/{settingsStore.js → settingsStore.svelte.js} +169 -128
- package/dist/stores/{workflowStore.d.ts → workflowStore.svelte.d.ts} +101 -65
- package/dist/stores/{workflowStore.js → workflowStore.svelte.js} +285 -239
- package/dist/stories/CanvasDecorator.svelte +50 -0
- package/dist/stories/CanvasDecorator.svelte.d.ts +8 -0
- package/dist/stories/NodeDecorator.svelte +74 -0
- package/dist/stories/NodeDecorator.svelte.d.ts +8 -0
- package/dist/stories/utils.d.ts +93 -0
- package/dist/stories/utils.js +122 -0
- package/dist/styles/base.css +114 -61
- package/dist/styles/toast.css +2 -2
- package/dist/styles/tokens.css +250 -185
- package/dist/svelte-app.d.ts +0 -6
- package/dist/svelte-app.js +13 -31
- package/dist/types/index.d.ts +2 -0
- package/dist/types/interrupt.d.ts +89 -5
- package/dist/types/interrupt.js +13 -1
- package/dist/types/playground.d.ts +5 -0
- package/dist/types/settings.js +1 -1
- package/dist/utils/colors.js +4 -4
- package/dist/utils/connections.js +33 -8
- package/dist/utils/icons.js +1 -1
- package/dist/utils/logger.d.ts +47 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/nodeWrapper.js +1 -1
- package/dist/utils/sanitize.d.ts +19 -0
- package/dist/utils/sanitize.js +31 -0
- package/dist/utils/validation.d.ts +29 -0
- package/dist/utils/validation.js +39 -0
- package/package.json +243 -232
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow Store for FlowDrop
|
|
2
|
+
* Workflow Store for FlowDrop (Svelte 5 Runes)
|
|
3
3
|
*
|
|
4
4
|
* Provides global state management for workflows with dirty state tracking
|
|
5
5
|
* and undo/redo history integration.
|
|
6
6
|
*
|
|
7
|
+
* **Important: Single-instance only.** This store uses module-level singletons.
|
|
8
|
+
* Only one FlowDrop editor instance per page is supported. Mounting multiple
|
|
9
|
+
* FlowDrop editors on the same page will cause them to share workflow state.
|
|
10
|
+
*
|
|
7
11
|
* @module stores/workflowStore
|
|
8
12
|
*/
|
|
9
|
-
import { writable, derived, get } from 'svelte/store';
|
|
10
13
|
import { DEFAULT_WORKFLOW_FORMAT } from '../types/index.js';
|
|
11
14
|
import { historyService } from '../services/historyService.js';
|
|
15
|
+
/**
|
|
16
|
+
* Safely build updated workflow metadata, providing defaults for required fields.
|
|
17
|
+
*/
|
|
18
|
+
function buildMetadata(existing, updates) {
|
|
19
|
+
return {
|
|
20
|
+
version: existing?.version ?? '1.0',
|
|
21
|
+
createdAt: existing?.createdAt ?? new Date().toISOString(),
|
|
22
|
+
updatedAt: new Date().toISOString(),
|
|
23
|
+
author: existing?.author,
|
|
24
|
+
tags: existing?.tags,
|
|
25
|
+
versionId: existing?.versionId,
|
|
26
|
+
updateNumber: existing?.updateNumber,
|
|
27
|
+
format: existing?.format,
|
|
28
|
+
...updates
|
|
29
|
+
};
|
|
30
|
+
}
|
|
12
31
|
// =========================================================================
|
|
13
|
-
// Core Workflow
|
|
32
|
+
// Core Workflow State (Svelte 5 Runes)
|
|
14
33
|
// =========================================================================
|
|
15
34
|
/** Global workflow state */
|
|
16
|
-
|
|
35
|
+
let workflowState = $state(null);
|
|
17
36
|
// =========================================================================
|
|
18
37
|
// Dirty State Tracking
|
|
19
38
|
// =========================================================================
|
|
20
39
|
/**
|
|
21
|
-
*
|
|
40
|
+
* State for tracking if there are unsaved changes
|
|
22
41
|
*
|
|
23
42
|
* This is set to true whenever the workflow changes after initialization.
|
|
24
43
|
* It can be reset to false by calling markAsSaved().
|
|
25
44
|
*/
|
|
26
|
-
|
|
45
|
+
let isDirtyState = $state(false);
|
|
27
46
|
/**
|
|
28
47
|
* Snapshot of the workflow when it was last saved
|
|
29
48
|
*
|
|
@@ -54,6 +73,152 @@ let isRestoringFromHistory = false;
|
|
|
54
73
|
* Can be disabled for bulk operations or when history is not needed.
|
|
55
74
|
*/
|
|
56
75
|
let historyEnabled = true;
|
|
76
|
+
// =========================================================================
|
|
77
|
+
// Getter Functions (Reactive State Access)
|
|
78
|
+
// =========================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Get the current workflow store value reactively
|
|
81
|
+
*
|
|
82
|
+
* @returns The current workflow or null
|
|
83
|
+
*/
|
|
84
|
+
export function getWorkflowStore() {
|
|
85
|
+
return workflowState;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the current dirty state reactively
|
|
89
|
+
*
|
|
90
|
+
* @returns true if there are unsaved changes
|
|
91
|
+
*/
|
|
92
|
+
export function getIsDirty() {
|
|
93
|
+
return isDirtyState;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the workflow ID reactively
|
|
97
|
+
*
|
|
98
|
+
* @returns The workflow ID or null
|
|
99
|
+
*/
|
|
100
|
+
export function getWorkflowId() {
|
|
101
|
+
return workflowState?.id ?? null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get the workflow name reactively
|
|
105
|
+
*
|
|
106
|
+
* @returns The workflow name or 'Untitled Workflow'
|
|
107
|
+
*/
|
|
108
|
+
export function getWorkflowName() {
|
|
109
|
+
return workflowState?.name ?? 'Untitled Workflow';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the workflow nodes reactively
|
|
113
|
+
*
|
|
114
|
+
* @returns Array of workflow nodes
|
|
115
|
+
*/
|
|
116
|
+
export function getWorkflowNodes() {
|
|
117
|
+
return workflowState?.nodes ?? [];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the workflow edges reactively
|
|
121
|
+
*
|
|
122
|
+
* @returns Array of workflow edges
|
|
123
|
+
*/
|
|
124
|
+
export function getWorkflowEdges() {
|
|
125
|
+
return workflowState?.edges ?? [];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get the workflow metadata reactively
|
|
129
|
+
*
|
|
130
|
+
* @returns The workflow metadata with defaults
|
|
131
|
+
*/
|
|
132
|
+
export function getWorkflowMetadata() {
|
|
133
|
+
return (workflowState?.metadata ?? {
|
|
134
|
+
version: '1.0.0',
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
updatedAt: new Date().toISOString(),
|
|
137
|
+
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
138
|
+
updateNumber: 0
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the current workflow format reactively
|
|
143
|
+
*
|
|
144
|
+
* @returns The workflow format string
|
|
145
|
+
*/
|
|
146
|
+
export function getWorkflowFormat() {
|
|
147
|
+
return workflowState?.metadata?.format ?? DEFAULT_WORKFLOW_FORMAT;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get workflow change summary reactively (useful for triggering saves)
|
|
151
|
+
*
|
|
152
|
+
* @returns Object with nodes, edges, and name
|
|
153
|
+
*/
|
|
154
|
+
export function getWorkflowChanged() {
|
|
155
|
+
return {
|
|
156
|
+
nodes: getWorkflowNodes(),
|
|
157
|
+
edges: getWorkflowEdges(),
|
|
158
|
+
name: getWorkflowName()
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get workflow validation state reactively
|
|
163
|
+
*
|
|
164
|
+
* @returns Validation info object
|
|
165
|
+
*/
|
|
166
|
+
export function getWorkflowValidation() {
|
|
167
|
+
const nodes = getWorkflowNodes();
|
|
168
|
+
const edges = getWorkflowEdges();
|
|
169
|
+
return {
|
|
170
|
+
hasNodes: nodes.length > 0,
|
|
171
|
+
hasEdges: edges.length > 0,
|
|
172
|
+
nodeCount: nodes.length,
|
|
173
|
+
edgeCount: edges.length,
|
|
174
|
+
isValid: nodes.length > 0 && edges.length >= 0
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get workflow metadata change summary reactively
|
|
179
|
+
*
|
|
180
|
+
* @returns Metadata change info
|
|
181
|
+
*/
|
|
182
|
+
export function getWorkflowMetadataChanged() {
|
|
183
|
+
const metadata = getWorkflowMetadata();
|
|
184
|
+
return {
|
|
185
|
+
createdAt: metadata.createdAt,
|
|
186
|
+
updatedAt: metadata.updatedAt,
|
|
187
|
+
version: metadata.version ?? '1.0.0'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get connected handles reactively
|
|
192
|
+
*
|
|
193
|
+
* Provides a Set of all handle IDs that are currently connected to edges.
|
|
194
|
+
* Used by node components to implement hideUnconnectedHandles functionality.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* import { getConnectedHandles } from './workflowStore.svelte.js';
|
|
199
|
+
*
|
|
200
|
+
* // Check if a specific handle is connected
|
|
201
|
+
* const isConnected = getConnectedHandles().has('node-1-input-data');
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export function getConnectedHandles() {
|
|
205
|
+
const edges = getWorkflowEdges();
|
|
206
|
+
const handles = new Set();
|
|
207
|
+
edges.forEach((edge) => {
|
|
208
|
+
// Add source handle (output port)
|
|
209
|
+
if (edge.sourceHandle) {
|
|
210
|
+
handles.add(edge.sourceHandle);
|
|
211
|
+
}
|
|
212
|
+
// Add target handle (input port)
|
|
213
|
+
if (edge.targetHandle) {
|
|
214
|
+
handles.add(edge.targetHandle);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return handles;
|
|
218
|
+
}
|
|
219
|
+
// =========================================================================
|
|
220
|
+
// Callback Setters
|
|
221
|
+
// =========================================================================
|
|
57
222
|
/**
|
|
58
223
|
* Set the dirty state change callback
|
|
59
224
|
*
|
|
@@ -70,6 +235,9 @@ export function setOnDirtyStateChange(callback) {
|
|
|
70
235
|
export function setOnWorkflowChange(callback) {
|
|
71
236
|
onWorkflowChangeCallback = callback;
|
|
72
237
|
}
|
|
238
|
+
// =========================================================================
|
|
239
|
+
// Internal Helpers
|
|
240
|
+
// =========================================================================
|
|
73
241
|
/**
|
|
74
242
|
* Create a snapshot of the workflow for comparison
|
|
75
243
|
*
|
|
@@ -107,14 +275,12 @@ function createSnapshot(workflow) {
|
|
|
107
275
|
* Compares current workflow with saved snapshot.
|
|
108
276
|
*/
|
|
109
277
|
function updateDirtyState() {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (isDirty !== previousDirty) {
|
|
115
|
-
isDirtyStore.set(isDirty);
|
|
278
|
+
const currentSnapshot = createSnapshot(workflowState);
|
|
279
|
+
const newIsDirty = currentSnapshot !== savedSnapshot;
|
|
280
|
+
if (newIsDirty !== isDirtyState) {
|
|
281
|
+
isDirtyState = newIsDirty;
|
|
116
282
|
if (onDirtyStateChangeCallback) {
|
|
117
|
-
onDirtyStateChangeCallback(
|
|
283
|
+
onDirtyStateChangeCallback(newIsDirty);
|
|
118
284
|
}
|
|
119
285
|
}
|
|
120
286
|
}
|
|
@@ -124,9 +290,8 @@ function updateDirtyState() {
|
|
|
124
290
|
* @param changeType - The type of change that occurred
|
|
125
291
|
*/
|
|
126
292
|
function notifyWorkflowChange(changeType) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
onWorkflowChangeCallback(workflow, changeType);
|
|
293
|
+
if (workflowState && onWorkflowChangeCallback) {
|
|
294
|
+
onWorkflowChangeCallback(workflowState, changeType);
|
|
130
295
|
}
|
|
131
296
|
updateDirtyState();
|
|
132
297
|
}
|
|
@@ -136,20 +301,19 @@ function notifyWorkflowChange(changeType) {
|
|
|
136
301
|
* Clears the dirty state by updating the saved snapshot.
|
|
137
302
|
*/
|
|
138
303
|
export function markAsSaved() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
isDirtyStore.set(false);
|
|
304
|
+
savedSnapshot = createSnapshot(workflowState);
|
|
305
|
+
isDirtyState = false;
|
|
142
306
|
if (onDirtyStateChangeCallback) {
|
|
143
307
|
onDirtyStateChangeCallback(false);
|
|
144
308
|
}
|
|
145
309
|
}
|
|
146
310
|
/**
|
|
147
|
-
* Check if there are unsaved changes
|
|
311
|
+
* Check if there are unsaved changes (non-reactive version for plain TS)
|
|
148
312
|
*
|
|
149
313
|
* @returns true if there are unsaved changes
|
|
150
314
|
*/
|
|
151
315
|
export function isDirty() {
|
|
152
|
-
return
|
|
316
|
+
return isDirtyState;
|
|
153
317
|
}
|
|
154
318
|
/**
|
|
155
319
|
* Enable or disable history recording
|
|
@@ -189,41 +353,20 @@ function pushToHistory(description, workflow) {
|
|
|
189
353
|
if (!historyEnabled || isRestoringFromHistory) {
|
|
190
354
|
return;
|
|
191
355
|
}
|
|
192
|
-
const workflowToPush = workflow ??
|
|
356
|
+
const workflowToPush = workflow ?? workflowState;
|
|
193
357
|
if (workflowToPush) {
|
|
194
358
|
historyService.push(workflowToPush, { description });
|
|
195
359
|
}
|
|
196
360
|
}
|
|
197
361
|
/**
|
|
198
|
-
* Get the current workflow
|
|
362
|
+
* Get the current workflow (non-reactive version for plain TS)
|
|
199
363
|
*
|
|
200
364
|
* @returns The current workflow or null
|
|
201
365
|
*/
|
|
202
366
|
export function getWorkflow() {
|
|
203
|
-
return
|
|
367
|
+
return workflowState;
|
|
204
368
|
}
|
|
205
369
|
// =========================================================================
|
|
206
|
-
// Derived Stores
|
|
207
|
-
// =========================================================================
|
|
208
|
-
/** Derived store for workflow ID */
|
|
209
|
-
export const workflowId = derived(workflowStore, ($workflow) => $workflow?.id ?? null);
|
|
210
|
-
/** Derived store for workflow name */
|
|
211
|
-
export const workflowName = derived(workflowStore, ($workflow) => $workflow?.name ?? 'Untitled Workflow');
|
|
212
|
-
/** Derived store for workflow nodes */
|
|
213
|
-
export const workflowNodes = derived(workflowStore, ($workflow) => $workflow?.nodes ?? []);
|
|
214
|
-
/** Derived store for workflow edges */
|
|
215
|
-
export const workflowEdges = derived(workflowStore, ($workflow) => $workflow?.edges ?? []);
|
|
216
|
-
/** Derived store for workflow metadata */
|
|
217
|
-
export const workflowMetadata = derived(workflowStore, ($workflow) => $workflow?.metadata ?? {
|
|
218
|
-
version: '1.0.0',
|
|
219
|
-
createdAt: new Date().toISOString(),
|
|
220
|
-
updatedAt: new Date().toISOString(),
|
|
221
|
-
versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
222
|
-
updateNumber: 0
|
|
223
|
-
});
|
|
224
|
-
/** Derived store for the current workflow format */
|
|
225
|
-
export const workflowFormat = derived(workflowStore, ($workflow) => $workflow?.metadata?.format ?? DEFAULT_WORKFLOW_FORMAT);
|
|
226
|
-
// =========================================================================
|
|
227
370
|
// Helper Functions
|
|
228
371
|
// =========================================================================
|
|
229
372
|
/**
|
|
@@ -281,10 +424,10 @@ export const workflowActions = {
|
|
|
281
424
|
* This sets the initial saved snapshot, clears dirty state, and initializes history.
|
|
282
425
|
*/
|
|
283
426
|
initialize: (workflow) => {
|
|
284
|
-
|
|
427
|
+
workflowState = workflow;
|
|
285
428
|
// Set the saved snapshot - workflow is "clean" after initialization
|
|
286
429
|
savedSnapshot = createSnapshot(workflow);
|
|
287
|
-
|
|
430
|
+
isDirtyState = false;
|
|
288
431
|
if (onDirtyStateChangeCallback) {
|
|
289
432
|
onDirtyStateChangeCallback(false);
|
|
290
433
|
}
|
|
@@ -298,7 +441,7 @@ export const workflowActions = {
|
|
|
298
441
|
* for every small change. History is pushed by specific actions or drag handlers.
|
|
299
442
|
*/
|
|
300
443
|
updateWorkflow: (workflow) => {
|
|
301
|
-
|
|
444
|
+
workflowState = workflow;
|
|
302
445
|
notifyWorkflowChange('metadata');
|
|
303
446
|
},
|
|
304
447
|
/**
|
|
@@ -308,7 +451,7 @@ export const workflowActions = {
|
|
|
308
451
|
*/
|
|
309
452
|
restoreFromHistory: (workflow) => {
|
|
310
453
|
isRestoringFromHistory = true;
|
|
311
|
-
|
|
454
|
+
workflowState = workflow;
|
|
312
455
|
notifyWorkflowChange('metadata');
|
|
313
456
|
isRestoringFromHistory = false;
|
|
314
457
|
},
|
|
@@ -316,70 +459,57 @@ export const workflowActions = {
|
|
|
316
459
|
* Update nodes
|
|
317
460
|
*/
|
|
318
461
|
updateNodes: (nodes) => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
updateNumber: ($workflow.metadata?.updateNumber ?? 0) + 1
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
});
|
|
462
|
+
if (!workflowState)
|
|
463
|
+
return;
|
|
464
|
+
// Check if nodes have actually changed to prevent infinite loops
|
|
465
|
+
if (!hasWorkflowDataChanged(workflowState, nodes, workflowState.edges)) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Generate unique version identifier
|
|
469
|
+
const versionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
470
|
+
workflowState = {
|
|
471
|
+
...workflowState,
|
|
472
|
+
nodes,
|
|
473
|
+
metadata: buildMetadata(workflowState.metadata, {
|
|
474
|
+
versionId,
|
|
475
|
+
updateNumber: (workflowState.metadata?.updateNumber ?? 0) + 1
|
|
476
|
+
})
|
|
477
|
+
};
|
|
339
478
|
notifyWorkflowChange('node_move');
|
|
340
479
|
},
|
|
341
480
|
/**
|
|
342
481
|
* Update edges
|
|
343
482
|
*/
|
|
344
483
|
updateEdges: (edges) => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
updateNumber: ($workflow.metadata?.updateNumber ?? 0) + 1
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
});
|
|
484
|
+
if (!workflowState)
|
|
485
|
+
return;
|
|
486
|
+
// Check if edges have actually changed to prevent infinite loops
|
|
487
|
+
if (!hasWorkflowDataChanged(workflowState, workflowState.nodes, edges)) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
// Generate unique version identifier
|
|
491
|
+
const versionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
492
|
+
workflowState = {
|
|
493
|
+
...workflowState,
|
|
494
|
+
edges,
|
|
495
|
+
metadata: buildMetadata(workflowState.metadata, {
|
|
496
|
+
versionId,
|
|
497
|
+
updateNumber: (workflowState.metadata?.updateNumber ?? 0) + 1
|
|
498
|
+
})
|
|
499
|
+
};
|
|
365
500
|
notifyWorkflowChange('edge_add');
|
|
366
501
|
},
|
|
367
502
|
/**
|
|
368
503
|
* Update workflow name
|
|
369
504
|
*/
|
|
370
505
|
updateName: (name) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
...$workflow.metadata,
|
|
379
|
-
updatedAt: new Date().toISOString()
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
});
|
|
506
|
+
if (!workflowState)
|
|
507
|
+
return;
|
|
508
|
+
workflowState = {
|
|
509
|
+
...workflowState,
|
|
510
|
+
name,
|
|
511
|
+
metadata: buildMetadata(workflowState.metadata)
|
|
512
|
+
};
|
|
383
513
|
notifyWorkflowChange('name');
|
|
384
514
|
},
|
|
385
515
|
/**
|
|
@@ -387,18 +517,13 @@ export const workflowActions = {
|
|
|
387
517
|
*/
|
|
388
518
|
addNode: (node) => {
|
|
389
519
|
pushToHistory('Add node');
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
...$workflow.metadata,
|
|
398
|
-
updatedAt: new Date().toISOString()
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
});
|
|
520
|
+
if (!workflowState)
|
|
521
|
+
return;
|
|
522
|
+
workflowState = {
|
|
523
|
+
...workflowState,
|
|
524
|
+
nodes: [...workflowState.nodes, node],
|
|
525
|
+
metadata: buildMetadata(workflowState.metadata)
|
|
526
|
+
};
|
|
402
527
|
notifyWorkflowChange('node_add');
|
|
403
528
|
},
|
|
404
529
|
/**
|
|
@@ -409,19 +534,14 @@ export const workflowActions = {
|
|
|
409
534
|
*/
|
|
410
535
|
removeNode: (nodeId) => {
|
|
411
536
|
pushToHistory('Delete node');
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
...$workflow.metadata,
|
|
421
|
-
updatedAt: new Date().toISOString()
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
});
|
|
537
|
+
if (!workflowState)
|
|
538
|
+
return;
|
|
539
|
+
workflowState = {
|
|
540
|
+
...workflowState,
|
|
541
|
+
nodes: workflowState.nodes.filter((node) => node.id !== nodeId),
|
|
542
|
+
edges: workflowState.edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId),
|
|
543
|
+
metadata: buildMetadata(workflowState.metadata)
|
|
544
|
+
};
|
|
425
545
|
notifyWorkflowChange('node_remove');
|
|
426
546
|
},
|
|
427
547
|
/**
|
|
@@ -429,18 +549,13 @@ export const workflowActions = {
|
|
|
429
549
|
*/
|
|
430
550
|
addEdge: (edge) => {
|
|
431
551
|
pushToHistory('Add connection');
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
...$workflow.metadata,
|
|
440
|
-
updatedAt: new Date().toISOString()
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
});
|
|
552
|
+
if (!workflowState)
|
|
553
|
+
return;
|
|
554
|
+
workflowState = {
|
|
555
|
+
...workflowState,
|
|
556
|
+
edges: [...workflowState.edges, edge],
|
|
557
|
+
metadata: buildMetadata(workflowState.metadata)
|
|
558
|
+
};
|
|
444
559
|
notifyWorkflowChange('edge_add');
|
|
445
560
|
},
|
|
446
561
|
/**
|
|
@@ -448,18 +563,13 @@ export const workflowActions = {
|
|
|
448
563
|
*/
|
|
449
564
|
removeEdge: (edgeId) => {
|
|
450
565
|
pushToHistory('Delete connection');
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
...$workflow.metadata,
|
|
459
|
-
updatedAt: new Date().toISOString()
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
});
|
|
566
|
+
if (!workflowState)
|
|
567
|
+
return;
|
|
568
|
+
workflowState = {
|
|
569
|
+
...workflowState,
|
|
570
|
+
edges: workflowState.edges.filter((edge) => edge.id !== edgeId),
|
|
571
|
+
metadata: buildMetadata(workflowState.metadata)
|
|
572
|
+
};
|
|
463
573
|
notifyWorkflowChange('edge_remove');
|
|
464
574
|
},
|
|
465
575
|
/**
|
|
@@ -469,18 +579,13 @@ export const workflowActions = {
|
|
|
469
579
|
*/
|
|
470
580
|
updateNode: (nodeId, updates) => {
|
|
471
581
|
pushToHistory('Update node config');
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
...$workflow.metadata,
|
|
480
|
-
updatedAt: new Date().toISOString()
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
});
|
|
582
|
+
if (!workflowState)
|
|
583
|
+
return;
|
|
584
|
+
workflowState = {
|
|
585
|
+
...workflowState,
|
|
586
|
+
nodes: workflowState.nodes.map((node) => node.id === nodeId ? { ...node, ...updates } : node),
|
|
587
|
+
metadata: buildMetadata(workflowState.metadata)
|
|
588
|
+
};
|
|
484
589
|
notifyWorkflowChange('node_config');
|
|
485
590
|
},
|
|
486
591
|
/**
|
|
@@ -489,9 +594,9 @@ export const workflowActions = {
|
|
|
489
594
|
* Resets the workflow and clears history.
|
|
490
595
|
*/
|
|
491
596
|
clear: () => {
|
|
492
|
-
|
|
597
|
+
workflowState = null;
|
|
493
598
|
savedSnapshot = null;
|
|
494
|
-
|
|
599
|
+
isDirtyState = false;
|
|
495
600
|
historyService.clear();
|
|
496
601
|
if (onDirtyStateChangeCallback) {
|
|
497
602
|
onDirtyStateChangeCallback(false);
|
|
@@ -501,18 +606,12 @@ export const workflowActions = {
|
|
|
501
606
|
* Update workflow metadata
|
|
502
607
|
*/
|
|
503
608
|
updateMetadata: (metadata) => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
...$workflow.metadata,
|
|
511
|
-
...metadata,
|
|
512
|
-
updatedAt: new Date().toISOString()
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
});
|
|
609
|
+
if (!workflowState)
|
|
610
|
+
return;
|
|
611
|
+
workflowState = {
|
|
612
|
+
...workflowState,
|
|
613
|
+
metadata: buildMetadata(workflowState.metadata, metadata)
|
|
614
|
+
};
|
|
516
615
|
notifyWorkflowChange('metadata');
|
|
517
616
|
},
|
|
518
617
|
/**
|
|
@@ -523,22 +622,16 @@ export const workflowActions = {
|
|
|
523
622
|
*/
|
|
524
623
|
batchUpdate: (updates) => {
|
|
525
624
|
pushToHistory('Batch update');
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
...$workflow.metadata,
|
|
537
|
-
...(updates.metadata && { ...updates.metadata }),
|
|
538
|
-
updatedAt: new Date().toISOString()
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
});
|
|
625
|
+
if (!workflowState)
|
|
626
|
+
return;
|
|
627
|
+
workflowState = {
|
|
628
|
+
...workflowState,
|
|
629
|
+
...(updates.nodes && { nodes: updates.nodes }),
|
|
630
|
+
...(updates.edges && { edges: updates.edges }),
|
|
631
|
+
...(updates.name && { name: updates.name }),
|
|
632
|
+
...(updates.description !== undefined && { description: updates.description }),
|
|
633
|
+
metadata: buildMetadata(workflowState.metadata, updates.metadata ?? undefined)
|
|
634
|
+
};
|
|
542
635
|
notifyWorkflowChange('metadata');
|
|
543
636
|
},
|
|
544
637
|
/**
|
|
@@ -554,50 +647,3 @@ export const workflowActions = {
|
|
|
554
647
|
pushToHistory(description, workflow);
|
|
555
648
|
}
|
|
556
649
|
};
|
|
557
|
-
// =========================================================================
|
|
558
|
-
// Additional Derived Stores
|
|
559
|
-
// =========================================================================
|
|
560
|
-
/** Derived store for workflow changes (useful for triggering saves) */
|
|
561
|
-
export const workflowChanged = derived([workflowNodes, workflowEdges, workflowName], ([nodes, edges, name]) => ({ nodes, edges, name }));
|
|
562
|
-
/** Derived store for workflow validation */
|
|
563
|
-
export const workflowValidation = derived([workflowNodes, workflowEdges], ([nodes, edges]) => ({
|
|
564
|
-
hasNodes: nodes.length > 0,
|
|
565
|
-
hasEdges: edges.length > 0,
|
|
566
|
-
nodeCount: nodes.length,
|
|
567
|
-
edgeCount: edges.length,
|
|
568
|
-
isValid: nodes.length > 0 && edges.length >= 0
|
|
569
|
-
}));
|
|
570
|
-
/** Derived store for workflow metadata changes */
|
|
571
|
-
export const workflowMetadataChanged = derived(workflowMetadata, (metadata) => ({
|
|
572
|
-
createdAt: metadata.createdAt,
|
|
573
|
-
updatedAt: metadata.updatedAt,
|
|
574
|
-
version: metadata.version ?? '1.0.0'
|
|
575
|
-
}));
|
|
576
|
-
/**
|
|
577
|
-
* Derived store for connected handles
|
|
578
|
-
*
|
|
579
|
-
* Provides a Set of all handle IDs that are currently connected to edges.
|
|
580
|
-
* Used by node components to implement hideUnconnectedHandles functionality.
|
|
581
|
-
*
|
|
582
|
-
* @example
|
|
583
|
-
* ```typescript
|
|
584
|
-
* import { connectedHandles } from './workflowStore.js';
|
|
585
|
-
*
|
|
586
|
-
* // Check if a specific handle is connected
|
|
587
|
-
* const isConnected = $connectedHandles.has('node-1-input-data');
|
|
588
|
-
* ```
|
|
589
|
-
*/
|
|
590
|
-
export const connectedHandles = derived(workflowEdges, (edges) => {
|
|
591
|
-
const handles = new Set();
|
|
592
|
-
edges.forEach((edge) => {
|
|
593
|
-
// Add source handle (output port)
|
|
594
|
-
if (edge.sourceHandle) {
|
|
595
|
-
handles.add(edge.sourceHandle);
|
|
596
|
-
}
|
|
597
|
-
// Add target handle (input port)
|
|
598
|
-
if (edge.targetHandle) {
|
|
599
|
-
handles.add(edge.targetHandle);
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
return handles;
|
|
603
|
-
});
|