@genfeedai/workflow-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/canvas.d.mts +27 -0
  2. package/dist/canvas.d.ts +27 -0
  3. package/dist/canvas.js +45 -0
  4. package/dist/canvas.mjs +16 -0
  5. package/dist/chunk-22PDGHNQ.mjs +737 -0
  6. package/dist/chunk-3SPPKCWR.js +458 -0
  7. package/dist/chunk-3YFFDHC5.js +300 -0
  8. package/dist/chunk-5HJFQVUR.js +61 -0
  9. package/dist/chunk-5LQ4QBR5.js +2 -0
  10. package/dist/chunk-6DOEUDD5.js +254 -0
  11. package/dist/chunk-7SKSRSS7.mjs +57 -0
  12. package/dist/chunk-AC6TWLRT.mjs +27 -0
  13. package/dist/chunk-ADWNF7V3.js +120 -0
  14. package/dist/chunk-BJ3R5R32.mjs +2163 -0
  15. package/dist/chunk-CETJJ73S.js +1555 -0
  16. package/dist/chunk-CSUBLSKZ.mjs +1002 -0
  17. package/dist/chunk-CV4M7CNU.mjs +251 -0
  18. package/dist/chunk-E323WAZG.mjs +272 -0
  19. package/dist/chunk-E544XUBL.js +378 -0
  20. package/dist/chunk-EC2ZIWOK.js +1007 -0
  21. package/dist/chunk-EFXQT23N.mjs +99 -0
  22. package/dist/chunk-EMUMKW5C.js +107 -0
  23. package/dist/chunk-FOMOOERN.js +2 -0
  24. package/dist/chunk-FT33LFII.mjs +21 -0
  25. package/dist/chunk-FT64PCUP.mjs +533 -0
  26. package/dist/chunk-H6LZKSLY.js +5678 -0
  27. package/dist/chunk-HPQT36RR.js +543 -0
  28. package/dist/chunk-JLWKW3G5.js +2 -0
  29. package/dist/chunk-L5TF4EHW.mjs +1 -0
  30. package/dist/chunk-LAJ34AH2.mjs +374 -0
  31. package/dist/chunk-LDN7IX4Y.mjs +1 -0
  32. package/dist/chunk-MLJJBBTB.mjs +1 -0
  33. package/dist/chunk-NSDLGLAQ.js +2166 -0
  34. package/dist/chunk-RJ262NXS.js +24 -0
  35. package/dist/chunk-RXNEDWK2.js +141 -0
  36. package/dist/chunk-SW7QNEZU.js +744 -0
  37. package/dist/chunk-UQQUWGHW.mjs +118 -0
  38. package/dist/chunk-VOGL2WCE.mjs +1542 -0
  39. package/dist/chunk-VRN3UWE5.mjs +138 -0
  40. package/dist/chunk-XV5Z5XYR.mjs +5640 -0
  41. package/dist/chunk-Z7PWFZG5.js +30 -0
  42. package/dist/chunk-ZJD5WMR3.mjs +418 -0
  43. package/dist/hooks.d.mts +255 -0
  44. package/dist/hooks.d.ts +255 -0
  45. package/dist/hooks.js +56 -0
  46. package/dist/hooks.mjs +11 -0
  47. package/dist/index.d.mts +29 -0
  48. package/dist/index.d.ts +29 -0
  49. package/dist/index.js +180 -0
  50. package/dist/index.mjs +19 -0
  51. package/dist/lib.d.mts +164 -0
  52. package/dist/lib.d.ts +164 -0
  53. package/dist/lib.js +144 -0
  54. package/dist/lib.mjs +3 -0
  55. package/dist/nodes.d.mts +128 -0
  56. package/dist/nodes.d.ts +128 -0
  57. package/dist/nodes.js +151 -0
  58. package/dist/nodes.mjs +14 -0
  59. package/dist/panels.d.mts +22 -0
  60. package/dist/panels.d.ts +22 -0
  61. package/dist/panels.js +21 -0
  62. package/dist/panels.mjs +4 -0
  63. package/dist/promptLibraryStore-BZnfmEkc.d.ts +464 -0
  64. package/dist/promptLibraryStore-zqb59nsu.d.mts +464 -0
  65. package/dist/provider.d.mts +29 -0
  66. package/dist/provider.d.ts +29 -0
  67. package/dist/provider.js +17 -0
  68. package/dist/provider.mjs +4 -0
  69. package/dist/stores.d.mts +96 -0
  70. package/dist/stores.d.ts +96 -0
  71. package/dist/stores.js +113 -0
  72. package/dist/stores.mjs +43 -0
  73. package/dist/toolbar.d.mts +73 -0
  74. package/dist/toolbar.d.ts +73 -0
  75. package/dist/toolbar.js +34 -0
  76. package/dist/toolbar.mjs +5 -0
  77. package/dist/types-ipAnBzAJ.d.mts +46 -0
  78. package/dist/types-ipAnBzAJ.d.ts +46 -0
  79. package/dist/ui.d.mts +67 -0
  80. package/dist/ui.d.ts +67 -0
  81. package/dist/ui.js +84 -0
  82. package/dist/ui.mjs +3 -0
  83. package/dist/workflowStore-4EGKJLYK.mjs +3 -0
  84. package/dist/workflowStore-KM32FDL7.js +12 -0
  85. package/package.json +117 -0
  86. package/src/styles/workflow-ui.css +186 -0
