@genfeedai/workflow-ui 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +11 -33
  2. package/dist/canvas.d.ts +22 -22
  3. package/dist/canvas.mjs +16 -17
  4. package/dist/{chunk-WBR34V4L.mjs → chunk-2FUPL67V.mjs} +1593 -1045
  5. package/dist/{chunk-4VEN4UN7.mjs → chunk-53XDE62A.mjs} +818 -623
  6. package/dist/{chunk-PCIWWD37.mjs → chunk-7LV4UAUS.mjs} +19 -19
  7. package/dist/{chunk-7SKSRSS7.mjs → chunk-B4EAAKYF.mjs} +16 -16
  8. package/dist/{chunk-ZJD5WMR3.mjs → chunk-C6MQBJFC.mjs} +45 -13
  9. package/dist/{chunk-7H3WJJYS.mjs → chunk-ESVULCFY.mjs} +12 -6
  10. package/dist/{chunk-GWBGK3KL.mjs → chunk-FWJIAW2E.mjs} +82 -47
  11. package/dist/{chunk-R727OFBR.mjs → chunk-GPYIIWD5.mjs} +404 -350
  12. package/dist/{chunk-OQREHJXK.mjs → chunk-IYFWAJBB.mjs} +208 -203
  13. package/dist/{chunk-2JQSKIWR.mjs → chunk-MGLAKMDP.mjs} +24 -23
  14. package/dist/{chunk-LT3ZJJL6.mjs → chunk-OJWVEEMM.mjs} +497 -399
  15. package/dist/{chunk-ZD2BADZO.mjs → chunk-ORVDYXDP.mjs} +221 -175
  16. package/dist/{chunk-CV4M7CNU.mjs → chunk-QQVHGJ2G.mjs} +149 -142
  17. package/dist/{chunk-6PSJTBNV.mjs → chunk-U4QPE4CY.mjs} +387 -347
  18. package/dist/{chunk-EFXQT23N.mjs → chunk-VVQ4CH77.mjs} +5 -5
  19. package/dist/{chunk-VRN3UWE5.mjs → chunk-XRC3O5GK.mjs} +73 -73
  20. package/dist/{chunk-FT33LFII.mjs → chunk-YUIK4AHM.mjs} +1 -1
  21. package/dist/{chunk-JT4Y5H3U.mjs → chunk-ZSITTZ4S.mjs} +630 -569
  22. package/dist/hooks.d.ts +37 -37
  23. package/dist/hooks.mjs +10 -10
  24. package/dist/index.d.ts +26 -11
  25. package/dist/index.mjs +99 -20
  26. package/dist/lib.d.ts +203 -203
  27. package/dist/lib.mjs +228 -199
  28. package/dist/nodes.d.ts +2 -2
  29. package/dist/nodes.mjs +12 -13
  30. package/dist/panels.d.ts +2 -3
  31. package/dist/panels.mjs +3 -3
  32. package/dist/provider.d.ts +2 -2
  33. package/dist/provider.mjs +2 -2
  34. package/dist/stores.d.ts +5 -5
  35. package/dist/stores.mjs +5 -5
  36. package/dist/toolbar.d.ts +42 -24
  37. package/dist/toolbar.mjs +4 -5
  38. package/dist/ui.d.ts +2 -2
  39. package/dist/ui.mjs +2 -2
  40. package/dist/{useCommentNavigation-BakbiiIc.d.ts → useRequiredInputs-ByoIS-fT.d.ts} +160 -160
  41. package/dist/{promptLibraryStore-Dl3Q3cP6.d.ts → workflowStore-Bsz0nd5c.d.ts} +368 -368
  42. package/dist/workflowStore-N2F7WIG3.mjs +2 -0
  43. package/package.json +79 -77
  44. package/src/styles/workflow-ui.css +56 -19
  45. package/dist/chunk-OY7BRSGG.mjs +0 -60
  46. package/dist/workflowStore-UAAKOOIK.mjs +0 -2
  47. package/dist/{types-IEKYuYhu.d.ts → types-CRXJnajq.d.ts} +1 -1
@@ -1,25 +1,31 @@
1
- import { nodeTypes, NodeDetailModal } from './chunk-WBR34V4L.mjs';
2
- import { calculateWorkflowCost, formatCost } from './chunk-2JQSKIWR.mjs';
3
- import { useContextMenu, useCanvasKeyboardShortcuts, ContextMenu } from './chunk-ZD2BADZO.mjs';
4
- import { useUIStore, useSettingsStore, useExecutionStore } from './chunk-LT3ZJJL6.mjs';
5
- import { useWorkflowStore, getHandleType } from './chunk-R727OFBR.mjs';
1
+ import { useContextMenu, useCanvasKeyboardShortcuts, ContextMenu } from './chunk-ORVDYXDP.mjs';
2
+ import { calculateWorkflowCost, formatCost } from './chunk-MGLAKMDP.mjs';
3
+ import { nodeTypes, NodeDetailModal } from './chunk-2FUPL67V.mjs';
4
+ import { useUIStore, useSettingsStore, useExecutionStore } from './chunk-OJWVEEMM.mjs';
5
+ import { useWorkflowStore, getHandleType } from './chunk-GPYIIWD5.mjs';
6
+ import { CONNECTION_RULES, getNodesByCategory, NODE_DEFINITIONS } from '@genfeedai/types';
6
7
  import { useReactFlow, getBezierPath, BaseEdge, useNodes, ViewportPortal, useViewport, useStore, ReactFlow, SelectionMode, ConnectionMode, Background, BackgroundVariant, Controls, MiniMap } from '@xyflow/react';
7
- import { Plus, Search, Pause, Play, Trash2, AlignHorizontalSpaceAround, AlignVerticalSpaceAround, Grid3X3, Ungroup, Group, Palette, Lock, Unlock, X, Keyboard, PanelLeft, XCircle, AlertTriangle, CheckCircle, Info, Check, Copy, DollarSign } from 'lucide-react';
8
+ import { Plus, Search, Play, Pause, Trash2, AlignHorizontalSpaceAround, AlignVerticalSpaceAround, Grid3X3, Ungroup, Group, Palette, Lock, Unlock, X, Keyboard, PanelLeft, AlertTriangle, CheckCircle, Info, XCircle, Check, Copy, DollarSign } from 'lucide-react';
8
9
  import { memo, useState, useRef, useMemo, useEffect, useCallback } from 'react';
9
- import '@xyflow/react/dist/style.css';
10
- import { CONNECTION_RULES, getNodesByCategory, NODE_DEFINITIONS } from '@genfeedai/types';
11
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
11
  import { clsx } from 'clsx';
12
+ import '@xyflow/react/dist/style.css';
13
13
  import { createPortal } from 'react-dom';
14
14
 
15
15
  var CATEGORY_LABELS = {
16
- input: "Input",
17
16
  ai: "AI",
18
- processing: "Processing",
17
+ composition: "Composition",
18
+ input: "Input",
19
19
  output: "Output",
20
- composition: "Composition"
20
+ processing: "Processing"
21
21
  };
22
- var CATEGORY_ORDER = ["input", "ai", "processing", "output", "composition"];
22
+ var CATEGORY_ORDER = [
23
+ "input",
24
+ "ai",
25
+ "processing",
26
+ "output",
27
+ "composition"
28
+ ];
23
29
  function ConnectionDropMenuComponent() {
24
30
  const { connectionDropMenu, closeConnectionDropMenu } = useUIStore();
25
31
  const { addNode, findCompatibleHandle, onConnect } = useWorkflowStore();
@@ -44,9 +50,9 @@ function ConnectionDropMenuComponent() {
44
50
  );
45
51
  if (hasCompatibleInput) {
46
52
  result.push({
47
- type: def.type,
53
+ category,
48
54
  label: def.label,
49
- category
55
+ type: def.type
50
56
  });
51
57
  }
52
58
  }
@@ -108,13 +114,22 @@ function ConnectionDropMenuComponent() {
108
114
  }
109
115
  closeConnectionDropMenu();
110
116
  },
111
- [connectionDropMenu, reactFlow, addNode, findCompatibleHandle, onConnect, closeConnectionDropMenu]
117
+ [
118
+ connectionDropMenu,
119
+ reactFlow,
120
+ addNode,
121
+ findCompatibleHandle,
122
+ onConnect,
123
+ closeConnectionDropMenu
124
+ ]
112
125
  );
