@genfeedai/workflow-ui 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +11 -33
  2. package/dist/canvas.d.ts +22 -22
  3. package/dist/canvas.mjs +16 -17
  4. package/dist/{chunk-WBR34V4L.mjs → chunk-2FUPL67V.mjs} +1593 -1045
  5. package/dist/{chunk-4VEN4UN7.mjs → chunk-53XDE62A.mjs} +818 -623
  6. package/dist/{chunk-PCIWWD37.mjs → chunk-7LV4UAUS.mjs} +19 -19
  7. package/dist/{chunk-7SKSRSS7.mjs → chunk-B4EAAKYF.mjs} +16 -16
  8. package/dist/{chunk-ZJD5WMR3.mjs → chunk-C6MQBJFC.mjs} +45 -13
  9. package/dist/{chunk-7H3WJJYS.mjs → chunk-ESVULCFY.mjs} +12 -6
  10. package/dist/{chunk-GWBGK3KL.mjs → chunk-FWJIAW2E.mjs} +82 -47
  11. package/dist/{chunk-R727OFBR.mjs → chunk-GPYIIWD5.mjs} +404 -350
  12. package/dist/{chunk-OQREHJXK.mjs → chunk-IYFWAJBB.mjs} +208 -203
  13. package/dist/{chunk-2JQSKIWR.mjs → chunk-MGLAKMDP.mjs} +24 -23
  14. package/dist/{chunk-LT3ZJJL6.mjs → chunk-OJWVEEMM.mjs} +497 -399
  15. package/dist/{chunk-ZD2BADZO.mjs → chunk-ORVDYXDP.mjs} +221 -175
  16. package/dist/{chunk-CV4M7CNU.mjs → chunk-QQVHGJ2G.mjs} +149 -142
  17. package/dist/{chunk-6PSJTBNV.mjs → chunk-U4QPE4CY.mjs} +387 -347
  18. package/dist/{chunk-EFXQT23N.mjs → chunk-VVQ4CH77.mjs} +5 -5
  19. package/dist/{chunk-VRN3UWE5.mjs → chunk-XRC3O5GK.mjs} +73 -73
  20. package/dist/{chunk-FT33LFII.mjs → chunk-YUIK4AHM.mjs} +1 -1
  21. package/dist/{chunk-JT4Y5H3U.mjs → chunk-ZSITTZ4S.mjs} +630 -569
  22. package/dist/hooks.d.ts +37 -37
  23. package/dist/hooks.mjs +10 -10
  24. package/dist/index.d.ts +26 -11
  25. package/dist/index.mjs +99 -20
  26. package/dist/lib.d.ts +203 -203
  27. package/dist/lib.mjs +228 -199
  28. package/dist/nodes.d.ts +2 -2
  29. package/dist/nodes.mjs +12 -13
  30. package/dist/panels.d.ts +2 -3
  31. package/dist/panels.mjs +3 -3
  32. package/dist/provider.d.ts +2 -2
  33. package/dist/provider.mjs +2 -2
  34. package/dist/stores.d.ts +5 -5
  35. package/dist/stores.mjs +5 -5
  36. package/dist/toolbar.d.ts +42 -24
  37. package/dist/toolbar.mjs +4 -5
  38. package/dist/ui.d.ts +2 -2
  39. package/dist/ui.mjs +2 -2
  40. package/dist/{useCommentNavigation-BakbiiIc.d.ts → useRequiredInputs-ByoIS-fT.d.ts} +160 -160
  41. package/dist/{promptLibraryStore-Dl3Q3cP6.d.ts → workflowStore-Bsz0nd5c.d.ts} +368 -368
  42. package/dist/workflowStore-N2F7WIG3.mjs +2 -0
  43. package/package.json +79 -77
  44. package/src/styles/workflow-ui.css +56 -19
  45. package/dist/chunk-OY7BRSGG.mjs +0 -60
  46. package/dist/workflowStore-UAAKOOIK.mjs +0 -2
  47. package/dist/{types-IEKYuYhu.d.ts → types-CRXJnajq.d.ts} +1 -1
@@ -1,7 +1,7 @@
1
1
  import { temporal } from 'zundo';
2
2
  import { create } from 'zustand';
3
3
  import { NODE_DEFINITIONS, CONNECTION_RULES } from '@genfeedai/types';
4
- import { addEdge, applyEdgeChanges, applyNodeChanges } from '@xyflow/react';
4
+ import { applyNodeChanges, applyEdgeChanges, addEdge } from '@xyflow/react';
5
5
  import { nanoid } from 'nanoid';
6
6
 
7
7
  // src/stores/workflow/helpers/equality.ts