@@ -0,0 +1,1542 @@
1
+ import { nodeTypes } from './chunk-XV5Z5XYR.mjs';
2
+ import { getMediaFromNode } from './chunk-E323WAZG.mjs';
3
+ import { useCanvasKeyboardShortcuts } from './chunk-UQQUWGHW.mjs';
4
+ import { Button } from './chunk-7SKSRSS7.mjs';
5
+ import { usePromptEditorStore } from './chunk-CV4M7CNU.mjs';
6
+ import { useUIStore, useSettingsStore, useExecutionStore } from './chunk-CSUBLSKZ.mjs';
7
+ import { require_dist, useWorkflowStore } from './chunk-BJ3R5R32.mjs';
8
+ import { __toESM } from './chunk-AC6TWLRT.mjs';
9
+ import { useNodes, ViewportPortal, useViewport, useStore, getBezierPath, BaseEdge, useReactFlow, ReactFlow, SelectionMode, ConnectionMode, Background, BackgroundVariant, Controls, MiniMap } from '@xyflow/react';
10
+ import { Pause, Palette, Lock, Unlock, Trash2, Search, X, Keyboard, PanelLeft, Download, ChevronLeft, ChevronRight, ZoomOut, ZoomIn } from 'lucide-react';
11
+ import { memo, useMemo, useState, useCallback, useEffect, useRef } from 'react';
12
+ import '@xyflow/react/dist/style.css';
13
+ import { clsx } from 'clsx';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+ import Image from 'next/image';
16
+
17
+ // src/canvas/WorkflowCanvas.tsx
18
+ var import_types3 = __toESM(require_dist());
19
+
20
+ // src/types/groups.ts
21
+ var GROUP_COLORS = {
22
+ purple: {
23
+ bg: "bg-purple-500/10",
24
+ border: "border-purple-500/30",
25
+ text: "text-purple-400"
26
+ },
27
+ blue: {
28
+ bg: "bg-blue-500/10",
29
+ border: "border-blue-500/30",
30
+ text: "text-blue-400"
31
+ },
32
+ green: {
33
+ bg: "bg-green-500/10",
34
+ border: "border-green-500/30",
35
+ text: "text-green-400"
36
+ },
37
+ yellow: {
38
+ bg: "bg-yellow-500/10",
39
+ border: "border-yellow-500/30",
40
+ text: "text-yellow-400"
41
+ },
42
+ orange: {
43
+ bg: "bg-orange-500/10",
44
+ border: "border-orange-500/30",
45
+ text: "text-orange-400"
46
+ },
47
+ red: {
48
+ bg: "bg-red-500/10",
49
+ border: "border-red-500/30",
50
+ text: "text-red-400"
51
+ },
52
+ pink: {
53
+ bg: "bg-pink-500/10",
54
+ border: "border-pink-500/30",
55
+ text: "text-pink-400"
56
+ },
57
+ gray: {
58
+ bg: "bg-gray-500/10",
59
+ border: "border-gray-500/30",
60
+ text: "text-gray-400"
61
+ }
62
+ };
63
+ var DEFAULT_GROUP_COLORS = [
64
+ "purple",
65
+ "blue",
66
+ "green",
67
+ "yellow",
68
+ "orange",
69
+ "red",
70
+ "pink",
71
+ "gray"
72
+ ];
73
+ var HEADER_HEIGHT = 32;
74
+ function calculateGroupBounds(nodeIds, nodeMap) {
75
+ if (nodeIds.length === 0) return null;
76
+ let minX = Infinity;
77
+ let minY = Infinity;
78
+ let maxX = -Infinity;
79
+ let maxY = -Infinity;
80
+ let foundAny = false;
81
+ for (const nodeId of nodeIds) {
82
+ const node = nodeMap.get(nodeId);
83
+ if (!node) continue;
84
+ foundAny = true;
85
+ const width = node.measured?.width ?? 200;
86
+ const height = node.measured?.height ?? 100;
87
+ minX = Math.min(minX, node.position.x);
88
+ minY = Math.min(minY, node.position.y);
89
+ maxX = Math.max(maxX, node.position.x + width);
90
+ maxY = Math.max(maxY, node.position.y + height);
91
+ }
92
+ if (!foundAny) return null;
93
+ const padding = 24;
94
+ return {
95
+ x: minX - padding,
96
+ y: minY - padding - HEADER_HEIGHT,
97
+ width: maxX - minX + padding * 2,
98
+ height: maxY - minY + padding * 2 + HEADER_HEIGHT
99
+ };
100
+ }
101
+ function GroupBackground({ group, bounds }) {
102
+ const colors = GROUP_COLORS[group.color ?? "purple"];
103
+ return /* @__PURE__ */ jsx(
104
+ "div",
105
+ {
106
+ className: clsx(
107
+ "absolute rounded-lg border-2 border-dashed",
108
+ colors.bg,
109
+ colors.border,
110
+ group.isLocked && "opacity-60"
111
+ ),
112
+ style: {
113
+ left: bounds.x,
114
+ top: bounds.y,
115
+ width: bounds.width,
116
+ height: bounds.height
117
+ }
118
+ }
119
+ );
120
+ }
121
+ function GroupControls({ group, bounds, nodeMap, zoom }) {
122
+ const { setNodes } = useReactFlow();
123
+ const { toggleGroupLock, deleteGroup, setGroupColor, setDirty, renameGroup } = useWorkflowStore();
124
+ const [showColorPicker, setShowColorPicker] = useState(false);
125
+ const [isEditing, setIsEditing] = useState(false);
126
+ const [editName, setEditName] = useState(group.name);
127
+ const inputRef = useRef(null);
128
+ const colorPickerRef = useRef(null);
129
+ const [isDragging, setIsDragging] = useState(false);
130
+ const dragStartRef = useRef(null);
131
+ const nodeStartPositionsRef = useRef(/* @__PURE__ */ new Map());
132
+ useEffect(() => {
133
+ if (!isEditing) {
134
+ setEditName(group.name);
135
+ }
136
+ }, [group.name, isEditing]);
137
+ useEffect(() => {
138
+ if (isEditing && inputRef.current) {
139
+ inputRef.current.focus();
140
+ inputRef.current.select();
141
+ }
142
+ }, [isEditing]);
143
+ useEffect(() => {
144
+ const handleClickOutside = (e) => {
145
+ if (colorPickerRef.current && !colorPickerRef.current.contains(e.target)) {
146
+ setShowColorPicker(false);
147
+ }
148
+ };
149
+ if (showColorPicker) {
150
+ document.addEventListener("mousedown", handleClickOutside);
151
+ }
152
+ return () => document.removeEventListener("mousedown", handleClickOutside);
153
+ }, [showColorPicker]);
154
+ const handleNameSubmit = useCallback(() => {
155
+ if (editName.trim() && editName !== group.name) {
156
+ renameGroup(group.id, editName.trim());
157
+ } else {
158
+ setEditName(group.name);
159
+ }
160
+ setIsEditing(false);
161
+ }, [editName, group.name, group.id, renameGroup]);
162
+ const handleKeyDown = useCallback(
163
+ (e) => {
164
+ if (e.key === "Enter") {
165
+ handleNameSubmit();
166
+ } else if (e.key === "Escape") {
167
+ setEditName(group.name);
168
+ setIsEditing(false);
169
+ }
170
+ },
171
+ [handleNameSubmit, group.name]
172
+ );
173
+ const handleMouseDown = useCallback(
174
+ (e) => {
175
+ if (group.isLocked) return;
176
+ if (e.target.closest("button") || e.target.closest("input")) {
177
+ return;
178
+ }
179
+ e.preventDefault();
180
+ e.stopPropagation();
181
+ setIsDragging(true);
182
+ dragStartRef.current = { x: e.clientX, y: e.clientY };
183
+ const positions = /* @__PURE__ */ new Map();
184
+ for (const nodeId of group.nodeIds) {
185
+ const node = nodeMap.get(nodeId);
186
+ if (node) {
187
+ positions.set(nodeId, { x: node.position.x, y: node.position.y });
188
+ }
189
+ }
190
+ nodeStartPositionsRef.current = positions;
191
+ },
192
+ [group.isLocked, group.nodeIds, nodeMap]
193
+ );
194
+ useEffect(() => {
195
+ if (!isDragging) return;
196
+ const handleMouseMove = (e) => {
197
+ if (!dragStartRef.current) return;
198
+ const deltaX = (e.clientX - dragStartRef.current.x) / zoom;
199
+ const deltaY = (e.clientY - dragStartRef.current.y) / zoom;
200
+ setNodes(
201
+ (currentNodes) => currentNodes.map((node) => {
202
+ const startPos = nodeStartPositionsRef.current.get(node.id);
203
+ if (!startPos) return node;
204
+ return {
205
+ ...node,
206
+ position: {
207
+ x: startPos.x + deltaX,
208
+ y: startPos.y + deltaY
209
+ }
210
+ };
211
+ })
212
+ );
213
+ };
214
+ const handleMouseUp = () => {
215
+ setDirty(true);
216
+ setIsDragging(false);
217
+ dragStartRef.current = null;
218
+ };
219
+ window.addEventListener("mousemove", handleMouseMove);
220
+ window.addEventListener("mouseup", handleMouseUp);
221
+ return () => {
222
+ window.removeEventListener("mousemove", handleMouseMove);
223
+ window.removeEventListener("mouseup", handleMouseUp);
224
+ };
225
+ }, [isDragging, zoom, setNodes, setDirty]);
226
+ const colors = GROUP_COLORS[group.color ?? "purple"];
227
+ const handleColorSelect = (color) => {
228
+ setGroupColor(group.id, color);
229
+ setShowColorPicker(false);
230
+ };
231
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
232
+ /* @__PURE__ */ jsxs(
233
+ "div",
234
+ {
235
+ onMouseDown: handleMouseDown,
236
+ className: clsx(
237
+ "absolute flex items-center justify-between px-3 rounded-t-lg select-none pointer-events-auto",
238
+ colors.bg,
239
+ colors.border,
240
+ "border-2 border-b-0 border-dashed",
241
+ !group.isLocked && "cursor-grab",
242
+ isDragging && "cursor-grabbing",
243
+ group.isLocked && "opacity-60"
244
+ ),
245
+ style: {
246
+ left: bounds.x,
247
+ top: bounds.y,
248
+ width: bounds.width,
249
+ height: HEADER_HEIGHT
250
+ },
251
+ children: [
252
+ isEditing ? /* @__PURE__ */ jsx(
253
+ "input",
254
+ {
255
+ ref: inputRef,
256
+ type: "text",
257
+ value: editName,
258
+ onChange: (e) => setEditName(e.target.value),
259
+ onBlur: handleNameSubmit,
260
+ onKeyDown: handleKeyDown,
261
+ className: clsx(
262
+ "flex-1 bg-transparent border-none outline-none text-sm font-medium px-0 py-0",
263
+ colors.text
264
+ ),
265
+ style: { minWidth: 0 }
266
+ }
267
+ ) : /* @__PURE__ */ jsx(
268
+ "span",
269
+ {
270
+ className: clsx("font-medium truncate cursor-text", colors.text),
271
+ style: { fontSize: 14 },
272
+ onClick: () => setIsEditing(true),
273
+ children: group.name
274
+ }
275
+ ),
276
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
277
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: colorPickerRef, children: [
278
+ /* @__PURE__ */ jsx(
279
+ "button",
280
+ {
281
+ onClick: (e) => {
282
+ e.stopPropagation();
283
+ setShowColorPicker(!showColorPicker);
284
+ },
285
+ className: clsx("p-1 rounded hover:bg-white/10 transition-colors", colors.text),
286
+ title: "Change group color",
287
+ children: /* @__PURE__ */ jsx(Palette, { className: "w-4 h-4" })
288
+ }
289
+ ),
290
+ showColorPicker && /* @__PURE__ */ jsx("div", { className: "absolute top-8 right-0 z-50 bg-card border border-border rounded-lg shadow-lg p-2 flex gap-1 flex-wrap w-[120px]", children: DEFAULT_GROUP_COLORS.map((color) => /* @__PURE__ */ jsx(
291
+ "button",
292
+ {
293
+ onClick: (e) => {
294
+ e.stopPropagation();
295
+ handleColorSelect(color);
296
+ },
297
+ className: clsx(
298
+ "w-6 h-6 rounded-md border-2 transition-transform hover:scale-110",
299
+ GROUP_COLORS[color].bg,
300
+ color === group.color ? "border-white" : "border-transparent"
301
+ ),
302
+ title: color
303
+ },
304
+ color
305
+ )) })
306
+ ] }),
307
+ /* @__PURE__ */ jsx(
308
+ "button",
309
+ {
310
+ onClick: (e) => {
311
+ e.stopPropagation();
312
+ toggleGroupLock(group.id);
313
+ },
314
+ className: clsx("p-1 rounded hover:bg-white/10 transition-colors", colors.text),
315
+ title: group.isLocked ? "Unlock group" : "Lock group",
316
+ children: group.isLocked ? /* @__PURE__ */ jsx(Lock, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Unlock, { className: "w-4 h-4" })
317
+ }
318
+ ),
319
+ /* @__PURE__ */ jsx(
320
+ "button",
321
+ {
322
+ onClick: (e) => {
323
+ e.stopPropagation();
324
+ deleteGroup(group.id);
325
+ },
326
+ className: clsx("p-1 rounded hover:bg-white/10 transition-colors", colors.text),
327
+ title: "Delete group",
328
+ children: /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" })
329
+ }
330
+ )
331
+ ] })
332
+ ]
333
+ }
334
+ ),
335
+ group.isLocked && /* @__PURE__ */ jsx(
336
+ "div",
337
+ {
338
+ className: clsx(
339
+ "absolute px-2 py-0.5 rounded text-xs font-medium pointer-events-none",
340
+ colors.bg,
341
+ colors.text
342
+ ),
343
+ style: {
344
+ left: bounds.x + 12,
345
+ top: bounds.y + HEADER_HEIGHT + 8
346
+ },
347
+ children: "LOCKED"
348
+ }
349
+ )
350
+ ] });
351
+ }
352
+ function GroupBackgroundsPortalComponent() {
353
+ const { groups } = useWorkflowStore();
354
+ const nodes = useNodes();
355
+ const nodeMap = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);
356
+ const groupBounds = useMemo(() => {
357
+ const result = /* @__PURE__ */ new Map();
358
+ for (const group of groups) {
359
+ const bounds = calculateGroupBounds(group.nodeIds, nodeMap);
360
+ if (bounds) {
361
+ result.set(group.id, bounds);
362
+ }
363
+ }
364
+ return result;
365
+ }, [groups, nodeMap]);
366
+ if (groups.length === 0) return null;
367
+ return /* @__PURE__ */ jsx(ViewportPortal, { children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 0, left: 0, zIndex: -1, pointerEvents: "none" }, children: groups.map((group) => {
368
+ const bounds = groupBounds.get(group.id);
369
+ if (!bounds) return null;
370
+ return /* @__PURE__ */ jsx(GroupBackground, { group, bounds }, group.id);
371
+ }) }) });
372
+ }
373
+ function GroupControlsOverlayComponent() {
374
+ const { groups } = useWorkflowStore();
375
+ const nodes = useNodes();
376
+ const { zoom } = useViewport();
377
+ const nodeMap = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);
378
+ const groupBounds = useMemo(() => {
379
+ const result = /* @__PURE__ */ new Map();
380
+ for (const group of groups) {
381
+ const bounds = calculateGroupBounds(group.nodeIds, nodeMap);
382
+ if (bounds) {
383
+ result.set(group.id, bounds);
384
+ }
385
+ }
386
+ return result;
387
+ }, [groups, nodeMap]);
388
+ if (groups.length === 0) return null;
389
+ return /* @__PURE__ */ jsx(ViewportPortal, { children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3, pointerEvents: "none" }, children: groups.map((group) => {
390
+ const bounds = groupBounds.get(group.id);
391
+ if (!bounds) return null;
392
+ return /* @__PURE__ */ jsx(
393
+ GroupControls,
394
+ {
395
+ group,
396
+ bounds,
397
+ nodeMap,
398
+ zoom
399
+ },
400
+ group.id
401
+ );
402
+ }) }) });
403
+ }
404
+ var GroupBackgroundsPortal = memo(GroupBackgroundsPortalComponent);
405
+ var GroupControlsOverlay = memo(GroupControlsOverlayComponent);
406
+ function GroupOverlayComponent() {
407
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
408
+ /* @__PURE__ */ jsx(GroupBackgroundsPortal, {}),
409
+ /* @__PURE__ */ jsx(GroupControlsOverlay, {})
410
+ ] });
411
+ }
412
+ var GroupOverlay = memo(GroupOverlayComponent);
413
+ var SNAP_THRESHOLD = 5;
414
+ function HelperLinesComponent({ draggingNodeId }) {
415
+ const [lines, setLines] = useState([]);
416
+ const nodes = useWorkflowStore((state) => state.nodes);
417
+ const transform = useStore((state) => state.transform);
418
+ const calculateHelperLines = useCallback(
419
+ (draggingId) => {
420
+ const draggingNode = nodes.find((n) => n.id === draggingId);
421
+ if (!draggingNode) {
422
+ setLines([]);
423
+ return;
424
+ }
425
+ const otherNodes = nodes.filter((n) => n.id !== draggingId && !n.selected);
426
+ if (otherNodes.length === 0) {
427
+ setLines([]);
428
+ return;
429
+ }
430
+ const dragWidth = draggingNode.measured?.width ?? draggingNode.width ?? 220;
431
+ const dragHeight = draggingNode.measured?.height ?? draggingNode.height ?? 100;
432
+ const dragLeft = draggingNode.position.x;
433
+ const dragRight = dragLeft + dragWidth;
434
+ const dragTop = draggingNode.position.y;
435
+ const dragBottom = dragTop + dragHeight;
436
+ const dragCenterX = dragLeft + dragWidth / 2;
437
+ const dragCenterY = dragTop + dragHeight / 2;
438
+ const newLines = [];
439
+ for (const node of otherNodes) {
440
+ const nodeWidth = node.measured?.width ?? node.width ?? 220;
441
+ const nodeHeight = node.measured?.height ?? node.height ?? 100;
442
+ const nodeLeft = node.position.x;
443
+ const nodeRight = nodeLeft + nodeWidth;
444
+ const nodeTop = node.position.y;
445
+ const nodeBottom = nodeTop + nodeHeight;
446
+ const nodeCenterX = nodeLeft + nodeWidth / 2;
447
+ const nodeCenterY = nodeTop + nodeHeight / 2;
448
+ const verticalChecks = [
449
+ { dragPos: dragLeft, nodePos: nodeLeft, label: "left-left" },
450
+ { dragPos: dragLeft, nodePos: nodeRight, label: "left-right" },
451
+ { dragPos: dragRight, nodePos: nodeLeft, label: "right-left" },
452
+ { dragPos: dragRight, nodePos: nodeRight, label: "right-right" },
453
+ { dragPos: dragCenterX, nodePos: nodeCenterX, label: "center-center-x" }
454
+ ];
455
+ for (const check of verticalChecks) {
456
+ if (Math.abs(check.dragPos - check.nodePos) <= SNAP_THRESHOLD) {
457
+ newLines.push({
458
+ type: "vertical",
459
+ position: check.nodePos,
460
+ start: Math.min(dragTop, nodeTop) - 20,
461
+ end: Math.max(dragBottom, nodeBottom) + 20
462
+ });
463
+ }
464
+ }
465
+ const horizontalChecks = [
466
+ { dragPos: dragTop, nodePos: nodeTop, label: "top-top" },
467
+ { dragPos: dragTop, nodePos: nodeBottom, label: "top-bottom" },
468
+ { dragPos: dragBottom, nodePos: nodeTop, label: "bottom-top" },
469
+ { dragPos: dragBottom, nodePos: nodeBottom, label: "bottom-bottom" },
470
+ { dragPos: dragCenterY, nodePos: nodeCenterY, label: "center-center-y" }
471
+ ];
472
+ for (const check of horizontalChecks) {
473
+ if (Math.abs(check.dragPos - check.nodePos) <= SNAP_THRESHOLD) {
474
+ newLines.push({
475
+ type: "horizontal",
476
+ position: check.nodePos,
477
+ start: Math.min(dragLeft, nodeLeft) - 20,
478
+ end: Math.max(dragRight, nodeRight) + 20
479
+ });
480
+ }
481
+ }
482
+ }
483
+ const uniqueLines = [];
484
+ const seenPositions = /* @__PURE__ */ new Set();
485
+ for (const line of newLines) {
486
+ const key = `${line.type}-${Math.round(line.position)}`;
487
+ if (!seenPositions.has(key)) {
488
+ seenPositions.add(key);
489
+ const existingLine = uniqueLines.find(
490
+ (l) => l.type === line.type && Math.abs(l.position - line.position) < 1
491
+ );
492
+ if (existingLine) {
493
+ existingLine.start = Math.min(existingLine.start, line.start);
494
+ existingLine.end = Math.max(existingLine.end, line.end);
495
+ } else {
496
+ uniqueLines.push(line);
497
+ }
498
+ }
499
+ }
500
+ setLines(uniqueLines);
501
+ },
502
+ [nodes]
503
+ );
504
+ useEffect(() => {
505
+ if (draggingNodeId) {
506
+ calculateHelperLines(draggingNodeId);
507
+ } else {
508
+ setLines([]);
509
+ }
510
+ }, [draggingNodeId, calculateHelperLines]);
511
+ if (lines.length === 0) return null;
512
+ const [tx, ty, zoom] = transform;
513
+ return /* @__PURE__ */ jsx(
514
+ "svg",
515
+ {
516
+ className: "absolute inset-0 pointer-events-none z-[1000]",
517
+ style: { width: "100%", height: "100%", overflow: "visible" },
518
+ children: lines.map((line, index) => {
519
+ if (line.type === "vertical") {
520
+ const x = line.position * zoom + tx;
521
+ const y1 = line.start * zoom + ty;
522
+ const y2 = line.end * zoom + ty;
523
+ return /* @__PURE__ */ jsx(
524
+ "line",
525
+ {
526
+ x1: x,
527
+ y1,
528
+ x2: x,
529
+ y2,
530
+ stroke: "#3b82f6",
531
+ strokeWidth: 1,
532
+ strokeDasharray: "4 2"
533
+ },
534
+ `v-${index}`
535
+ );
536
+ } else {
537
+ const y = line.position * zoom + ty;
538
+ const x1 = line.start * zoom + tx;
539
+ const x2 = line.end * zoom + tx;
540
+ return /* @__PURE__ */ jsx(
541
+ "line",
542
+ {
543
+ x1,
544
+ y1: y,
545
+ x2,
546
+ y2: y,
547
+ stroke: "#3b82f6",
548
+ strokeWidth: 1,
549
+ strokeDasharray: "4 2"
550
+ },
551
+ `h-${index}`
552
+ );
553
+ }
554
+ })
555
+ }
556
+ );
557
+ }
558
+ var HelperLines = memo(HelperLinesComponent);
559
+
560
+ // src/canvas/NodeSearch.tsx
561
+ var import_types = __toESM(require_dist());
562
+ function NodeSearch() {
563
+ const { activeModal, closeModal } = useUIStore();
564
+ const { nodes, setSelectedNodeIds } = useWorkflowStore();
565
+ const reactFlow = useReactFlow();
566
+ const [search, setSearch] = useState("");
567
+ const [selectedIndex, setSelectedIndex] = useState(0);
568
+ const listRef = useRef(null);
569
+ const backdropRef = useRef(null);
570
+ const inputRef = useRef(null);
571
+ const isOpen = activeModal === "nodeSearch";
572
+ useEffect(() => {
573
+ if (isOpen && inputRef.current) {
574
+ inputRef.current.focus();
575
+ }
576
+ }, [isOpen]);
577
+ const filteredNodes = useMemo(() => {
578
+ if (!search.trim()) return nodes;
579
+ const query = search.toLowerCase();
580
+ return nodes.filter((node) => {
581
+ const label = (node.data.label || "").toLowerCase();
582
+ const type = (node.type || "").toLowerCase();
583
+ const comment = (node.data.comment || "").toLowerCase();
584
+ const nodeDefLabel = import_types.NODE_DEFINITIONS[node.type]?.label?.toLowerCase() || "";
585
+ return label.includes(query) || type.includes(query) || comment.includes(query) || nodeDefLabel.includes(query);
586
+ });
587
+ }, [nodes, search]);
588
+ useEffect(() => {
589
+ setSelectedIndex(0);
590
+ }, []);
591
+ useEffect(() => {
592
+ if (listRef.current) {
593
+ const selectedItem = listRef.current.children[selectedIndex];
594
+ selectedItem?.scrollIntoView({ block: "nearest" });
595
+ }
596
+ }, [selectedIndex]);
597
+ const handleSelectNode = useCallback(
598
+ (node) => {
599
+ setSelectedNodeIds([node.id]);
600
+ reactFlow.fitView({
601
+ nodes: [node],
602
+ padding: 0.5,
603
+ duration: 300
604
+ });
605
+ closeModal();
606
+ setSearch("");
607
+ },
608
+ [setSelectedNodeIds, reactFlow, closeModal]
609
+ );
610
+ const handleKeyDown = useCallback(
611
+ (e) => {
612
+ if (e.key === "ArrowDown") {
613
+ e.preventDefault();
614
+ setSelectedIndex((prev) => Math.min(prev + 1, filteredNodes.length - 1));
615
+ } else if (e.key === "ArrowUp") {
616
+ e.preventDefault();
617
+ setSelectedIndex((prev) => Math.max(prev - 1, 0));
618
+ } else if (e.key === "Enter" && filteredNodes[selectedIndex]) {
619
+ e.preventDefault();
620
+ handleSelectNode(filteredNodes[selectedIndex]);
621
+ } else if (e.key === "Escape") {
622
+ e.preventDefault();
623
+ handleClose();
624
+ }
625
+ },
626
+ [filteredNodes, selectedIndex, handleSelectNode]
627
+ );
628
+ const handleClose = () => {
629
+ closeModal();
630
+ setSearch("");
631
+ setSelectedIndex(0);
632
+ };
633
+ const handleBackdropClick = (e) => {
634
+ if (e.target === backdropRef.current) {
635
+ handleClose();
636
+ }
637
+ };
638
+ if (!isOpen) return null;
639
+ return /* @__PURE__ */ jsx(
640
+ "div",
641
+ {
642
+ ref: backdropRef,
643
+ onClick: handleBackdropClick,
644
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[20vh]",
645
+ style: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
646
+ children: /* @__PURE__ */ jsxs(
647
+ "div",
648
+ {
649
+ className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-full max-w-lg",
650
+ role: "dialog",
651
+ "aria-label": "Find Node",
652
+ children: [
653
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[var(--border)]", children: [
654
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
655
+ /* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-[var(--muted-foreground)]" }),
656
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Find Node" })
657
+ ] }),
658
+ /* @__PURE__ */ jsx(
659
+ "button",
660
+ {
661
+ onClick: handleClose,
662
+ className: "p-1 rounded hover:bg-[var(--secondary)] transition-colors",
663
+ children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
664
+ }
665
+ )
666
+ ] }),
667
+ /* @__PURE__ */ jsxs("div", { className: "p-4", onKeyDown: handleKeyDown, children: [
668
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-3", children: [
669
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
670
+ /* @__PURE__ */ jsx(
671
+ "input",
672
+ {
673
+ ref: inputRef,
674
+ type: "text",
675
+ placeholder: "Search nodes by name, type, or comment...",
676
+ value: search,
677
+ onChange: (e) => setSearch(e.target.value),
678
+ className: "w-full pl-9 pr-3 py-2 text-sm bg-[var(--secondary)] border border-[var(--border)] rounded-md outline-none focus:ring-1 focus:ring-[var(--ring)]",
679
+ autoFocus: true
680
+ }
681
+ )
682
+ ] }),
683
+ /* @__PURE__ */ jsx("div", { ref: listRef, className: "max-h-[300px] overflow-y-auto space-y-1", children: filteredNodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center text-muted-foreground py-8", children: search ? `No nodes found for "${search}"` : "No nodes in workflow" }) : filteredNodes.map((node, index) => {
684
+ const nodeDef = import_types.NODE_DEFINITIONS[node.type];
685
+ const comment = node.data.comment;
686
+ return /* @__PURE__ */ jsxs(
687
+ "button",
688
+ {
689
+ onClick: () => handleSelectNode(node),
690
+ className: `w-full flex items-center gap-3 p-2 rounded text-left transition-colors ${index === selectedIndex ? "bg-primary/10 border border-primary/30" : "hover:bg-secondary/50 border border-transparent"}`,
691
+ children: [
692
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded bg-secondary flex items-center justify-center text-xs font-medium", children: nodeDef?.label?.charAt(0) || "?" }),
693
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
694
+ /* @__PURE__ */ jsx("div", { className: "font-medium truncate text-sm", children: node.data.label }),
695
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground truncate", children: [
696
+ nodeDef?.label || node.type,
697
+ comment && ` \xB7 ${comment}`
698
+ ] })
699
+ ] })
700
+ ]
701
+ },
702
+ node.id
703
+ );
704
+ }) }),
705
+ filteredNodes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-border text-xs text-muted-foreground flex gap-4", children: [
706
+ /* @__PURE__ */ jsxs("span", { children: [
707
+ /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "up/down" }),
708
+ " Navigate"
709
+ ] }),
710
+ /* @__PURE__ */ jsxs("span", { children: [
711
+ /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "Enter" }),
712
+ " Select"
713
+ ] }),
714
+ /* @__PURE__ */ jsxs("span", { children: [
715
+ /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "Esc" }),
716
+ " Close"
717
+ ] })
718
+ ] })
719
+ ] })
720
+ ]
721
+ }
722
+ )
723
+ }
724
+ );
725
+ }
726
+ var SHORTCUTS = [
727
+ // Navigation
728
+ { keys: "Scroll", description: "Pan canvas", category: "Navigation" },
729
+ { keys: "Ctrl + Scroll", description: "Zoom in/out", category: "Navigation" },
730
+ { keys: "F", description: "Fit view to selection (or all)", category: "Navigation" },
731
+ { keys: "M", description: "Toggle sidebar", category: "Navigation" },
732
+ // Selection
733
+ { keys: "Click", description: "Select node", category: "Selection" },
734
+ { keys: "Shift + Click", description: "Add to selection", category: "Selection" },
735
+ { keys: "Drag", description: "Marquee select", category: "Selection" },
736
+ { keys: "Ctrl + A", description: "Select all nodes", category: "Selection" },
737
+ { keys: "Ctrl + F", description: "Search nodes", category: "Selection" },
738
+ // Editing
739
+ { keys: "Ctrl + Z", description: "Undo", category: "Editing" },
740
+ { keys: "Ctrl + Shift + Z", description: "Redo", category: "Editing" },
741
+ { keys: "Ctrl + C", description: "Copy", category: "Editing" },
742
+ { keys: "Ctrl + X", description: "Cut", category: "Editing" },
743
+ { keys: "Ctrl + V", description: "Paste", category: "Editing" },
744
+ { keys: "Ctrl + D", description: "Duplicate", category: "Editing" },
745
+ { keys: "Delete / Backspace", description: "Delete selected", category: "Editing" },
746
+ // Nodes
747
+ { keys: "Shift + I", description: "Add Image Gen node", category: "Nodes" },
748
+ { keys: "Shift + V", description: "Add Video Gen node", category: "Nodes" },
749
+ { keys: "Shift + P", description: "Add Prompt node", category: "Nodes" },
750
+ { keys: "Shift + L", description: "Add LLM node", category: "Nodes" },
751
+ // Organization
752
+ { keys: "L", description: "Toggle lock on selected", category: "Organization" },
753
+ { keys: "Ctrl + G", description: "Group selected nodes", category: "Organization" },
754
+ { keys: "Ctrl + Shift + G", description: "Ungroup", category: "Organization" },
755
+ { keys: "Ctrl + Shift + L", description: "Unlock all nodes", category: "Organization" },
756
+ // Help
757
+ { keys: "?", description: "Show this help", category: "Help" }
758
+ ];
759
+ var CATEGORIES = ["Navigation", "Selection", "Editing", "Nodes", "Organization", "Help"];
760
+ function ShortcutHelpModal() {
761
+ const { activeModal, closeModal } = useUIStore();
762
+ const [searchQuery, setSearchQuery] = useState("");
763
+ const backdropRef = useRef(null);
764
+ const inputRef = useRef(null);
765
+ const isOpen = activeModal === "shortcutHelp";
766
+ const filteredShortcuts = useMemo(() => {
767
+ if (!searchQuery.trim()) return SHORTCUTS;
768
+ const query = searchQuery.toLowerCase();
769
+ return SHORTCUTS.filter(
770
+ (shortcut) => shortcut.keys.toLowerCase().includes(query) || shortcut.description.toLowerCase().includes(query) || shortcut.category.toLowerCase().includes(query)
771
+ );
772
+ }, [searchQuery]);
773
+ const groupedShortcuts = useMemo(() => {
774
+ const grouped = {};
775
+ for (const category of CATEGORIES) {
776
+ const items = filteredShortcuts.filter((s) => s.category === category);
777
+ if (items.length > 0) {
778
+ grouped[category] = items;
779
+ }
780
+ }
781
+ return grouped;
782
+ }, [filteredShortcuts]);
783
+ const handleClose = () => {
784
+ closeModal();
785
+ setSearchQuery("");
786
+ };
787
+ const handleBackdropClick = (e) => {
788
+ if (e.target === backdropRef.current) {
789
+ handleClose();
790
+ }
791
+ };
792
+ if (!isOpen) return null;
793
+ return /* @__PURE__ */ jsx(
794
+ "div",
795
+ {
796
+ ref: backdropRef,
797
+ onClick: handleBackdropClick,
798
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
799
+ style: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
800
+ children: /* @__PURE__ */ jsxs(
801
+ "div",
802
+ {
803
+ className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-full max-w-2xl",
804
+ role: "dialog",
805
+ "aria-label": "Keyboard Shortcuts",
806
+ children: [
807
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[var(--border)]", children: [
808
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
809
+ /* @__PURE__ */ jsx(Keyboard, { className: "w-4 h-4 text-[var(--muted-foreground)]" }),
810
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Keyboard Shortcuts" })
811
+ ] }),
812
+ /* @__PURE__ */ jsx(
813
+ "button",
814
+ {
815
+ onClick: handleClose,
816
+ className: "p-1 rounded hover:bg-[var(--secondary)] transition-colors",
817
+ children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
818
+ }
819
+ )
820
+ ] }),
821
+ /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
822
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-4", children: [
823
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
824
+ /* @__PURE__ */ jsx(
825
+ "input",
826
+ {
827
+ ref: inputRef,
828
+ type: "text",
829
+ placeholder: "Search shortcuts...",
830
+ value: searchQuery,
831
+ onChange: (e) => setSearchQuery(e.target.value),
832
+ className: "w-full pl-9 pr-3 py-2 text-sm bg-[var(--secondary)] border border-[var(--border)] rounded-md outline-none focus:ring-1 focus:ring-[var(--ring)]",
833
+ autoFocus: true
834
+ }
835
+ )
836
+ ] }),
837
+ /* @__PURE__ */ jsxs("div", { className: "max-h-[60vh] overflow-y-auto space-y-6 pr-2", children: [
838
+ Object.entries(groupedShortcuts).map(([category, shortcuts]) => /* @__PURE__ */ jsxs("div", { children: [
839
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-muted-foreground mb-2", children: category }),
840
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: shortcuts.map((shortcut) => /* @__PURE__ */ jsxs(
841
+ "div",
842
+ {
843
+ className: "flex items-center justify-between py-1.5 px-2 rounded hover:bg-secondary/50",
844
+ children: [
845
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: shortcut.description }),
846
+ /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-mono bg-secondary rounded border border-border", children: shortcut.keys })
847
+ ]
848
+ },
849
+ shortcut.keys
850
+ )) })
851
+ ] }, category)),
852
+ filteredShortcuts.length === 0 && /* @__PURE__ */ jsxs("div", { className: "text-center text-muted-foreground py-8", children: [
853
+ 'No shortcuts found for "',
854
+ searchQuery,
855
+ '"'
856
+ ] })
857
+ ] })
858
+ ] })
859
+ ]
860
+ }
861
+ )
862
+ }
863
+ );
864
+ }
865
+
866
+ // src/nodes/NodeDetailModal.tsx
867
+ var import_types2 = __toESM(require_dist());
868
+ var PROMPT_NODE_TYPES = ["prompt"];
869
+ function NodeDetailModal() {
870
+ const { activeModal, nodeDetailNodeId, nodeDetailStartIndex, closeNodeDetailModal } = useUIStore();
871
+ const { getNodeById } = useWorkflowStore();
872
+ const { openEditor } = usePromptEditorStore();
873
+ const [zoomLevel, setZoomLevel] = useState(1);
874
+ const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
875
+ const [isPanning, setIsPanning] = useState(false);
876
+ const [panStart, setPanStart] = useState({ x: 0, y: 0 });
877
+ const [currentIndex, setCurrentIndex] = useState(0);
878
+ const node = useMemo(() => {
879
+ if (!nodeDetailNodeId) return null;
880
+ return getNodeById(nodeDetailNodeId);
881
+ }, [nodeDetailNodeId, getNodeById]);
882
+ useEffect(() => {
883
+ if (activeModal !== "nodeDetail" || !node) return;
884
+ if (PROMPT_NODE_TYPES.includes(node.type)) {
885
+ const promptData = node.data;
886
+ closeNodeDetailModal();
887
+ openEditor(node.id, promptData.prompt ?? "");
888
+ }
889
+ }, [activeModal, node, closeNodeDetailModal, openEditor]);
890
+ const mediaInfo = useMemo(() => {
891
+ if (!node) return { url: null, type: null };
892
+ return getMediaFromNode(node.type, node.data);
893
+ }, [node]);
894
+ const nodeDef = useMemo(() => {
895
+ if (!node) return null;
896
+ return import_types2.NODE_DEFINITIONS[node.type];
897
+ }, [node]);
898
+ const imageUrls = mediaInfo.urls ?? [];
899
+ const hasMultipleImages = imageUrls.length > 1;
900
+ const displayUrl = hasMultipleImages ? imageUrls[currentIndex] ?? mediaInfo.url : mediaInfo.url;
901
+ const goToPrevious = useCallback(() => {
902
+ setCurrentIndex((prev) => Math.max(prev - 1, 0));
903
+ setZoomLevel(1);
904
+ setPanOffset({ x: 0, y: 0 });
905
+ }, []);
906
+ const goToNext = useCallback(() => {
907
+ setCurrentIndex((prev) => Math.min(prev + 1, imageUrls.length - 1));
908
+ setZoomLevel(1);
909
+ setPanOffset({ x: 0, y: 0 });
910
+ }, [imageUrls.length]);
911
+ useEffect(() => {
912
+ setZoomLevel(1);
913
+ setPanOffset({ x: 0, y: 0 });
914
+ setCurrentIndex(nodeDetailStartIndex);
915
+ }, [nodeDetailNodeId, nodeDetailStartIndex]);
916
+ useEffect(() => {
917
+ const handleKeyDown = (e) => {
918
+ if (activeModal !== "nodeDetail") return;
919
+ if (e.key === "Escape") {
920
+ closeNodeDetailModal();
921
+ }
922
+ if (e.key === "+" || e.key === "=") {
923
+ setZoomLevel((prev) => Math.min(prev + 0.25, 4));
924
+ }
925
+ if (e.key === "-") {
926
+ setZoomLevel((prev) => Math.max(prev - 0.25, 0.25));
927
+ }
928
+ if (e.key === "0") {
929
+ setZoomLevel(1);
930
+ setPanOffset({ x: 0, y: 0 });
931
+ }
932
+ if (e.key === "ArrowLeft") {
933
+ goToPrevious();
934
+ }
935
+ if (e.key === "ArrowRight") {
936
+ goToNext();
937
+ }
938
+ };
939
+ window.addEventListener("keydown", handleKeyDown);
940
+ return () => window.removeEventListener("keydown", handleKeyDown);
941
+ }, [activeModal, closeNodeDetailModal, goToPrevious, goToNext]);
942
+ const handleMouseDown = useCallback(
943
+ (e) => {
944
+ if (zoomLevel > 1) {
945
+ setIsPanning(true);
946
+ setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
947
+ }
948
+ },
949
+ [zoomLevel, panOffset]
950
+ );
951
+ const handleMouseMove = useCallback(
952
+ (e) => {
953
+ if (isPanning) {
954
+ setPanOffset({
955
+ x: e.clientX - panStart.x,
956
+ y: e.clientY - panStart.y
957
+ });
958
+ }
959
+ },
960
+ [isPanning, panStart]
961
+ );
962
+ const handleMouseUp = useCallback(() => {
963
+ setIsPanning(false);
964
+ }, []);
965
+ const handleDownload = useCallback(() => {
966
+ const url = displayUrl ?? mediaInfo.url;
967
+ if (!url) return;
968
+ const link = document.createElement("a");
969
+ link.href = url;
970
+ const suffix = hasMultipleImages ? `_${currentIndex + 1}` : "";
971
+ link.download = `${node?.data.label || "output"}${suffix}.${mediaInfo.type === "video" ? "mp4" : "png"}`;
972
+ document.body.appendChild(link);
973
+ link.click();
974
+ document.body.removeChild(link);
975
+ }, [displayUrl, mediaInfo, node, hasMultipleImages, currentIndex]);
976
+ if (activeModal !== "nodeDetail" || !node || !nodeDef) {
977
+ return null;
978
+ }
979
+ if (PROMPT_NODE_TYPES.includes(node.type)) {
980
+ return null;
981
+ }
982
+ const nodeData = node.data;
983
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
984
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 bg-black/80", onClick: closeNodeDetailModal }),
985
+ /* @__PURE__ */ jsxs("div", { className: "fixed inset-4 z-50 flex flex-col bg-card rounded-lg border border-border shadow-xl overflow-hidden", children: [
986
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [
987
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
988
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium text-foreground", children: nodeData.label }),
989
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground bg-secondary px-2 py-0.5 rounded", children: nodeDef.label })
990
+ ] }),
991
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
992
+ displayUrl && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleDownload, children: [
993
+ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
994
+ "Download"
995
+ ] }),
996
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: closeNodeDetailModal, children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" }) })
997
+ ] })
998
+ ] }),
999
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxs(
1000
+ "div",
1001
+ {
1002
+ className: "relative w-full h-full flex items-center justify-center bg-background overflow-hidden",
1003
+ onMouseDown: handleMouseDown,
1004
+ onMouseMove: handleMouseMove,
1005
+ onMouseUp: handleMouseUp,
1006
+ onMouseLeave: handleMouseUp,
1007
+ style: { cursor: zoomLevel > 1 ? isPanning ? "grabbing" : "grab" : "default" },
1008
+ children: [
1009
+ displayUrl ? /* @__PURE__ */ jsxs(
1010
+ "div",
1011
+ {
1012
+ className: "transition-transform duration-100",
1013
+ style: {
1014
+ transform: `scale(${zoomLevel}) translate(${panOffset.x / zoomLevel}px, ${panOffset.y / zoomLevel}px)`
1015
+ },
1016
+ children: [
1017
+ mediaInfo.type === "image" && /* @__PURE__ */ jsx(
1018
+ Image,
1019
+ {
1020
+ src: displayUrl,
1021
+ alt: nodeData.label,
1022
+ width: 800,
1023
+ height: 600,
1024
+ className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] object-contain rounded-lg",
1025
+ unoptimized: true
1026
+ }
1027
+ ),
1028
+ mediaInfo.type === "video" && /* @__PURE__ */ jsx(
1029
+ "video",
1030
+ {
1031
+ src: displayUrl,
1032
+ controls: true,
1033
+ autoPlay: true,
1034
+ loop: true,
1035
+ className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] rounded-lg"
1036
+ }
1037
+ )
1038
+ ]
1039
+ }
1040
+ ) : /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-center", children: [
1041
+ /* @__PURE__ */ jsx("p", { className: "text-lg", children: "No preview available" }),
1042
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-2", children: "Generate content to see the preview" })
1043
+ ] }),
1044
+ hasMultipleImages && currentIndex > 0 && /* @__PURE__ */ jsx(
1045
+ Button,
1046
+ {
1047
+ variant: "ghost",
1048
+ size: "icon-sm",
1049
+ onClick: goToPrevious,
1050
+ className: "absolute left-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
1051
+ title: "Previous image (\u2190)",
1052
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-5 h-5" })
1053
+ }
1054
+ ),
1055
+ hasMultipleImages && currentIndex < imageUrls.length - 1 && /* @__PURE__ */ jsx(
1056
+ Button,
1057
+ {
1058
+ variant: "ghost",
1059
+ size: "icon-sm",
1060
+ onClick: goToNext,
1061
+ className: "absolute right-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
1062
+ title: "Next image (\u2192)",
1063
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5" })
1064
+ }
1065
+ ),
1066
+ hasMultipleImages && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 bg-card/80 border border-border rounded-full px-3 py-1 text-xs text-muted-foreground shadow-md", children: [
1067
+ currentIndex + 1,
1068
+ " / ",
1069
+ imageUrls.length
1070
+ ] }),
1071
+ displayUrl && mediaInfo.type === "image" && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 right-4 flex items-center gap-2 bg-card border border-border rounded-lg p-1", children: [
1072
+ /* @__PURE__ */ jsx(
1073
+ Button,
1074
+ {
1075
+ variant: "ghost",
1076
+ size: "icon-sm",
1077
+ onClick: () => setZoomLevel((prev) => Math.max(prev - 0.25, 0.25)),
1078
+ title: "Zoom out (-)",
1079
+ children: /* @__PURE__ */ jsx(ZoomOut, { className: "w-4 h-4" })
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground w-12 text-center", children: [
1083
+ Math.round(zoomLevel * 100),
1084
+ "%"
1085
+ ] }),
1086
+ /* @__PURE__ */ jsx(
1087
+ Button,
1088
+ {
1089
+ variant: "ghost",
1090
+ size: "icon-sm",
1091
+ onClick: () => setZoomLevel((prev) => Math.min(prev + 0.25, 4)),
1092
+ title: "Zoom in (+)",
1093
+ children: /* @__PURE__ */ jsx(ZoomIn, { className: "w-4 h-4" })
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ jsx(
1097
+ Button,
1098
+ {
1099
+ variant: "ghost",
1100
+ size: "sm",
1101
+ onClick: () => {
1102
+ setZoomLevel(1);
1103
+ setPanOffset({ x: 0, y: 0 });
1104
+ },
1105
+ title: "Reset zoom (0)",
1106
+ children: "Reset"
1107
+ }
1108
+ )
1109
+ ] })
1110
+ ]
1111
+ }
1112
+ ) })
1113
+ ] })
1114
+ ] });
1115
+ }
1116
+ var DEFAULT_NODE_COLOR = "#6b7280";
1117
+ function supportsImageInput(schema) {
1118
+ if (!schema) return true;
1119
+ const properties = schema.properties;
1120
+ if (!properties) return true;
1121
+ return !!(properties.image || properties.image_input || properties.start_image || properties.first_frame_image || properties.reference_images);
1122
+ }
1123
+ function getEdgeDataType(edge, nodeMap) {
1124
+ const sourceNode = nodeMap.get(edge.source);
1125
+ if (!sourceNode) return null;
1126
+ const nodeDef = import_types3.NODE_DEFINITIONS[sourceNode.type];
1127
+ if (!nodeDef) return null;
1128
+ const sourceHandle = nodeDef.outputs.find((h) => h.id === edge.sourceHandle);
1129
+ return sourceHandle?.type ?? null;
1130
+ }
1131
+ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
1132
+ const {
1133
+ nodes,
1134
+ edges,
1135
+ onNodesChange,
1136
+ onEdgesChange,
1137
+ onConnect,
1138
+ isValidConnection,
1139
+ findCompatibleHandle,
1140
+ addNode,
1141
+ selectedNodeIds,
1142
+ setSelectedNodeIds,
1143
+ toggleNodeLock,
1144
+ createGroup,
1145
+ deleteGroup,
1146
+ unlockAllNodes,
1147
+ groups,
1148
+ getConnectedNodeIds
1149
+ } = useWorkflowStore();
1150
+ const {
1151
+ selectNode,
1152
+ setHighlightedNodeIds,
1153
+ highlightedNodeIds,
1154
+ showPalette,
1155
+ togglePalette,
1156
+ openModal
1157
+ } = useUIStore();
1158
+ const reactFlow = useReactFlow();
1159
+ const { edgeStyle, showMinimap } = useSettingsStore();
1160
+ const isRunning = useExecutionStore((state) => state.isRunning);
1161
+ const currentNodeId = useExecutionStore((state) => state.currentNodeId);
1162
+ const executingNodeIds = useExecutionStore((state) => state.executingNodeIds);
1163
+ const activeNodeExecutions = useExecutionStore((state) => state.activeNodeExecutions);
1164
+ const hasActiveNodeExecutions = useExecutionStore((state) => state.activeNodeExecutions.size > 0);
1165
+ const [isMinimapVisible, setIsMinimapVisible] = useState(false);
1166
+ const [draggingNodeId, setDraggingNodeId] = useState(null);
1167
+ const hideTimeoutRef = useRef(null);
1168
+ const MINIMAP_HIDE_DELAY = 1500;
1169
+ const openShortcutHelp = useCallback(() => openModal("shortcutHelp"), [openModal]);
1170
+ const openNodeSearch = useCallback(() => openModal("nodeSearch"), [openModal]);
1171
+ useCanvasKeyboardShortcuts({
1172
+ selectedNodeIds,
1173
+ groups,
1174
+ nodes,
1175
+ toggleNodeLock,
1176
+ createGroup,
1177
+ deleteGroup,
1178
+ unlockAllNodes,
1179
+ addNode,
1180
+ togglePalette,
1181
+ fitView: reactFlow.fitView,
1182
+ openShortcutHelp,
1183
+ openNodeSearch
1184
+ });
1185
+ useEffect(() => {
1186
+ if (selectedNodeIds.length === 0) {
1187
+ setHighlightedNodeIds([]);
1188
+ } else {
1189
+ const connectedIds = getConnectedNodeIds(selectedNodeIds);
1190
+ setHighlightedNodeIds(connectedIds);
1191
+ }
1192
+ }, [selectedNodeIds, getConnectedNodeIds, setHighlightedNodeIds]);
1193
+ const nodeMap = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);
1194
+ const isEdgeTargetingDisabledInput = useCallback(
1195
+ (edge) => {
1196
+ const targetNode = nodeMap.get(edge.target);
1197
+ if (!targetNode) return false;
1198
+ if (targetNode.type === "imageGen" && edge.targetHandle === "images") {
1199
+ const nodeData = targetNode.data;
1200
+ const hasImageSupport = supportsImageInput(nodeData?.selectedModel?.inputSchema);
1201
+ return !hasImageSupport;
1202
+ }
1203
+ if (targetNode.type === "videoGen") {
1204
+ if (edge.targetHandle === "image" || edge.targetHandle === "lastFrame") {
1205
+ const nodeData = targetNode.data;
1206
+ const hasImageSupport = supportsImageInput(nodeData?.selectedModel?.inputSchema);
1207
+ return !hasImageSupport;
1208
+ }
1209
+ }
1210
+ return false;
1211
+ },
1212
+ [nodeMap]
1213
+ );
1214
+ const styledEdges = useMemo(() => {
1215
+ const executionScope = executingNodeIds.length > 0 ? new Set(executingNodeIds) : null;
1216
+ const highlightedSet = highlightedNodeIds.length > 0 ? new Set(highlightedNodeIds) : null;
1217
+ return edges.map((edge) => {
1218
+ const dataType = getEdgeDataType(edge, nodeMap);
1219
+ const typeClass = dataType ? `edge-${dataType}` : "";
1220
+ const isDisabledTarget = isEdgeTargetingDisabledInput(edge);
1221
+ if (!isRunning && hasActiveNodeExecutions) {
1222
+ const isActiveEdge = activeNodeExecutions.has(edge.source) || activeNodeExecutions.has(edge.target);
1223
+ if (isActiveEdge && !isDisabledTarget) {
1224
+ return {
1225
+ ...edge,
1226
+ animated: false,
1227
+ className: `${typeClass} executing`.trim()
1228
+ };
1229
+ }
1230
+ }
1231
+ if (isRunning) {
1232
+ const isInExecutionScope = !executionScope || executionScope.has(edge.source) || executionScope.has(edge.target);
1233
+ const isExecutingEdge = currentNodeId && (edge.source === currentNodeId || edge.target === currentNodeId);
1234
+ if (isDisabledTarget) {
1235
+ return {
1236
+ ...edge,
1237
+ animated: false,
1238
+ className: `${typeClass} edge-disabled`.trim()
1239
+ };
1240
+ }
1241
+ if (!isInExecutionScope) {
1242
+ return {
1243
+ ...edge,
1244
+ animated: false,
1245
+ className: typeClass
1246
+ };
1247
+ }
1248
+ return {
1249
+ ...edge,
1250
+ animated: false,
1251
+ className: `${typeClass} ${isExecutingEdge ? "executing" : "active-pipe"}`.trim()
1252
+ };
1253
+ }
1254
+ if (isDisabledTarget) {
1255
+ return {
1256
+ ...edge,
1257
+ className: `${typeClass} edge-disabled`.trim()
1258
+ };
1259
+ }
1260
+ if (highlightedSet) {
1261
+ const isConnected = highlightedSet.has(edge.source) && highlightedSet.has(edge.target);
1262
+ return {
1263
+ ...edge,
1264
+ className: `${typeClass} ${isConnected ? "highlighted" : "dimmed"}`.trim()
1265
+ };
1266
+ }
1267
+ return {
1268
+ ...edge,
1269
+ className: typeClass
1270
+ };
1271
+ });
1272
+ }, [
1273
+ edges,
1274
+ nodeMap,
1275
+ highlightedNodeIds,
1276
+ isRunning,
1277
+ currentNodeId,
1278
+ executingNodeIds,
1279
+ activeNodeExecutions,
1280
+ hasActiveNodeExecutions,
1281
+ isEdgeTargetingDisabledInput
1282
+ ]);
1283
+ const handleNodeClick = useCallback(
1284
+ (_event, node) => {
1285
+ selectNode(node.id);
1286
+ },
1287
+ [selectNode]
1288
+ );
1289
+ const handlePaneClick = useCallback(() => {
1290
+ selectNode(null);
1291
+ setSelectedNodeIds([]);
1292
+ }, [selectNode, setSelectedNodeIds]);
1293
+ const handleSelectionChange = useCallback(
1294
+ ({ nodes: selectedNodes }) => {
1295
+ setSelectedNodeIds(selectedNodes.map((n) => n.id));
1296
+ },
1297
+ [setSelectedNodeIds]
1298
+ );
1299
+ const handleNodeContextMenu = useCallback(
1300
+ (event, node) => {
1301
+ event.preventDefault();
1302
+ },
1303
+ [selectedNodeIds]
1304
+ );
1305
+ const handleEdgeContextMenu = useCallback(
1306
+ (event, edge) => {
1307
+ event.preventDefault();
1308
+ },
1309
+ []
1310
+ );
1311
+ const handlePaneContextMenu = useCallback(
1312
+ (event) => {
1313
+ event.preventDefault();
1314
+ },
1315
+ []
1316
+ );
1317
+ const handleSelectionContextMenu = useCallback(
1318
+ (event, selectedNodes) => {
1319
+ event.preventDefault();
1320
+ },
1321
+ []
1322
+ );
1323
+ const handleDrop = useCallback(
1324
+ (event) => {
1325
+ event.preventDefault();
1326
+ const nodeType = event.dataTransfer.getData("nodeType");
1327
+ if (!nodeType) return;
1328
+ const position = reactFlow.screenToFlowPosition({
1329
+ x: event.clientX,
1330
+ y: event.clientY
1331
+ });
1332
+ addNode(nodeType, position);
1333
+ },
1334
+ [addNode, reactFlow]
1335
+ );
1336
+ const handleDragOver = useCallback((event) => {
1337
+ event.preventDefault();
1338
+ event.dataTransfer.dropEffect = "move";
1339
+ }, []);
1340
+ const handleNodeDragStart = useCallback((_event, node) => {
1341
+ setDraggingNodeId(node.id);
1342
+ }, []);
1343
+ const handleNodeDrag = useCallback((_event, node) => {
1344
+ setDraggingNodeId(node.id);
1345
+ }, []);
1346
+ const handleNodeDragStop = useCallback(() => {
1347
+ setDraggingNodeId(null);
1348
+ }, []);
1349
+ const handleConnectEnd = useCallback(
1350
+ (event, connectionState) => {
1351
+ const state = connectionState;
1352
+ const sourceNodeId = state.fromNode?.id;
1353
+ const sourceHandleId = state.fromHandle?.id;
1354
+ if (!sourceNodeId || !sourceHandleId) return;
1355
+ const target = event.target;
1356
+ const nodeElement = target.closest(".react-flow__node");
1357
+ if (!nodeElement) return;
1358
+ const targetNodeId = nodeElement.getAttribute("data-id");
1359
+ if (!targetNodeId || targetNodeId === sourceNodeId) return;
1360
+ const droppedOnHandle = target.closest(".react-flow__handle");
1361
+ if (droppedOnHandle) return;
1362
+ const compatibleHandle = findCompatibleHandle(sourceNodeId, sourceHandleId, targetNodeId);
1363
+ if (!compatibleHandle) return;
1364
+ onConnect({
1365
+ source: sourceNodeId,
1366
+ sourceHandle: sourceHandleId,
1367
+ target: targetNodeId,
1368
+ targetHandle: compatibleHandle
1369
+ });
1370
+ },
1371
+ [findCompatibleHandle, onConnect]
1372
+ );
1373
+ const checkValidConnection = useCallback(
1374
+ (connection) => {
1375
+ const conn = {
1376
+ source: connection.source,
1377
+ target: connection.target,
1378
+ sourceHandle: connection.sourceHandle ?? null,
1379
+ targetHandle: connection.targetHandle ?? null
1380
+ };
1381
+ return isValidConnection(conn);
1382
+ },
1383
+ [isValidConnection]
1384
+ );
1385
+ const handleMoveStart = useCallback(() => {
1386
+ if (!showMinimap) return;
1387
+ if (hideTimeoutRef.current) {
1388
+ clearTimeout(hideTimeoutRef.current);
1389
+ hideTimeoutRef.current = null;
1390
+ }
1391
+ setIsMinimapVisible(true);
1392
+ }, [showMinimap]);
1393
+ const handleMoveEnd = useCallback(() => {
1394
+ if (!showMinimap) return;
1395
+ if (hideTimeoutRef.current) {
1396
+ clearTimeout(hideTimeoutRef.current);
1397
+ }
1398
+ hideTimeoutRef.current = setTimeout(() => {
1399
+ setIsMinimapVisible(false);
1400
+ hideTimeoutRef.current = null;
1401
+ }, MINIMAP_HIDE_DELAY);
1402
+ }, [showMinimap]);
1403
+ useEffect(() => {
1404
+ return () => {
1405
+ if (hideTimeoutRef.current) {
1406
+ clearTimeout(hideTimeoutRef.current);
1407
+ }
1408
+ };
1409
+ }, []);
1410
+ return /* @__PURE__ */ jsxs("div", { className: "w-full h-full relative", onDrop: handleDrop, onDragOver: handleDragOver, children: [
1411
+ !showPalette && /* @__PURE__ */ jsx(
1412
+ "button",
1413
+ {
1414
+ onClick: togglePalette,
1415
+ className: "absolute top-3 left-3 z-10 p-1.5 bg-[var(--background)] border border-[var(--border)] rounded-md hover:bg-[var(--secondary)] transition-colors group",
1416
+ title: "Open sidebar (M)",
1417
+ children: /* @__PURE__ */ jsx(PanelLeft, { className: "w-4 h-4 text-[var(--muted-foreground)] group-hover:text-[var(--foreground)]" })
1418
+ }
1419
+ ),
1420
+ /* @__PURE__ */ jsxs(
1421
+ ReactFlow,
1422
+ {
1423
+ nodes,
1424
+ edges: styledEdges,
1425
+ onNodesChange,
1426
+ onEdgesChange,
1427
+ onConnect,
1428
+ onConnectEnd: handleConnectEnd,
1429
+ onNodeClick: handleNodeClick,
1430
+ onPaneClick: handlePaneClick,
1431
+ onSelectionChange: handleSelectionChange,
1432
+ onNodeContextMenu: handleNodeContextMenu,
1433
+ onEdgeContextMenu: handleEdgeContextMenu,
1434
+ onPaneContextMenu: handlePaneContextMenu,
1435
+ onSelectionContextMenu: handleSelectionContextMenu,
1436
+ onNodeDragStart: handleNodeDragStart,
1437
+ onNodeDrag: handleNodeDrag,
1438
+ onNodeDragStop: handleNodeDragStop,
1439
+ isValidConnection: checkValidConnection,
1440
+ nodeTypes: nodeTypesProp ?? nodeTypes,
1441
+ fitView: true,
1442
+ snapToGrid: true,
1443
+ snapGrid: [16, 16],
1444
+ connectionMode: ConnectionMode.Loose,
1445
+ selectionMode: SelectionMode.Partial,
1446
+ selectionOnDrag: true,
1447
+ panOnDrag: [1, 2],
1448
+ onMoveStart: handleMoveStart,
1449
+ onMoveEnd: handleMoveEnd,
1450
+ deleteKeyCode: ["Backspace", "Delete"],
1451
+ defaultEdgeOptions: {
1452
+ type: edgeStyle,
1453
+ animated: false
1454
+ },
1455
+ edgesFocusable: true,
1456
+ edgesReconnectable: true,
1457
+ proOptions: { hideAttribution: true },
1458
+ onlyRenderVisibleElements: nodes.length > 50,
1459
+ children: [
1460
+ /* @__PURE__ */ jsx(GroupOverlay, {}),
1461
+ /* @__PURE__ */ jsx(
1462
+ Background,
1463
+ {
1464
+ variant: BackgroundVariant.Dots,
1465
+ gap: 16,
1466
+ size: 1,
1467
+ color: "rgba(255, 255, 255, 0.08)"
1468
+ }
1469
+ ),
1470
+ /* @__PURE__ */ jsx(Controls, {}),
1471
+ showMinimap && /* @__PURE__ */ jsx(
1472
+ MiniMap,
1473
+ {
1474
+ nodeStrokeWidth: 0,
1475
+ nodeColor: () => DEFAULT_NODE_COLOR,
1476
+ zoomable: true,
1477
+ pannable: true,
1478
+ maskColor: "rgba(0, 0, 0, 0.8)",
1479
+ className: `!bg-transparent !border-[var(--border)] !rounded-lg transition-opacity duration-300 ${isMinimapVisible ? "opacity-100" : "opacity-0 pointer-events-none"}`
1480
+ }
1481
+ ),
1482
+ /* @__PURE__ */ jsx(HelperLines, { draggingNodeId })
1483
+ ]
1484
+ }
1485
+ ),
1486
+ /* @__PURE__ */ jsx(NodeDetailModal, {}),
1487
+ /* @__PURE__ */ jsx(ShortcutHelpModal, {}),
1488
+ /* @__PURE__ */ jsx(NodeSearch, {})
1489
+ ] });
1490
+ }
1491
+ function PauseEdgeComponent({
1492
+ id,
1493
+ sourceX,
1494
+ sourceY,
1495
+ targetX,
1496
+ targetY,
1497
+ sourcePosition,
1498
+ targetPosition,
1499
+ style = {},
1500
+ markerEnd,
1501
+ data
1502
+ }) {
1503
+ const [edgePath, labelX, labelY] = getBezierPath({
1504
+ sourceX,
1505
+ sourceY,
1506
+ sourcePosition,
1507
+ targetX,
1508
+ targetY,
1509
+ targetPosition
1510
+ });
1511
+ const hasPause = data?.hasPause === true;
1512
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1513
+ /* @__PURE__ */ jsx(
1514
+ BaseEdge,
1515
+ {
1516
+ path: edgePath,
1517
+ markerEnd,
1518
+ style: {
1519
+ ...style,
1520
+ ...hasPause && {
1521
+ strokeDasharray: "5 5",
1522
+ stroke: "#f59e0b"
1523
+ }
1524
+ }
1525
+ }
1526
+ ),
1527
+ hasPause && /* @__PURE__ */ jsx(
1528
+ "foreignObject",
1529
+ {
1530
+ width: 20,
1531
+ height: 20,
1532
+ x: labelX - 10,
1533
+ y: labelY - 10,
1534
+ className: "pointer-events-none",
1535
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-5 h-5 rounded-full bg-amber-500 text-white", children: /* @__PURE__ */ jsx(Pause, { className: "w-3 h-3" }) })
1536
+ }
1537
+ )
1538
+ ] });
1539
+ }
1540
+ var PauseEdge = memo(PauseEdgeComponent);
1541
+
1542
+ export { DEFAULT_GROUP_COLORS, GROUP_COLORS, GroupOverlay, HelperLines, NodeSearch, PauseEdge, ShortcutHelpModal, WorkflowCanvas };