113
126
  const handleKeyDown = useCallback(
114
127
  (e) => {
115
128
  if (e.key === "ArrowDown") {
116
129
  e.preventDefault();
117
- setSelectedIndex((prev) => Math.min(prev + 1, filteredNodes.length - 1));
130
+ setSelectedIndex(
131
+ (prev) => Math.min(prev + 1, filteredNodes.length - 1)
132
+ );
118
133
  } else if (e.key === "ArrowUp") {
119
134
  e.preventDefault();
120
135
  setSelectedIndex((prev) => Math.max(prev - 1, 0));
@@ -135,81 +150,152 @@ function ConnectionDropMenuComponent() {
135
150
  };
136
151
  if (!isOpen || !connectionDropMenu) return null;
137
152
  const menuStyle = {
138
- position: "fixed",
139
153
  left: connectionDropMenu.screenPosition.x,
154
+ position: "fixed",
140
155
  top: connectionDropMenu.screenPosition.y,
141
156
  zIndex: 50
142
157
  };
143
158
  let flatIndex = 0;
144
- return /* @__PURE__ */ jsx("div", { ref: backdropRef, className: "fixed inset-0 z-40", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs(
159
+ return /* @__PURE__ */ jsx(
145
160
  "div",
146
161
  {
147
- style: menuStyle,
148
- className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-64 max-h-80 flex flex-col",
149
- role: "dialog",
150
- "aria-label": "Add Node",
162
+ ref: backdropRef,
163
+ className: "fixed inset-0 z-40",
164
+ onClick: handleBackdropClick,
165
+ children: /* @__PURE__ */ jsxs(
166
+ "div",
167
+ {
168
+ style: menuStyle,
169
+ className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-64 max-h-80 flex flex-col",
170
+ role: "dialog",
171
+ "aria-label": "Add Node",
172
+ children: [
173
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-[var(--border)]", children: [
174
+ /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5 text-[var(--muted-foreground)]" }),
175
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Add Connected Node" })
176
+ ] }),
177
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2", onKeyDown: handleKeyDown, children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
178
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--muted-foreground)]" }),
179
+ /* @__PURE__ */ jsx(
180
+ "input",
181
+ {
182
+ ref: inputRef,
183
+ type: "text",
184
+ placeholder: "Search compatible nodes...",
185
+ value: search,
186
+ onChange: (e) => setSearch(e.target.value),
187
+ className: "w-full pl-7 pr-2 py-1.5 text-xs bg-[var(--secondary)] border border-[var(--border)] rounded-md outline-none focus:ring-1 focus:ring-[var(--ring)]"
188
+ }
189
+ )
190
+ ] }) }),
191
+ /* @__PURE__ */ jsx(
192
+ "div",
193
+ {
194
+ ref: listRef,
195
+ className: "flex-1 overflow-y-auto px-1.5 pb-1.5",
196
+ onKeyDown: handleKeyDown,
197
+ children: filteredNodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center text-[var(--muted-foreground)] text-xs py-4", children: "No compatible nodes found" }) : Object.entries(groupedNodes).map(([category, nodes]) => {
198
+ if (!nodes || nodes.length === 0) return null;
199
+ return /* @__PURE__ */ jsxs("div", { className: "mb-1", children: [
200
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold text-[var(--muted-foreground)] uppercase tracking-wider px-2 py-1", children: CATEGORY_LABELS[category] }),
201
+ nodes.map((node) => {
202
+ const currentIndex = flatIndex++;
203
+ return /* @__PURE__ */ jsx(
204
+ "button",
205
+ {
206
+ "data-node-item": true,
207
+ onClick: () => handleSelect(node.type),
208
+ className: `w-full text-left px-2 py-1.5 rounded text-xs transition-colors ${currentIndex === selectedIndex ? "bg-[var(--primary)]/10 text-[var(--foreground)]" : "text-[var(--foreground)] hover:bg-[var(--secondary)]"}`,
209
+ children: node.label
210
+ },
211
+ node.type
212
+ );
213
+ })
214
+ ] }, category);
215
+ })
216
+ }
217
+ )
218
+ ]
219
+ }
220
+ )
221
+ }
222
+ );
223
+ }
224
+ var ConnectionDropMenu = memo(ConnectionDropMenuComponent);
225
+ function EdgeToolbarComponent() {
226
+ const { selectedEdgeId, selectEdge } = useUIStore();
227
+ const { edges, toggleEdgePause, removeEdge } = useWorkflowStore();
228
+ const reactFlow = useReactFlow();
229
+ const selectedEdge = useMemo(
230
+ () => selectedEdgeId ? edges.find((e) => e.id === selectedEdgeId) : null,
231
+ [selectedEdgeId, edges]
232
+ );
233
+ const position = useMemo(() => {
234
+ if (!selectedEdge) return null;
235
+ const sourceNode = reactFlow.getNode(selectedEdge.source);
236
+ const targetNode = reactFlow.getNode(selectedEdge.target);
237
+ if (!sourceNode || !targetNode) return null;
238
+ const midX = (sourceNode.position.x + (sourceNode.measured?.width ?? 280) / 2 + (targetNode.position.x + (targetNode.measured?.width ?? 280) / 2)) / 2;
239
+ const midY = (sourceNode.position.y + (sourceNode.measured?.height ?? 200) / 2 + (targetNode.position.y + (targetNode.measured?.height ?? 200) / 2)) / 2;
240
+ return reactFlow.flowToScreenPosition({ x: midX, y: midY });
241
+ }, [selectedEdge, reactFlow]);
242
+ if (!selectedEdge || !position) return null;
243
+ const hasPause = selectedEdge.data?.hasPause === true;
244
+ const handleTogglePause = (e) => {
245
+ e.stopPropagation();
246
+ toggleEdgePause(selectedEdge.id);
247
+ };
248
+ const handleDelete = (e) => {
249
+ e.stopPropagation();
250
+ removeEdge(selectedEdge.id);
251
+ selectEdge(null);
252
+ };
253
+ return /* @__PURE__ */ jsxs(
254
+ "div",
255
+ {
256
+ className: "fixed z-30 flex items-center gap-1 bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-lg px-1.5 py-1",
257
+ style: {
258
+ left: position.x,
259
+ top: position.y - 40,
260
+ transform: "translateX(-50%)"
261
+ },
262
+ onMouseDown: (e) => e.stopPropagation(),
151
263
  children: [
152
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-[var(--border)]", children: [
153
- /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5 text-[var(--muted-foreground)]" }),
154
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Add Connected Node" })
155
- ] }),
156
- /* @__PURE__ */ jsx("div", { className: "px-3 py-2", onKeyDown: handleKeyDown, children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
157
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--muted-foreground)]" }),
158
- /* @__PURE__ */ jsx(
159
- "input",
160
- {
161
- ref: inputRef,
162
- type: "text",
163
- placeholder: "Search compatible nodes...",
164
- value: search,
165
- onChange: (e) => setSearch(e.target.value),
166
- className: "w-full pl-7 pr-2 py-1.5 text-xs bg-[var(--secondary)] border border-[var(--border)] rounded-md outline-none focus:ring-1 focus:ring-[var(--ring)]"
167
- }
168
- )
169
- ] }) }),
170
264
  /* @__PURE__ */ jsx(
171
- "div",
265
+ "button",
172
266
  {
173
- ref: listRef,
174
- className: "flex-1 overflow-y-auto px-1.5 pb-1.5",
175
- onKeyDown: handleKeyDown,
176
- children: filteredNodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center text-[var(--muted-foreground)] text-xs py-4", children: "No compatible nodes found" }) : Object.entries(groupedNodes).map(([category, nodes]) => {
177
- if (!nodes || nodes.length === 0) return null;
178
- return /* @__PURE__ */ jsxs("div", { className: "mb-1", children: [
179
- /* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold text-[var(--muted-foreground)] uppercase tracking-wider px-2 py-1", children: CATEGORY_LABELS[category] }),
180
- nodes.map((node) => {
181
- const currentIndex = flatIndex++;
182
- return /* @__PURE__ */ jsx(
183
- "button",
184
- {
185
- "data-node-item": true,
186
- onClick: () => handleSelect(node.type),
187
- className: `w-full text-left px-2 py-1.5 rounded text-xs transition-colors ${currentIndex === selectedIndex ? "bg-[var(--primary)]/10 text-[var(--foreground)]" : "text-[var(--foreground)] hover:bg-[var(--secondary)]"}`,
188
- children: node.label
189
- },
190
- node.type
191
- );
192
- })
193
- ] }, category);
194
- })
267
+ onClick: handleTogglePause,
268
+ title: hasPause ? "Resume edge" : "Pause edge",
269
+ className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
270
+ children: hasPause ? /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Pause, { className: "h-3.5 w-3.5" })
271
+ }
272
+ ),
273
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-[var(--border)]" }),
274
+ /* @__PURE__ */ jsx(
275
+ "button",
276
+ {
277
+ onClick: handleDelete,
278
+ title: "Delete edge",
279
+ className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-red-500/10 hover:text-red-500",
280
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
195
281
  }
196
282
  )
197
283
  ]
198
284
  }
199
- ) });
285
+ );
200
286
  }
201
- var ConnectionDropMenu = memo(ConnectionDropMenuComponent);
287
+ var EdgeToolbar = memo(EdgeToolbarComponent);
202
288
  var DATA_TYPE_COLORS = {
289
+ audio: ["#f97316", "#fb923c"],
290
+ // orange
203
291
  image: ["#3b82f6", "#60a5fa"],
204
292
  // blue
205
- video: ["#8b5cf6", "#a78bfa"],
206
- // purple
293
+ number: ["#6b7280", "#9ca3af"],
294
+ // gray
207
295
  text: ["#22c55e", "#4ade80"],
208
296
  // green
209
- audio: ["#f97316", "#fb923c"],
210
- // orange
211
- number: ["#6b7280", "#9ca3af"]
212
- // gray
297
+ video: ["#8b5cf6", "#a78bfa"]
298
+ // purple
213
299
  };
214
300
  var DEFAULT_COLORS = ["#6b7280", "#9ca3af"];
215
301
  function EditableEdgeComponent({
@@ -226,12 +312,12 @@ function EditableEdgeComponent({
226
312
  selected
227
313
  }) {
228
314
  const [edgePath, labelX, labelY] = getBezierPath({
315
+ sourcePosition,
229
316
  sourceX,
230
317
  sourceY,
231
- sourcePosition,
318
+ targetPosition,
232
319
  targetX,
233
- targetY,
234
- targetPosition
320
+ targetY
235
321
  });
236
322
  const hasPause = data?.hasPause === true;
237
323
  const dataType = data?.dataType ?? null;
@@ -298,201 +384,48 @@ function EditableEdgeComponent({
298
384
  ] });
299
385
  }
300
386
  var EditableEdge = memo(EditableEdgeComponent);
301
- function EdgeToolbarComponent() {
302
- const { selectedEdgeId, selectEdge } = useUIStore();
303
- const { edges, toggleEdgePause, removeEdge } = useWorkflowStore();
304
- const reactFlow = useReactFlow();
305
- const selectedEdge = useMemo(
306
- () => selectedEdgeId ? edges.find((e) => e.id === selectedEdgeId) : null,
307
- [selectedEdgeId, edges]
308
- );
309
- const position = useMemo(() => {
310
- if (!selectedEdge) return null;
311
- const sourceNode = reactFlow.getNode(selectedEdge.source);
312
- const targetNode = reactFlow.getNode(selectedEdge.target);
313
- if (!sourceNode || !targetNode) return null;
314
- const midX = (sourceNode.position.x + (sourceNode.measured?.width ?? 280) / 2 + (targetNode.position.x + (targetNode.measured?.width ?? 280) / 2)) / 2;
315
- const midY = (sourceNode.position.y + (sourceNode.measured?.height ?? 200) / 2 + (targetNode.position.y + (targetNode.measured?.height ?? 200) / 2)) / 2;
316
- return reactFlow.flowToScreenPosition({ x: midX, y: midY });
317
- }, [selectedEdge, reactFlow]);
318
- if (!selectedEdge || !position) return null;
319
- const hasPause = selectedEdge.data?.hasPause === true;
320
- const handleTogglePause = (e) => {
321
- e.stopPropagation();
322
- toggleEdgePause(selectedEdge.id);
323
- };
324
- const handleDelete = (e) => {
325
- e.stopPropagation();
326
- removeEdge(selectedEdge.id);
327
- selectEdge(null);
328
- };
329
- return /* @__PURE__ */ jsxs(
330
- "div",
331
- {
332
- className: "fixed z-30 flex items-center gap-1 bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-lg px-1.5 py-1",
333
- style: {
334
- left: position.x,
335
- top: position.y - 40,
336
- transform: "translateX(-50%)"
337
- },
338
- onMouseDown: (e) => e.stopPropagation(),
339
- children: [
340
- /* @__PURE__ */ jsx(
341
- "button",
342
- {
343
- onClick: handleTogglePause,
344
- title: hasPause ? "Resume edge" : "Pause edge",
345
- className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
346
- children: hasPause ? /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Pause, { className: "h-3.5 w-3.5" })
347
- }
348
- ),
349
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-[var(--border)]" }),
350
- /* @__PURE__ */ jsx(
351
- "button",
352
- {
353
- onClick: handleDelete,
354
- title: "Delete edge",
355
- className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-red-500/10 hover:text-red-500",
356
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
357
- }
358
- )
359
- ]
360
- }
361
- );
362
- }
363
- var EdgeToolbar = memo(EdgeToolbarComponent);
364
- var REFERENCE_COLOR = "#52525b";
365
- function ReferenceEdge({
366
- id,
367
- sourceX,
368
- sourceY,
369
- targetX,
370
- targetY,
371
- sourcePosition,
372
- targetPosition,
373
- style,
374
- markerEnd,
375
- source,
376
- target
377
- }) {
378
- const nodes = useWorkflowStore((state) => state.nodes);
379
- const isConnectedToSelection = useMemo(() => {
380
- const selectedNodes = nodes.filter((n) => n.selected);
381
- if (selectedNodes.length === 0) return false;
382
- return selectedNodes.some((n) => n.id === source || n.id === target);
383
- }, [nodes, source, target]);
384
- const [edgePath] = useMemo(() => {
385
- return getBezierPath({
386
- sourceX,
387
- sourceY,
388
- sourcePosition,
389
- targetX,
390
- targetY,
391
- targetPosition,
392
- curvature: 0.25
393
- });
394
- }, [sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition]);
395
- const gradientId = useMemo(() => {
396
- const selectionKey = isConnectedToSelection ? "active" : "dimmed";
397
- return `reference-gradient-${selectionKey}-${id}`;
398
- }, [isConnectedToSelection, id]);
399
- return /* @__PURE__ */ jsxs(Fragment, { children: [
400
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: [
401
- /* @__PURE__ */ jsx(
402
- "stop",
403
- {
404
- offset: "0%",
405
- stopColor: REFERENCE_COLOR,
406
- stopOpacity: isConnectedToSelection ? 1 : 0.25
407
- }
408
- ),
409
- /* @__PURE__ */ jsx(
410
- "stop",
411
- {
412
- offset: "50%",
413
- stopColor: REFERENCE_COLOR,
414
- stopOpacity: isConnectedToSelection ? 0.55 : 0.1
415
- }
416
- ),
417
- /* @__PURE__ */ jsx(
418
- "stop",
419
- {
420
- offset: "100%",
421
- stopColor: REFERENCE_COLOR,
422
- stopOpacity: isConnectedToSelection ? 1 : 0.25
423
- }
424
- )
425
- ] }) }),
426
- /* @__PURE__ */ jsx(
427
- BaseEdge,
428
- {
429
- id,
430
- path: edgePath,
431
- markerEnd,
432
- style: {
433
- ...style,
434
- stroke: `url(#${gradientId})`,
435
- strokeWidth: 2,
436
- strokeDasharray: "6 4",
437
- strokeLinecap: "round",
438
- strokeLinejoin: "round"
439
- }
440
- }
441
- ),
442
- /* @__PURE__ */ jsx(
443
- "path",
444
- {
445
- d: edgePath,
446
- fill: "none",
447
- strokeWidth: 10,
448
- stroke: "transparent",
449
- className: "react-flow__edge-interaction"
450
- }
451
- )
452
- ] });
453
- }
454
387
 
