@genfeedai/workflow-ui 0.1.3 → 0.1.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 (84) hide show
  1. package/dist/canvas.d.mts +16 -2
  2. package/dist/canvas.mjs +10 -8
  3. package/dist/chunk-6PSJTBNV.mjs +638 -0
  4. package/dist/chunk-7H3WJJYS.mjs +52 -0
  5. package/dist/{chunk-HCXI63ME.mjs → chunk-AUQGOJOQ.mjs} +27 -4
  6. package/dist/{chunk-AOTUCJMA.mjs → chunk-GWBGK3KL.mjs} +2 -2
  7. package/dist/chunk-JTPADIUO.mjs +130 -0
  8. package/dist/{chunk-SQK4JDYY.mjs → chunk-LT3ZJJL6.mjs} +9 -2
  9. package/dist/{chunk-7P2JWDC7.mjs → chunk-O5II6BOJ.mjs} +1198 -254
  10. package/dist/{chunk-AUZR6REQ.mjs → chunk-OQREHJXK.mjs} +1 -1
  11. package/dist/chunk-OY7BRSGG.mjs +60 -0
  12. package/dist/{chunk-E3YBVMYZ.mjs → chunk-PANZDSP6.mjs} +274 -305
  13. package/dist/chunk-PCIWWD37.mjs +90 -0
  14. package/dist/{chunk-RIGVIEYB.mjs → chunk-R727OFBR.mjs} +11 -1
  15. package/dist/chunk-ZD2BADZO.mjs +1294 -0
  16. package/dist/contextMenuStore-DMg0hJQ1.d.mts +22 -0
  17. package/dist/hooks.d.mts +53 -244
  18. package/dist/hooks.mjs +6 -6
  19. package/dist/index.d.mts +11 -7
  20. package/dist/index.mjs +13 -11
  21. package/dist/lib.d.mts +250 -4
  22. package/dist/lib.mjs +562 -2
  23. package/dist/nodes.d.mts +3 -1
  24. package/dist/nodes.mjs +6 -6
  25. package/dist/panels.mjs +3 -4
  26. package/dist/{promptLibraryStore-zqb59nsu.d.mts → promptLibraryStore-Bgw5LzvD.d.mts} +33 -5
  27. package/dist/provider.d.mts +2 -2
  28. package/dist/provider.mjs +0 -1
  29. package/dist/stores.d.mts +4 -3
  30. package/dist/stores.mjs +3 -40
  31. package/dist/toolbar.d.mts +3 -1
  32. package/dist/toolbar.mjs +5 -4
  33. package/dist/{types-ipAnBzAJ.d.mts → types-CF6DPx0P.d.mts} +8 -3
  34. package/dist/ui.d.mts +1 -1
  35. package/dist/ui.mjs +0 -1
  36. package/dist/{hooks.d.ts → useCommentNavigation-NzJjkaj2.d.mts} +15 -2
  37. package/dist/workflowStore-UAAKOOIK.mjs +2 -0
  38. package/package.json +30 -24
  39. package/dist/canvas.d.ts +0 -27
  40. package/dist/canvas.js +0 -45
  41. package/dist/chunk-3SPPKCWR.js +0 -458
  42. package/dist/chunk-3TMV3K34.js +0 -534
  43. package/dist/chunk-3YFFDHC5.js +0 -300
  44. package/dist/chunk-4MZ62VMF.js +0 -37
  45. package/dist/chunk-5HJFQVUR.js +0 -61
  46. package/dist/chunk-5LQ4QBR5.js +0 -2
  47. package/dist/chunk-6DOEUDD5.js +0 -254
  48. package/dist/chunk-AXFOCPPP.js +0 -998
  49. package/dist/chunk-BMFRA6GK.js +0 -1546
  50. package/dist/chunk-E323WAZG.mjs +0 -272
  51. package/dist/chunk-ECD5J2BA.js +0 -6022
  52. package/dist/chunk-EMGXUNBL.js +0 -120
  53. package/dist/chunk-EMUMKW5C.js +0 -107
  54. package/dist/chunk-FOMOOERN.js +0 -2
  55. package/dist/chunk-IASLG6IA.mjs +0 -118
  56. package/dist/chunk-IHF35QZD.js +0 -1095
  57. package/dist/chunk-JLWKW3G5.js +0 -2
  58. package/dist/chunk-KDIWRSYV.js +0 -375
  59. package/dist/chunk-L5TF4EHW.mjs +0 -1
  60. package/dist/chunk-RJ262NXS.js +0 -24
  61. package/dist/chunk-RXNEDWK2.js +0 -141
  62. package/dist/chunk-SEV2DWKF.js +0 -744
  63. package/dist/chunk-ZJWP5KGZ.mjs +0 -33
  64. package/dist/hooks.js +0 -56
  65. package/dist/index.d.ts +0 -29
  66. package/dist/index.js +0 -180
  67. package/dist/lib.d.ts +0 -164
  68. package/dist/lib.js +0 -144
  69. package/dist/nodes.d.ts +0 -128
  70. package/dist/nodes.js +0 -151
  71. package/dist/panels.d.ts +0 -22
  72. package/dist/panels.js +0 -21
  73. package/dist/promptLibraryStore-BZnfmEkc.d.ts +0 -464
  74. package/dist/provider.d.ts +0 -29
  75. package/dist/provider.js +0 -17
  76. package/dist/stores.d.ts +0 -96
  77. package/dist/stores.js +0 -113
  78. package/dist/toolbar.d.ts +0 -73
  79. package/dist/toolbar.js +0 -34
  80. package/dist/types-ipAnBzAJ.d.ts +0 -46
  81. package/dist/ui.d.ts +0 -67
  82. package/dist/ui.js +0 -84
  83. package/dist/workflowStore-7SDJC4UR.mjs +0 -3
  84. package/dist/workflowStore-LNJQ5RZG.js +0 -12