@@ -24,7 +24,8 @@ function nodeEquals(a, b) {
24
24
  if (a === b) return true;
25
25
  if (a.id !== b.id) return false;
26
26
  if (a.type !== b.type) return false;
27
- if (a.position.x !== b.position.x || a.position.y !== b.position.y) return false;
27
+ if (a.position.x !== b.position.x || a.position.y !== b.position.y)
28
+ return false;
28
29
  if (a.width !== b.width || a.height !== b.height) return false;
29
30
  const aData = a.data;
30
31
  const bData = b.data;
@@ -68,34 +69,34 @@ function groupEquals(a, b) {
68
69
 
69
70
  // src/stores/workflow/slices/chatSlice.ts
70
71
  var createChatSlice = (set, get) => ({
71
- chatMessages: [],
72
- isChatOpen: false,
73
72
  addChatMessage: (role, content) => {
74
73
  set((state) => ({
75
74
  chatMessages: [
76
75
  ...state.chatMessages,
77
76
  {
77
+ content,
78
78
  id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
79
79
  role,
80
- content,
81
80
  timestamp: Date.now()
82
81
  }
83
82
  ]
84
83
  }));
85
84
  },
85
+ applyChatEditOperations: (operations) => {
86
+ const state = get();
87
+ state.captureSnapshot();
88
+ return state.applyEditOperations(operations);
89
+ },
90
+ chatMessages: [],
86
91
  clearChatMessages: () => {
87
92
  set({ chatMessages: [] });
88
93
  },
89
- toggleChat: () => {
90
- set((state) => ({ isChatOpen: !state.isChatOpen }));
91
- },
94
+ isChatOpen: false,
92
95
  setChatOpen: (open) => {
93
96
  set({ isChatOpen: open });
94
97
  },
95
- applyChatEditOperations: (operations) => {
96
- const state = get();
97
- state.captureSnapshot();
98
- return state.applyEditOperations(operations);
98
+ toggleChat: () => {
99
+ set((state) => ({ isChatOpen: !state.isChatOpen }));
99
100
  }
100
101
  });
101
102
  function generateId() {
@@ -111,23 +112,48 @@ function getHandleType(nodeType, handleId, direction) {
111
112
 
112
113
  // src/stores/workflow/slices/edgeSlice.ts
113
114
  var createEdgeSlice = (set, get) => ({
114
- onNodesChange: (changes) => {
115
- const hasMeaningfulChange = changes.some(
116
- (change) => change.type === "add" || change.type === "remove" || change.type === "replace"
115
+ findCompatibleHandle: (sourceNodeId, sourceHandleId, targetNodeId) => {
116
+ const { nodes, edges } = get();
117
+ const sourceNode = nodes.find((n) => n.id === sourceNodeId);
118
+ const targetNode = nodes.find((n) => n.id === targetNodeId);
119
+ if (!sourceNode || !targetNode) return null;
120
+ const sourceType = getHandleType(
121
+ sourceNode.type,
122
+ sourceHandleId,
123
+ "source"
117
124
  );
118
- set((state) => ({
119
- nodes: applyNodeChanges(changes, state.nodes),
120
- ...hasMeaningfulChange && { isDirty: true }
121
- }));
125
+ if (!sourceType) return null;
126
+ const targetDef = NODE_DEFINITIONS[targetNode.type];
127
+ if (!targetDef) return null;
128
+ const existingTargetHandles = new Set(
129
+ edges.filter((e) => e.target === targetNodeId).map((e) => e.targetHandle)
130
+ );
131
+ for (const input of targetDef.inputs) {
132
+ const hasExistingConnection = existingTargetHandles.has(input.id);
133
+ if (hasExistingConnection && !input.multiple) continue;
134
+ if (CONNECTION_RULES[sourceType]?.includes(input.type)) {
135
+ return input.id;
136
+ }
137
+ }
138
+ return null;
122
139
  },
123
- onEdgesChange: (changes) => {
124
- const hasMeaningfulChange = changes.some(
125
- (change) => change.type === "add" || change.type === "remove" || change.type === "replace"
140
+ isValidConnection: (connection) => {
141
+ const { nodes } = get();
142
+ const sourceNode = nodes.find((n) => n.id === connection.source);
143
+ const targetNode = nodes.find((n) => n.id === connection.target);
144
+ if (!sourceNode || !targetNode) return false;
145
+ const sourceType = getHandleType(
146
+ sourceNode.type,
147
+ connection.sourceHandle ?? null,
148
+ "source"
126
149
  );
127
- set((state) => ({
128
- edges: applyEdgeChanges(changes, state.edges),
129
- ...hasMeaningfulChange && { isDirty: true }
130
- }));
150
+ const targetType = getHandleType(
151
+ targetNode.type,
152
+ connection.targetHandle ?? null,
153
+ "target"
154
+ );
155
+ if (!sourceType || !targetType) return false;
156
+ return CONNECTION_RULES[sourceType]?.includes(targetType) ?? false;
131
157
  },
132
158
  onConnect: (connection) => {
133
159
  const { isValidConnection, propagateOutputsDownstream } = get();
@@ -147,6 +173,24 @@ var createEdgeSlice = (set, get) => ({
147
173
  propagateOutputsDownstream(connection.source);
148
174
  }
149
175
  },
176
+ onEdgesChange: (changes) => {
177
+ const hasMeaningfulChange = changes.some(
178
+ (change) => change.type === "add" || change.type === "remove" || change.type === "replace"
179
+ );
180
+ set((state) => ({
181
+ edges: applyEdgeChanges(changes, state.edges),
182
+ ...hasMeaningfulChange && { isDirty: true }
183
+ }));
184
+ },
185
+ onNodesChange: (changes) => {
186
+ const hasMeaningfulChange = changes.some(
187
+ (change) => change.type === "add" || change.type === "remove" || change.type === "replace"
188
+ );
189
+ set((state) => ({
190
+ nodes: applyNodeChanges(changes, state.nodes),
191
+ ...hasMeaningfulChange && { isDirty: true }
192
+ }));
193
+ },
150
194
  removeEdge: (edgeId) => {
151
195
  set((state) => ({
152
196
  edges: state.edges.filter((edge) => edge.id !== edgeId),
@@ -173,45 +217,6 @@ var createEdgeSlice = (set, get) => ({
173
217
  ),
174
218
  isDirty: true
175
219
  }));
176
- },
177
- isValidConnection: (connection) => {
178
- const { nodes } = get();
179
- const sourceNode = nodes.find((n) => n.id === connection.source);
180
- const targetNode = nodes.find((n) => n.id === connection.target);
181
- if (!sourceNode || !targetNode) return false;
182
- const sourceType = getHandleType(
183
- sourceNode.type,
184
- connection.sourceHandle ?? null,
185
- "source"
186
- );
187
- const targetType = getHandleType(
188
- targetNode.type,
189
- connection.targetHandle ?? null,
190
- "target"
191
- );
192
- if (!sourceType || !targetType) return false;
193
- return CONNECTION_RULES[sourceType]?.includes(targetType) ?? false;
194
- },
195
- findCompatibleHandle: (sourceNodeId, sourceHandleId, targetNodeId) => {
196
- const { nodes, edges } = get();
197
- const sourceNode = nodes.find((n) => n.id === sourceNodeId);
198
- const targetNode = nodes.find((n) => n.id === targetNodeId);
199
- if (!sourceNode || !targetNode) return null;
200
- const sourceType = getHandleType(sourceNode.type, sourceHandleId, "source");
201
- if (!sourceType) return null;
202
- const targetDef = NODE_DEFINITIONS[targetNode.type];
203
- if (!targetDef) return null;
204
- const existingTargetHandles = new Set(
205
- edges.filter((e) => e.target === targetNodeId).map((e) => e.targetHandle)
206
- );
207
- for (const input of targetDef.inputs) {
208
- const hasExistingConnection = existingTargetHandles.has(input.id);
209
- if (hasExistingConnection && !input.multiple) continue;
210
- if (CONNECTION_RULES[sourceType]?.includes(input.type)) {
211
- return input.id;
212
- }
213
- }
214
- return null;
215
220
  }
216
221
  });
217
222
 
@@ -227,17 +232,25 @@ var DEFAULT_GROUP_COLORS = [
227
232
  "gray"
228
233
  ];
229
234
  var createGroupSlice = (set, get) => ({
235
+ addToGroup: (groupId, nodeIds) => {
236
+ set((state) => ({
237
+ groups: state.groups.map(
238
+ (g) => g.id === groupId ? { ...g, nodeIds: [.../* @__PURE__ */ new Set([...g.nodeIds, ...nodeIds])] } : g
239
+ ),
240
+ isDirty: true
241
+ }));
242
+ },
230
243
  createGroup: (nodeIds, name) => {
231
244
  if (nodeIds.length === 0) return "";
232
245
  const groupId = generateId();
233
246
  const { groups } = get();
234
247
  const colorIndex = groups.length % DEFAULT_GROUP_COLORS.length;
235
248
  const newGroup = {
249
+ color: DEFAULT_GROUP_COLORS[colorIndex],
236
250
  id: groupId,
237
- name: name ?? `Group ${groups.length + 1}`,
238
- nodeIds,
239
251
  isLocked: false,
240
- color: DEFAULT_GROUP_COLORS[colorIndex]
252
+ name: name ?? `Group ${groups.length + 1}`,
253
+ nodeIds
241
254
  };
242
255
  set((state) => ({
243
256
  groups: [...state.groups, newGroup],
@@ -251,19 +264,29 @@ var createGroupSlice = (set, get) => ({
251
264
  isDirty: true
252
265
  }));
253
266
  },
254
- addToGroup: (groupId, nodeIds) => {
267
+ getGroupById: (groupId) => {
268
+ return get().groups.find((g) => g.id === groupId);
269
+ },
270
+ getGroupByNodeId: (nodeId) => {
271
+ return get().groups.find((g) => g.nodeIds.includes(nodeId));
272
+ },
273
+ removeFromGroup: (groupId, nodeIds) => {
255
274
  set((state) => ({
256
275
  groups: state.groups.map(
257
- (g) => g.id === groupId ? { ...g, nodeIds: [.../* @__PURE__ */ new Set([...g.nodeIds, ...nodeIds])] } : g
276
+ (g) => g.id === groupId ? { ...g, nodeIds: g.nodeIds.filter((id) => !nodeIds.includes(id)) } : g
258
277
  ),
259
278
  isDirty: true
260
279
  }));
261
280
  },
262
- removeFromGroup: (groupId, nodeIds) => {
281
+ renameGroup: (groupId, name) => {
263
282
  set((state) => ({
264
- groups: state.groups.map(
265
- (g) => g.id === groupId ? { ...g, nodeIds: g.nodeIds.filter((id) => !nodeIds.includes(id)) } : g
266
- ),
283
+ groups: state.groups.map((g) => g.id === groupId ? { ...g, name } : g),
284
+ isDirty: true
285
+ }));
286
+ },
287
+ setGroupColor: (groupId, color) => {
288
+ set((state) => ({
289
+ groups: state.groups.map((g) => g.id === groupId ? { ...g, color } : g),
267
290
  isDirty: true
268
291
  }));
269
292
  },
@@ -272,7 +295,9 @@ var createGroupSlice = (set, get) => ({
272
295
  const group = groups.find((g) => g.id === groupId);
273
296
  if (!group) return;
274
297
  set((state) => ({
275
- groups: state.groups.map((g) => g.id === groupId ? { ...g, isLocked: !g.isLocked } : g),
298
+ groups: state.groups.map(
299
+ (g) => g.id === groupId ? { ...g, isLocked: !g.isLocked } : g
300
+ ),
276
301
  isDirty: true
277
302
  }));
278
303
  if (!group.isLocked) {
@@ -280,24 +305,6 @@ var createGroupSlice = (set, get) => ({
280
305
  } else {
281
306
  unlockMultipleNodes(group.nodeIds);
282
307
  }
283
- },
284
- renameGroup: (groupId, name) => {
285
- set((state) => ({
286
- groups: state.groups.map((g) => g.id === groupId ? { ...g, name } : g),
287
- isDirty: true
288
- }));
289
- },
290
- setGroupColor: (groupId, color) => {
291
- set((state) => ({
292
- groups: state.groups.map((g) => g.id === groupId ? { ...g, color } : g),
293
- isDirty: true
294
- }));
295
- },
296
- getGroupByNodeId: (nodeId) => {
297
- return get().groups.find((g) => g.nodeIds.includes(nodeId));
298
- },
299
- getGroupById: (groupId) => {
300
- return get().groups.find((g) => g.id === groupId);
301
308
  }
302
309
  });
303
310
 
@@ -316,9 +323,15 @@ function getOutputType(sourceType) {
316
323
  if (["prompt", "llm", "tweetParser", "transcribe"].includes(sourceType)) {
317
324
  return "text";
318
325
  }
319
- if (["imageGen", "image", "imageInput", "upscale", "resize", "reframe", "imageGridSplit"].includes(
320
- sourceType
321
- )) {
326
+ if ([
327
+ "imageGen",
328
+ "image",
329
+ "imageInput",
330
+ "upscale",
331
+ "resize",
332
+ "reframe",
333
+ "imageGridSplit"
334
+ ].includes(sourceType)) {
322
335
  return "image";
323
336
  }
324
337
  if ([
@@ -345,10 +358,10 @@ function mapOutputToInput(output, sourceType, targetType) {
345
358
  const outputType = getOutputType(sourceType);
346
359
  if (targetType === "download") {
347
360
  if (outputType === "video") {
348
- return { inputVideo: output, inputImage: null, inputType: "video" };
361
+ return { inputImage: null, inputType: "video", inputVideo: output };
349
362
  }
350
363
  if (outputType === "image") {
351
- return { inputImage: output, inputVideo: null, inputType: "image" };
364
+ return { inputImage: output, inputType: "image", inputVideo: null };
352
365
  }
353
366
  return null;
354
367
  }
@@ -362,11 +375,16 @@ function mapOutputToInput(output, sourceType, targetType) {
362
375
  }
363
376
  if (outputType === "image") {
364
377
  if (["upscale", "reframe"].includes(targetType)) {
365
- return { inputImage: output, inputVideo: null, inputType: "image" };
378
+ return { inputImage: output, inputType: "image", inputVideo: null };
366
379
  }
367
- if (["videoGen", "lipSync", "voiceChange", "motionControl", "resize", "animation"].includes(
368
- targetType
369
- )) {
380
+ if ([
381
+ "videoGen",
382
+ "lipSync",
383
+ "voiceChange",
384
+ "motionControl",
385
+ "resize",
386
+ "animation"
387
+ ].includes(targetType)) {
370
388
  return { inputImage: output };
371
389
  }
372
390
  if (targetType === "imageGen") {
@@ -375,7 +393,7 @@ function mapOutputToInput(output, sourceType, targetType) {
375
393
  }
376
394
  if (outputType === "video") {
377
395
  if (["upscale", "reframe"].includes(targetType)) {
378
- return { inputVideo: output, inputImage: null, inputType: "video" };
396
+ return { inputImage: null, inputType: "video", inputVideo: output };
379
397
  }
380
398
  if ([
381
399
  "lipSync",
@@ -407,7 +425,13 @@ function collectGalleryUpdate(sourceData, currentOutput, existingGalleryImages,
407
425
  }
408
426
  if (allImages.length === 0) return null;
409
427
  return {
410
- images: [.../* @__PURE__ */ new Set([...existingGalleryImages, ...pendingUpdateImages, ...allImages])]
428
+ images: [
429
+ .../* @__PURE__ */ new Set([
430
+ ...existingGalleryImages,
431
+ ...pendingUpdateImages,
432
+ ...allImages
433
+ ])
434
+ ]
411
435
  };
412
436
  }
413
437
  function computeDownstreamUpdates(sourceNodeId, initialOutput, nodes, edges) {
@@ -443,7 +467,11 @@ function computeDownstreamUpdates(sourceNodeId, initialOutput, nodes, edges) {
443
467
  }
444
468
  continue;
445
469
  }
446
- const inputUpdate = mapOutputToInput(current.output, currentNode.type, targetNode.type);
470
+ const inputUpdate = mapOutputToInput(
471
+ current.output,
472
+ currentNode.type,
473
+ targetNode.type
474
+ );
447
475
  if (inputUpdate) {
448
476
  const existing = updates.get(edge.target) ?? {};
449
477
  updates.set(edge.target, { ...existing, ...inputUpdate });
@@ -495,50 +523,52 @@ function propagateExistingOutputs(nodes, propagateFn) {
495
523
  var createLockingSlice = (set, get) => ({
496
524
  _setNodeLockState: (predicate, lock) => {
497
525
  set((state) => ({
526
+ isDirty: true,
498
527
  nodes: state.nodes.map(
499
528
  (n) => predicate(n.id) ? {
500
529
  ...n,
501
- draggable: !lock,
502
530
  data: {
503
531
  ...n.data,
504
532
  isLocked: lock,
505
533
  lockTimestamp: lock ? Date.now() : void 0,
506
534
  ...lock && { cachedOutput: getNodeOutput(n) }
507
- }
535
+ },
536
+ draggable: !lock
508
537
  } : n
509
- ),
510
- isDirty: true
538
+ )
511
539
  }));
512
540
  },
513
- toggleNodeLock: (nodeId) => {
514
- const node = get().getNodeById(nodeId);
515
- if (!node) return;
516
- const shouldLock = !(node.data.isLocked ?? false);
517
- get()._setNodeLockState((id) => id === nodeId, shouldLock);
541
+ isNodeLocked: (nodeId) => {
542
+ const { nodes, groups } = get();
543
+ const node = nodes.find((n) => n.id === nodeId);
544
+ if (!node) return false;
545
+ if (node.data.isLocked) return true;
546
+ return groups.some(
547
+ (group) => group.isLocked && group.nodeIds.includes(nodeId)
548
+ );
549
+ },
550
+ lockMultipleNodes: (nodeIds) => {
551
+ get()._setNodeLockState((id) => nodeIds.includes(id), true);
518
552
  },
519
553
  lockNode: (nodeId) => {
520
554
  const node = get().getNodeById(nodeId);
521
555
  if (!node || node.data.isLocked) return;
522
556
  get()._setNodeLockState((id) => id === nodeId, true);
523
557
  },
524
- unlockNode: (nodeId) => {
525
- get()._setNodeLockState((id) => id === nodeId, false);
558
+ toggleNodeLock: (nodeId) => {
559
+ const node = get().getNodeById(nodeId);
560
+ if (!node) return;
561
+ const shouldLock = !(node.data.isLocked ?? false);
562
+ get()._setNodeLockState((id) => id === nodeId, shouldLock);
526
563
  },
527
- lockMultipleNodes: (nodeIds) => {
528
- get()._setNodeLockState((id) => nodeIds.includes(id), true);
564
+ unlockAllNodes: () => {
565
+ get()._setNodeLockState(() => true, false);
529
566
  },
530
567
  unlockMultipleNodes: (nodeIds) => {
531
568
  get()._setNodeLockState((id) => nodeIds.includes(id), false);
532
569
  },
533
- unlockAllNodes: () => {
534
- get()._setNodeLockState(() => true, false);
535
- },
536
- isNodeLocked: (nodeId) => {
537
- const { nodes, groups } = get();
538
- const node = nodes.find((n) => n.id === nodeId);
539
- if (!node) return false;
540
- if (node.data.isLocked) return true;
541
- return groups.some((group) => group.isLocked && group.nodeIds.includes(nodeId));
570
+ unlockNode: (nodeId) => {
571
+ get()._setNodeLockState((id) => id === nodeId, false);
542
572
  }
543
573
  });
544
574
  var createNodeSlice = (set, get) => ({
@@ -547,28 +577,28 @@ var createNodeSlice = (set, get) => ({
547
577
  if (!nodeDef) return "";
548
578
  const id = generateId();
549
579
  const newNode = {
550
- id,
551
- type,
552
- position,
553
580
  data: {
554
581
  ...nodeDef.defaultData,
555
582
  label: nodeDef.label,
556
583
  status: "idle"
557
584
  },
558
- ...type === "download" && { width: 280, height: 320 }
585
+ id,
586
+ position,
587
+ type,
588
+ ...type === "download" && { height: 320, width: 280 }
559
589
  };
560
590
  set((state) => ({
561
- nodes: [...state.nodes, newNode],
562
- isDirty: true
591
+ isDirty: true,
592
+ nodes: [...state.nodes, newNode]
563
593
  }));
564
594
  return id;
565
595
  },
566
596
  addNodesAndEdges: (newNodes, newEdges) => {
567
597
  if (newNodes.length === 0) return;
568
598
  set((state) => ({
569
- nodes: [...state.nodes, ...newNodes],
570
599
  edges: [...state.edges, ...newEdges],
571
- isDirty: true
600
+ isDirty: true,
601
+ nodes: [...state.nodes, ...newNodes]
572
602
  }));
573
603
  const { propagateOutputsDownstream } = get();
574
604
  const sourceNodeIds = new Set(newEdges.map((e) => e.source));
@@ -576,49 +606,17 @@ var createNodeSlice = (set, get) => ({
576
606
  propagateOutputsDownstream(sourceId);
577
607
  }
578
608
  },
579
- updateNodeData: (nodeId, data) => {
580
- const { nodes, propagateOutputsDownstream } = get();
581
- const node = nodes.find((n) => n.id === nodeId);
582
- const TRANSIENT_KEYS = /* @__PURE__ */ new Set(["status", "progress", "error", "jobId"]);
583
- const dataKeys = Object.keys(data);
584
- const hasPersistedChange = dataKeys.some((key) => !TRANSIENT_KEYS.has(key));
609
+ addToGlobalHistory: (item) => {
610
+ const id = `history-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
585
611
  set((state) => ({
586
- nodes: state.nodes.map((n) => n.id === nodeId ? { ...n, data: { ...n.data, ...data } } : n),
587
- ...hasPersistedChange && { isDirty: true }
612
+ globalImageHistory: [{ ...item, id }, ...state.globalImageHistory].slice(
613
+ 0,
614
+ 100
615
+ )
588
616
  }));
589
- const inputNodeTypes = [
590
- "prompt",
591
- "image",
592
- "imageInput",
593
- "video",
594
- "videoInput",
595
- "audio",
596
- "audioInput",
597
- "tweetParser"
598
- ];
599
- const hasOutputUpdate = "outputImage" in data || "outputImages" in data || "outputVideo" in data || "outputAudio" in data || "outputText" in data;
600
- if (node && (inputNodeTypes.includes(node.type) || hasOutputUpdate)) {
601
- if (hasOutputUpdate) {
602
- const dataRecord = data;
603
- if ("outputImages" in dataRecord) {
604
- propagateOutputsDownstream(nodeId);
605
- } else {
606
- const outputValue = dataRecord.outputImage ?? dataRecord.outputVideo ?? dataRecord.outputAudio ?? dataRecord.outputText;
607
- if (typeof outputValue === "string") {
608
- propagateOutputsDownstream(nodeId, outputValue);
609
- }
610
- }
611
- } else {
612
- propagateOutputsDownstream(nodeId);
613
- }
614
- }
615
617
  },
616
- removeNode: (nodeId) => {
617
- set((state) => ({
618
- nodes: state.nodes.filter((node) => node.id !== nodeId),
619
- edges: state.edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId),
620
- isDirty: true
621
- }));
618
+ clearGlobalHistory: () => {
619
+ set({ globalImageHistory: [] });
622
620
  },
623
621
  duplicateNode: (nodeId) => {
624
622
  const { nodes, edges, edgeStyle, propagateOutputsDownstream } = get();
@@ -627,18 +625,20 @@ var createNodeSlice = (set, get) => ({
627
625
  const newId = generateId();
628
626
  const newNode = {
629
627
  ...node,
628
+ data: {
629
+ ...node.data,
630
+ jobId: null,
631
+ status: "idle"
632
+ },
630
633
  id: newId,
631
634
  position: {
632
635
  x: node.position.x + 50,
633
636
  y: node.position.y + 50
634
- },
635
- data: {
636
- ...node.data,
637
- status: "idle",
638
- jobId: null
639
637
  }
640
638
  };
641
- const incomingEdges = edges.filter((e) => e.target === nodeId && e.source !== nodeId);
639
+ const incomingEdges = edges.filter(
640
+ (e) => e.target === nodeId && e.source !== nodeId
641
+ );
642
642
  const clonedEdges = incomingEdges.map((edge) => ({
643
643
  ...edge,
644
644
  id: generateId(),
@@ -646,9 +646,9 @@ var createNodeSlice = (set, get) => ({
646
646
  type: edgeStyle
647
647
  }));
648
648
  set((state) => ({
649
- nodes: [...state.nodes, newNode],
650
649
  edges: [...state.edges, ...clonedEdges],
651
- isDirty: true
650
+ isDirty: true,
651
+ nodes: [...state.nodes, newNode]
652
652
  }));
653
653
  const sourcesNotified = /* @__PURE__ */ new Set();
654
654
  for (const edge of incomingEdges) {
@@ -665,22 +665,66 @@ var createNodeSlice = (set, get) => ({
665
665
  if (!sourceNode) return;
666
666
  const output = outputValue ?? getNodeOutput(sourceNode);
667
667
  if (!output) return;
668
- const updates = computeDownstreamUpdates(sourceNodeId, output, nodes, edges);
668
+ const updates = computeDownstreamUpdates(
669
+ sourceNodeId,
670
+ output,
671
+ nodes,
672
+ edges
673
+ );
669
674
  if (updates.size === 0) return;
670
675
  if (!hasStateChanged(updates, nodes)) return;
671
676
  set((state) => ({
672
- nodes: applyNodeUpdates(state.nodes, updates),
673
- isDirty: true
677
+ isDirty: true,
678
+ nodes: applyNodeUpdates(state.nodes, updates)
674
679
  }));
675
680
  },
676
- addToGlobalHistory: (item) => {
677
- const id = `history-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
681
+ removeNode: (nodeId) => {
678
682
  set((state) => ({
679
- globalImageHistory: [{ ...item, id }, ...state.globalImageHistory].slice(0, 100)
683
+ edges: state.edges.filter(
684
+ (edge) => edge.source !== nodeId && edge.target !== nodeId
685
+ ),
686
+ isDirty: true,
687
+ nodes: state.nodes.filter((node) => node.id !== nodeId)
680
688
  }));
681
689
  },
682
- clearGlobalHistory: () => {
683
- set({ globalImageHistory: [] });
690
+ updateNodeData: (nodeId, data) => {
691
+ const { nodes, propagateOutputsDownstream } = get();
692
+ const node = nodes.find((n) => n.id === nodeId);
693
+ const TRANSIENT_KEYS = /* @__PURE__ */ new Set(["status", "progress", "error", "jobId"]);
694
+ const dataKeys = Object.keys(data);
695
+ const hasPersistedChange = dataKeys.some((key) => !TRANSIENT_KEYS.has(key));
696
+ set((state) => ({
697
+ nodes: state.nodes.map(
698
+ (n) => n.id === nodeId ? { ...n, data: { ...n.data, ...data } } : n
699
+ ),
700
+ ...hasPersistedChange && { isDirty: true }
701
+ }));
702
+ const inputNodeTypes = [
703
+ "prompt",
704
+ "image",
705
+ "imageInput",
706
+ "video",
707
+ "videoInput",
708
+ "audio",
709
+ "audioInput",
710
+ "tweetParser"
711
+ ];
712
+ const hasOutputUpdate = "outputImage" in data || "outputImages" in data || "outputVideo" in data || "outputAudio" in data || "outputText" in data;
713
+ if (node && (inputNodeTypes.includes(node.type) || hasOutputUpdate)) {
714
+ if (hasOutputUpdate) {
715
+ const dataRecord = data;
716
+ if ("outputImages" in dataRecord) {
717
+ propagateOutputsDownstream(nodeId);
718
+ } else {
719
+ const outputValue = dataRecord.outputImage ?? dataRecord.outputVideo ?? dataRecord.outputAudio ?? dataRecord.outputText;
720
+ if (typeof outputValue === "string") {
721
+ propagateOutputsDownstream(nodeId, outputValue);
722
+ }
723
+ }
724
+ } else {
725
+ propagateOutputsDownstream(nodeId);
726
+ }
727
+ }
684
728
  }
685
729
  });
686
730
  function normalizeEdgeTypes(edges) {
@@ -703,49 +747,46 @@ function hydrateWorkflowNodes(nodes) {
703
747
  });
704
748
  }
705
749
  var createPersistenceSlice = (set, get) => ({
706
- loadWorkflow: (workflow) => {
707
- const hydratedNodes = hydrateWorkflowNodes(workflow.nodes);
708
- set({
709
- nodes: hydratedNodes,
710
- edges: normalizeEdgeTypes(workflow.edges),
711
- edgeStyle: workflow.edgeStyle,
712
- workflowName: workflow.name,
713
- workflowId: null,
714
- isDirty: true,
715
- groups: workflow.groups ?? [],
716
- selectedNodeIds: []
717
- });
718
- propagateExistingOutputs(hydratedNodes, get().propagateOutputsDownstream);
719
- set({ isDirty: false });
720
- },
721
750
  clearWorkflow: () => {
722
751
  set({
723
- nodes: [],
724
752
  edges: [],
725
- workflowName: "Untitled Workflow",
726
- workflowId: null,
727
- isDirty: false,
728
753
  groups: [],
729
- selectedNodeIds: []
754
+ isDirty: false,
755
+ nodes: [],
756
+ selectedNodeIds: [],
757
+ workflowId: null,
758
+ workflowName: "Untitled Workflow"
730
759
  });
731
760
  },
761
+ createNewWorkflow: async () => {
762
+ throw new Error(
763
+ "createNewWorkflow not implemented - consuming app must provide API integration"
764
+ );
765
+ },
766
+ deleteWorkflow: async () => {
767
+ throw new Error(
768
+ "deleteWorkflow not implemented - consuming app must provide API integration"
769
+ );
770
+ },
771
+ duplicateWorkflowApi: async () => {
772
+ throw new Error(
773
+ "duplicateWorkflowApi not implemented - consuming app must provide API integration"
774
+ );
775
+ },
732
776
  exportWorkflow: () => {
733
777
  const { nodes, edges, edgeStyle, workflowName, groups } = get();
734
778
  return {
735
- version: 1,
736
- name: workflowName,
779
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
737
780
  description: "",
738
- nodes,
739
- edges,
740
781
  edgeStyle,
782
+ edges,
741
783
  groups,
742
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
743
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
784
+ name: workflowName,
785
+ nodes,
786
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
787
+ version: 1
744
788
  };
745
789
  },
746
- getNodeById: (id) => {
747
- return get().nodes.find((node) => node.id === id);
748
- },
749
790
  getConnectedInputs: (nodeId) => {
750
791
  const { nodes, edges } = get();
751
792
  const inputs = /* @__PURE__ */ new Map();
@@ -800,25 +841,95 @@ var createPersistenceSlice = (set, get) => ({
800
841
  }
801
842
  return Array.from(connected);
802
843
  },
844
+ getNodeById: (id) => {
845
+ return get().nodes.find((node) => node.id === id);
846
+ },
847
+ getNodesWithComments: () => {
848
+ const { nodes } = get();
849
+ return nodes.filter((node) => {
850
+ const data = node.data;
851
+ return data.comment?.trim();
852
+ }).sort((a, b) => {
853
+ if (Math.abs(a.position.y - b.position.y) < 50) {
854
+ return a.position.x - b.position.x;
855
+ }
856
+ return a.position.y - b.position.y;
857
+ });
858
+ },
859
+ getUnviewedCommentCount: () => {
860
+ const { nodes, viewedCommentIds } = get();
861
+ return nodes.filter((node) => {
862
+ const data = node.data;
863
+ return data.comment?.trim() && !viewedCommentIds.has(node.id);
864
+ }).length;
865
+ },
866
+ listWorkflows: async () => {
867
+ throw new Error(
868
+ "listWorkflows not implemented - consuming app must provide API integration"
869
+ );
870
+ },
871
+ loadWorkflow: (workflow) => {
872
+ const hydratedNodes = hydrateWorkflowNodes(workflow.nodes);
873
+ set({
874
+ edgeStyle: workflow.edgeStyle,
875
+ edges: normalizeEdgeTypes(workflow.edges),
876
+ groups: workflow.groups ?? [],
877
+ isDirty: true,
878
+ nodes: hydratedNodes,
879
+ selectedNodeIds: [],
880
+ workflowId: null,
881
+ workflowName: workflow.name
882
+ });
883
+ propagateExistingOutputs(hydratedNodes, get().propagateOutputsDownstream);
884
+ set({ isDirty: false });
885
+ },
886
+ loadWorkflowById: async () => {
887
+ throw new Error(
888
+ "loadWorkflowById not implemented - consuming app must provide API integration"
889
+ );
890
+ },
891
+ markCommentViewed: (nodeId) => {
892
+ set((state) => {
893
+ const newSet = new Set(state.viewedCommentIds);
894
+ newSet.add(nodeId);
895
+ return { viewedCommentIds: newSet };
896
+ });
897
+ },
898
+ // API operations - stubs that throw by default.
899
+ // Consuming apps override these via the store creator or by extending the slice.
900
+ saveWorkflow: async () => {
901
+ throw new Error(
902
+ "saveWorkflow not implemented - consuming app must provide API integration"
903
+ );
904
+ },
905
+ setDirty: (dirty) => {
906
+ set({ isDirty: dirty });
907
+ },
908
+ setNavigationTarget: (nodeId) => {
909
+ set({ navigationTargetId: nodeId });
910
+ },
911
+ setWorkflowName: (name) => {
912
+ set({ isDirty: true, workflowName: name });
913
+ },
803
914
  validateWorkflow: () => {
804
915
  const { nodes, edges } = get();
805
916
  const errors = [];
806
917
  const warnings = [];
807
918
  if (nodes.length === 0) {
808
919
  errors.push({
809
- nodeId: "",
810
920
  message: "Workflow is empty - add some nodes first",
921
+ nodeId: "",
811
922
  severity: "error"
812
923
  });
813
- return { isValid: false, errors, warnings };
924
+ return { errors, isValid: false, warnings };
814
925
  }
815
926
  if (edges.length === 0 && nodes.length > 1) {
816
927
  errors.push({
817
- nodeId: "",
818
928
  message: "No connections - connect your nodes together",
929
+ nodeId: "",
819
930
  severity: "error"
820
931
  });
821
- return { isValid: false, errors, warnings };
932
+ return { errors, isValid: false, warnings };
822
933
  }
823
934
  const hasNodeOutput = (node) => {
824
935
  const data = node.data;
@@ -841,19 +952,23 @@ var createPersistenceSlice = (set, get) => ({
841
952
  const incomingEdges = edges.filter((e) => e.target === node.id);
842
953
  for (const input of nodeDef.inputs) {
843
954
  if (input.required) {
844
- const connectionEdge = incomingEdges.find((e) => e.targetHandle === input.id);
955
+ const connectionEdge = incomingEdges.find(
956
+ (e) => e.targetHandle === input.id
957
+ );
845
958
  if (!connectionEdge) {
846
959
  errors.push({
847
- nodeId: node.id,
848
960
  message: `Missing required input: ${input.label}`,
961
+ nodeId: node.id,
849
962
  severity: "error"
850
963
  });
851
964
  } else {
852
- const sourceNode = nodes.find((n) => n.id === connectionEdge.source);
965
+ const sourceNode = nodes.find(
966
+ (n) => n.id === connectionEdge.source
967
+ );
853
968
  if (sourceNode && !hasNodeOutput(sourceNode)) {
854
969
  errors.push({
855
- nodeId: sourceNode.id,
856
970
  message: `${sourceNode.data.label} is empty`,
971
+ nodeId: sourceNode.id,
857
972
  severity: "error"
858
973
  });
859
974
  }
@@ -878,8 +993,8 @@ var createPersistenceSlice = (set, get) => ({
878
993
  for (const node of nodes) {
879
994
  if (hasCycle(node.id)) {
880
995
  errors.push({
881
- nodeId: node.id,
882
996
  message: "Workflow contains a cycle",
997
+ nodeId: node.id,
883
998
  severity: "error"
884
999
  });
885
1000
  break;
@@ -890,146 +1005,82 @@ var createPersistenceSlice = (set, get) => ({
890
1005
  const refData = node.data;
891
1006
  if (!refData.referencedWorkflowId) {
892
1007
  errors.push({
893
- nodeId: node.id,
894
1008
  message: "Subworkflow node must reference a workflow",
1009
+ nodeId: node.id,
895
1010
  severity: "error"
896
1011
  });
897
1012
  } else if (!refData.cachedInterface) {
898
1013
  warnings.push({
899
- nodeId: node.id,
900
1014
  message: "Subworkflow interface not loaded - refresh to update handles",
1015
+ nodeId: node.id,
901
1016
  severity: "warning"
902
1017
  });
903
1018
  }
904
1019
  }
905
1020
  }
906
1021
  return {
907
- isValid: errors.length === 0,
908
1022
  errors,
1023
+ isValid: errors.length === 0,
909
1024
  warnings
910
1025
  };
911
- },
912
- setDirty: (dirty) => {
913
- set({ isDirty: dirty });
914
- },
915
- setWorkflowName: (name) => {
916
- set({ workflowName: name, isDirty: true });
917
- },
918
- // API operations - stubs that throw by default.
919
- // Consuming apps override these via the store creator or by extending the slice.
920
- saveWorkflow: async () => {
921
- throw new Error("saveWorkflow not implemented - consuming app must provide API integration");
922
- },
923
- loadWorkflowById: async () => {
924
- throw new Error(
925
- "loadWorkflowById not implemented - consuming app must provide API integration"
926
- );
927
- },
928
- listWorkflows: async () => {
929
- throw new Error("listWorkflows not implemented - consuming app must provide API integration");
930
- },
931
- deleteWorkflow: async () => {
932
- throw new Error("deleteWorkflow not implemented - consuming app must provide API integration");
933
- },
934
- duplicateWorkflowApi: async () => {
935
- throw new Error(
936
- "duplicateWorkflowApi not implemented - consuming app must provide API integration"
937
- );
938
- },
939
- createNewWorkflow: async () => {
940
- throw new Error(
941
- "createNewWorkflow not implemented - consuming app must provide API integration"
942
- );
943
- },
944
- getNodesWithComments: () => {
945
- const { nodes } = get();
946
- return nodes.filter((node) => {
947
- const data = node.data;
948
- return data.comment?.trim();
949
- }).sort((a, b) => {
950
- if (Math.abs(a.position.y - b.position.y) < 50) {
951
- return a.position.x - b.position.x;
952
- }
953
- return a.position.y - b.position.y;
954
- });
955
- },
956
- markCommentViewed: (nodeId) => {
957
- set((state) => {
958
- const newSet = new Set(state.viewedCommentIds);
959
- newSet.add(nodeId);
960
- return { viewedCommentIds: newSet };
961
- });
962
- },
963
- setNavigationTarget: (nodeId) => {
964
- set({ navigationTargetId: nodeId });
965
- },
966
- getUnviewedCommentCount: () => {
967
- const { nodes, viewedCommentIds } = get();
968
- return nodes.filter((node) => {
969
- const data = node.data;
970
- return data.comment?.trim() && !viewedCommentIds.has(node.id);
971
- }).length;
972
1026
  }
973
1027
  });
974
1028
 
975
1029
  // src/stores/workflow/slices/selectionSlice.ts
976
1030
  var createSelectionSlice = (set) => ({
977
- setSelectedNodeIds: (nodeIds) => {
978
- set({ selectedNodeIds: nodeIds });
979
- },
980
1031
  addToSelection: (nodeId) => {
981
1032
  set((state) => ({
982
1033
  selectedNodeIds: state.selectedNodeIds.includes(nodeId) ? state.selectedNodeIds : [...state.selectedNodeIds, nodeId]
983
1034
  }));
984
1035
  },
1036
+ clearSelection: () => {
1037
+ set({ selectedNodeIds: [] });
1038
+ },
985
1039
  removeFromSelection: (nodeId) => {
986
1040
  set((state) => ({
987
1041
  selectedNodeIds: state.selectedNodeIds.filter((id) => id !== nodeId)
988
1042
  }));
989
1043
  },
990
- clearSelection: () => {
991
- set({ selectedNodeIds: [] });
1044
+ setSelectedNodeIds: (nodeIds) => {
1045
+ set({ selectedNodeIds: nodeIds });
992
1046
  }
993
1047
  });
994
1048
 
995
1049
  // src/stores/workflow/slices/snapshotSlice.ts
996
1050
  function defaultApplyEditOperations(_operations, state) {
997
- return { nodes: state.nodes, edges: state.edges, applied: 0, skipped: [] };
1051
+ return { applied: 0, edges: state.edges, nodes: state.nodes, skipped: [] };
998
1052
  }
999
1053
  var createSnapshotSlice = (set, get) => ({
1000
- previousWorkflowSnapshot: null,
1001
- manualChangeCount: 0,
1054
+ applyEditOperations: (operations) => {
1055
+ const state = get();
1056
+ const result = defaultApplyEditOperations(operations, {
1057
+ edges: state.edges,
1058
+ nodes: state.nodes
1059
+ });
1060
+ set({
1061
+ edges: result.edges,
1062
+ isDirty: true,
1063
+ nodes: result.nodes
1064
+ });
1065
+ return { applied: result.applied, skipped: result.skipped };
1066
+ },
1002
1067
  captureSnapshot: () => {
1003
1068
  const state = get();
1004
1069
  const snapshot = {
1005
- nodes: JSON.parse(JSON.stringify(state.nodes)),
1070
+ edgeStyle: state.edgeStyle,
1006
1071
  edges: JSON.parse(JSON.stringify(state.edges)),
1007
1072
  groups: JSON.parse(JSON.stringify(state.groups)),
1008
- edgeStyle: state.edgeStyle
1073
+ nodes: JSON.parse(JSON.stringify(state.nodes))
1009
1074
  };
1010
1075
  set({
1011
- previousWorkflowSnapshot: snapshot,
1012
- manualChangeCount: 0
1076
+ manualChangeCount: 0,
1077
+ previousWorkflowSnapshot: snapshot
1013
1078
  });
1014
1079
  },
1015
- revertToSnapshot: () => {
1016
- const state = get();
1017
- if (state.previousWorkflowSnapshot) {
1018
- set({
1019
- nodes: state.previousWorkflowSnapshot.nodes,
1020
- edges: state.previousWorkflowSnapshot.edges,
1021
- groups: state.previousWorkflowSnapshot.groups,
1022
- edgeStyle: state.previousWorkflowSnapshot.edgeStyle,
1023
- previousWorkflowSnapshot: null,
1024
- manualChangeCount: 0,
1025
- isDirty: true
1026
- });
1027
- }
1028
- },
1029
1080
  clearSnapshot: () => {
1030
1081
  set({
1031
- previousWorkflowSnapshot: null,
1032
- manualChangeCount: 0
1082
+ manualChangeCount: 0,
1083
+ previousWorkflowSnapshot: null
1033
1084
  });
1034
1085
  },
1035
1086
  incrementManualChangeCount: () => {
@@ -1037,44 +1088,47 @@ var createSnapshotSlice = (set, get) => ({
1037
1088
  const newCount = state.manualChangeCount + 1;
1038
1089
  if (newCount >= 3) {
1039
1090
  set({
1040
- previousWorkflowSnapshot: null,
1041
- manualChangeCount: 0
1091
+ manualChangeCount: 0,
1092
+ previousWorkflowSnapshot: null
1042
1093
  });
1043
1094
  } else {
1044
1095
  set({ manualChangeCount: newCount });
1045
1096
  }
1046
1097
  },
1047
- applyEditOperations: (operations) => {
1098
+ manualChangeCount: 0,
1099
+ previousWorkflowSnapshot: null,
1100
+ revertToSnapshot: () => {
1048
1101
  const state = get();
1049
- const result = defaultApplyEditOperations(operations, {
1050
- nodes: state.nodes,
1051
- edges: state.edges
1052
- });
1053
- set({
1054
- nodes: result.nodes,
1055
- edges: result.edges,
1056
- isDirty: true
1057
- });
1058
- return { applied: result.applied, skipped: result.skipped };
1102
+ if (state.previousWorkflowSnapshot) {
1103
+ set({
1104
+ edgeStyle: state.previousWorkflowSnapshot.edgeStyle,
1105
+ edges: state.previousWorkflowSnapshot.edges,
1106
+ groups: state.previousWorkflowSnapshot.groups,
1107
+ isDirty: true,
1108
+ manualChangeCount: 0,
1109
+ nodes: state.previousWorkflowSnapshot.nodes,
1110
+ previousWorkflowSnapshot: null
1111
+ });
1112
+ }
1059
1113
  }
1060
1114
  });
1061
1115
 
1062
1116
  // src/stores/workflow/workflowStore.ts
1063
1117
  var storeCreator = ((...args) => ({
1064
- // Initial state
1065
- nodes: [],
1066
- edges: [],
1067
1118
  edgeStyle: "default",
1068
- workflowName: "Untitled Workflow",
1069
- workflowId: null,
1119
+ edges: [],
1120
+ globalImageHistory: [],
1121
+ groups: [],
1070
1122
  isDirty: false,
1071
- isSaving: false,
1072
1123
  isLoading: false,
1073
- groups: [],
1124
+ isSaving: false,
1125
+ navigationTargetId: null,
1126
+ // Initial state
1127
+ nodes: [],
1074
1128
  selectedNodeIds: [],
1075
1129
  viewedCommentIds: /* @__PURE__ */ new Set(),
1076
- navigationTargetId: null,
1077
- globalImageHistory: [],
1130
+ workflowId: null,
1131
+ workflowName: "Untitled Workflow",
1078
1132
  // Compose slices
1079
1133
  ...createNodeSlice(...args),
1080
1134
  ...createEdgeSlice(...args),
@@ -1087,16 +1141,16 @@ var storeCreator = ((...args) => ({
1087
1141
  }));
1088
1142
  var useWorkflowStore = create()(
1089
1143
  temporal(storeCreator, {
1144
+ // Optimized equality check using shallow comparison instead of JSON.stringify
1145
+ equality: temporalStateEquals,
1146
+ // Limit history to prevent memory issues
1147
+ limit: 50,
1090
1148
  // Only track meaningful state (not UI flags like isDirty, isSaving, etc.)
1091
1149
  partialize: (state) => ({
1092
- nodes: state.nodes,
1093
1150
  edges: state.edges,
1094
- groups: state.groups
1095
- }),
1096
- // Limit history to prevent memory issues
1097
- limit: 50,
1098
- // Optimized equality check using shallow comparison instead of JSON.stringify
1099
- equality: temporalStateEquals
1151
+ groups: state.groups,
1152
+ nodes: state.nodes
1153
+ })
1100
1154
  })
1101
1155
  );
1102
1156