455
388
  // src/types/groups.ts
456
389
  var GROUP_COLORS = {
457
- purple: {
458
- bg: "bg-purple-500/10",
459
- border: "border-purple-500/30",
460
- text: "text-purple-400"
461
- },
462
390
  blue: {
463
391
  bg: "bg-blue-500/10",
464
392
  border: "border-blue-500/30",
465
393
  text: "text-blue-400"
466
394
  },
395
+ gray: {
396
+ bg: "bg-gray-500/10",
397
+ border: "border-gray-500/30",
398
+ text: "text-gray-400"
399
+ },
467
400
  green: {
468
401
  bg: "bg-green-500/10",
469
402
  border: "border-green-500/30",
470
403
  text: "text-green-400"
471
404
  },
472
- yellow: {
473
- bg: "bg-yellow-500/10",
474
- border: "border-yellow-500/30",
475
- text: "text-yellow-400"
476
- },
477
405
  orange: {
478
406
  bg: "bg-orange-500/10",
479
407
  border: "border-orange-500/30",
480
408
  text: "text-orange-400"
481
409
  },
482
- red: {
483
- bg: "bg-red-500/10",
484
- border: "border-red-500/30",
485
- text: "text-red-400"
486
- },
487
410
  pink: {
488
411
  bg: "bg-pink-500/10",
489
412
  border: "border-pink-500/30",
490
413
  text: "text-pink-400"
491
414
  },
492
- gray: {
493
- bg: "bg-gray-500/10",
494
- border: "border-gray-500/30",
495
- text: "text-gray-400"
415
+ purple: {
416
+ bg: "bg-purple-500/10",
417
+ border: "border-purple-500/30",
418
+ text: "text-purple-400"
419
+ },
420
+ red: {
421
+ bg: "bg-red-500/10",
422
+ border: "border-red-500/30",
423
+ text: "text-red-400"
424
+ },
425
+ yellow: {
426
+ bg: "bg-yellow-500/10",
427
+ border: "border-yellow-500/30",
428
+ text: "text-yellow-400"
496
429
  }
497
430
  };
498
431
  var DEFAULT_GROUP_COLORS = [
@@ -527,10 +460,10 @@ function calculateGroupBounds(nodeIds, nodeMap) {
527
460
  if (!foundAny) return null;
528
461
  const padding = 24;
529
462
  return {
530
- x: minX - padding,
531
- y: minY - padding - HEADER_HEIGHT,
463
+ height: maxY - minY + padding * 2 + HEADER_HEIGHT,
532
464
  width: maxX - minX + padding * 2,
533
- height: maxY - minY + padding * 2 + HEADER_HEIGHT
465
+ x: minX - padding,
466
+ y: minY - padding - HEADER_HEIGHT
534
467
  };
535
468
  }
536
469
  function GroupBackground({ group, bounds }) {
@@ -545,10 +478,10 @@ function GroupBackground({ group, bounds }) {
545
478
  group.isLocked && "opacity-60"
546
479
  ),
547
480
  style: {
481
+ height: bounds.height,
548
482
  left: bounds.x,
549
483
  top: bounds.y,
550
- width: bounds.width,
551
- height: bounds.height
484
+ width: bounds.width
552
485
  }
553
486
  }
554
487
  );
@@ -563,7 +496,9 @@ function GroupControls({ group, bounds, nodeMap, zoom }) {
563
496
  const colorPickerRef = useRef(null);
564
497
  const [isDragging, setIsDragging] = useState(false);
565
498
  const dragStartRef = useRef(null);
566
- const nodeStartPositionsRef = useRef(/* @__PURE__ */ new Map());
499
+ const nodeStartPositionsRef = useRef(
500
+ /* @__PURE__ */ new Map()
501
+ );
567
502
  useEffect(() => {
568
503
  if (!isEditing) {
569
504
  setEditName(group.name);
@@ -678,10 +613,10 @@ function GroupControls({ group, bounds, nodeMap, zoom }) {
678
613
  group.isLocked && "opacity-60"
679
614
  ),
680
615
  style: {
616
+ height: HEADER_HEIGHT,
681
617
  left: bounds.x,
682
618
  top: bounds.y,
683
- width: bounds.width,
684
- height: HEADER_HEIGHT
619
+ width: bounds.width
685
620
  },
686
621
  children: [
687
622
  isEditing ? /* @__PURE__ */ jsx(
@@ -717,7 +652,10 @@ function GroupControls({ group, bounds, nodeMap, zoom }) {
717
652
  e.stopPropagation();
718
653
  setShowColorPicker(!showColorPicker);
719
654
  },
720
- className: clsx("p-1 rounded hover:bg-white/10 transition-colors", colors.text),
655
+ className: clsx(
656
+ "p-1 rounded hover:bg-white/10 transition-colors",
657
+ colors.text
658
+ ),
721
659
  title: "Change group color",
722
660
  children: /* @__PURE__ */ jsx(Palette, { className: "w-4 h-4" })
723
661
  }
@@ -746,7 +684,10 @@ function GroupControls({ group, bounds, nodeMap, zoom }) {
746
684
  e.stopPropagation();
747
685
  toggleGroupLock(group.id);
748
686
  },
749
- className: clsx("p-1 rounded hover:bg-white/10 transition-colors", colors.text),
687
+ className: clsx(
688
+ "p-1 rounded hover:bg-white/10 transition-colors",
689
+ colors.text
690
+ ),
750
691
  title: group.isLocked ? "Unlock group" : "Lock group",
751
692
  children: group.isLocked ? /* @__PURE__ */ jsx(Lock, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Unlock, { className: "w-4 h-4" })
752
693
  }
@@ -758,7 +699,10 @@ function GroupControls({ group, bounds, nodeMap, zoom }) {
758
699
  e.stopPropagation();
759
700
  deleteGroup(group.id);
760
701
  },
761
- className: clsx("p-1 rounded hover:bg-white/10 transition-colors", colors.text),
702
+ className: clsx(
703
+ "p-1 rounded hover:bg-white/10 transition-colors",
704
+ colors.text
705
+ ),
762
706
  title: "Delete group",
763
707
  children: /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" })
764
708
  }
@@ -799,11 +743,23 @@ function GroupBackgroundsPortalComponent() {
799
743
  return result;
800
744
  }, [groups, nodeMap]);
801
745
  if (groups.length === 0) return null;
802
- return /* @__PURE__ */ jsx(ViewportPortal, { children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 0, left: 0, zIndex: -1, pointerEvents: "none" }, children: groups.map((group) => {
803
- const bounds = groupBounds.get(group.id);
804
- if (!bounds) return null;
805
- return /* @__PURE__ */ jsx(GroupBackground, { group, bounds }, group.id);
806
- }) }) });
746
+ return /* @__PURE__ */ jsx(ViewportPortal, { children: /* @__PURE__ */ jsx(
747
+ "div",
748
+ {
749
+ style: {
750
+ left: 0,
751
+ pointerEvents: "none",
752
+ position: "absolute",
753
+ top: 0,
754
+ zIndex: -1
755
+ },
756
+ children: groups.map((group) => {
757
+ const bounds = groupBounds.get(group.id);
758
+ if (!bounds) return null;
759
+ return /* @__PURE__ */ jsx(GroupBackground, { group, bounds }, group.id);
760
+ })
761
+ }
762
+ ) });
807
763
  }
808
764
  function GroupControlsOverlayComponent() {
809
765
  const { groups } = useWorkflowStore();
@@ -821,20 +777,32 @@ function GroupControlsOverlayComponent() {
821
777
  return result;
822
778
  }, [groups, nodeMap]);
823
779
  if (groups.length === 0) return null;
824
- return /* @__PURE__ */ jsx(ViewportPortal, { children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3, pointerEvents: "none" }, children: groups.map((group) => {
825
- const bounds = groupBounds.get(group.id);
826
- if (!bounds) return null;
827
- return /* @__PURE__ */ jsx(
828
- GroupControls,
829
- {
830
- group,
831
- bounds,
832
- nodeMap,
833
- zoom
780
+ return /* @__PURE__ */ jsx(ViewportPortal, { children: /* @__PURE__ */ jsx(
781
+ "div",
782
+ {
783
+ style: {
784
+ left: 0,
785
+ pointerEvents: "none",
786
+ position: "absolute",
787
+ top: 0,
788
+ zIndex: 1e3
834
789
  },
835
- group.id
836
- );
837
- }) }) });
790
+ children: groups.map((group) => {
791
+ const bounds = groupBounds.get(group.id);
792
+ if (!bounds) return null;
793
+ return /* @__PURE__ */ jsx(
794
+ GroupControls,
795
+ {
796
+ group,
797
+ bounds,
798
+ nodeMap,
799
+ zoom
800
+ },
801
+ group.id
802
+ );
803
+ })
804
+ }
805
+ ) });
838
806
  }
839
807
  var GroupBackgroundsPortal = memo(GroupBackgroundsPortalComponent);
840
808
  var GroupControlsOverlay = memo(GroupControlsOverlayComponent);