@@ -1,1095 +0,0 @@
1
- 'use strict';
2
-
3
- var zundo = require('zundo');
4
- var zustand = require('zustand');
5
- var types = require('@genfeedai/types');
6
- var react = require('@xyflow/react');
7
- var nanoid = require('nanoid');
8
-
9
- // src/stores/workflow/helpers/equality.ts
10
- function temporalStateEquals(a, b) {
11
- if (a === b) return true;
12
- if (!arraysShallowEqual(a.nodes, b.nodes, nodeEquals)) return false;
13
- if (!arraysShallowEqual(a.edges, b.edges, edgeEquals)) return false;
14
- if (!arraysShallowEqual(a.groups, b.groups, groupEquals)) return false;
15
- return true;
16
- }
17
- function arraysShallowEqual(a, b, itemEquals) {
18
- if (a === b) return true;
19
- if (a.length !== b.length) return false;
20
- for (let i = 0; i < a.length; i++) {
21
- if (!itemEquals(a[i], b[i])) return false;
22
- }
23
- return true;
24
- }
25
- function nodeEquals(a, b) {
26
- if (a === b) return true;
27
- if (a.id !== b.id) return false;
28
- if (a.type !== b.type) return false;
29
- if (a.position.x !== b.position.x || a.position.y !== b.position.y) return false;
30
- if (a.width !== b.width || a.height !== b.height) return false;
31
- const aData = a.data;
32
- const bData = b.data;
33
- if (aData.status !== bData.status) return false;
34
- if (aData.outputImage !== bData.outputImage) return false;
35
- if (aData.outputVideo !== bData.outputVideo) return false;
36
- if (aData.outputText !== bData.outputText) return false;
37
- if (aData.outputAudio !== bData.outputAudio) return false;
38
- if (aData.prompt !== bData.prompt) return false;
39
- if (aData.image !== bData.image) return false;
40
- if (aData.video !== bData.video) return false;
41
- if (aData.audio !== bData.audio) return false;
42
- if (aData.inputPrompt !== bData.inputPrompt) return false;
43
- if (aData.inputImage !== bData.inputImage) return false;
44
- if (aData.inputVideo !== bData.inputVideo) return false;
45
- if (aData.inputAudio !== bData.inputAudio) return false;
46
- if (aData.inputText !== bData.inputText) return false;
47
- if (aData.model !== bData.model) return false;
48
- if (aData.schemaParams !== bData.schemaParams) {
49
- if (JSON.stringify(aData.schemaParams) !== JSON.stringify(bData.schemaParams)) {
50
- return false;
51
- }
52
- }
53
- return true;
54
- }
55
- function edgeEquals(a, b) {
56
- if (a === b) return true;
57
- return a.id === b.id && a.source === b.source && a.target === b.target && a.sourceHandle === b.sourceHandle && a.targetHandle === b.targetHandle;
58
- }
59
- function groupEquals(a, b) {
60
- if (a === b) return true;
61
- if (a.id !== b.id) return false;
62
- if (a.name !== b.name) return false;
63
- if (a.color !== b.color) return false;
64
- if (a.nodeIds.length !== b.nodeIds.length) return false;
65
- for (let i = 0; i < a.nodeIds.length; i++) {
66
- if (a.nodeIds[i] !== b.nodeIds[i]) return false;
67
- }
68
- return true;
69
- }
70
-
71
- // src/stores/workflow/slices/chatSlice.ts
72
- var createChatSlice = (set, get) => ({
73
- chatMessages: [],
74
- isChatOpen: false,
75
- addChatMessage: (role, content) => {
76
- set((state) => ({
77
- chatMessages: [
78
- ...state.chatMessages,
79
- {
80
- id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
81
- role,
82
- content,
83
- timestamp: Date.now()
84
- }
85
- ]
86
- }));
87
- },
88
- clearChatMessages: () => {
89
- set({ chatMessages: [] });
90
- },
91
- toggleChat: () => {
92
- set((state) => ({ isChatOpen: !state.isChatOpen }));
93
- },
94
- setChatOpen: (open) => {
95
- set({ isChatOpen: open });
96
- },
97
- applyChatEditOperations: (operations) => {
98
- const state = get();
99
- state.captureSnapshot();
100
- return state.applyEditOperations(operations);
101
- }
102
- });
103
- function generateId() {
104
- return nanoid.nanoid(8);
105
- }
106
- function getHandleType(nodeType, handleId, direction) {
107
- const nodeDef = types.NODE_DEFINITIONS[nodeType];
108
- if (!nodeDef) return null;
109
- const handles = direction === "source" ? nodeDef.outputs : nodeDef.inputs;
110
- const handle = handles.find((h) => h.id === handleId);
111
- return handle?.type ?? null;
112
- }
113
-
114
- // src/stores/workflow/slices/edgeSlice.ts
115
- var createEdgeSlice = (set, get) => ({
116
- onNodesChange: (changes) => {
117
- const hasMeaningfulChange = changes.some(
118
- (change) => change.type === "add" || change.type === "remove" || change.type === "replace"
119
- );
120
- set((state) => ({
121
- nodes: react.applyNodeChanges(changes, state.nodes),
122
- ...hasMeaningfulChange && { isDirty: true }
123
- }));
124
- },
125
- onEdgesChange: (changes) => {
126
- const hasMeaningfulChange = changes.some(
127
- (change) => change.type === "add" || change.type === "remove" || change.type === "replace"
128
- );
129
- set((state) => ({
130
- edges: react.applyEdgeChanges(changes, state.edges),
131
- ...hasMeaningfulChange && { isDirty: true }
132
- }));
133
- },
134
- onConnect: (connection) => {
135
- const { isValidConnection, propagateOutputsDownstream } = get();
136
- if (!isValidConnection(connection)) return;
137
- set((state) => ({
138
- edges: react.addEdge(
139
- {
140
- ...connection,
141
- id: generateId(),
142
- type: state.edgeStyle
143
- },
144
- state.edges
145
- ),
146
- isDirty: true
147
- }));
148
- if (connection.source) {
149
- propagateOutputsDownstream(connection.source);
150
- }
151
- },
152
- removeEdge: (edgeId) => {
153
- set((state) => ({
154
- edges: state.edges.filter((edge) => edge.id !== edgeId),
155
- isDirty: true
156
- }));
157
- },
158
- setEdgeStyle: (style) => {
159
- set((state) => ({
160
- edgeStyle: style,
161
- edges: state.edges.map((edge) => ({ ...edge, type: style })),
162
- isDirty: true
163
- }));
164
- },
165
- toggleEdgePause: (edgeId) => {
166
- set((state) => ({
167
- edges: state.edges.map(
168
- (edge) => edge.id === edgeId ? {
169
- ...edge,
170
- data: {
171
- ...edge.data,
172
- hasPause: !edge.data?.hasPause
173
- }
174
- } : edge
175
- ),
176
- isDirty: true
177
- }));
178
- },
179
- isValidConnection: (connection) => {
180
- const { nodes } = get();
181
- const sourceNode = nodes.find((n) => n.id === connection.source);
182
- const targetNode = nodes.find((n) => n.id === connection.target);
183
- if (!sourceNode || !targetNode) return false;
184
- const sourceType = getHandleType(
185
- sourceNode.type,
186
- connection.sourceHandle ?? null,
187
- "source"
188
- );
189
- const targetType = getHandleType(
190
- targetNode.type,
191
- connection.targetHandle ?? null,
192
- "target"
193
- );
194
- if (!sourceType || !targetType) return false;
195
- return types.CONNECTION_RULES[sourceType]?.includes(targetType) ?? false;
196
- },
197
- findCompatibleHandle: (sourceNodeId, sourceHandleId, targetNodeId) => {
198
- const { nodes, edges } = get();
199
- const sourceNode = nodes.find((n) => n.id === sourceNodeId);
200
- const targetNode = nodes.find((n) => n.id === targetNodeId);
201
- if (!sourceNode || !targetNode) return null;
202
- const sourceType = getHandleType(sourceNode.type, sourceHandleId, "source");
203
- if (!sourceType) return null;
204
- const targetDef = types.NODE_DEFINITIONS[targetNode.type];
205
- if (!targetDef) return null;
206
- const existingTargetHandles = new Set(
207
- edges.filter((e) => e.target === targetNodeId).map((e) => e.targetHandle)
208
- );
209
- for (const input of targetDef.inputs) {
210
- const hasExistingConnection = existingTargetHandles.has(input.id);
211
- if (hasExistingConnection && !input.multiple) continue;
212
- if (types.CONNECTION_RULES[sourceType]?.includes(input.type)) {
213
- return input.id;
214
- }
215
- }
216
- return null;
217
- }
218
- });
219
-
220
- // src/stores/workflow/slices/groupSlice.ts
221
- var DEFAULT_GROUP_COLORS = [
222
- "purple",
223
- "blue",
224
- "green",
225
- "yellow",
226
- "orange",
227
- "red",
228
- "pink",
229
- "gray"
230
- ];
231
- var createGroupSlice = (set, get) => ({
232
- createGroup: (nodeIds, name) => {
233
- if (nodeIds.length === 0) return "";
234
- const groupId = generateId();
235
- const { groups } = get();
236
- const colorIndex = groups.length % DEFAULT_GROUP_COLORS.length;
237
- const newGroup = {
238
- id: groupId,
239
- name: name ?? `Group ${groups.length + 1}`,
240
- nodeIds,
241
- isLocked: false,
242
- color: DEFAULT_GROUP_COLORS[colorIndex]
243
- };
244
- set((state) => ({
245
- groups: [...state.groups, newGroup],
246
- isDirty: true
247
- }));
248
- return groupId;
249
- },
250
- deleteGroup: (groupId) => {
251
- set((state) => ({
252
- groups: state.groups.filter((g) => g.id !== groupId),
253
- isDirty: true
254
- }));
255
- },
256
- addToGroup: (groupId, nodeIds) => {
257
- set((state) => ({
258
- groups: state.groups.map(
259
- (g) => g.id === groupId ? { ...g, nodeIds: [.../* @__PURE__ */ new Set([...g.nodeIds, ...nodeIds])] } : g
260
- ),
261
- isDirty: true
262
- }));
263
- },
264
- removeFromGroup: (groupId, nodeIds) => {
265
- set((state) => ({
266
- groups: state.groups.map(
267
- (g) => g.id === groupId ? { ...g, nodeIds: g.nodeIds.filter((id) => !nodeIds.includes(id)) } : g
268
- ),
269
- isDirty: true
270
- }));
271
- },
272
- toggleGroupLock: (groupId) => {
273
- const { groups, lockMultipleNodes, unlockMultipleNodes } = get();
274
- const group = groups.find((g) => g.id === groupId);
275
- if (!group) return;
276
- set((state) => ({
277
- groups: state.groups.map((g) => g.id === groupId ? { ...g, isLocked: !g.isLocked } : g),
278
- isDirty: true
279
- }));
280
- if (!group.isLocked) {
281
- lockMultipleNodes(group.nodeIds);
282
- } else {
283
- unlockMultipleNodes(group.nodeIds);
284
- }
285
- },
286
- renameGroup: (groupId, name) => {
287
- set((state) => ({
288
- groups: state.groups.map((g) => g.id === groupId ? { ...g, name } : g),
289
- isDirty: true
290
- }));
291
- },
292
- setGroupColor: (groupId, color) => {
293
- set((state) => ({
294
- groups: state.groups.map((g) => g.id === groupId ? { ...g, color } : g),
295
- isDirty: true
296
- }));
297
- },
298
- getGroupByNodeId: (nodeId) => {
299
- return get().groups.find((g) => g.nodeIds.includes(nodeId));
300
- },
301
- getGroupById: (groupId) => {
302
- return get().groups.find((g) => g.id === groupId);
303
- }
304
- });
305
-
306
- // src/stores/workflow/helpers/propagation.ts
307
- function getNodeOutput(node) {
308
- const data = node.data;
309
- const outputImages = data.outputImages;
310
- if (outputImages?.length) return outputImages[0];
311
- const output = data.outputImage ?? data.outputVideo ?? data.outputText ?? data.outputAudio ?? data.prompt ?? data.extractedTweet ?? data.image ?? data.video ?? data.audio ?? null;
312
- if (output === null) return null;
313
- if (typeof output === "string") return output;
314
- if (Array.isArray(output) && output.length > 0) return String(output[0]);
315
- return null;
316
- }
317
- function getOutputType(sourceType) {
318
- if (["prompt", "llm", "tweetParser", "transcribe"].includes(sourceType)) {
319
- return "text";
320
- }
321
- if (["imageGen", "image", "imageInput", "upscale", "resize", "reframe", "imageGridSplit"].includes(
322
- sourceType
323
- )) {
324
- return "image";
325
- }
326
- if ([
327
- "videoGen",
328
- "video",
329
- "videoInput",
330
- "animation",
331
- "videoStitch",
332
- "lipSync",
333
- "voiceChange",
334
- "motionControl",
335
- "videoTrim",
336
- "videoFrameExtract",
337
- "subtitle"
338
- ].includes(sourceType)) {
339
- return "video";
340
- }
341
- if (["textToSpeech", "audio", "audioInput"].includes(sourceType)) {
342
- return "audio";
343
- }
344
- return null;
345
- }
346
- function mapOutputToInput(output, sourceType, targetType) {
347
- const outputType = getOutputType(sourceType);
348
- if (targetType === "download") {
349
- if (outputType === "video") {
350
- return { inputVideo: output, inputImage: null, inputType: "video" };
351
- }
352
- if (outputType === "image") {
353
- return { inputImage: output, inputVideo: null, inputType: "image" };
354
- }
355
- return null;
356
- }
357
- if (outputType === "text") {
358
- if (["textToSpeech", "subtitle"].includes(targetType)) {
359
- return { inputText: output };
360
- }
361
- if (["imageGen", "videoGen", "llm", "motionControl"].includes(targetType)) {
362
- return { inputPrompt: output };
363
- }
364
- }
365
- if (outputType === "image") {
366
- if (["upscale", "reframe"].includes(targetType)) {
367
- return { inputImage: output, inputVideo: null, inputType: "image" };
368
- }
369
- if (["videoGen", "lipSync", "voiceChange", "motionControl", "resize", "animation"].includes(
370
- targetType
371
- )) {
372
- return { inputImage: output };
373
- }
374
- if (targetType === "imageGen") {
375
- return { inputImages: [output] };
376
- }
377
- }
378
- if (outputType === "video") {
379
- if (["upscale", "reframe"].includes(targetType)) {
380
- return { inputVideo: output, inputImage: null, inputType: "video" };
381
- }
382
- if ([
383
- "lipSync",
384
- "voiceChange",
385
- "resize",
386
- "videoStitch",
387
- "videoTrim",
388
- "videoFrameExtract",
389
- "subtitle",
390
- "transcribe"
391
- ].includes(targetType)) {
392
- return { inputVideo: output };
393
- }
394
- }
395
- if (outputType === "audio") {
396
- if (["lipSync", "voiceChange", "transcribe"].includes(targetType)) {
397
- return { inputAudio: output };
398
- }
399
- }
400
- return null;
401
- }
402
- function collectGalleryUpdate(sourceData, currentOutput, existingGalleryImages, pendingUpdateImages) {
403
- const allImages = [];
404
- const outputImagesArr = sourceData.outputImages;
405
- if (outputImagesArr?.length) {
406
- allImages.push(...outputImagesArr);
407
- } else if (typeof currentOutput === "string") {
408
- allImages.push(currentOutput);
409
- }
410
- if (allImages.length === 0) return null;
411
- return {
412
- images: [.../* @__PURE__ */ new Set([...existingGalleryImages, ...pendingUpdateImages, ...allImages])]
413
- };
414
- }
415
- function computeDownstreamUpdates(sourceNodeId, initialOutput, nodes, edges) {
416
- const updates = /* @__PURE__ */ new Map();
417
- const visited = /* @__PURE__ */ new Set();
418
- const queue = [
419
- { nodeId: sourceNodeId, output: initialOutput }
420
- ];
421
- while (queue.length > 0) {
422
- const current = queue.shift();
423
- if (visited.has(current.nodeId)) continue;
424
- visited.add(current.nodeId);
425
- const currentNode = nodes.find((n) => n.id === current.nodeId);
426
- if (!currentNode) continue;
427
- const downstreamEdges = edges.filter((e) => e.source === current.nodeId);
428
- for (const edge of downstreamEdges) {
429
- const targetNode = nodes.find((n) => n.id === edge.target);
430
- if (!targetNode) continue;
431
- if (targetNode.type === "outputGallery") {
432
- const sourceData = currentNode.data;
433
- const existing = updates.get(edge.target) ?? {};
434
- const pendingImages = existing.images ?? [];
435
- const targetData = targetNode.data;
436
- const galleryExisting = targetData.images ?? [];
437
- const galleryUpdate = collectGalleryUpdate(
438
- sourceData,
439
- current.output,
440
- galleryExisting,
441
- pendingImages
442
- );
443
- if (galleryUpdate) {
444
- updates.set(edge.target, { ...existing, ...galleryUpdate });
445
- }
446
- continue;
447
- }
448
- const inputUpdate = mapOutputToInput(current.output, currentNode.type, targetNode.type);
449
- if (inputUpdate) {
450
- const existing = updates.get(edge.target) ?? {};
451
- updates.set(edge.target, { ...existing, ...inputUpdate });
452
- const targetOutput = getNodeOutput(targetNode);
453
- if (targetOutput && !visited.has(edge.target)) {
454
- queue.push({ nodeId: edge.target, output: targetOutput });
455
- }
456
- }
457
- }
458
- }
459
- return updates;
460
- }
461
- function hasStateChanged(updates, nodes) {
462
- for (const [nodeId, update] of updates) {
463
- const existingNode = nodes.find((n) => n.id === nodeId);
464
- if (!existingNode) continue;
465
- const existingData = existingNode.data;
466
- for (const [key, value] of Object.entries(update)) {
467
- const prev = existingData[key];
468
- if (Array.isArray(prev) && Array.isArray(value)) {
469
- if (prev.length !== value.length || prev.some((v, i) => v !== value[i])) {
470
- return true;
471
- }
472
- } else if (prev !== value) {
473
- return true;
474
- }
475
- }
476
- }
477
- return false;
478
- }
479
- function applyNodeUpdates(nodes, updates) {
480
- return nodes.map((n) => {
481
- const update = updates.get(n.id);
482
- if (update) {
483
- return { ...n, data: { ...n.data, ...update } };
484
- }
485
- return n;
486
- });
487
- }
488
- function propagateExistingOutputs(nodes, propagateFn) {
489
- for (const node of nodes) {
490
- if (getNodeOutput(node) !== null) {
491
- propagateFn(node.id);
492
- }
493
- }
494
- }
495
-
496
- // src/stores/workflow/slices/lockingSlice.ts
497
- var createLockingSlice = (set, get) => ({
498
- _setNodeLockState: (predicate, lock) => {
499
- set((state) => ({
500
- nodes: state.nodes.map(
501
- (n) => predicate(n.id) ? {
502
- ...n,
503
- draggable: !lock,
504
- data: {
505
- ...n.data,
506
- isLocked: lock,
507
- lockTimestamp: lock ? Date.now() : void 0,
508
- ...lock && { cachedOutput: getNodeOutput(n) }
509
- }
510
- } : n
511
- ),
512
- isDirty: true
513
- }));
514
- },
515
- toggleNodeLock: (nodeId) => {
516
- const node = get().getNodeById(nodeId);
517
- if (!node) return;
518
- const shouldLock = !(node.data.isLocked ?? false);
519
- get()._setNodeLockState((id) => id === nodeId, shouldLock);
520
- },
521
- lockNode: (nodeId) => {
522
- const node = get().getNodeById(nodeId);
523
- if (!node || node.data.isLocked) return;
524
- get()._setNodeLockState((id) => id === nodeId, true);
525
- },
526
- unlockNode: (nodeId) => {
527
- get()._setNodeLockState((id) => id === nodeId, false);
528
- },
529
- lockMultipleNodes: (nodeIds) => {
530
- get()._setNodeLockState((id) => nodeIds.includes(id), true);
531
- },
532
- unlockMultipleNodes: (nodeIds) => {
533
- get()._setNodeLockState((id) => nodeIds.includes(id), false);
534
- },
535
- unlockAllNodes: () => {
536
- get()._setNodeLockState(() => true, false);
537
- },
538
- isNodeLocked: (nodeId) => {
539
- const { nodes, groups } = get();
540
- const node = nodes.find((n) => n.id === nodeId);
541
- if (!node) return false;
542
- if (node.data.isLocked) return true;
543
- return groups.some((group) => group.isLocked && group.nodeIds.includes(nodeId));
544
- }
545
- });
546
- var createNodeSlice = (set, get) => ({
547
- addNode: (type, position) => {
548
- const nodeDef = types.NODE_DEFINITIONS[type];
549
- if (!nodeDef) return "";
550
- const id = generateId();
551
- const newNode = {
552
- id,
553
- type,
554
- position,
555
- data: {
556
- ...nodeDef.defaultData,
557
- label: nodeDef.label,
558
- status: "idle"
559
- },
560
- ...type === "download" && { width: 280, height: 320 }
561
- };
562
- set((state) => ({
563
- nodes: [...state.nodes, newNode],
564
- isDirty: true
565
- }));
566
- return id;
567
- },
568
- addNodesAndEdges: (newNodes, newEdges) => {
569
- if (newNodes.length === 0) return;
570
- set((state) => ({
571
- nodes: [...state.nodes, ...newNodes],
572
- edges: [...state.edges, ...newEdges],
573
- isDirty: true
574
- }));
575
- const { propagateOutputsDownstream } = get();
576
- const sourceNodeIds = new Set(newEdges.map((e) => e.source));
577
- for (const sourceId of sourceNodeIds) {
578
- propagateOutputsDownstream(sourceId);
579
- }
580
- },
581
- updateNodeData: (nodeId, data) => {
582
- const { nodes, propagateOutputsDownstream } = get();
583
- const node = nodes.find((n) => n.id === nodeId);
584
- const TRANSIENT_KEYS = /* @__PURE__ */ new Set(["status", "progress", "error", "jobId"]);
585
- const dataKeys = Object.keys(data);
586
- const hasPersistedChange = dataKeys.some((key) => !TRANSIENT_KEYS.has(key));
587
- set((state) => ({
588
- nodes: state.nodes.map((n) => n.id === nodeId ? { ...n, data: { ...n.data, ...data } } : n),
589
- ...hasPersistedChange && { isDirty: true }
590
- }));
591
- const inputNodeTypes = [
592
- "prompt",
593
- "image",
594
- "imageInput",
595
- "video",
596
- "videoInput",
597
- "audio",
598
- "audioInput",
599
- "tweetParser"
600
- ];
601
- const hasOutputUpdate = "outputImage" in data || "outputImages" in data || "outputVideo" in data || "outputAudio" in data || "outputText" in data;
602
- if (node && (inputNodeTypes.includes(node.type) || hasOutputUpdate)) {
603
- if (hasOutputUpdate) {
604
- const dataRecord = data;
605
- if ("outputImages" in dataRecord) {
606
- propagateOutputsDownstream(nodeId);
607
- } else {
608
- const outputValue = dataRecord.outputImage ?? dataRecord.outputVideo ?? dataRecord.outputAudio ?? dataRecord.outputText;
609
- if (typeof outputValue === "string") {
610
- propagateOutputsDownstream(nodeId, outputValue);
611
- }
612
- }
613
- } else {
614
- propagateOutputsDownstream(nodeId);
615
- }
616
- }
617
- },
618
- removeNode: (nodeId) => {
619
- set((state) => ({
620
- nodes: state.nodes.filter((node) => node.id !== nodeId),
621
- edges: state.edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId),
622
- isDirty: true
623
- }));
624
- },
625
- duplicateNode: (nodeId) => {
626
- const { nodes, edges, edgeStyle, propagateOutputsDownstream } = get();
627
- const node = nodes.find((n) => n.id === nodeId);
628
- if (!node) return null;
629
- const newId = generateId();
630
- const newNode = {
631
- ...node,
632
- id: newId,
633
- position: {
634
- x: node.position.x + 50,
635
- y: node.position.y + 50
636
- },
637
- data: {
638
- ...node.data,
639
- status: "idle",
640
- jobId: null
641
- }
642
- };
643
- const incomingEdges = edges.filter((e) => e.target === nodeId && e.source !== nodeId);
644
- const clonedEdges = incomingEdges.map((edge) => ({
645
- ...edge,
646
- id: generateId(),
647
- target: newId,
648
- type: edgeStyle
649
- }));
650
- set((state) => ({
651
- nodes: [...state.nodes, newNode],
652
- edges: [...state.edges, ...clonedEdges],
653
- isDirty: true
654
- }));
655
- const sourcesNotified = /* @__PURE__ */ new Set();
656
- for (const edge of incomingEdges) {
657
- if (!sourcesNotified.has(edge.source)) {
658
- sourcesNotified.add(edge.source);
659
- propagateOutputsDownstream(edge.source);
660
- }
661
- }
662
- return newId;
663
- },
664
- propagateOutputsDownstream: (sourceNodeId, outputValue) => {
665
- const { nodes, edges } = get();
666
- const sourceNode = nodes.find((n) => n.id === sourceNodeId);
667
- if (!sourceNode) return;
668
- const output = outputValue ?? getNodeOutput(sourceNode);
669
- if (!output) return;
670
- const updates = computeDownstreamUpdates(sourceNodeId, output, nodes, edges);
671
- if (updates.size === 0) return;
672
- if (!hasStateChanged(updates, nodes)) return;
673
- set((state) => ({
674
- nodes: applyNodeUpdates(state.nodes, updates),
675
- isDirty: true
676
- }));
677
- }
678
- });
679
- function normalizeEdgeTypes(edges) {
680
- return edges.map((edge) => ({
681
- ...edge,
682
- type: edge.type === "bezier" ? "default" : edge.type
683
- }));
684
- }
685
- function hydrateWorkflowNodes(nodes) {
686
- return nodes.map((node) => {
687
- const nodeDef = types.NODE_DEFINITIONS[node.type];
688
- if (!nodeDef) return node;
689
- return {
690
- ...node,
691
- data: {
692
- ...nodeDef.defaultData,
693
- ...node.data
694
- }
695
- };
696
- });
697
- }
698
- var createPersistenceSlice = (set, get) => ({
699
- loadWorkflow: (workflow) => {
700
- const hydratedNodes = hydrateWorkflowNodes(workflow.nodes);
701
- set({
702
- nodes: hydratedNodes,
703
- edges: normalizeEdgeTypes(workflow.edges),
704
- edgeStyle: workflow.edgeStyle,
705
- workflowName: workflow.name,
706
- workflowId: null,
707
- isDirty: true,
708
- groups: workflow.groups ?? [],
709
- selectedNodeIds: []
710
- });
711
- propagateExistingOutputs(hydratedNodes, get().propagateOutputsDownstream);
712
- set({ isDirty: false });
713
- },
714
- clearWorkflow: () => {
715
- set({
716
- nodes: [],
717
- edges: [],
718
- workflowName: "Untitled Workflow",
719
- workflowId: null,
720
- isDirty: false,
721
- groups: [],
722
- selectedNodeIds: []
723
- });
724
- },
725
- exportWorkflow: () => {
726
- const { nodes, edges, edgeStyle, workflowName, groups } = get();
727
- return {
728
- version: 1,
729
- name: workflowName,
730
- description: "",
731
- nodes,
732
- edges,
733
- edgeStyle,
734
- groups,
735
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
736
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
737
- };
738
- },
739
- getNodeById: (id) => {
740
- return get().nodes.find((node) => node.id === id);
741
- },
742
- getConnectedInputs: (nodeId) => {
743
- const { nodes, edges } = get();
744
- const inputs = /* @__PURE__ */ new Map();
745
- const incomingEdges = edges.filter((edge) => edge.target === nodeId);
746
- for (const edge of incomingEdges) {
747
- const sourceNode = nodes.find((n) => n.id === edge.source);
748
- if (!sourceNode) continue;
749
- const handleId = edge.targetHandle;
750
- if (!handleId) continue;
751
- const sourceData = sourceNode.data;
752
- let value = null;
753
- if (edge.sourceHandle === "image") {
754
- value = sourceData.outputImage ?? sourceData.image ?? null;
755
- } else if (edge.sourceHandle === "video") {
756
- value = sourceData.outputVideo ?? sourceData.video ?? null;
757
- } else if (edge.sourceHandle === "text") {
758
- value = sourceData.outputText ?? sourceData.prompt ?? null;
759
- } else if (edge.sourceHandle === "audio") {
760
- value = sourceData.outputAudio ?? sourceData.audio ?? null;
761
- }
762
- if (value) {
763
- const existing = inputs.get(handleId);
764
- if (existing) {
765
- if (Array.isArray(existing)) {
766
- inputs.set(handleId, [...existing, value]);
767
- } else {
768
- inputs.set(handleId, [existing, value]);
769
- }
770
- } else {
771
- inputs.set(handleId, value);
772
- }
773
- }
774
- }
775
- return inputs;
776
- },
777
- getConnectedNodeIds: (nodeIds) => {
778
- const { edges } = get();
779
- const connected = new Set(nodeIds);
780
- const visited = /* @__PURE__ */ new Set();
781
- const queue = [...nodeIds];
782
- while (queue.length > 0) {
783
- const currentId = queue.shift();
784
- if (visited.has(currentId)) continue;
785
- visited.add(currentId);
786
- const upstreamEdges = edges.filter((e) => e.target === currentId);
787
- for (const edge of upstreamEdges) {
788
- if (!connected.has(edge.source)) {
789
- connected.add(edge.source);
790
- queue.push(edge.source);
791
- }
792
- }
793
- }
794
- return Array.from(connected);
795
- },
796
- validateWorkflow: () => {
797
- const { nodes, edges } = get();
798
- const errors = [];
799
- const warnings = [];
800
- if (nodes.length === 0) {
801
- errors.push({
802
- nodeId: "",
803
- message: "Workflow is empty - add some nodes first",
804
- severity: "error"
805
- });
806
- return { isValid: false, errors, warnings };
807
- }
808
- if (edges.length === 0 && nodes.length > 1) {
809
- errors.push({
810
- nodeId: "",
811
- message: "No connections - connect your nodes together",
812
- severity: "error"
813
- });
814
- return { isValid: false, errors, warnings };
815
- }
816
- const hasNodeOutput = (node) => {
817
- const data = node.data;
818
- switch (node.type) {
819
- case "prompt":
820
- return Boolean(data.prompt?.trim());
821
- case "imageInput":
822
- return Boolean(data.image);
823
- case "videoInput":
824
- return Boolean(data.video);
825
- case "audioInput":
826
- return Boolean(data.audio);
827
- default:
828
- return true;
829
- }
830
- };
831
- for (const node of nodes) {
832
- const nodeDef = types.NODE_DEFINITIONS[node.type];
833
- if (!nodeDef) continue;
834
- const incomingEdges = edges.filter((e) => e.target === node.id);
835
- for (const input of nodeDef.inputs) {
836
- if (input.required) {
837
- const connectionEdge = incomingEdges.find((e) => e.targetHandle === input.id);
838
- if (!connectionEdge) {
839
- errors.push({
840
- nodeId: node.id,
841
- message: `Missing required input: ${input.label}`,
842
- severity: "error"
843
- });
844
- } else {
845
- const sourceNode = nodes.find((n) => n.id === connectionEdge.source);
846
- if (sourceNode && !hasNodeOutput(sourceNode)) {
847
- errors.push({
848
- nodeId: sourceNode.id,
849
- message: `${sourceNode.data.label} is empty`,
850
- severity: "error"
851
- });
852
- }
853
- }
854
- }
855
- }
856
- }
857
- const visited = /* @__PURE__ */ new Set();
858
- const recStack = /* @__PURE__ */ new Set();
859
- function hasCycle(nodeId) {
860
- if (recStack.has(nodeId)) return true;
861
- if (visited.has(nodeId)) return false;
862
- visited.add(nodeId);
863
- recStack.add(nodeId);
864
- const outgoing = edges.filter((e) => e.source === nodeId);
865
- for (const edge of outgoing) {
866
- if (hasCycle(edge.target)) return true;
867
- }
868
- recStack.delete(nodeId);
869
- return false;
870
- }
871
- for (const node of nodes) {
872
- if (hasCycle(node.id)) {
873
- errors.push({
874
- nodeId: node.id,
875
- message: "Workflow contains a cycle",
876
- severity: "error"
877
- });
878
- break;
879
- }
880
- }
881
- for (const node of nodes) {
882
- if (node.type === "workflowRef") {
883
- const refData = node.data;
884
- if (!refData.referencedWorkflowId) {
885
- errors.push({
886
- nodeId: node.id,
887
- message: "Subworkflow node must reference a workflow",
888
- severity: "error"
889
- });
890
- } else if (!refData.cachedInterface) {
891
- warnings.push({
892
- nodeId: node.id,
893
- message: "Subworkflow interface not loaded - refresh to update handles",
894
- severity: "warning"
895
- });
896
- }
897
- }
898
- }
899
- return {
900
- isValid: errors.length === 0,
901
- errors,
902
- warnings
903
- };
904
- },
905
- setDirty: (dirty) => {
906
- set({ isDirty: dirty });
907
- },
908
- setWorkflowName: (name) => {
909
- set({ workflowName: name, isDirty: true });
910
- },
911
- // API operations - stubs that throw by default.
912
- // Consuming apps override these via the store creator or by extending the slice.
913
- saveWorkflow: async () => {
914
- throw new Error("saveWorkflow not implemented - consuming app must provide API integration");
915
- },
916
- loadWorkflowById: async () => {
917
- throw new Error(
918
- "loadWorkflowById not implemented - consuming app must provide API integration"
919
- );
920
- },
921
- listWorkflows: async () => {
922
- throw new Error("listWorkflows not implemented - consuming app must provide API integration");
923
- },
924
- deleteWorkflow: async () => {
925
- throw new Error("deleteWorkflow not implemented - consuming app must provide API integration");
926
- },
927
- duplicateWorkflowApi: async () => {
928
- throw new Error(
929
- "duplicateWorkflowApi not implemented - consuming app must provide API integration"
930
- );
931
- },
932
- createNewWorkflow: async () => {
933
- throw new Error(
934
- "createNewWorkflow not implemented - consuming app must provide API integration"
935
- );
936
- },
937
- getNodesWithComments: () => {
938
- const { nodes } = get();
939
- return nodes.filter((node) => {
940
- const data = node.data;
941
- return data.comment?.trim();
942
- }).sort((a, b) => {
943
- if (Math.abs(a.position.y - b.position.y) < 50) {
944
- return a.position.x - b.position.x;
945
- }
946
- return a.position.y - b.position.y;
947
- });
948
- },
949
- markCommentViewed: (nodeId) => {
950
- set((state) => {
951
- const newSet = new Set(state.viewedCommentIds);
952
- newSet.add(nodeId);
953
- return { viewedCommentIds: newSet };
954
- });
955
- },
956
- setNavigationTarget: (nodeId) => {
957
- set({ navigationTargetId: nodeId });
958
- },
959
- getUnviewedCommentCount: () => {
960
- const { nodes, viewedCommentIds } = get();
961
- return nodes.filter((node) => {
962
- const data = node.data;
963
- return data.comment?.trim() && !viewedCommentIds.has(node.id);
964
- }).length;
965
- }
966
- });
967
-
968
- // src/stores/workflow/slices/selectionSlice.ts
969
- var createSelectionSlice = (set) => ({
970
- setSelectedNodeIds: (nodeIds) => {
971
- set({ selectedNodeIds: nodeIds });
972
- },
973
- addToSelection: (nodeId) => {
974
- set((state) => ({
975
- selectedNodeIds: state.selectedNodeIds.includes(nodeId) ? state.selectedNodeIds : [...state.selectedNodeIds, nodeId]
976
- }));
977
- },
978
- removeFromSelection: (nodeId) => {
979
- set((state) => ({
980
- selectedNodeIds: state.selectedNodeIds.filter((id) => id !== nodeId)
981
- }));
982
- },
983
- clearSelection: () => {
984
- set({ selectedNodeIds: [] });
985
- }
986
- });
987
-
988
- // src/stores/workflow/slices/snapshotSlice.ts
989
- function defaultApplyEditOperations(_operations, state) {
990
- return { nodes: state.nodes, edges: state.edges, applied: 0, skipped: [] };
991
- }
992
- var createSnapshotSlice = (set, get) => ({
993
- previousWorkflowSnapshot: null,
994
- manualChangeCount: 0,
995
- captureSnapshot: () => {
996
- const state = get();
997
- const snapshot = {
998
- nodes: JSON.parse(JSON.stringify(state.nodes)),
999
- edges: JSON.parse(JSON.stringify(state.edges)),
1000
- groups: JSON.parse(JSON.stringify(state.groups)),
1001
- edgeStyle: state.edgeStyle
1002
- };
1003
- set({
1004
- previousWorkflowSnapshot: snapshot,
1005
- manualChangeCount: 0
1006
- });
1007
- },
1008
- revertToSnapshot: () => {
1009
- const state = get();
1010
- if (state.previousWorkflowSnapshot) {
1011
- set({
1012
- nodes: state.previousWorkflowSnapshot.nodes,
1013
- edges: state.previousWorkflowSnapshot.edges,
1014
- groups: state.previousWorkflowSnapshot.groups,
1015
- edgeStyle: state.previousWorkflowSnapshot.edgeStyle,
1016
- previousWorkflowSnapshot: null,
1017
- manualChangeCount: 0,
1018
- isDirty: true
1019
- });
1020
- }
1021
- },
1022
- clearSnapshot: () => {
1023
- set({
1024
- previousWorkflowSnapshot: null,
1025
- manualChangeCount: 0
1026
- });
1027
- },
1028
- incrementManualChangeCount: () => {
1029
- const state = get();
1030
- const newCount = state.manualChangeCount + 1;
1031
- if (newCount >= 3) {
1032
- set({
1033
- previousWorkflowSnapshot: null,
1034
- manualChangeCount: 0
1035
- });
1036
- } else {
1037
- set({ manualChangeCount: newCount });
1038
- }
1039
- },
1040
- applyEditOperations: (operations) => {
1041
- const state = get();
1042
- const result = defaultApplyEditOperations(operations, {
1043
- nodes: state.nodes,
1044
- edges: state.edges
1045
- });
1046
- set({
1047
- nodes: result.nodes,
1048
- edges: result.edges,
1049
- isDirty: true
1050
- });
1051
- return { applied: result.applied, skipped: result.skipped };
1052
- }
1053
- });
1054
-
1055
- // src/stores/workflow/workflowStore.ts
1056
- var storeCreator = ((...args) => ({
1057
- // Initial state
1058
- nodes: [],
1059
- edges: [],
1060
- edgeStyle: "default",
1061
- workflowName: "Untitled Workflow",
1062
- workflowId: null,
1063
- isDirty: false,
1064
- isSaving: false,
1065
- isLoading: false,
1066
- groups: [],
1067
- selectedNodeIds: [],
1068
- viewedCommentIds: /* @__PURE__ */ new Set(),
1069
- navigationTargetId: null,
1070
- // Compose slices
1071
- ...createNodeSlice(...args),
1072
- ...createEdgeSlice(...args),
1073
- ...createLockingSlice(...args),
1074
- ...createGroupSlice(...args),
1075
- ...createSelectionSlice(...args),
1076
- ...createPersistenceSlice(...args),
1077
- ...createSnapshotSlice(...args),
1078
- ...createChatSlice(...args)
1079
- }));
1080
- var useWorkflowStore = zustand.create()(
1081
- zundo.temporal(storeCreator, {
1082
- // Only track meaningful state (not UI flags like isDirty, isSaving, etc.)
1083
- partialize: (state) => ({
1084
- nodes: state.nodes,
1085
- edges: state.edges,
1086
- groups: state.groups
1087
- }),
1088
- // Limit history to prevent memory issues
1089
- limit: 50,
1090
- // Optimized equality check using shallow comparison instead of JSON.stringify
1091
- equality: temporalStateEquals
1092
- })
1093
- );
1094
-
1095
- exports.useWorkflowStore = useWorkflowStore;