@@ -857,7 +825,9 @@ function HelperLinesComponent({ draggingNodeId }) {
857
825
  setLines([]);
858
826
  return;
859
827
  }
860
- const otherNodes = nodes.filter((n) => n.id !== draggingId && !n.selected);
828
+ const otherNodes = nodes.filter(
829
+ (n) => n.id !== draggingId && !n.selected
830
+ );
861
831
  if (otherNodes.length === 0) {
862
832
  setLines([]);
863
833
  return;
@@ -881,36 +851,44 @@ function HelperLinesComponent({ draggingNodeId }) {
881
851
  const nodeCenterX = nodeLeft + nodeWidth / 2;
882
852
  const nodeCenterY = nodeTop + nodeHeight / 2;
883
853
  const verticalChecks = [
884
- { dragPos: dragLeft, nodePos: nodeLeft, label: "left-left" },
885
- { dragPos: dragLeft, nodePos: nodeRight, label: "left-right" },
886
- { dragPos: dragRight, nodePos: nodeLeft, label: "right-left" },
887
- { dragPos: dragRight, nodePos: nodeRight, label: "right-right" },
888
- { dragPos: dragCenterX, nodePos: nodeCenterX, label: "center-center-x" }
854
+ { dragPos: dragLeft, label: "left-left", nodePos: nodeLeft },
855
+ { dragPos: dragLeft, label: "left-right", nodePos: nodeRight },
856
+ { dragPos: dragRight, label: "right-left", nodePos: nodeLeft },
857
+ { dragPos: dragRight, label: "right-right", nodePos: nodeRight },
858
+ {
859
+ dragPos: dragCenterX,
860
+ label: "center-center-x",
861
+ nodePos: nodeCenterX
862
+ }
889
863
  ];
890
864
  for (const check of verticalChecks) {
891
865
  if (Math.abs(check.dragPos - check.nodePos) <= SNAP_THRESHOLD) {
892
866
  newLines.push({
893
- type: "vertical",
867
+ end: Math.max(dragBottom, nodeBottom) + 20,
894
868
  position: check.nodePos,
895
869
  start: Math.min(dragTop, nodeTop) - 20,
896
- end: Math.max(dragBottom, nodeBottom) + 20
870
+ type: "vertical"
897
871
  });
898
872
  }
899
873
  }
900
874
  const horizontalChecks = [
901
- { dragPos: dragTop, nodePos: nodeTop, label: "top-top" },
902
- { dragPos: dragTop, nodePos: nodeBottom, label: "top-bottom" },
903
- { dragPos: dragBottom, nodePos: nodeTop, label: "bottom-top" },
904
- { dragPos: dragBottom, nodePos: nodeBottom, label: "bottom-bottom" },
905
- { dragPos: dragCenterY, nodePos: nodeCenterY, label: "center-center-y" }
875
+ { dragPos: dragTop, label: "top-top", nodePos: nodeTop },
876
+ { dragPos: dragTop, label: "top-bottom", nodePos: nodeBottom },
877
+ { dragPos: dragBottom, label: "bottom-top", nodePos: nodeTop },
878
+ { dragPos: dragBottom, label: "bottom-bottom", nodePos: nodeBottom },
879
+ {
880
+ dragPos: dragCenterY,
881
+ label: "center-center-y",
882
+ nodePos: nodeCenterY
883
+ }
906
884
  ];
907
885
  for (const check of horizontalChecks) {
908
886
  if (Math.abs(check.dragPos - check.nodePos) <= SNAP_THRESHOLD) {
909
887
  newLines.push({
910
- type: "horizontal",
888
+ end: Math.max(dragRight, nodeRight) + 20,
911
889
  position: check.nodePos,
912
890
  start: Math.min(dragLeft, nodeLeft) - 20,
913
- end: Math.max(dragRight, nodeRight) + 20
891
+ type: "horizontal"
914
892
  });
915
893
  }
916
894
  }
@@ -949,7 +927,7 @@ function HelperLinesComponent({ draggingNodeId }) {
949
927
  "svg",
950
928
  {
951
929
  className: "absolute inset-0 pointer-events-none z-[1000]",
952
- style: { width: "100%", height: "100%", overflow: "visible" },
930
+ style: { height: "100%", overflow: "visible", width: "100%" },
953
931
  children: lines.map((line, index) => {
954
932
  if (line.type === "vertical") {
955
933
  const x = line.position * zoom + tx;
@@ -1030,9 +1008,9 @@ function NodeSearch() {
1030
1008
  (node) => {
1031
1009
  setSelectedNodeIds([node.id]);
1032
1010
  reactFlow.fitView({
1011
+ duration: 300,
1033
1012
  nodes: [node],
1034
- padding: 0.5,
1035
- duration: 300
1013
+ padding: 0.5
1036
1014
  });
1037
1015
  closeModal();
1038
1016
  setSearch("");
@@ -1043,7 +1021,9 @@ function NodeSearch() {
1043
1021
  (e) => {
1044
1022
  if (e.key === "ArrowDown") {
1045
1023
  e.preventDefault();
1046
- setSelectedIndex((prev) => Math.min(prev + 1, filteredNodes.length - 1));
1024
+ setSelectedIndex(
1025
+ (prev) => Math.min(prev + 1, filteredNodes.length - 1)
1026
+ );
1047
1027
  } else if (e.key === "ArrowUp") {
1048
1028
  e.preventDefault();
1049
1029
  setSelectedIndex((prev) => Math.max(prev - 1, 0));
@@ -1078,117 +1058,302 @@ function NodeSearch() {
1078
1058
  children: /* @__PURE__ */ jsxs(
1079
1059
  "div",
1080
1060
  {
1081
- className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-full max-w-lg",
1082
- role: "dialog",
1083
- "aria-label": "Find Node",
1084
- children: [
1085
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[var(--border)]", children: [
1086
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1087
- /* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-[var(--muted-foreground)]" }),
1088
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Find Node" })
1089
- ] }),
1090
- /* @__PURE__ */ jsx(
1091
- "button",
1092
- {
1093
- onClick: handleClose,
1094
- className: "p-1 rounded hover:bg-[var(--secondary)] transition-colors",
1095
- children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
1096
- }
1097
- )
1098
- ] }),
1099
- /* @__PURE__ */ jsxs("div", { className: "p-4", onKeyDown: handleKeyDown, children: [
1100
- /* @__PURE__ */ jsxs("div", { className: "relative mb-3", children: [
1101
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
1102
- /* @__PURE__ */ jsx(
1103
- "input",
1104
- {
1105
- ref: inputRef,
1106
- type: "text",
1107
- placeholder: "Search nodes by name, type, or comment...",
1108
- value: search,
1109
- onChange: (e) => setSearch(e.target.value),
1110
- 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)]",
1111
- autoFocus: true
1112
- }
1113
- )
1114
- ] }),
1115
- /* @__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) => {
1116
- const nodeDef = NODE_DEFINITIONS[node.type];
1117
- const comment = node.data.comment;
1118
- return /* @__PURE__ */ jsxs(
1119
- "button",
1120
- {
1121
- onClick: () => handleSelectNode(node),
1122
- 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"}`,
1123
- children: [
1124
- /* @__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) || "?" }),
1125
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1126
- /* @__PURE__ */ jsx("div", { className: "font-medium truncate text-sm", children: node.data.label }),
1127
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground truncate", children: [
1128
- nodeDef?.label || node.type,
1129
- comment && ` \xB7 ${comment}`
1130
- ] })
1131
- ] })
1132
- ]
1133
- },
1134
- node.id
1135
- );
1136
- }) }),
1137
- filteredNodes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-border text-xs text-muted-foreground flex gap-4", children: [
1138
- /* @__PURE__ */ jsxs("span", { children: [
1139
- /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "up/down" }),
1140
- " Navigate"
1141
- ] }),
1142
- /* @__PURE__ */ jsxs("span", { children: [
1143
- /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "Enter" }),
1144
- " Select"
1145
- ] }),
1146
- /* @__PURE__ */ jsxs("span", { children: [
1147
- /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "Esc" }),
1148
- " Close"
1149
- ] })
1150
- ] })
1151
- ] })
1152
- ]
1061
+ className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-full max-w-lg",
1062
+ role: "dialog",
1063
+ "aria-label": "Find Node",
1064
+ children: [
1065
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[var(--border)]", children: [
1066
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1067
+ /* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-[var(--muted-foreground)]" }),
1068
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Find Node" })
1069
+ ] }),
1070
+ /* @__PURE__ */ jsx(
1071
+ "button",
1072
+ {
1073
+ onClick: handleClose,
1074
+ className: "p-1 rounded hover:bg-[var(--secondary)] transition-colors",
1075
+ children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
1076
+ }
1077
+ )
1078
+ ] }),
1079
+ /* @__PURE__ */ jsxs("div", { className: "p-4", onKeyDown: handleKeyDown, children: [
1080
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-3", children: [
1081
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
1082
+ /* @__PURE__ */ jsx(
1083
+ "input",
1084
+ {
1085
+ ref: inputRef,
1086
+ type: "text",
1087
+ placeholder: "Search nodes by name, type, or comment...",
1088
+ value: search,
1089
+ onChange: (e) => setSearch(e.target.value),
1090
+ 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)]",
1091
+ autoFocus: true
1092
+ }
1093
+ )
1094
+ ] }),
1095
+ /* @__PURE__ */ jsx(
1096
+ "div",
1097
+ {
1098
+ ref: listRef,
1099
+ className: "max-h-[300px] overflow-y-auto space-y-1",
1100
+ 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) => {
1101
+ const nodeDef = NODE_DEFINITIONS[node.type];
1102
+ const comment = node.data.comment;
1103
+ return /* @__PURE__ */ jsxs(
1104
+ "button",
1105
+ {
1106
+ onClick: () => handleSelectNode(node),
1107
+ 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"}`,
1108
+ children: [
1109
+ /* @__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) || "?" }),
1110
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1111
+ /* @__PURE__ */ jsx("div", { className: "font-medium truncate text-sm", children: node.data.label }),
1112
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground truncate", children: [
1113
+ nodeDef?.label || node.type,
1114
+ comment && ` \xB7 ${comment}`
1115
+ ] })
1116
+ ] })
1117
+ ]
1118
+ },
1119
+ node.id
1120
+ );
1121
+ })
1122
+ }
1123
+ ),
1124
+ filteredNodes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-border text-xs text-muted-foreground flex gap-4", children: [
1125
+ /* @__PURE__ */ jsxs("span", { children: [
1126
+ /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "up/down" }),
1127
+ " ",
1128
+ "Navigate"
1129
+ ] }),
1130
+ /* @__PURE__ */ jsxs("span", { children: [
1131
+ /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "Enter" }),
1132
+ " ",
1133
+ "Select"
1134
+ ] }),
1135
+ /* @__PURE__ */ jsxs("span", { children: [
1136
+ /* @__PURE__ */ jsx("kbd", { className: "px-1.5 py-0.5 bg-secondary rounded", children: "Esc" }),
1137
+ " ",
1138
+ "Close"
1139
+ ] })
1140
+ ] })
1141
+ ] })
1142
+ ]
1143
+ }
1144
+ )
1145
+ }
1146
+ );
1147
+ }
1148
+ function PauseEdgeComponent({
1149
+ id,
1150
+ sourceX,
1151
+ sourceY,
1152
+ targetX,
1153
+ targetY,
1154
+ sourcePosition,
1155
+ targetPosition,
1156
+ style = {},
1157
+ markerEnd,
1158
+ data
1159
+ }) {
1160
+ const [edgePath, labelX, labelY] = getBezierPath({
1161
+ sourcePosition,
1162
+ sourceX,
1163
+ sourceY,
1164
+ targetPosition,
1165
+ targetX,
1166
+ targetY
1167
+ });
1168
+ const hasPause = data?.hasPause === true;
1169
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1170
+ /* @__PURE__ */ jsx(
1171
+ BaseEdge,
1172
+ {
1173
+ path: edgePath,
1174
+ markerEnd,
1175
+ style: {
1176
+ ...style,
1177
+ ...hasPause && {
1178
+ stroke: "#f59e0b",
1179
+ strokeDasharray: "5 5"
1180
+ }
1181
+ }
1182
+ }
1183
+ ),
1184
+ hasPause && /* @__PURE__ */ jsx(
1185
+ "foreignObject",
1186
+ {
1187
+ width: 20,
1188
+ height: 20,
1189
+ x: labelX - 10,
1190
+ y: labelY - 10,
1191
+ className: "pointer-events-none",
1192
+ 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" }) })
1193
+ }
1194
+ )
1195
+ ] });
1196
+ }
1197
+ var PauseEdge = memo(PauseEdgeComponent);
1198
+ var REFERENCE_COLOR = "#52525b";
1199
+ function ReferenceEdge({
1200
+ id,
1201
+ sourceX,
1202
+ sourceY,
1203
+ targetX,
1204
+ targetY,
1205
+ sourcePosition,
1206
+ targetPosition,
1207
+ style,
1208
+ markerEnd,
1209
+ source,
1210
+ target
1211
+ }) {
1212
+ const nodes = useWorkflowStore((state) => state.nodes);
1213
+ const isConnectedToSelection = useMemo(() => {
1214
+ const selectedNodes = nodes.filter((n) => n.selected);
1215
+ if (selectedNodes.length === 0) return false;
1216
+ return selectedNodes.some((n) => n.id === source || n.id === target);
1217
+ }, [nodes, source, target]);
1218
+ const [edgePath] = useMemo(() => {
1219
+ return getBezierPath({
1220
+ curvature: 0.25,
1221
+ sourcePosition,
1222
+ sourceX,
1223
+ sourceY,
1224
+ targetPosition,
1225
+ targetX,
1226
+ targetY
1227
+ });
1228
+ }, [sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition]);
1229
+ const gradientId = useMemo(() => {
1230
+ const selectionKey = isConnectedToSelection ? "active" : "dimmed";
1231
+ return `reference-gradient-${selectionKey}-${id}`;
1232
+ }, [isConnectedToSelection, id]);
1233
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1234
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: [
1235
+ /* @__PURE__ */ jsx(
1236
+ "stop",
1237
+ {
1238
+ offset: "0%",
1239
+ stopColor: REFERENCE_COLOR,
1240
+ stopOpacity: isConnectedToSelection ? 1 : 0.25
1241
+ }
1242
+ ),
1243
+ /* @__PURE__ */ jsx(
1244
+ "stop",
1245
+ {
1246
+ offset: "50%",
1247
+ stopColor: REFERENCE_COLOR,
1248
+ stopOpacity: isConnectedToSelection ? 0.55 : 0.1
1249
+ }
1250
+ ),
1251
+ /* @__PURE__ */ jsx(
1252
+ "stop",
1253
+ {
1254
+ offset: "100%",
1255
+ stopColor: REFERENCE_COLOR,
1256
+ stopOpacity: isConnectedToSelection ? 1 : 0.25
1153
1257
  }
1154
1258
  )
1155
- }
1156
- );
1259
+ ] }) }),
1260
+ /* @__PURE__ */ jsx(
1261
+ BaseEdge,
1262
+ {
1263
+ id,
1264
+ path: edgePath,
1265
+ markerEnd,
1266
+ style: {
1267
+ ...style,
1268
+ stroke: `url(#${gradientId})`,
1269
+ strokeDasharray: "6 4",
1270
+ strokeLinecap: "round",
1271
+ strokeLinejoin: "round",
1272
+ strokeWidth: 2
1273
+ }
1274
+ }
1275
+ ),
1276
+ /* @__PURE__ */ jsx(
1277
+ "path",
1278
+ {
1279
+ d: edgePath,
1280
+ fill: "none",
1281
+ strokeWidth: 10,
1282
+ stroke: "transparent",
1283
+ className: "react-flow__edge-interaction"
1284
+ }
1285
+ )
1286
+ ] });
1157
1287
  }
1158
1288
  var SHORTCUTS = [
1159
1289
  // Navigation
1160
- { keys: "Scroll", description: "Pan canvas", category: "Navigation" },
1161
- { keys: "Ctrl + Scroll", description: "Zoom in/out", category: "Navigation" },
1162
- { keys: "F", description: "Fit view to selection (or all)", category: "Navigation" },
1163
- { keys: "M", description: "Toggle sidebar", category: "Navigation" },
1290
+ { category: "Navigation", description: "Pan canvas", keys: "Scroll" },
1291
+ { category: "Navigation", description: "Zoom in/out", keys: "Ctrl + Scroll" },
1292
+ {
1293
+ category: "Navigation",
1294
+ description: "Fit view to selection (or all)",
1295
+ keys: "F"
1296
+ },
1297
+ { category: "Navigation", description: "Toggle sidebar", keys: "M" },
1164
1298
  // Selection
1165
- { keys: "Click", description: "Select node", category: "Selection" },
1166
- { keys: "Shift + Click", description: "Add to selection", category: "Selection" },
1167
- { keys: "Drag", description: "Marquee select", category: "Selection" },
1168
- { keys: "Ctrl + A", description: "Select all nodes", category: "Selection" },
1169
- { keys: "Ctrl + F", description: "Search nodes", category: "Selection" },
1299
+ { category: "Selection", description: "Select node", keys: "Click" },
1300
+ {
1301
+ category: "Selection",
1302
+ description: "Add to selection",
1303
+ keys: "Shift + Click"
1304
+ },
1305
+ { category: "Selection", description: "Marquee select", keys: "Drag" },
1306
+ { category: "Selection", description: "Select all nodes", keys: "Ctrl + A" },
1307
+ { category: "Selection", description: "Search nodes", keys: "Ctrl + F" },
1170
1308
  // Editing
1171
- { keys: "Ctrl + Z", description: "Undo", category: "Editing" },
1172
- { keys: "Ctrl + Shift + Z", description: "Redo", category: "Editing" },
1173
- { keys: "Ctrl + C", description: "Copy", category: "Editing" },
1174
- { keys: "Ctrl + X", description: "Cut", category: "Editing" },
1175
- { keys: "Ctrl + V", description: "Paste", category: "Editing" },
1176
- { keys: "Ctrl + D", description: "Duplicate", category: "Editing" },
1177
- { keys: "Delete / Backspace", description: "Delete selected", category: "Editing" },
1309
+ { category: "Editing", description: "Undo", keys: "Ctrl + Z" },
1310
+ { category: "Editing", description: "Redo", keys: "Ctrl + Shift + Z" },
1311
+ { category: "Editing", description: "Copy", keys: "Ctrl + C" },
1312
+ { category: "Editing", description: "Cut", keys: "Ctrl + X" },
1313
+ { category: "Editing", description: "Paste", keys: "Ctrl + V" },
1314
+ { category: "Editing", description: "Duplicate", keys: "Ctrl + D" },
1315
+ {
1316
+ category: "Editing",
1317
+ description: "Delete selected",
1318
+ keys: "Delete / Backspace"
1319
+ },
1178
1320
  // Nodes
1179
- { keys: "Shift + I", description: "Add Image Gen node", category: "Nodes" },
1180
- { keys: "Shift + V", description: "Add Video Gen node", category: "Nodes" },
1181
- { keys: "Shift + P", description: "Add Prompt node", category: "Nodes" },
1182
- { keys: "Shift + L", description: "Add LLM node", category: "Nodes" },
1321
+ { category: "Nodes", description: "Add Image Gen node", keys: "Shift + I" },
1322
+ { category: "Nodes", description: "Add Video Gen node", keys: "Shift + V" },
1323
+ { category: "Nodes", description: "Add Prompt node", keys: "Shift + P" },
1324
+ { category: "Nodes", description: "Add LLM node", keys: "Shift + L" },
1183
1325
  // Organization
1184
- { keys: "L", description: "Toggle lock on selected", category: "Organization" },
1185
- { keys: "Ctrl + G", description: "Group selected nodes", category: "Organization" },
1186
- { keys: "Ctrl + Shift + G", description: "Ungroup", category: "Organization" },
1187
- { keys: "Ctrl + Shift + L", description: "Unlock all nodes", category: "Organization" },
1326
+ {
1327
+ category: "Organization",
1328
+ description: "Toggle lock on selected",
1329
+ keys: "L"
1330
+ },
1331
+ {
1332
+ category: "Organization",
1333
+ description: "Group selected nodes",
1334
+ keys: "Ctrl + G"
1335
+ },
1336
+ {
1337
+ category: "Organization",
1338
+ description: "Ungroup",
1339
+ keys: "Ctrl + Shift + G"
1340
+ },
1341
+ {
1342
+ category: "Organization",
1343
+ description: "Unlock all nodes",
1344
+ keys: "Ctrl + Shift + L"
1345
+ },
1188
1346
  // Help
1189
- { keys: "?", description: "Show this help", category: "Help" }
1347
+ { category: "Help", description: "Show this help", keys: "?" }
1348
+ ];
1349
+ var CATEGORIES = [
1350
+ "Navigation",
1351
+ "Selection",
1352
+ "Editing",
1353
+ "Nodes",
1354
+ "Organization",
1355
+ "Help"
1190
1356
  ];
1191
- var CATEGORIES = ["Navigation", "Selection", "Editing", "Nodes", "Organization", "Help"];
1192
1357
  function ShortcutHelpModal() {
1193
1358
  const { activeModal, closeModal } = useUIStore();
1194
1359
  const [searchQuery, setSearchQuery] = useState("");
@@ -1346,7 +1511,7 @@ function CostModal() {
1346
1511
  }
1347
1512
  )
1348
1513
  ] }),
1349
- /* @__PURE__ */ jsx("div", { className: "p-4", children: breakdown.nodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center text-[var(--muted-foreground)] py-8", children: "No billable nodes in workflow" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1514
+ /* @__PURE__ */ jsx("div", { className: "p-4", children: breakdown.items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center text-[var(--muted-foreground)] py-8", children: "No billable nodes in workflow" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1350
1515
  /* @__PURE__ */ jsx("div", { className: "max-h-[40vh] overflow-y-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1351
1516
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs text-[var(--muted-foreground)] border-b border-[var(--border)]", children: [
1352
1517
  /* @__PURE__ */ jsx("th", { className: "pb-2 font-medium", children: "Node" }),
@@ -1354,15 +1519,22 @@ function CostModal() {
1354
1519
  /* @__PURE__ */ jsx("th", { className: "pb-2 font-medium", children: "Unit" }),
1355
1520
  /* @__PURE__ */ jsx("th", { className: "pb-2 font-medium text-right", children: "Cost" })
1356
1521
  ] }) }),
1357
- /* @__PURE__ */ jsx("tbody", { children: breakdown.nodes.map((estimate) => /* @__PURE__ */ jsxs(
1522
+ /* @__PURE__ */ jsx("tbody", { children: breakdown.items.map((estimate) => /* @__PURE__ */ jsxs(
1358
1523
  "tr",
1359
1524
  {
1360
1525
  className: "border-b border-[var(--border)] last:border-0",
1361
1526
  children: [
1362
- /* @__PURE__ */ jsx("td", { className: "py-2 truncate max-w-[120px]", title: estimate.nodeLabel, children: estimate.nodeLabel }),
1527
+ /* @__PURE__ */ jsx(
1528
+ "td",
1529
+ {
1530
+ className: "py-2 truncate max-w-[120px]",
1531
+ title: estimate.nodeLabel,
1532
+ children: estimate.nodeLabel
1533
+ }
1534
+ ),
1363
1535
  /* @__PURE__ */ jsx("td", { className: "py-2 text-[var(--muted-foreground)] font-mono text-xs", children: estimate.model }),
1364
1536
  /* @__PURE__ */ jsx("td", { className: "py-2 text-[var(--muted-foreground)] text-xs", children: estimate.unit }),
1365
- /* @__PURE__ */ jsx("td", { className: "py-2 text-right font-mono", children: formatCost(estimate.cost) })
1537
+ /* @__PURE__ */ jsx("td", { className: "py-2 text-right font-mono", children: formatCost(estimate.subtotal) })
1366
1538
  ]
1367
1539
  },
1368
1540
  estimate.nodeId
@@ -1527,7 +1699,14 @@ function HistorySidebar({
1527
1699
  viewBox: "0 0 24 24",
1528
1700
  stroke: "currentColor",
1529
1701
  strokeWidth: 2,
1530
- children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })
1702
+ children: /* @__PURE__ */ jsx(
1703
+ "path",
1704
+ {
1705
+ strokeLinecap: "round",
1706
+ strokeLinejoin: "round",
1707
+ d: "M6 18L18 6M6 6l12 12"
1708
+ }
1709
+ )
1531
1710
  }
1532
1711
  )
1533
1712
  }
@@ -1573,8 +1752,12 @@ function GlobalImageHistory() {
1573
1752
  const [showSidebar, setShowSidebar] = useState(false);
1574
1753
  const drawerRef = useRef(null);
1575
1754
  const triggerRef = useRef(null);
1576
- const history = useWorkflowStore((state) => state.globalImageHistory ?? EMPTY_HISTORY);
1577
- const clearGlobalHistory = useWorkflowStore((state) => state.clearGlobalHistory);
1755
+ const history = useWorkflowStore(
1756
+ (state) => state.globalImageHistory ?? EMPTY_HISTORY
1757
+ );
1758
+ const clearGlobalHistory = useWorkflowStore(
1759
+ (state) => state.clearGlobalHistory
1760
+ );
1578
1761
  const fanItems = history.slice(0, 10);
1579
1762
  const hasOverflow = history.length > 10;
1580
1763
  useEffect(() => {
@@ -1603,21 +1786,24 @@ function GlobalImageHistory() {
1603
1786
  }
1604
1787
  return () => document.removeEventListener("keydown", handleKeyDown);
1605
1788
  }, [isOpen, showSidebar]);
1606
- const handleDragStart = useCallback((e, item) => {
1607
- e.dataTransfer.setData(
1608
- "application/history-image",
1609
- JSON.stringify({
1610
- image: item.image,
1611
- prompt: item.prompt,
1612
- timestamp: item.timestamp
1613
- })
1614
- );
1615
- e.dataTransfer.effectAllowed = "copy";
1616
- setTimeout(() => {
1617
- setIsOpen(false);
1618
- setShowSidebar(false);
1619
- }, 0);
1620
- }, []);
1789
+ const handleDragStart = useCallback(
1790
+ (e, item) => {
1791
+ e.dataTransfer.setData(
1792
+ "application/history-image",
1793
+ JSON.stringify({
1794
+ image: item.image,
1795
+ prompt: item.prompt,
1796
+ timestamp: item.timestamp
1797
+ })
1798
+ );
1799
+ e.dataTransfer.effectAllowed = "copy";
1800
+ setTimeout(() => {
1801
+ setIsOpen(false);
1802
+ setShowSidebar(false);
1803
+ }, 0);
1804
+ },
1805
+ []
1806
+ );
1621
1807
  const handleShowAll = useCallback(() => {
1622
1808
  setIsOpen(false);
1623
1809
  setShowSidebar(true);
@@ -1679,7 +1865,10 @@ function GlobalImageHistory() {
1679
1865
  item.id
1680
1866
  )) }),
1681
1867
  hasOverflow && (() => {
1682
- const topItemPos = calculateFanPosition(fanItems.length - 1, fanItems.length);
1868
+ const topItemPos = calculateFanPosition(
1869
+ fanItems.length - 1,
1870
+ fanItems.length
1871
+ );
1683
1872
  return /* @__PURE__ */ jsxs(
1684
1873
  "button",
1685
1874
  {
@@ -1716,8 +1905,17 @@ function GlobalImageHistory() {
1716
1905
  var NODE_GAP = 32;
1717
1906
  var EST_NODE_WIDTH = 280;
1718
1907
  var EST_NODE_HEIGHT = 200;
1719
- function MultiSelectToolbarComponent({ onDownloadAsZip }) {
1720
- const { nodes, selectedNodeIds, onNodesChange, createGroup, deleteGroup, groups } = useWorkflowStore();
1908
+ function MultiSelectToolbarComponent({
1909
+ onDownloadAsZip
1910
+ }) {
1911
+ const {
1912
+ nodes,
1913
+ selectedNodeIds,
1914
+ onNodesChange,
1915
+ createGroup,
1916
+ deleteGroup,
1917
+ groups
1918
+ } = useWorkflowStore();
1721
1919
  const reactFlow = useReactFlow();
1722
1920
  const selectedNodes = useMemo(
1723
1921
  () => nodes.filter((n) => selectedNodeIds.includes(n.id)),
@@ -1725,7 +1923,9 @@ function MultiSelectToolbarComponent({ onDownloadAsZip }) {
1725
1923
  );
1726
1924
  const selectedGroup = useMemo(() => {
1727
1925
  if (selectedNodes.length < 2) return null;
1728
- return groups.find((g) => selectedNodeIds.every((id) => g.nodeIds.includes(id))) ?? null;
1926
+ return groups.find(
1927
+ (g) => selectedNodeIds.every((id) => g.nodeIds.includes(id))
1928
+ ) ?? null;
1729
1929
  }, [groups, selectedNodeIds, selectedNodes.length]);
1730
1930
  const toolbarPosition = useMemo(() => {
1731
1931
  if (selectedNodes.length < 2) return null;
@@ -1746,29 +1946,33 @@ function MultiSelectToolbarComponent({ onDownloadAsZip }) {
1746
1946
  }, [selectedNodes, reactFlow]);
1747
1947
  const stackHorizontal = useCallback(() => {
1748
1948
  if (selectedNodes.length < 2) return;
1749
- const sorted = [...selectedNodes].sort((a, b) => a.position.x - b.position.x);
1949
+ const sorted = [...selectedNodes].sort(
1950
+ (a, b) => a.position.x - b.position.x
1951
+ );
1750
1952
  const baseY = sorted[0].position.y;
1751
1953
  const changes = sorted.map((node, i) => ({
1752
- type: "position",
1753
1954
  id: node.id,
1754
1955
  position: {
1755
1956
  x: sorted[0].position.x + i * (EST_NODE_WIDTH + NODE_GAP),
1756
1957
  y: baseY
1757
- }
1958
+ },
1959
+ type: "position"
1758
1960
  }));
1759
1961
  onNodesChange(changes);
1760
1962
  }, [selectedNodes, onNodesChange]);
1761
1963
  const stackVertical = useCallback(() => {
1762
1964
  if (selectedNodes.length < 2) return;
1763
- const sorted = [...selectedNodes].sort((a, b) => a.position.y - b.position.y);
1965
+ const sorted = [...selectedNodes].sort(
1966
+ (a, b) => a.position.y - b.position.y
1967
+ );
1764
1968
  const baseX = sorted[0].position.x;
1765
1969
  const changes = sorted.map((node, i) => ({
1766
- type: "position",
1767
1970
  id: node.id,
1768
1971
  position: {
1769
1972
  x: baseX,
1770
1973
  y: sorted[0].position.y + i * (EST_NODE_HEIGHT + NODE_GAP)
1771
- }
1974
+ },
1975
+ type: "position"
1772
1976
  }));
1773
1977
  onNodesChange(changes);
1774
1978
  }, [selectedNodes, onNodesChange]);
@@ -1783,12 +1987,12 @@ function MultiSelectToolbarComponent({ onDownloadAsZip }) {
1783
1987
  const baseX = sorted[0].position.x;
1784
1988
  const baseY = sorted[0].position.y;
1785
1989
  const changes = sorted.map((node, i) => ({
1786
- type: "position",
1787
1990
  id: node.id,
1788
1991
  position: {
1789
1992
  x: baseX + i % cols * (EST_NODE_WIDTH + NODE_GAP),
1790
1993
  y: baseY + Math.floor(i / cols) * (EST_NODE_HEIGHT + NODE_GAP)
1791
- }
1994
+ },
1995
+ type: "position"
1792
1996
  }));
1793
1997
  onNodesChange(changes);
1794
1998
  }, [selectedNodes, onNodesChange]);
@@ -1865,16 +2069,16 @@ function MultiSelectToolbarComponent({ onDownloadAsZip }) {
1865
2069
  }
1866
2070
  var MultiSelectToolbar = memo(MultiSelectToolbarComponent);
1867
2071
  var typeStyles = {
2072
+ error: "bg-red-900 border-red-700 text-red-100",
1868
2073
  info: "bg-neutral-800 border-neutral-600 text-neutral-100",
1869
2074
  success: "bg-green-900 border-green-700 text-green-100",
1870
- warning: "bg-orange-900 border-orange-600 text-orange-100",
1871
- error: "bg-red-900 border-red-700 text-red-100"
2075
+ warning: "bg-orange-900 border-orange-600 text-orange-100"
1872
2076
  };
1873
2077
  var typeIcons = {
2078
+ error: XCircle,
1874
2079
  info: Info,
1875
2080
  success: CheckCircle,
1876
- warning: AlertTriangle,
1877
- error: XCircle
2081
+ warning: AlertTriangle
1878
2082
  };
1879
2083
  function NotificationItem({
1880
2084
  id,
@@ -1955,11 +2159,14 @@ function getEdgeDataType(edge, nodeMap) {
1955
2159
  return sourceHandle?.type ?? null;
1956
2160
  }
1957
2161
  var edgeTypesMap = {
1958
- default: EditableEdge,
1959
2162
  bezier: EditableEdge,
2163
+ default: EditableEdge,
1960
2164
  reference: ReferenceEdge
1961
2165
  };
1962
- function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2166
+ function WorkflowCanvas({
2167
+ nodeTypes: nodeTypesProp,
2168
+ onDownloadAsZip
2169
+ } = {}) {
1963
2170
  const {
1964
2171
  nodes,
1965
2172
  edges,
@@ -1993,8 +2200,12 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
1993
2200
  const isRunning = useExecutionStore((state) => state.isRunning);
1994
2201
  const currentNodeId = useExecutionStore((state) => state.currentNodeId);
1995
2202
  const executingNodeIds = useExecutionStore((state) => state.executingNodeIds);
1996
- const activeNodeExecutions = useExecutionStore((state) => state.activeNodeExecutions);
1997
- const hasActiveNodeExecutions = useExecutionStore((state) => state.activeNodeExecutions.size > 0);
2203
+ const activeNodeExecutions = useExecutionStore(
2204
+ (state) => state.activeNodeExecutions
2205
+ );
2206
+ const hasActiveNodeExecutions = useExecutionStore(
2207
+ (state) => state.activeNodeExecutions.size > 0
2208
+ );
1998
2209
  const [isMinimapVisible, setIsMinimapVisible] = useState(false);
1999
2210
  const [draggingNodeId, setDraggingNodeId] = useState(null);
2000
2211
  const hideTimeoutRef = useRef(null);
@@ -2009,32 +2220,42 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2009
2220
  openSelectionMenu,
2010
2221
  close: closeContextMenu
2011
2222
  } = useContextMenu();
2012
- const openShortcutHelp = useCallback(() => openModal("shortcutHelp"), [openModal]);
2013
- const openNodeSearch = useCallback(() => openModal("nodeSearch"), [openModal]);
2223
+ const openShortcutHelp = useCallback(
2224
+ () => openModal("shortcutHelp"),
2225
+ [openModal]
2226
+ );
2227
+ const openNodeSearch = useCallback(
2228
+ () => openModal("nodeSearch"),
2229
+ [openModal]
2230
+ );
2014
2231
  const deleteSelectedElements = useCallback(() => {
2015
2232
  const nodesToDelete = nodes.filter((n) => selectedNodeIds.includes(n.id));
2016
2233
  const edgesToDelete = edges.filter((e) => e.selected);
2017
2234
  if (nodesToDelete.length > 0) {
2018
- onNodesChange(nodesToDelete.map((n) => ({ type: "remove", id: n.id })));
2235
+ onNodesChange(
2236
+ nodesToDelete.map((n) => ({ id: n.id, type: "remove" }))
2237
+ );
2019
2238
  }
2020
2239
  if (edgesToDelete.length > 0) {
2021
- onEdgesChange(edgesToDelete.map((e) => ({ type: "remove", id: e.id })));
2240
+ onEdgesChange(
2241
+ edgesToDelete.map((e) => ({ id: e.id, type: "remove" }))
2242
+ );
2022
2243
  }
2023
2244
  }, [nodes, edges, selectedNodeIds, onNodesChange, onEdgesChange]);
2024
2245
  useCanvasKeyboardShortcuts({
2025
- selectedNodeIds,
2026
- groups,
2027
- nodes,
2028
- toggleNodeLock,
2246
+ addNode,
2029
2247
  createGroup,
2030
2248
  deleteGroup,
2031
- unlockAllNodes,
2032
- addNode,
2033
- togglePalette,
2249
+ deleteSelectedElements,
2034
2250
  fitView: reactFlow.fitView,
2035
- openShortcutHelp,
2251
+ groups,
2252
+ nodes,
2036
2253
  openNodeSearch,
2037
- deleteSelectedElements
2254
+ openShortcutHelp,
2255
+ selectedNodeIds,
2256
+ toggleNodeLock,
2257
+ togglePalette,
2258
+ unlockAllNodes
2038
2259
  });
2039
2260
  useEffect(() => {
2040
2261
  if (selectedNodeIds.length === 0) {
@@ -2051,13 +2272,17 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2051
2272
  if (!targetNode) return false;
2052
2273
  if (targetNode.type === "imageGen" && edge.targetHandle === "images") {
2053
2274
  const nodeData = targetNode.data;
2054
- const hasImageSupport = supportsImageInput(nodeData?.selectedModel?.inputSchema);
2275
+ const hasImageSupport = supportsImageInput(
2276
+ nodeData?.selectedModel?.inputSchema
2277
+ );
2055
2278
  return !hasImageSupport;
2056
2279
  }
2057
2280
  if (targetNode.type === "videoGen") {
2058
2281
  if (edge.targetHandle === "image" || edge.targetHandle === "lastFrame") {
2059
2282
  const nodeData = targetNode.data;
2060
- const hasImageSupport = supportsImageInput(nodeData?.selectedModel?.inputSchema);
2283
+ const hasImageSupport = supportsImageInput(
2284
+ nodeData?.selectedModel?.inputSchema
2285
+ );
2061
2286
  return !hasImageSupport;
2062
2287
  }
2063
2288
  }
@@ -2078,9 +2303,9 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2078
2303
  if (isActiveEdge && !isDisabledTarget) {
2079
2304
  return {
2080
2305
  ...edge,
2081
- data: enrichedData,
2082
2306
  animated: false,
2083
- className: `${typeClass} executing`.trim()
2307
+ className: `${typeClass} executing`.trim(),
2308
+ data: enrichedData
2084
2309
  };
2085
2310
  }
2086
2311
  }
@@ -2090,45 +2315,45 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2090
2315
  if (isDisabledTarget) {
2091
2316
  return {
2092
2317
  ...edge,
2093
- data: enrichedData,
2094
2318
  animated: false,
2095
- className: `${typeClass} edge-disabled`.trim()
2319
+ className: `${typeClass} edge-disabled`.trim(),
2320
+ data: enrichedData
2096
2321
  };
2097
2322
  }
2098
2323
  if (!isInExecutionScope) {
2099
2324
  return {
2100
2325
  ...edge,
2101
- data: enrichedData,
2102
2326
  animated: false,
2103
- className: typeClass
2327
+ className: typeClass,
2328
+ data: enrichedData
2104
2329
  };
2105
2330
  }
2106
2331
  return {
2107
2332
  ...edge,
2108
- data: enrichedData,
2109
2333
  animated: false,
2110
- className: `${typeClass} ${isExecutingEdge ? "executing" : "active-pipe"}`.trim()
2334
+ className: `${typeClass} ${isExecutingEdge ? "executing" : "active-pipe"}`.trim(),
2335
+ data: enrichedData
2111
2336
  };
2112
2337
  }
2113
2338
  if (isDisabledTarget) {
2114
2339
  return {
2115
2340
  ...edge,
2116
- data: enrichedData,
2117
- className: `${typeClass} edge-disabled`.trim()
2341
+ className: `${typeClass} edge-disabled`.trim(),
2342
+ data: enrichedData
2118
2343
  };
2119
2344
  }
2120
2345
  if (highlightedSet) {
2121
2346
  const isConnected = highlightedSet.has(edge.source) && highlightedSet.has(edge.target);
2122
2347
  return {
2123
2348
  ...edge,
2124
- data: enrichedData,
2125
- className: `${typeClass} ${isConnected ? "highlighted" : "dimmed"}`.trim()
2349
+ className: `${typeClass} ${isConnected ? "highlighted" : "dimmed"}`.trim(),
2350
+ data: enrichedData
2126
2351
  };
2127
2352
  }
2128
2353
  return {
2129
2354
  ...edge,
2130
- data: enrichedData,
2131
- className: typeClass
2355
+ className: typeClass,
2356
+ data: enrichedData
2132
2357
  };
2133
2358
  });
2134
2359
  }, [
@@ -2201,7 +2426,9 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2201
2426
  const handleDrop = useCallback(
2202
2427
  (event) => {
2203
2428
  event.preventDefault();
2204
- const historyData = event.dataTransfer.getData("application/history-image");
2429
+ const historyData = event.dataTransfer.getData(
2430
+ "application/history-image"
2431
+ );
2205
2432
  if (historyData) {
2206
2433
  try {
2207
2434
  const parsed = JSON.parse(historyData);
@@ -2232,9 +2459,12 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2232
2459
  event.preventDefault();
2233
2460
  event.dataTransfer.dropEffect = "move";
2234
2461
  }, []);
2235
- const handleNodeDragStart = useCallback((_event, node) => {
2236
- setDraggingNodeId(node.id);
2237
- }, []);
2462
+ const handleNodeDragStart = useCallback(
2463
+ (_event, node) => {
2464
+ setDraggingNodeId(node.id);
2465
+ },
2466
+ []
2467
+ );
2238
2468
  const handleNodeDrag = useCallback((_event, node) => {
2239
2469
  setDraggingNodeId(node.id);
2240
2470
  }, []);
@@ -2260,13 +2490,16 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2260
2490
  if (!sourceHandleType) return;
2261
2491
  const clientX = "clientX" in event ? event.clientX : event.touches?.[0]?.clientX ?? 0;
2262
2492
  const clientY = "clientY" in event ? event.clientY : event.touches?.[0]?.clientY ?? 0;
2263
- const flowPosition = reactFlow.screenToFlowPosition({ x: clientX, y: clientY });
2493
+ const flowPosition = reactFlow.screenToFlowPosition({
2494
+ x: clientX,
2495
+ y: clientY
2496
+ });
2264
2497
  openConnectionDropMenu({
2265
2498
  position: flowPosition,
2266
2499
  screenPosition: { x: clientX, y: clientY },
2267
- sourceNodeId,
2268
2500
  sourceHandleId,
2269
- sourceHandleType
2501
+ sourceHandleType,
2502
+ sourceNodeId
2270
2503
  });
2271
2504
  return;
2272
2505
  }
@@ -2274,7 +2507,11 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2274
2507
  if (!targetNodeId || targetNodeId === sourceNodeId) return;
2275
2508
  const droppedOnHandle = target.closest(".react-flow__handle");
2276
2509
  if (droppedOnHandle) return;
2277
- const compatibleHandle = findCompatibleHandle(sourceNodeId, sourceHandleId, targetNodeId);
2510
+ const compatibleHandle = findCompatibleHandle(
2511
+ sourceNodeId,
2512
+ sourceHandleId,
2513
+ targetNodeId
2514
+ );
2278
2515
  if (!compatibleHandle) return;
2279
2516
  onConnect({
2280
2517
  source: sourceNodeId,
@@ -2289,8 +2526,8 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2289
2526
  (connection) => {
2290
2527
  const conn = {
2291
2528
  source: connection.source,
2292
- target: connection.target,
2293
2529
  sourceHandle: connection.sourceHandle ?? null,
2530
+ target: connection.target,
2294
2531
  targetHandle: connection.targetHandle ?? null
2295
2532
  };
2296
2533
  return isValidConnection(conn);
@@ -2322,156 +2559,114 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
2322
2559
  }
2323
2560
  };
2324
2561
  }, []);
2325
- return /* @__PURE__ */ jsxs("div", { className: "w-full h-full relative", onDrop: handleDrop, onDragOver: handleDragOver, children: [
2326
- !showPalette && /* @__PURE__ */ jsx(
2327
- "button",
2328
- {
2329
- onClick: togglePalette,
2330
- 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",
2331
- title: "Open sidebar (M)",
2332
- children: /* @__PURE__ */ jsx(PanelLeft, { className: "w-4 h-4 text-[var(--muted-foreground)] group-hover:text-[var(--foreground)]" })
2333
- }
2334
- ),
2335
- /* @__PURE__ */ jsxs(
2336
- ReactFlow,
2337
- {
2338
- nodes,
2339
- edges: styledEdges,
2340
- onNodesChange,
2341
- onEdgesChange,
2342
- onConnect,
2343
- onConnectEnd: handleConnectEnd,
2344
- onNodeClick: handleNodeClick,
2345
- onPaneClick: handlePaneClick,
2346
- onSelectionChange: handleSelectionChange,
2347
- onNodeContextMenu: handleNodeContextMenu,
2348
- onEdgeContextMenu: handleEdgeContextMenu,
2349
- onPaneContextMenu: handlePaneContextMenu,
2350
- onSelectionContextMenu: handleSelectionContextMenu,
2351
- onNodeDragStart: handleNodeDragStart,
2352
- onNodeDrag: handleNodeDrag,
2353
- onNodeDragStop: handleNodeDragStop,
2354
- onEdgeClick: handleEdgeClick,
2355
- isValidConnection: checkValidConnection,
2356
- nodeTypes: nodeTypesProp ?? nodeTypes,
2357
- edgeTypes: edgeTypesMap,
2358
- fitView: true,
2359
- snapToGrid: true,
2360
- snapGrid: [16, 16],
2361
- minZoom: 0.1,
2362
- maxZoom: 4,
2363
- nodeDragThreshold: 5,
2364
- connectionMode: ConnectionMode.Loose,
2365
- selectionMode: SelectionMode.Partial,
2366
- selectionOnDrag: true,
2367
- panOnDrag: [1, 2],
2368
- onMoveStart: handleMoveStart,
2369
- onMoveEnd: handleMoveEnd,
2370
- deleteKeyCode: ["Backspace", "Delete"],
2371
- defaultEdgeOptions: {
2372
- type: edgeStyle,
2373
- animated: false
2374
- },
2375
- edgesFocusable: true,
2376
- edgesReconnectable: true,
2377
- proOptions: { hideAttribution: true },
2378
- onlyRenderVisibleElements: nodes.length > 50,
2379
- children: [
2380
- /* @__PURE__ */ jsx(GroupOverlay, {}),
2381
- /* @__PURE__ */ jsx(
2382
- Background,
2383
- {
2384
- variant: BackgroundVariant.Dots,
2385
- gap: 16,
2386
- size: 1,
2387
- color: "rgba(255, 255, 255, 0.08)"
2388
- }
2389
- ),
2390
- /* @__PURE__ */ jsx(Controls, {}),
2391
- showMinimap && /* @__PURE__ */ jsx(
2392
- MiniMap,
2393
- {
2394
- nodeStrokeWidth: 0,
2395
- nodeColor: () => DEFAULT_NODE_COLOR,
2396
- zoomable: true,
2397
- pannable: true,
2398
- maskColor: "rgba(0, 0, 0, 0.8)",
2399
- className: `!bg-transparent !border-[var(--border)] !rounded-lg transition-opacity duration-300 ${isMinimapVisible ? "opacity-100" : "opacity-0 pointer-events-none"}`
2400
- }
2401
- ),
2402
- /* @__PURE__ */ jsx(HelperLines, { draggingNodeId })
2403
- ]
2404
- }
2405
- ),
2406
- isContextMenuOpen && /* @__PURE__ */ jsx(
2407
- ContextMenu,
2408
- {
2409
- x: contextMenuPosition.x,
2410
- y: contextMenuPosition.y,
2411
- items: contextMenuItems,
2412
- onClose: closeContextMenu
2413
- }
2414
- ),
2415
- /* @__PURE__ */ jsx(EdgeToolbar, {}),
2416
- /* @__PURE__ */ jsx(MultiSelectToolbar, { onDownloadAsZip }),
2417
- /* @__PURE__ */ jsx(NodeDetailModal, {}),
2418
- /* @__PURE__ */ jsx(ShortcutHelpModal, {}),
2419
- /* @__PURE__ */ jsx(NodeSearch, {}),
2420
- /* @__PURE__ */ jsx(ConnectionDropMenu, {}),
2421
- /* @__PURE__ */ jsx(CostModal, {}),
2422
- /* @__PURE__ */ jsx(NotificationToast, {}),
2423
- /* @__PURE__ */ jsx(GlobalImageHistory, {})
2424
- ] });
2425
- }
2426
- function PauseEdgeComponent({
2427
- id,
2428
- sourceX,
2429
- sourceY,
2430
- targetX,
2431
- targetY,
2432
- sourcePosition,
2433
- targetPosition,
2434
- style = {},
2435
- markerEnd,
2436
- data
2437
- }) {
2438
- const [edgePath, labelX, labelY] = getBezierPath({
2439
- sourceX,
2440
- sourceY,
2441
- sourcePosition,
2442
- targetX,
2443
- targetY,
2444
- targetPosition
2445
- });
2446
- const hasPause = data?.hasPause === true;
2447
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2448
- /* @__PURE__ */ jsx(
2449
- BaseEdge,
2450
- {
2451
- path: edgePath,
2452
- markerEnd,
2453
- style: {
2454
- ...style,
2455
- ...hasPause && {
2456
- strokeDasharray: "5 5",
2457
- stroke: "#f59e0b"
2562
+ return /* @__PURE__ */ jsxs(
2563
+ "div",
2564
+ {
2565
+ className: "w-full h-full relative",
2566
+ onDrop: handleDrop,
2567
+ onDragOver: handleDragOver,
2568
+ children: [
2569
+ !showPalette && /* @__PURE__ */ jsx(
2570
+ "button",
2571
+ {
2572
+ onClick: togglePalette,
2573
+ 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",
2574
+ title: "Open sidebar (M)",
2575
+ children: /* @__PURE__ */ jsx(PanelLeft, { className: "w-4 h-4 text-[var(--muted-foreground)] group-hover:text-[var(--foreground)]" })
2458
2576
  }
2459
- }
2460
- }
2461
- ),
2462
- hasPause && /* @__PURE__ */ jsx(
2463
- "foreignObject",
2464
- {
2465
- width: 20,
2466
- height: 20,
2467
- x: labelX - 10,
2468
- y: labelY - 10,
2469
- className: "pointer-events-none",
2470
- 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" }) })
2471
- }
2472
- )
2473
- ] });
2577
+ ),
2578
+ /* @__PURE__ */ jsxs(
2579
+ ReactFlow,
2580
+ {
2581
+ nodes,
2582
+ edges: styledEdges,
2583
+ onNodesChange,
2584
+ onEdgesChange,
2585
+ onConnect,
2586
+ onConnectEnd: handleConnectEnd,
2587
+ onNodeClick: handleNodeClick,
2588
+ onPaneClick: handlePaneClick,
2589
+ onSelectionChange: handleSelectionChange,
2590
+ onNodeContextMenu: handleNodeContextMenu,
2591
+ onEdgeContextMenu: handleEdgeContextMenu,
2592
+ onPaneContextMenu: handlePaneContextMenu,
2593
+ onSelectionContextMenu: handleSelectionContextMenu,
2594
+ onNodeDragStart: handleNodeDragStart,
2595
+ onNodeDrag: handleNodeDrag,
2596
+ onNodeDragStop: handleNodeDragStop,
2597
+ onEdgeClick: handleEdgeClick,
2598
+ isValidConnection: checkValidConnection,
2599
+ nodeTypes: nodeTypesProp ?? nodeTypes,
2600
+ edgeTypes: edgeTypesMap,
2601
+ fitView: true,
2602
+ snapToGrid: true,
2603
+ snapGrid: [16, 16],
2604
+ minZoom: 0.1,
2605
+ maxZoom: 4,
2606
+ nodeDragThreshold: 5,
2607
+ connectionMode: ConnectionMode.Loose,
2608
+ selectionMode: SelectionMode.Partial,
2609
+ selectionOnDrag: true,
2610
+ panOnDrag: [1, 2],
2611
+ onMoveStart: handleMoveStart,
2612
+ onMoveEnd: handleMoveEnd,
2613
+ deleteKeyCode: ["Backspace", "Delete"],
2614
+ defaultEdgeOptions: {
2615
+ animated: false,
2616
+ type: edgeStyle
2617
+ },
2618
+ edgesFocusable: true,
2619
+ edgesReconnectable: true,
2620
+ proOptions: { hideAttribution: true },
2621
+ onlyRenderVisibleElements: nodes.length > 50,
2622
+ children: [
2623
+ /* @__PURE__ */ jsx(GroupOverlay, {}),
2624
+ /* @__PURE__ */ jsx(
2625
+ Background,
2626
+ {
2627
+ variant: BackgroundVariant.Dots,
2628
+ gap: 16,
2629
+ size: 1,
2630
+ color: "rgba(255, 255, 255, 0.08)"
2631
+ }
2632
+ ),
2633
+ /* @__PURE__ */ jsx(Controls, {}),
2634
+ showMinimap && /* @__PURE__ */ jsx(
2635
+ MiniMap,
2636
+ {
2637
+ nodeStrokeWidth: 0,
2638
+ nodeColor: () => DEFAULT_NODE_COLOR,
2639
+ zoomable: true,
2640
+ pannable: true,
2641
+ maskColor: "rgba(0, 0, 0, 0.8)",
2642
+ className: `!bg-transparent !border-[var(--border)] !rounded-lg transition-opacity duration-300 ${isMinimapVisible ? "opacity-100" : "opacity-0 pointer-events-none"}`
2643
+ }
2644
+ ),
2645
+ /* @__PURE__ */ jsx(HelperLines, { draggingNodeId })
2646
+ ]
2647
+ }
2648
+ ),
2649
+ isContextMenuOpen && /* @__PURE__ */ jsx(
2650
+ ContextMenu,
2651
+ {
2652
+ x: contextMenuPosition.x,
2653
+ y: contextMenuPosition.y,
2654
+ items: contextMenuItems,
2655
+ onClose: closeContextMenu
2656
+ }
2657
+ ),
2658
+ /* @__PURE__ */ jsx(EdgeToolbar, {}),
2659
+ /* @__PURE__ */ jsx(MultiSelectToolbar, { onDownloadAsZip }),
2660
+ /* @__PURE__ */ jsx(NodeDetailModal, {}),
2661
+ /* @__PURE__ */ jsx(ShortcutHelpModal, {}),
2662
+ /* @__PURE__ */ jsx(NodeSearch, {}),
2663
+ /* @__PURE__ */ jsx(ConnectionDropMenu, {}),
2664
+ /* @__PURE__ */ jsx(CostModal, {}),
2665
+ /* @__PURE__ */ jsx(NotificationToast, {}),
2666
+ /* @__PURE__ */ jsx(GlobalImageHistory, {})
2667
+ ]
2668
+ }
2669
+ );
2474
2670
  }
2475
- var PauseEdge = memo(PauseEdgeComponent);
2476
2671
 
2477
2672
  export { ConnectionDropMenu, DEFAULT_GROUP_COLORS, EdgeToolbar, EditableEdge, GROUP_COLORS, GlobalImageHistory, GroupOverlay, HelperLines, NodeSearch, NotificationToast, PauseEdge, ReferenceEdge, ShortcutHelpModal, WorkflowCanvas };