@genfeedai/workflow-ui 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas.d.mts +16 -2
- package/dist/canvas.mjs +10 -8
- package/dist/chunk-6PSJTBNV.mjs +638 -0
- package/dist/chunk-7H3WJJYS.mjs +52 -0
- package/dist/{chunk-HCXI63ME.mjs → chunk-AUQGOJOQ.mjs} +27 -4
- package/dist/{chunk-AOTUCJMA.mjs → chunk-GWBGK3KL.mjs} +2 -2
- package/dist/chunk-JTPADIUO.mjs +130 -0
- package/dist/{chunk-SQK4JDYY.mjs → chunk-LT3ZJJL6.mjs} +9 -2
- package/dist/{chunk-7P2JWDC7.mjs → chunk-O5II6BOJ.mjs} +1198 -254
- package/dist/{chunk-AUZR6REQ.mjs → chunk-OQREHJXK.mjs} +1 -1
- package/dist/chunk-OY7BRSGG.mjs +60 -0
- package/dist/{chunk-E3YBVMYZ.mjs → chunk-PANZDSP6.mjs} +274 -305
- package/dist/chunk-PCIWWD37.mjs +90 -0
- package/dist/{chunk-RIGVIEYB.mjs → chunk-R727OFBR.mjs} +11 -1
- package/dist/chunk-ZD2BADZO.mjs +1294 -0
- package/dist/contextMenuStore-DMg0hJQ1.d.mts +22 -0
- package/dist/hooks.d.mts +53 -244
- package/dist/hooks.mjs +6 -6
- package/dist/index.d.mts +11 -7
- package/dist/index.mjs +13 -11
- package/dist/lib.d.mts +250 -4
- package/dist/lib.mjs +562 -2
- package/dist/nodes.d.mts +3 -1
- package/dist/nodes.mjs +6 -6
- package/dist/panels.mjs +3 -4
- package/dist/{promptLibraryStore-zqb59nsu.d.mts → promptLibraryStore-Bgw5LzvD.d.mts} +33 -5
- package/dist/provider.d.mts +2 -2
- package/dist/provider.mjs +0 -1
- package/dist/stores.d.mts +4 -3
- package/dist/stores.mjs +3 -40
- package/dist/toolbar.d.mts +3 -1
- package/dist/toolbar.mjs +5 -4
- package/dist/{types-ipAnBzAJ.d.mts → types-CF6DPx0P.d.mts} +8 -3
- package/dist/ui.d.mts +1 -1
- package/dist/ui.mjs +0 -1
- package/dist/{hooks.d.ts → useCommentNavigation-NzJjkaj2.d.mts} +15 -2
- package/dist/workflowStore-UAAKOOIK.mjs +2 -0
- package/package.json +30 -24
- package/dist/canvas.d.ts +0 -27
- package/dist/canvas.js +0 -45
- package/dist/chunk-3SPPKCWR.js +0 -458
- package/dist/chunk-3TMV3K34.js +0 -534
- package/dist/chunk-3YFFDHC5.js +0 -300
- package/dist/chunk-4MZ62VMF.js +0 -37
- package/dist/chunk-5HJFQVUR.js +0 -61
- package/dist/chunk-5LQ4QBR5.js +0 -2
- package/dist/chunk-6DOEUDD5.js +0 -254
- package/dist/chunk-AXFOCPPP.js +0 -998
- package/dist/chunk-BMFRA6GK.js +0 -1546
- package/dist/chunk-E323WAZG.mjs +0 -272
- package/dist/chunk-ECD5J2BA.js +0 -6022
- package/dist/chunk-EMGXUNBL.js +0 -120
- package/dist/chunk-EMUMKW5C.js +0 -107
- package/dist/chunk-FOMOOERN.js +0 -2
- package/dist/chunk-IASLG6IA.mjs +0 -118
- package/dist/chunk-IHF35QZD.js +0 -1095
- package/dist/chunk-JLWKW3G5.js +0 -2
- package/dist/chunk-KDIWRSYV.js +0 -375
- package/dist/chunk-L5TF4EHW.mjs +0 -1
- package/dist/chunk-RJ262NXS.js +0 -24
- package/dist/chunk-RXNEDWK2.js +0 -141
- package/dist/chunk-SEV2DWKF.js +0 -744
- package/dist/chunk-ZJWP5KGZ.mjs +0 -33
- package/dist/hooks.js +0 -56
- package/dist/index.d.ts +0 -29
- package/dist/index.js +0 -180
- package/dist/lib.d.ts +0 -164
- package/dist/lib.js +0 -144
- package/dist/nodes.d.ts +0 -128
- package/dist/nodes.js +0 -151
- package/dist/panels.d.ts +0 -22
- package/dist/panels.js +0 -21
- package/dist/promptLibraryStore-BZnfmEkc.d.ts +0 -464
- package/dist/provider.d.ts +0 -29
- package/dist/provider.js +0 -17
- package/dist/stores.d.ts +0 -96
- package/dist/stores.js +0 -113
- package/dist/toolbar.d.ts +0 -73
- package/dist/toolbar.js +0 -34
- package/dist/types-ipAnBzAJ.d.ts +0 -46
- package/dist/ui.d.ts +0 -67
- package/dist/ui.js +0 -84
- package/dist/workflowStore-7SDJC4UR.mjs +0 -3
- package/dist/workflowStore-LNJQ5RZG.js +0 -12
|
@@ -1,18 +1,456 @@
|
|
|
1
|
-
import { nodeTypes } from './chunk-
|
|
2
|
-
import {
|
|
3
|
-
import { useCanvasKeyboardShortcuts } from './chunk-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { Pause, Palette, Lock, Unlock, Trash2, Search, X, Keyboard, PanelLeft, Download, ChevronLeft, ChevronRight, ZoomOut, ZoomIn } from 'lucide-react';
|
|
10
|
-
import { memo, useMemo, useState, useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import { nodeTypes, NodeDetailModal } from './chunk-PANZDSP6.mjs';
|
|
2
|
+
import { calculateWorkflowCost, formatCost } from './chunk-JTPADIUO.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';
|
|
6
|
+
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 { memo, useState, useRef, useMemo, useEffect, useCallback } from 'react';
|
|
11
9
|
import '@xyflow/react/dist/style.css';
|
|
12
|
-
import { NODE_DEFINITIONS } from '@genfeedai/types';
|
|
13
|
-
import { clsx } from 'clsx';
|
|
10
|
+
import { CONNECTION_RULES, getNodesByCategory, NODE_DEFINITIONS } from '@genfeedai/types';
|
|
14
11
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
15
|
-
import
|
|
12
|
+
import { clsx } from 'clsx';
|
|
13
|
+
import { createPortal } from 'react-dom';
|
|
14
|
+
|
|
15
|
+
var CATEGORY_LABELS = {
|
|
16
|
+
input: "Input",
|
|
17
|
+
ai: "AI",
|
|
18
|
+
processing: "Processing",
|
|
19
|
+
output: "Output",
|
|
20
|
+
composition: "Composition"
|
|
21
|
+
};
|
|
22
|
+
var CATEGORY_ORDER = ["input", "ai", "processing", "output", "composition"];
|
|
23
|
+
function ConnectionDropMenuComponent() {
|
|
24
|
+
const { connectionDropMenu, closeConnectionDropMenu } = useUIStore();
|
|
25
|
+
const { addNode, findCompatibleHandle, onConnect } = useWorkflowStore();
|
|
26
|
+
const reactFlow = useReactFlow();
|
|
27
|
+
const [search, setSearch] = useState("");
|
|
28
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
29
|
+
const inputRef = useRef(null);
|
|
30
|
+
const listRef = useRef(null);
|
|
31
|
+
const backdropRef = useRef(null);
|
|
32
|
+
const isOpen = connectionDropMenu !== null;
|
|
33
|
+
const compatibleNodes = useMemo(() => {
|
|
34
|
+
if (!connectionDropMenu) return [];
|
|
35
|
+
const sourceType = connectionDropMenu.sourceHandleType;
|
|
36
|
+
const compatibleTargetTypes = CONNECTION_RULES[sourceType] ?? [];
|
|
37
|
+
const nodesByCategory = getNodesByCategory();
|
|
38
|
+
const result = [];
|
|
39
|
+
for (const category of CATEGORY_ORDER) {
|
|
40
|
+
const defs = nodesByCategory[category] ?? [];
|
|
41
|
+
for (const def of defs) {
|
|
42
|
+
const hasCompatibleInput = def.inputs.some(
|
|
43
|
+
(input) => compatibleTargetTypes.includes(input.type)
|
|
44
|
+
);
|
|
45
|
+
if (hasCompatibleInput) {
|
|
46
|
+
result.push({
|
|
47
|
+
type: def.type,
|
|
48
|
+
label: def.label,
|
|
49
|
+
category
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}, [connectionDropMenu]);
|
|
56
|
+
const filteredNodes = useMemo(() => {
|
|
57
|
+
if (!search.trim()) return compatibleNodes;
|
|
58
|
+
const query = search.toLowerCase();
|
|
59
|
+
return compatibleNodes.filter(
|
|
60
|
+
(n) => n.label.toLowerCase().includes(query) || n.type.toLowerCase().includes(query)
|
|
61
|
+
);
|
|
62
|
+
}, [compatibleNodes, search]);
|
|
63
|
+
const groupedNodes = useMemo(() => {
|
|
64
|
+
const grouped = {};
|
|
65
|
+
for (const node of filteredNodes) {
|
|
66
|
+
if (!grouped[node.category]) grouped[node.category] = [];
|
|
67
|
+
grouped[node.category].push(node);
|
|
68
|
+
}
|
|
69
|
+
return grouped;
|
|
70
|
+
}, [filteredNodes]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (isOpen) {
|
|
73
|
+
setSearch("");
|
|
74
|
+
setSelectedIndex(0);
|
|
75
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
76
|
+
}
|
|
77
|
+
}, [isOpen]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setSelectedIndex(0);
|
|
80
|
+
}, [search]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (listRef.current) {
|
|
83
|
+
const items = listRef.current.querySelectorAll("[data-node-item]");
|
|
84
|
+
const selected = items[selectedIndex];
|
|
85
|
+
selected?.scrollIntoView({ block: "nearest" });
|
|
86
|
+
}
|
|
87
|
+
}, [selectedIndex]);
|
|
88
|
+
const handleSelect = useCallback(
|
|
89
|
+
(nodeType) => {
|
|
90
|
+
if (!connectionDropMenu) return;
|
|
91
|
+
const position = reactFlow.screenToFlowPosition({
|
|
92
|
+
x: connectionDropMenu.screenPosition.x,
|
|
93
|
+
y: connectionDropMenu.screenPosition.y
|
|
94
|
+
});
|
|
95
|
+
const newNodeId = addNode(nodeType, position);
|
|
96
|
+
const compatibleHandle = findCompatibleHandle(
|
|
97
|
+
connectionDropMenu.sourceNodeId,
|
|
98
|
+
connectionDropMenu.sourceHandleId,
|
|
99
|
+
newNodeId
|
|
100
|
+
);
|
|
101
|
+
if (compatibleHandle) {
|
|
102
|
+
onConnect({
|
|
103
|
+
source: connectionDropMenu.sourceNodeId,
|
|
104
|
+
sourceHandle: connectionDropMenu.sourceHandleId,
|
|
105
|
+
target: newNodeId,
|
|
106
|
+
targetHandle: compatibleHandle
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
closeConnectionDropMenu();
|
|
110
|
+
},
|
|
111
|
+
[connectionDropMenu, reactFlow, addNode, findCompatibleHandle, onConnect, closeConnectionDropMenu]
|
|
112
|
+
);
|
|
113
|
+
const handleKeyDown = useCallback(
|
|
114
|
+
(e) => {
|
|
115
|
+
if (e.key === "ArrowDown") {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
setSelectedIndex((prev) => Math.min(prev + 1, filteredNodes.length - 1));
|
|
118
|
+
} else if (e.key === "ArrowUp") {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
121
|
+
} else if (e.key === "Enter" && filteredNodes[selectedIndex]) {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
handleSelect(filteredNodes[selectedIndex].type);
|
|
124
|
+
} else if (e.key === "Escape") {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
closeConnectionDropMenu();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
[filteredNodes, selectedIndex, handleSelect, closeConnectionDropMenu]
|
|
130
|
+
);
|
|
131
|
+
const handleBackdropClick = (e) => {
|
|
132
|
+
if (e.target === backdropRef.current) {
|
|
133
|
+
closeConnectionDropMenu();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
if (!isOpen || !connectionDropMenu) return null;
|
|
137
|
+
const menuStyle = {
|
|
138
|
+
position: "fixed",
|
|
139
|
+
left: connectionDropMenu.screenPosition.x,
|
|
140
|
+
top: connectionDropMenu.screenPosition.y,
|
|
141
|
+
zIndex: 50
|
|
142
|
+
};
|
|
143
|
+
let flatIndex = 0;
|
|
144
|
+
return /* @__PURE__ */ jsx("div", { ref: backdropRef, className: "fixed inset-0 z-40", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs(
|
|
145
|
+
"div",
|
|
146
|
+
{
|
|
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",
|
|
151
|
+
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
|
+
/* @__PURE__ */ jsx(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
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
|
+
})
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
) });
|
|
200
|
+
}
|
|
201
|
+
var ConnectionDropMenu = memo(ConnectionDropMenuComponent);
|
|
202
|
+
var DATA_TYPE_COLORS = {
|
|
203
|
+
image: ["#3b82f6", "#60a5fa"],
|
|
204
|
+
// blue
|
|
205
|
+
video: ["#8b5cf6", "#a78bfa"],
|
|
206
|
+
// purple
|
|
207
|
+
text: ["#22c55e", "#4ade80"],
|
|
208
|
+
// green
|
|
209
|
+
audio: ["#f97316", "#fb923c"],
|
|
210
|
+
// orange
|
|
211
|
+
number: ["#6b7280", "#9ca3af"]
|
|
212
|
+
// gray
|
|
213
|
+
};
|
|
214
|
+
var DEFAULT_COLORS = ["#6b7280", "#9ca3af"];
|
|
215
|
+
function EditableEdgeComponent({
|
|
216
|
+
id,
|
|
217
|
+
sourceX,
|
|
218
|
+
sourceY,
|
|
219
|
+
targetX,
|
|
220
|
+
targetY,
|
|
221
|
+
sourcePosition,
|
|
222
|
+
targetPosition,
|
|
223
|
+
style = {},
|
|
224
|
+
markerEnd,
|
|
225
|
+
data,
|
|
226
|
+
selected
|
|
227
|
+
}) {
|
|
228
|
+
const [edgePath, labelX, labelY] = getBezierPath({
|
|
229
|
+
sourceX,
|
|
230
|
+
sourceY,
|
|
231
|
+
sourcePosition,
|
|
232
|
+
targetX,
|
|
233
|
+
targetY,
|
|
234
|
+
targetPosition
|
|
235
|
+
});
|
|
236
|
+
const hasPause = data?.hasPause === true;
|
|
237
|
+
const dataType = data?.dataType ?? null;
|
|
238
|
+
const [colorStart, colorEnd] = dataType ? DATA_TYPE_COLORS[dataType] ?? DEFAULT_COLORS : DEFAULT_COLORS;
|
|
239
|
+
const gradientId = `edge-gradient-${id}`;
|
|
240
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
241
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: [
|
|
242
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: colorStart }),
|
|
243
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: colorEnd })
|
|
244
|
+
] }) }),
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
246
|
+
"path",
|
|
247
|
+
{
|
|
248
|
+
d: edgePath,
|
|
249
|
+
fill: "none",
|
|
250
|
+
stroke: "transparent",
|
|
251
|
+
strokeWidth: 20,
|
|
252
|
+
className: "react-flow__edge-interaction"
|
|
253
|
+
}
|
|
254
|
+
),
|
|
255
|
+
/* @__PURE__ */ jsx(
|
|
256
|
+
BaseEdge,
|
|
257
|
+
{
|
|
258
|
+
path: edgePath,
|
|
259
|
+
markerEnd,
|
|
260
|
+
style: {
|
|
261
|
+
...style,
|
|
262
|
+
stroke: `url(#${gradientId})`,
|
|
263
|
+
strokeWidth: selected ? 3 : 2,
|
|
264
|
+
...hasPause && {
|
|
265
|
+
strokeDasharray: "8 4"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
),
|
|
270
|
+
hasPause && /* @__PURE__ */ jsx(
|
|
271
|
+
"foreignObject",
|
|
272
|
+
{
|
|
273
|
+
width: 20,
|
|
274
|
+
height: 20,
|
|
275
|
+
x: labelX - 10,
|
|
276
|
+
y: labelY - 10,
|
|
277
|
+
className: "pointer-events-none",
|
|
278
|
+
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-5 h-5 rounded-full bg-amber-500 text-white shadow-sm", children: /* @__PURE__ */ jsx(Pause, { className: "w-3 h-3" }) })
|
|
279
|
+
}
|
|
280
|
+
),
|
|
281
|
+
selected && !hasPause && /* @__PURE__ */ jsx(
|
|
282
|
+
"foreignObject",
|
|
283
|
+
{
|
|
284
|
+
width: 8,
|
|
285
|
+
height: 8,
|
|
286
|
+
x: labelX - 4,
|
|
287
|
+
y: labelY - 4,
|
|
288
|
+
className: "pointer-events-none",
|
|
289
|
+
children: /* @__PURE__ */ jsx(
|
|
290
|
+
"div",
|
|
291
|
+
{
|
|
292
|
+
className: "w-2 h-2 rounded-full",
|
|
293
|
+
style: { backgroundColor: colorStart }
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
] });
|
|
299
|
+
}
|
|
300
|
+
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
|
+
}
|
|
16
454
|
|
|
17
455
|
// src/types/groups.ts
|
|
18
456
|
var GROUP_COLORS = {
|
|
@@ -856,254 +1294,651 @@ function ShortcutHelpModal() {
|
|
|
856
1294
|
}
|
|
857
1295
|
);
|
|
858
1296
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const
|
|
862
|
-
const
|
|
863
|
-
const
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
if (
|
|
871
|
-
|
|
872
|
-
}, [nodeDetailNodeId, getNodeById]);
|
|
873
|
-
useEffect(() => {
|
|
874
|
-
if (activeModal !== "nodeDetail" || !node) return;
|
|
875
|
-
if (PROMPT_NODE_TYPES.includes(node.type)) {
|
|
876
|
-
const promptData = node.data;
|
|
877
|
-
closeNodeDetailModal();
|
|
878
|
-
openEditor(node.id, promptData.prompt ?? "");
|
|
1297
|
+
function CostModal() {
|
|
1298
|
+
const { activeModal, closeModal } = useUIStore();
|
|
1299
|
+
const nodes = useWorkflowStore((state) => state.nodes);
|
|
1300
|
+
const actualCost = useExecutionStore((state) => state.actualCost);
|
|
1301
|
+
const backdropRef = useRef(null);
|
|
1302
|
+
const isOpen = activeModal === "cost";
|
|
1303
|
+
const breakdown = useMemo(() => calculateWorkflowCost(nodes), [nodes]);
|
|
1304
|
+
const handleClose = () => {
|
|
1305
|
+
closeModal();
|
|
1306
|
+
};
|
|
1307
|
+
const handleBackdropClick = (e) => {
|
|
1308
|
+
if (e.target === backdropRef.current) {
|
|
1309
|
+
handleClose();
|
|
879
1310
|
}
|
|
880
|
-
}
|
|
881
|
-
const
|
|
882
|
-
if (
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
setCurrentIndex((prev) => Math.min(prev + 1, imageUrls.length - 1));
|
|
899
|
-
setZoomLevel(1);
|
|
900
|
-
setPanOffset({ x: 0, y: 0 });
|
|
901
|
-
}, [imageUrls.length]);
|
|
902
|
-
useEffect(() => {
|
|
903
|
-
setZoomLevel(1);
|
|
904
|
-
setPanOffset({ x: 0, y: 0 });
|
|
905
|
-
setCurrentIndex(nodeDetailStartIndex);
|
|
906
|
-
}, [nodeDetailNodeId, nodeDetailStartIndex]);
|
|
907
|
-
useEffect(() => {
|
|
908
|
-
const handleKeyDown = (e) => {
|
|
909
|
-
if (activeModal !== "nodeDetail") return;
|
|
910
|
-
if (e.key === "Escape") {
|
|
911
|
-
closeNodeDetailModal();
|
|
912
|
-
}
|
|
913
|
-
if (e.key === "+" || e.key === "=") {
|
|
914
|
-
setZoomLevel((prev) => Math.min(prev + 0.25, 4));
|
|
915
|
-
}
|
|
916
|
-
if (e.key === "-") {
|
|
917
|
-
setZoomLevel((prev) => Math.max(prev - 0.25, 0.25));
|
|
918
|
-
}
|
|
919
|
-
if (e.key === "0") {
|
|
920
|
-
setZoomLevel(1);
|
|
921
|
-
setPanOffset({ x: 0, y: 0 });
|
|
922
|
-
}
|
|
923
|
-
if (e.key === "ArrowLeft") {
|
|
924
|
-
goToPrevious();
|
|
925
|
-
}
|
|
926
|
-
if (e.key === "ArrowRight") {
|
|
927
|
-
goToNext();
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
931
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
932
|
-
}, [activeModal, closeNodeDetailModal, goToPrevious, goToNext]);
|
|
933
|
-
const handleMouseDown = useCallback(
|
|
934
|
-
(e) => {
|
|
935
|
-
if (zoomLevel > 1) {
|
|
936
|
-
setIsPanning(true);
|
|
937
|
-
setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
|
|
938
|
-
}
|
|
939
|
-
},
|
|
940
|
-
[zoomLevel, panOffset]
|
|
941
|
-
);
|
|
942
|
-
const handleMouseMove = useCallback(
|
|
943
|
-
(e) => {
|
|
944
|
-
if (isPanning) {
|
|
945
|
-
setPanOffset({
|
|
946
|
-
x: e.clientX - panStart.x,
|
|
947
|
-
y: e.clientY - panStart.y
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
},
|
|
951
|
-
[isPanning, panStart]
|
|
952
|
-
);
|
|
953
|
-
const handleMouseUp = useCallback(() => {
|
|
954
|
-
setIsPanning(false);
|
|
955
|
-
}, []);
|
|
956
|
-
const handleDownload = useCallback(() => {
|
|
957
|
-
const url = displayUrl ?? mediaInfo.url;
|
|
958
|
-
if (!url) return;
|
|
959
|
-
const link = document.createElement("a");
|
|
960
|
-
link.href = url;
|
|
961
|
-
const suffix = hasMultipleImages ? `_${currentIndex + 1}` : "";
|
|
962
|
-
link.download = `${node?.data.label || "output"}${suffix}.${mediaInfo.type === "video" ? "mp4" : "png"}`;
|
|
963
|
-
document.body.appendChild(link);
|
|
964
|
-
link.click();
|
|
965
|
-
document.body.removeChild(link);
|
|
966
|
-
}, [displayUrl, mediaInfo, node, hasMultipleImages, currentIndex]);
|
|
967
|
-
if (activeModal !== "nodeDetail" || !node || !nodeDef) {
|
|
968
|
-
return null;
|
|
969
|
-
}
|
|
970
|
-
if (PROMPT_NODE_TYPES.includes(node.type)) {
|
|
971
|
-
return null;
|
|
972
|
-
}
|
|
973
|
-
const nodeData = node.data;
|
|
974
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
975
|
-
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 bg-black/80", onClick: closeNodeDetailModal }),
|
|
976
|
-
/* @__PURE__ */ jsxs("div", { className: "fixed inset-4 z-50 flex flex-col bg-card rounded-lg border border-border shadow-xl overflow-hidden", children: [
|
|
977
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [
|
|
978
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
979
|
-
/* @__PURE__ */ jsx("h2", { className: "text-lg font-medium text-foreground", children: nodeData.label }),
|
|
980
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground bg-secondary px-2 py-0.5 rounded", children: nodeDef.label })
|
|
981
|
-
] }),
|
|
982
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
983
|
-
displayUrl && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleDownload, children: [
|
|
984
|
-
/* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
|
|
985
|
-
"Download"
|
|
986
|
-
] }),
|
|
987
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: closeNodeDetailModal, children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" }) })
|
|
988
|
-
] })
|
|
989
|
-
] }),
|
|
990
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxs(
|
|
1311
|
+
};
|
|
1312
|
+
const handleKeyDown = (e) => {
|
|
1313
|
+
if (e.key === "Escape") {
|
|
1314
|
+
handleClose();
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
if (!isOpen) return null;
|
|
1318
|
+
const hasActualCost = actualCost > 0;
|
|
1319
|
+
const variance = hasActualCost ? actualCost - breakdown.total : 0;
|
|
1320
|
+
return /* @__PURE__ */ jsx(
|
|
1321
|
+
"div",
|
|
1322
|
+
{
|
|
1323
|
+
ref: backdropRef,
|
|
1324
|
+
onClick: handleBackdropClick,
|
|
1325
|
+
onKeyDown: handleKeyDown,
|
|
1326
|
+
className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
|
|
1327
|
+
style: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
|
1328
|
+
children: /* @__PURE__ */ jsxs(
|
|
991
1329
|
"div",
|
|
992
1330
|
{
|
|
993
|
-
className: "
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
onMouseUp: handleMouseUp,
|
|
997
|
-
onMouseLeave: handleMouseUp,
|
|
998
|
-
style: { cursor: zoomLevel > 1 ? isPanning ? "grabbing" : "grab" : "default" },
|
|
1331
|
+
className: "bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl w-full max-w-lg",
|
|
1332
|
+
role: "dialog",
|
|
1333
|
+
"aria-label": "Cost Breakdown",
|
|
999
1334
|
children: [
|
|
1000
|
-
|
|
1001
|
-
"div",
|
|
1002
|
-
|
|
1003
|
-
className: "
|
|
1004
|
-
|
|
1005
|
-
transform: `scale(${zoomLevel}) translate(${panOffset.x / zoomLevel}px, ${panOffset.y / zoomLevel}px)`
|
|
1006
|
-
},
|
|
1007
|
-
children: [
|
|
1008
|
-
mediaInfo.type === "image" && /* @__PURE__ */ jsx(
|
|
1009
|
-
Image,
|
|
1010
|
-
{
|
|
1011
|
-
src: displayUrl,
|
|
1012
|
-
alt: nodeData.label,
|
|
1013
|
-
width: 800,
|
|
1014
|
-
height: 600,
|
|
1015
|
-
className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] object-contain rounded-lg",
|
|
1016
|
-
unoptimized: true
|
|
1017
|
-
}
|
|
1018
|
-
),
|
|
1019
|
-
mediaInfo.type === "video" && /* @__PURE__ */ jsx(
|
|
1020
|
-
"video",
|
|
1021
|
-
{
|
|
1022
|
-
src: displayUrl,
|
|
1023
|
-
controls: true,
|
|
1024
|
-
autoPlay: true,
|
|
1025
|
-
loop: true,
|
|
1026
|
-
className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] rounded-lg"
|
|
1027
|
-
}
|
|
1028
|
-
)
|
|
1029
|
-
]
|
|
1030
|
-
}
|
|
1031
|
-
) : /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-center", children: [
|
|
1032
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg", children: "No preview available" }),
|
|
1033
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm mt-2", children: "Generate content to see the preview" })
|
|
1034
|
-
] }),
|
|
1035
|
-
hasMultipleImages && currentIndex > 0 && /* @__PURE__ */ jsx(
|
|
1036
|
-
Button,
|
|
1037
|
-
{
|
|
1038
|
-
variant: "ghost",
|
|
1039
|
-
size: "icon-sm",
|
|
1040
|
-
onClick: goToPrevious,
|
|
1041
|
-
className: "absolute left-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
|
|
1042
|
-
title: "Previous image (\u2190)",
|
|
1043
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-5 h-5" })
|
|
1044
|
-
}
|
|
1045
|
-
),
|
|
1046
|
-
hasMultipleImages && currentIndex < imageUrls.length - 1 && /* @__PURE__ */ jsx(
|
|
1047
|
-
Button,
|
|
1048
|
-
{
|
|
1049
|
-
variant: "ghost",
|
|
1050
|
-
size: "icon-sm",
|
|
1051
|
-
onClick: goToNext,
|
|
1052
|
-
className: "absolute right-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
|
|
1053
|
-
title: "Next image (\u2192)",
|
|
1054
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5" })
|
|
1055
|
-
}
|
|
1056
|
-
),
|
|
1057
|
-
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: [
|
|
1058
|
-
currentIndex + 1,
|
|
1059
|
-
" / ",
|
|
1060
|
-
imageUrls.length
|
|
1061
|
-
] }),
|
|
1062
|
-
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: [
|
|
1335
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[var(--border)]", children: [
|
|
1336
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1337
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "w-4 h-4 text-[var(--muted-foreground)]" }),
|
|
1338
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Cost Breakdown" })
|
|
1339
|
+
] }),
|
|
1063
1340
|
/* @__PURE__ */ jsx(
|
|
1064
|
-
|
|
1341
|
+
"button",
|
|
1065
1342
|
{
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
title: "Zoom out (-)",
|
|
1070
|
-
children: /* @__PURE__ */ jsx(ZoomOut, { className: "w-4 h-4" })
|
|
1343
|
+
onClick: handleClose,
|
|
1344
|
+
className: "p-1 rounded hover:bg-[var(--secondary)] transition-colors",
|
|
1345
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
1071
1346
|
}
|
|
1072
|
-
)
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1347
|
+
)
|
|
1348
|
+
] }),
|
|
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: [
|
|
1350
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-[40vh] overflow-y-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
1351
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs text-[var(--muted-foreground)] border-b border-[var(--border)]", children: [
|
|
1352
|
+
/* @__PURE__ */ jsx("th", { className: "pb-2 font-medium", children: "Node" }),
|
|
1353
|
+
/* @__PURE__ */ jsx("th", { className: "pb-2 font-medium", children: "Model" }),
|
|
1354
|
+
/* @__PURE__ */ jsx("th", { className: "pb-2 font-medium", children: "Unit" }),
|
|
1355
|
+
/* @__PURE__ */ jsx("th", { className: "pb-2 font-medium text-right", children: "Cost" })
|
|
1356
|
+
] }) }),
|
|
1357
|
+
/* @__PURE__ */ jsx("tbody", { children: breakdown.nodes.map((estimate) => /* @__PURE__ */ jsxs(
|
|
1358
|
+
"tr",
|
|
1359
|
+
{
|
|
1360
|
+
className: "border-b border-[var(--border)] last:border-0",
|
|
1361
|
+
children: [
|
|
1362
|
+
/* @__PURE__ */ jsx("td", { className: "py-2 truncate max-w-[120px]", title: estimate.nodeLabel, children: estimate.nodeLabel }),
|
|
1363
|
+
/* @__PURE__ */ jsx("td", { className: "py-2 text-[var(--muted-foreground)] font-mono text-xs", children: estimate.model }),
|
|
1364
|
+
/* @__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) })
|
|
1366
|
+
]
|
|
1367
|
+
},
|
|
1368
|
+
estimate.nodeId
|
|
1369
|
+
)) })
|
|
1370
|
+
] }) }),
|
|
1371
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 pt-3 border-t border-[var(--border)] space-y-1.5", children: [
|
|
1372
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm", children: [
|
|
1373
|
+
/* @__PURE__ */ jsx("span", { className: "text-[var(--muted-foreground)]", children: "Estimated Total" }),
|
|
1374
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: formatCost(breakdown.total) })
|
|
1375
|
+
] }),
|
|
1376
|
+
hasActualCost && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1377
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm", children: [
|
|
1378
|
+
/* @__PURE__ */ jsx("span", { className: "text-[var(--muted-foreground)]", children: "Actual Cost" }),
|
|
1379
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-green-500", children: formatCost(actualCost) })
|
|
1380
|
+
] }),
|
|
1381
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm", children: [
|
|
1382
|
+
/* @__PURE__ */ jsx("span", { className: "text-[var(--muted-foreground)]", children: "Variance" }),
|
|
1383
|
+
/* @__PURE__ */ jsxs(
|
|
1384
|
+
"span",
|
|
1385
|
+
{
|
|
1386
|
+
className: `font-mono text-xs ${variance > 0 ? "text-red-400" : variance < 0 ? "text-green-400" : "text-[var(--muted-foreground)]"}`,
|
|
1387
|
+
children: [
|
|
1388
|
+
variance > 0 ? "+" : "",
|
|
1389
|
+
formatCost(variance)
|
|
1390
|
+
]
|
|
1391
|
+
}
|
|
1392
|
+
)
|
|
1393
|
+
] })
|
|
1394
|
+
] })
|
|
1395
|
+
] })
|
|
1396
|
+
] }) })
|
|
1397
|
+
]
|
|
1398
|
+
}
|
|
1399
|
+
)
|
|
1400
|
+
}
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
var EMPTY_HISTORY = [];
|
|
1404
|
+
function formatRelativeTime(timestamp) {
|
|
1405
|
+
const diff = Date.now() - timestamp;
|
|
1406
|
+
const seconds = Math.floor(diff / 1e3);
|
|
1407
|
+
const minutes = Math.floor(seconds / 60);
|
|
1408
|
+
const hours = Math.floor(minutes / 60);
|
|
1409
|
+
if (hours > 0) return `${hours}h ago`;
|
|
1410
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
1411
|
+
return "Just now";
|
|
1412
|
+
}
|
|
1413
|
+
function calculateFanPosition(index, _total) {
|
|
1414
|
+
const verticalSpacing = 60;
|
|
1415
|
+
const curveStrength = 0.15;
|
|
1416
|
+
const xOffset = index * index * curveStrength;
|
|
1417
|
+
const x = -28 + xOffset;
|
|
1418
|
+
const y = -(index * verticalSpacing + 56);
|
|
1419
|
+
return { x, y };
|
|
1420
|
+
}
|
|
1421
|
+
function FanItem({
|
|
1422
|
+
item,
|
|
1423
|
+
index,
|
|
1424
|
+
total,
|
|
1425
|
+
onDragStart
|
|
1426
|
+
}) {
|
|
1427
|
+
const { x, y } = calculateFanPosition(index);
|
|
1428
|
+
const delay = index * 30;
|
|
1429
|
+
return /* @__PURE__ */ jsx(
|
|
1430
|
+
"button",
|
|
1431
|
+
{
|
|
1432
|
+
draggable: true,
|
|
1433
|
+
onDragStart: (e) => onDragStart(e, item),
|
|
1434
|
+
className: "absolute w-14 h-14 rounded-lg overflow-hidden border-2 border-neutral-600 hover:border-blue-500 shadow-lg cursor-grab active:cursor-grabbing transition-colors duration-150 animate-fan-enter group",
|
|
1435
|
+
style: {
|
|
1436
|
+
"--fan-x": `${x}px`,
|
|
1437
|
+
"--fan-y": `${y}px`,
|
|
1438
|
+
animationDelay: `${delay}ms`,
|
|
1439
|
+
zIndex: 10 - index
|
|
1440
|
+
},
|
|
1441
|
+
title: `${formatRelativeTime(item.timestamp)}
|
|
1442
|
+
${item.prompt?.substring(0, 50) || ""}...`,
|
|
1443
|
+
children: /* @__PURE__ */ jsx(
|
|
1444
|
+
"img",
|
|
1445
|
+
{
|
|
1446
|
+
src: item.image,
|
|
1447
|
+
alt: `History ${index + 1}`,
|
|
1448
|
+
className: "w-full h-full object-cover pointer-events-none",
|
|
1449
|
+
draggable: false
|
|
1450
|
+
}
|
|
1451
|
+
)
|
|
1452
|
+
}
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
function HistorySidebar({
|
|
1456
|
+
history,
|
|
1457
|
+
onClear,
|
|
1458
|
+
onClose,
|
|
1459
|
+
onDragStart,
|
|
1460
|
+
triggerRect
|
|
1461
|
+
}) {
|
|
1462
|
+
const sidebarRef = useRef(null);
|
|
1463
|
+
useEffect(() => {
|
|
1464
|
+
const handleClickOutside = (event) => {
|
|
1465
|
+
if (sidebarRef.current && !sidebarRef.current.contains(event.target)) {
|
|
1466
|
+
onClose();
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1470
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
1471
|
+
}, [onClose]);
|
|
1472
|
+
useEffect(() => {
|
|
1473
|
+
const handleKeyDown = (e) => {
|
|
1474
|
+
if (e.key === "Escape") onClose();
|
|
1475
|
+
};
|
|
1476
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1477
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1478
|
+
}, [onClose]);
|
|
1479
|
+
const sidebarStyle = {
|
|
1480
|
+
position: "fixed",
|
|
1481
|
+
zIndex: 200
|
|
1482
|
+
};
|
|
1483
|
+
if (triggerRect) {
|
|
1484
|
+
const left = Math.max(16, triggerRect.left - 140);
|
|
1485
|
+
const bottom = window.innerHeight - triggerRect.top + 8;
|
|
1486
|
+
sidebarStyle.left = `${left}px`;
|
|
1487
|
+
sidebarStyle.bottom = `${bottom}px`;
|
|
1488
|
+
} else {
|
|
1489
|
+
sidebarStyle.right = "100px";
|
|
1490
|
+
sidebarStyle.bottom = "100px";
|
|
1491
|
+
}
|
|
1492
|
+
return createPortal(
|
|
1493
|
+
/* @__PURE__ */ jsxs(
|
|
1494
|
+
"div",
|
|
1495
|
+
{
|
|
1496
|
+
ref: sidebarRef,
|
|
1497
|
+
className: "w-80 max-h-[420px] bg-neutral-800 border border-neutral-600 rounded-lg shadow-xl flex flex-col",
|
|
1498
|
+
style: sidebarStyle,
|
|
1499
|
+
children: [
|
|
1500
|
+
/* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-b border-neutral-700 flex items-center justify-between shrink-0", children: [
|
|
1501
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm text-neutral-200 font-medium", children: [
|
|
1502
|
+
"All History (",
|
|
1503
|
+
history.length,
|
|
1504
|
+
")"
|
|
1505
|
+
] }),
|
|
1506
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1077
1507
|
/* @__PURE__ */ jsx(
|
|
1078
|
-
|
|
1508
|
+
"button",
|
|
1079
1509
|
{
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
children: /* @__PURE__ */ jsx(ZoomIn, { className: "w-4 h-4" })
|
|
1510
|
+
onClick: onClear,
|
|
1511
|
+
className: "text-[10px] text-neutral-500 hover:text-red-400 transition-colors",
|
|
1512
|
+
title: "Clear all history",
|
|
1513
|
+
children: "Clear All"
|
|
1085
1514
|
}
|
|
1086
1515
|
),
|
|
1087
1516
|
/* @__PURE__ */ jsx(
|
|
1088
|
-
|
|
1517
|
+
"button",
|
|
1089
1518
|
{
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1519
|
+
onClick: onClose,
|
|
1520
|
+
className: "w-5 h-5 rounded hover:bg-neutral-700 flex items-center justify-center text-neutral-400 hover:text-white transition-colors",
|
|
1521
|
+
title: "Close",
|
|
1522
|
+
children: /* @__PURE__ */ jsx(
|
|
1523
|
+
"svg",
|
|
1524
|
+
{
|
|
1525
|
+
className: "w-3 h-3",
|
|
1526
|
+
fill: "none",
|
|
1527
|
+
viewBox: "0 0 24 24",
|
|
1528
|
+
stroke: "currentColor",
|
|
1529
|
+
strokeWidth: 2,
|
|
1530
|
+
children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })
|
|
1531
|
+
}
|
|
1532
|
+
)
|
|
1098
1533
|
}
|
|
1099
1534
|
)
|
|
1100
1535
|
] })
|
|
1101
|
-
]
|
|
1536
|
+
] }),
|
|
1537
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-2 space-y-1.5", children: history.map((item, index) => /* @__PURE__ */ jsxs(
|
|
1538
|
+
"div",
|
|
1539
|
+
{
|
|
1540
|
+
draggable: true,
|
|
1541
|
+
onDragStart: (e) => onDragStart(e, item),
|
|
1542
|
+
className: "flex gap-3 p-2 rounded-lg hover:bg-neutral-700/50 cursor-grab active:cursor-grabbing group transition-colors",
|
|
1543
|
+
children: [
|
|
1544
|
+
/* @__PURE__ */ jsx("div", { className: "w-14 h-14 rounded overflow-hidden shrink-0 border border-neutral-600 group-hover:border-blue-500 transition-colors", children: /* @__PURE__ */ jsx(
|
|
1545
|
+
"img",
|
|
1546
|
+
{
|
|
1547
|
+
src: item.image,
|
|
1548
|
+
alt: `History ${index + 1}`,
|
|
1549
|
+
className: "w-full h-full object-cover pointer-events-none",
|
|
1550
|
+
draggable: false
|
|
1551
|
+
}
|
|
1552
|
+
) }),
|
|
1553
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 flex flex-col justify-center", children: [
|
|
1554
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-neutral-300 truncate", children: item.prompt?.substring(0, 60) || "No prompt" }),
|
|
1555
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-neutral-500 mt-0.5", children: [
|
|
1556
|
+
formatRelativeTime(item.timestamp),
|
|
1557
|
+
item.model ? ` \xB7 ${item.model}` : ""
|
|
1558
|
+
] })
|
|
1559
|
+
] })
|
|
1560
|
+
]
|
|
1561
|
+
},
|
|
1562
|
+
item.id
|
|
1563
|
+
)) }),
|
|
1564
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-2 border-t border-neutral-700 bg-neutral-900/50 shrink-0", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-500", children: "Drag images to canvas to create nodes" }) })
|
|
1565
|
+
]
|
|
1566
|
+
}
|
|
1567
|
+
),
|
|
1568
|
+
document.body
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
function GlobalImageHistory() {
|
|
1572
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
1573
|
+
const [showSidebar, setShowSidebar] = useState(false);
|
|
1574
|
+
const drawerRef = useRef(null);
|
|
1575
|
+
const triggerRef = useRef(null);
|
|
1576
|
+
const history = useWorkflowStore((state) => state.globalImageHistory ?? EMPTY_HISTORY);
|
|
1577
|
+
const clearGlobalHistory = useWorkflowStore((state) => state.clearGlobalHistory);
|
|
1578
|
+
const fanItems = history.slice(0, 10);
|
|
1579
|
+
const hasOverflow = history.length > 10;
|
|
1580
|
+
useEffect(() => {
|
|
1581
|
+
const handleClickOutside = (event) => {
|
|
1582
|
+
if (drawerRef.current && !drawerRef.current.contains(event.target)) {
|
|
1583
|
+
setIsOpen(false);
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
if (isOpen && !showSidebar) {
|
|
1587
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1588
|
+
}
|
|
1589
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
1590
|
+
}, [isOpen, showSidebar]);
|
|
1591
|
+
useEffect(() => {
|
|
1592
|
+
const handleKeyDown = (e) => {
|
|
1593
|
+
if (e.key === "Escape") {
|
|
1594
|
+
if (showSidebar) {
|
|
1595
|
+
setShowSidebar(false);
|
|
1596
|
+
} else {
|
|
1597
|
+
setIsOpen(false);
|
|
1102
1598
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
if (isOpen || showSidebar) {
|
|
1602
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1603
|
+
}
|
|
1604
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1605
|
+
}, [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
|
+
}, []);
|
|
1621
|
+
const handleShowAll = useCallback(() => {
|
|
1622
|
+
setIsOpen(false);
|
|
1623
|
+
setShowSidebar(true);
|
|
1624
|
+
}, []);
|
|
1625
|
+
const handleCloseSidebar = useCallback(() => {
|
|
1626
|
+
setShowSidebar(false);
|
|
1627
|
+
}, []);
|
|
1628
|
+
const handleClear = useCallback(() => {
|
|
1629
|
+
clearGlobalHistory();
|
|
1630
|
+
setIsOpen(false);
|
|
1631
|
+
setShowSidebar(false);
|
|
1632
|
+
}, [clearGlobalHistory]);
|
|
1633
|
+
if (history.length === 0) return null;
|
|
1634
|
+
return /* @__PURE__ */ jsxs("div", { ref: drawerRef, className: "absolute bottom-4 right-64 z-10", children: [
|
|
1635
|
+
/* @__PURE__ */ jsxs(
|
|
1636
|
+
"button",
|
|
1637
|
+
{
|
|
1638
|
+
ref: triggerRef,
|
|
1639
|
+
onClick: () => setIsOpen(!isOpen),
|
|
1640
|
+
className: "relative w-8 h-8 rounded-lg flex items-center justify-center bg-neutral-800 hover:bg-neutral-700 border border-neutral-600 text-neutral-400 hover:text-white shadow-lg transition-colors",
|
|
1641
|
+
title: `${history.length} image${history.length > 1 ? "s" : ""} in history`,
|
|
1642
|
+
children: [
|
|
1643
|
+
/* @__PURE__ */ jsx(
|
|
1644
|
+
"svg",
|
|
1645
|
+
{
|
|
1646
|
+
className: "w-4 h-4",
|
|
1647
|
+
fill: "none",
|
|
1648
|
+
viewBox: "0 0 24 24",
|
|
1649
|
+
stroke: "currentColor",
|
|
1650
|
+
strokeWidth: 2,
|
|
1651
|
+
children: /* @__PURE__ */ jsx(
|
|
1652
|
+
"path",
|
|
1653
|
+
{
|
|
1654
|
+
strokeLinecap: "round",
|
|
1655
|
+
strokeLinejoin: "round",
|
|
1656
|
+
d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
1657
|
+
}
|
|
1658
|
+
)
|
|
1659
|
+
}
|
|
1660
|
+
),
|
|
1661
|
+
/* @__PURE__ */ jsx("span", { className: "absolute -top-1.5 -right-1.5 min-w-[18px] h-[18px] px-1 bg-blue-500 rounded-full text-[10px] text-white flex items-center justify-center font-bold", children: history.length > 99 ? "99+" : history.length })
|
|
1662
|
+
]
|
|
1663
|
+
}
|
|
1664
|
+
),
|
|
1665
|
+
isOpen && /* @__PURE__ */ jsxs(
|
|
1666
|
+
"div",
|
|
1667
|
+
{
|
|
1668
|
+
className: "absolute bottom-full right-1/2 translate-x-1/2 mb-2",
|
|
1669
|
+
style: { zIndex: 100 },
|
|
1670
|
+
children: [
|
|
1671
|
+
/* @__PURE__ */ jsx("div", { className: "relative w-0 h-0", children: fanItems.map((item, index) => /* @__PURE__ */ jsx(
|
|
1672
|
+
FanItem,
|
|
1673
|
+
{
|
|
1674
|
+
item,
|
|
1675
|
+
index,
|
|
1676
|
+
total: fanItems.length,
|
|
1677
|
+
onDragStart: handleDragStart
|
|
1678
|
+
},
|
|
1679
|
+
item.id
|
|
1680
|
+
)) }),
|
|
1681
|
+
hasOverflow && (() => {
|
|
1682
|
+
const topItemPos = calculateFanPosition(fanItems.length - 1, fanItems.length);
|
|
1683
|
+
return /* @__PURE__ */ jsxs(
|
|
1684
|
+
"button",
|
|
1685
|
+
{
|
|
1686
|
+
onClick: handleShowAll,
|
|
1687
|
+
className: "absolute animate-fan-enter bg-neutral-800 hover:bg-neutral-700 border border-neutral-600 rounded-lg px-2 py-1 text-[10px] text-neutral-300 hover:text-white shadow-lg transition-colors whitespace-nowrap",
|
|
1688
|
+
style: {
|
|
1689
|
+
"--fan-x": `${topItemPos.x}px`,
|
|
1690
|
+
"--fan-y": `${topItemPos.y - 60}px`,
|
|
1691
|
+
animationDelay: `${fanItems.length * 30}ms`
|
|
1692
|
+
},
|
|
1693
|
+
children: [
|
|
1694
|
+
"+",
|
|
1695
|
+
history.length - 10,
|
|
1696
|
+
" more"
|
|
1697
|
+
]
|
|
1698
|
+
}
|
|
1699
|
+
);
|
|
1700
|
+
})()
|
|
1701
|
+
]
|
|
1702
|
+
}
|
|
1703
|
+
),
|
|
1704
|
+
showSidebar && /* @__PURE__ */ jsx(
|
|
1705
|
+
HistorySidebar,
|
|
1706
|
+
{
|
|
1707
|
+
history,
|
|
1708
|
+
onClear: handleClear,
|
|
1709
|
+
onClose: handleCloseSidebar,
|
|
1710
|
+
onDragStart: handleDragStart,
|
|
1711
|
+
triggerRect: triggerRef.current?.getBoundingClientRect() || null
|
|
1712
|
+
}
|
|
1713
|
+
)
|
|
1105
1714
|
] });
|
|
1106
1715
|
}
|
|
1716
|
+
var NODE_GAP = 32;
|
|
1717
|
+
var EST_NODE_WIDTH = 280;
|
|
1718
|
+
var EST_NODE_HEIGHT = 200;
|
|
1719
|
+
function MultiSelectToolbarComponent({ onDownloadAsZip }) {
|
|
1720
|
+
const { nodes, selectedNodeIds, onNodesChange, createGroup, deleteGroup, groups } = useWorkflowStore();
|
|
1721
|
+
const reactFlow = useReactFlow();
|
|
1722
|
+
const selectedNodes = useMemo(
|
|
1723
|
+
() => nodes.filter((n) => selectedNodeIds.includes(n.id)),
|
|
1724
|
+
[nodes, selectedNodeIds]
|
|
1725
|
+
);
|
|
1726
|
+
const selectedGroup = useMemo(() => {
|
|
1727
|
+
if (selectedNodes.length < 2) return null;
|
|
1728
|
+
return groups.find((g) => selectedNodeIds.every((id) => g.nodeIds.includes(id))) ?? null;
|
|
1729
|
+
}, [groups, selectedNodeIds, selectedNodes.length]);
|
|
1730
|
+
const toolbarPosition = useMemo(() => {
|
|
1731
|
+
if (selectedNodes.length < 2) return null;
|
|
1732
|
+
let minX = Infinity;
|
|
1733
|
+
let minY = Infinity;
|
|
1734
|
+
let maxX = -Infinity;
|
|
1735
|
+
for (const node of selectedNodes) {
|
|
1736
|
+
const x = node.position.x;
|
|
1737
|
+
const y = node.position.y;
|
|
1738
|
+
const w = node.measured?.width ?? EST_NODE_WIDTH;
|
|
1739
|
+
if (x < minX) minX = x;
|
|
1740
|
+
if (y < minY) minY = y;
|
|
1741
|
+
if (x + w > maxX) maxX = x + w;
|
|
1742
|
+
}
|
|
1743
|
+
const centerX = (minX + maxX) / 2;
|
|
1744
|
+
const topY = minY - 48;
|
|
1745
|
+
return reactFlow.flowToScreenPosition({ x: centerX, y: topY });
|
|
1746
|
+
}, [selectedNodes, reactFlow]);
|
|
1747
|
+
const stackHorizontal = useCallback(() => {
|
|
1748
|
+
if (selectedNodes.length < 2) return;
|
|
1749
|
+
const sorted = [...selectedNodes].sort((a, b) => a.position.x - b.position.x);
|
|
1750
|
+
const baseY = sorted[0].position.y;
|
|
1751
|
+
const changes = sorted.map((node, i) => ({
|
|
1752
|
+
type: "position",
|
|
1753
|
+
id: node.id,
|
|
1754
|
+
position: {
|
|
1755
|
+
x: sorted[0].position.x + i * (EST_NODE_WIDTH + NODE_GAP),
|
|
1756
|
+
y: baseY
|
|
1757
|
+
}
|
|
1758
|
+
}));
|
|
1759
|
+
onNodesChange(changes);
|
|
1760
|
+
}, [selectedNodes, onNodesChange]);
|
|
1761
|
+
const stackVertical = useCallback(() => {
|
|
1762
|
+
if (selectedNodes.length < 2) return;
|
|
1763
|
+
const sorted = [...selectedNodes].sort((a, b) => a.position.y - b.position.y);
|
|
1764
|
+
const baseX = sorted[0].position.x;
|
|
1765
|
+
const changes = sorted.map((node, i) => ({
|
|
1766
|
+
type: "position",
|
|
1767
|
+
id: node.id,
|
|
1768
|
+
position: {
|
|
1769
|
+
x: baseX,
|
|
1770
|
+
y: sorted[0].position.y + i * (EST_NODE_HEIGHT + NODE_GAP)
|
|
1771
|
+
}
|
|
1772
|
+
}));
|
|
1773
|
+
onNodesChange(changes);
|
|
1774
|
+
}, [selectedNodes, onNodesChange]);
|
|
1775
|
+
const arrangeGrid = useCallback(() => {
|
|
1776
|
+
if (selectedNodes.length < 2) return;
|
|
1777
|
+
const cols = Math.ceil(Math.sqrt(selectedNodes.length));
|
|
1778
|
+
const sorted = [...selectedNodes].sort((a, b) => {
|
|
1779
|
+
const rowDiff = Math.floor(a.position.y / EST_NODE_HEIGHT) - Math.floor(b.position.y / EST_NODE_HEIGHT);
|
|
1780
|
+
if (rowDiff !== 0) return rowDiff;
|
|
1781
|
+
return a.position.x - b.position.x;
|
|
1782
|
+
});
|
|
1783
|
+
const baseX = sorted[0].position.x;
|
|
1784
|
+
const baseY = sorted[0].position.y;
|
|
1785
|
+
const changes = sorted.map((node, i) => ({
|
|
1786
|
+
type: "position",
|
|
1787
|
+
id: node.id,
|
|
1788
|
+
position: {
|
|
1789
|
+
x: baseX + i % cols * (EST_NODE_WIDTH + NODE_GAP),
|
|
1790
|
+
y: baseY + Math.floor(i / cols) * (EST_NODE_HEIGHT + NODE_GAP)
|
|
1791
|
+
}
|
|
1792
|
+
}));
|
|
1793
|
+
onNodesChange(changes);
|
|
1794
|
+
}, [selectedNodes, onNodesChange]);
|
|
1795
|
+
const handleGroup = useCallback(() => {
|
|
1796
|
+
if (selectedNodeIds.length < 2) return;
|
|
1797
|
+
createGroup(selectedNodeIds);
|
|
1798
|
+
}, [selectedNodeIds, createGroup]);
|
|
1799
|
+
const handleUngroup = useCallback(() => {
|
|
1800
|
+
if (!selectedGroup) return;
|
|
1801
|
+
deleteGroup(selectedGroup.id);
|
|
1802
|
+
}, [selectedGroup, deleteGroup]);
|
|
1803
|
+
if (selectedNodes.length < 2 || !toolbarPosition) return null;
|
|
1804
|
+
return /* @__PURE__ */ jsxs(
|
|
1805
|
+
"div",
|
|
1806
|
+
{
|
|
1807
|
+
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",
|
|
1808
|
+
style: {
|
|
1809
|
+
left: toolbarPosition.x,
|
|
1810
|
+
top: toolbarPosition.y,
|
|
1811
|
+
transform: "translateX(-50%)"
|
|
1812
|
+
},
|
|
1813
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1814
|
+
children: [
|
|
1815
|
+
/* @__PURE__ */ jsx("span", { className: "px-1.5 text-xs font-medium text-[var(--muted-foreground)]", children: selectedNodeIds.length }),
|
|
1816
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-[var(--border)]" }),
|
|
1817
|
+
/* @__PURE__ */ jsx(
|
|
1818
|
+
"button",
|
|
1819
|
+
{
|
|
1820
|
+
onClick: stackHorizontal,
|
|
1821
|
+
title: "Stack horizontal",
|
|
1822
|
+
className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
1823
|
+
children: /* @__PURE__ */ jsx(AlignHorizontalSpaceAround, { className: "h-3.5 w-3.5" })
|
|
1824
|
+
}
|
|
1825
|
+
),
|
|
1826
|
+
/* @__PURE__ */ jsx(
|
|
1827
|
+
"button",
|
|
1828
|
+
{
|
|
1829
|
+
onClick: stackVertical,
|
|
1830
|
+
title: "Stack vertical",
|
|
1831
|
+
className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
1832
|
+
children: /* @__PURE__ */ jsx(AlignVerticalSpaceAround, { className: "h-3.5 w-3.5" })
|
|
1833
|
+
}
|
|
1834
|
+
),
|
|
1835
|
+
/* @__PURE__ */ jsx(
|
|
1836
|
+
"button",
|
|
1837
|
+
{
|
|
1838
|
+
onClick: arrangeGrid,
|
|
1839
|
+
title: "Arrange as grid",
|
|
1840
|
+
className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
1841
|
+
children: /* @__PURE__ */ jsx(Grid3X3, { className: "h-3.5 w-3.5" })
|
|
1842
|
+
}
|
|
1843
|
+
),
|
|
1844
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-[var(--border)]" }),
|
|
1845
|
+
selectedGroup ? /* @__PURE__ */ jsx(
|
|
1846
|
+
"button",
|
|
1847
|
+
{
|
|
1848
|
+
onClick: handleUngroup,
|
|
1849
|
+
title: "Ungroup",
|
|
1850
|
+
className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
1851
|
+
children: /* @__PURE__ */ jsx(Ungroup, { className: "h-3.5 w-3.5" })
|
|
1852
|
+
}
|
|
1853
|
+
) : /* @__PURE__ */ jsx(
|
|
1854
|
+
"button",
|
|
1855
|
+
{
|
|
1856
|
+
onClick: handleGroup,
|
|
1857
|
+
title: "Group",
|
|
1858
|
+
className: "flex h-7 w-7 items-center justify-center rounded text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
1859
|
+
children: /* @__PURE__ */ jsx(Group, { className: "h-3.5 w-3.5" })
|
|
1860
|
+
}
|
|
1861
|
+
)
|
|
1862
|
+
]
|
|
1863
|
+
}
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
var MultiSelectToolbar = memo(MultiSelectToolbarComponent);
|
|
1867
|
+
var typeStyles = {
|
|
1868
|
+
info: "bg-neutral-800 border-neutral-600 text-neutral-100",
|
|
1869
|
+
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"
|
|
1872
|
+
};
|
|
1873
|
+
var typeIcons = {
|
|
1874
|
+
info: Info,
|
|
1875
|
+
success: CheckCircle,
|
|
1876
|
+
warning: AlertTriangle,
|
|
1877
|
+
error: XCircle
|
|
1878
|
+
};
|
|
1879
|
+
function NotificationItem({
|
|
1880
|
+
id,
|
|
1881
|
+
type,
|
|
1882
|
+
title,
|
|
1883
|
+
message
|
|
1884
|
+
}) {
|
|
1885
|
+
const removeNotification = useUIStore((state) => state.removeNotification);
|
|
1886
|
+
const [copied, setCopied] = useState(false);
|
|
1887
|
+
const handleCopy = useCallback(async () => {
|
|
1888
|
+
const textToCopy = message ? `${title}
|
|
1889
|
+
|
|
1890
|
+
${message}` : title;
|
|
1891
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
1892
|
+
setCopied(true);
|
|
1893
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1894
|
+
}, [title, message]);
|
|
1895
|
+
const Icon = typeIcons[type];
|
|
1896
|
+
return /* @__PURE__ */ jsx(
|
|
1897
|
+
"div",
|
|
1898
|
+
{
|
|
1899
|
+
className: `flex flex-col rounded-lg border shadow-xl animate-in fade-in slide-in-from-top-4 duration-300 max-w-md ${typeStyles[type]}`,
|
|
1900
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
1901
|
+
/* @__PURE__ */ jsx(Icon, { className: "w-5 h-5 shrink-0" }),
|
|
1902
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1903
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: title }),
|
|
1904
|
+
message && /* @__PURE__ */ jsx("p", { className: "text-xs opacity-80 mt-0.5 break-words", children: message })
|
|
1905
|
+
] }),
|
|
1906
|
+
/* @__PURE__ */ jsx(
|
|
1907
|
+
"button",
|
|
1908
|
+
{
|
|
1909
|
+
onClick: handleCopy,
|
|
1910
|
+
className: "p-1 rounded hover:bg-white/10 transition-colors shrink-0",
|
|
1911
|
+
title: "Copy message",
|
|
1912
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Copy, { className: "w-4 h-4" })
|
|
1913
|
+
}
|
|
1914
|
+
),
|
|
1915
|
+
/* @__PURE__ */ jsx(
|
|
1916
|
+
"button",
|
|
1917
|
+
{
|
|
1918
|
+
onClick: () => removeNotification(id),
|
|
1919
|
+
className: "p-1 rounded hover:bg-white/10 transition-colors shrink-0",
|
|
1920
|
+
title: "Dismiss",
|
|
1921
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
1922
|
+
}
|
|
1923
|
+
)
|
|
1924
|
+
] })
|
|
1925
|
+
}
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
function NotificationToast() {
|
|
1929
|
+
const notifications = useUIStore((state) => state.notifications);
|
|
1930
|
+
if (notifications.length === 0) return null;
|
|
1931
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed top-6 right-6 z-[200] flex flex-col gap-2", children: notifications.map((notification) => /* @__PURE__ */ jsx(
|
|
1932
|
+
NotificationItem,
|
|
1933
|
+
{
|
|
1934
|
+
id: notification.id,
|
|
1935
|
+
type: notification.type,
|
|
1936
|
+
title: notification.title,
|
|
1937
|
+
message: notification.message
|
|
1938
|
+
},
|
|
1939
|
+
notification.id
|
|
1940
|
+
)) });
|
|
1941
|
+
}
|
|
1107
1942
|
var DEFAULT_NODE_COLOR = "#6b7280";
|
|
1108
1943
|
function supportsImageInput(schema) {
|
|
1109
1944
|
if (!schema) return true;
|
|
@@ -1119,7 +1954,12 @@ function getEdgeDataType(edge, nodeMap) {
|
|
|
1119
1954
|
const sourceHandle = nodeDef.outputs.find((h) => h.id === edge.sourceHandle);
|
|
1120
1955
|
return sourceHandle?.type ?? null;
|
|
1121
1956
|
}
|
|
1122
|
-
|
|
1957
|
+
var edgeTypesMap = {
|
|
1958
|
+
default: EditableEdge,
|
|
1959
|
+
bezier: EditableEdge,
|
|
1960
|
+
reference: ReferenceEdge
|
|
1961
|
+
};
|
|
1962
|
+
function WorkflowCanvas({ nodeTypes: nodeTypesProp, onDownloadAsZip } = {}) {
|
|
1123
1963
|
const {
|
|
1124
1964
|
nodes,
|
|
1125
1965
|
edges,
|
|
@@ -1140,11 +1980,13 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1140
1980
|
} = useWorkflowStore();
|
|
1141
1981
|
const {
|
|
1142
1982
|
selectNode,
|
|
1983
|
+
selectEdge,
|
|
1143
1984
|
setHighlightedNodeIds,
|
|
1144
1985
|
highlightedNodeIds,
|
|
1145
1986
|
showPalette,
|
|
1146
1987
|
togglePalette,
|
|
1147
|
-
openModal
|
|
1988
|
+
openModal,
|
|
1989
|
+
openConnectionDropMenu
|
|
1148
1990
|
} = useUIStore();
|
|
1149
1991
|
const reactFlow = useReactFlow();
|
|
1150
1992
|
const { edgeStyle, showMinimap } = useSettingsStore();
|
|
@@ -1157,8 +1999,28 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1157
1999
|
const [draggingNodeId, setDraggingNodeId] = useState(null);
|
|
1158
2000
|
const hideTimeoutRef = useRef(null);
|
|
1159
2001
|
const MINIMAP_HIDE_DELAY = 1500;
|
|
2002
|
+
const {
|
|
2003
|
+
isOpen: isContextMenuOpen,
|
|
2004
|
+
position: contextMenuPosition,
|
|
2005
|
+
menuItems: contextMenuItems,
|
|
2006
|
+
openNodeMenu,
|
|
2007
|
+
openEdgeMenu,
|
|
2008
|
+
openPaneMenu,
|
|
2009
|
+
openSelectionMenu,
|
|
2010
|
+
close: closeContextMenu
|
|
2011
|
+
} = useContextMenu();
|
|
1160
2012
|
const openShortcutHelp = useCallback(() => openModal("shortcutHelp"), [openModal]);
|
|
1161
2013
|
const openNodeSearch = useCallback(() => openModal("nodeSearch"), [openModal]);
|
|
2014
|
+
const deleteSelectedElements = useCallback(() => {
|
|
2015
|
+
const nodesToDelete = nodes.filter((n) => selectedNodeIds.includes(n.id));
|
|
2016
|
+
const edgesToDelete = edges.filter((e) => e.selected);
|
|
2017
|
+
if (nodesToDelete.length > 0) {
|
|
2018
|
+
onNodesChange(nodesToDelete.map((n) => ({ type: "remove", id: n.id })));
|
|
2019
|
+
}
|
|
2020
|
+
if (edgesToDelete.length > 0) {
|
|
2021
|
+
onEdgesChange(edgesToDelete.map((e) => ({ type: "remove", id: e.id })));
|
|
2022
|
+
}
|
|
2023
|
+
}, [nodes, edges, selectedNodeIds, onNodesChange, onEdgesChange]);
|
|
1162
2024
|
useCanvasKeyboardShortcuts({
|
|
1163
2025
|
selectedNodeIds,
|
|
1164
2026
|
groups,
|
|
@@ -1171,7 +2033,8 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1171
2033
|
togglePalette,
|
|
1172
2034
|
fitView: reactFlow.fitView,
|
|
1173
2035
|
openShortcutHelp,
|
|
1174
|
-
openNodeSearch
|
|
2036
|
+
openNodeSearch,
|
|
2037
|
+
deleteSelectedElements
|
|
1175
2038
|
});
|
|
1176
2039
|
useEffect(() => {
|
|
1177
2040
|
if (selectedNodeIds.length === 0) {
|
|
@@ -1209,11 +2072,13 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1209
2072
|
const dataType = getEdgeDataType(edge, nodeMap);
|
|
1210
2073
|
const typeClass = dataType ? `edge-${dataType}` : "";
|
|
1211
2074
|
const isDisabledTarget = isEdgeTargetingDisabledInput(edge);
|
|
2075
|
+
const enrichedData = { ...edge.data, dataType };
|
|
1212
2076
|
if (!isRunning && hasActiveNodeExecutions) {
|
|
1213
2077
|
const isActiveEdge = activeNodeExecutions.has(edge.source) || activeNodeExecutions.has(edge.target);
|
|
1214
2078
|
if (isActiveEdge && !isDisabledTarget) {
|
|
1215
2079
|
return {
|
|
1216
2080
|
...edge,
|
|
2081
|
+
data: enrichedData,
|
|
1217
2082
|
animated: false,
|
|
1218
2083
|
className: `${typeClass} executing`.trim()
|
|
1219
2084
|
};
|
|
@@ -1225,6 +2090,7 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1225
2090
|
if (isDisabledTarget) {
|
|
1226
2091
|
return {
|
|
1227
2092
|
...edge,
|
|
2093
|
+
data: enrichedData,
|
|
1228
2094
|
animated: false,
|
|
1229
2095
|
className: `${typeClass} edge-disabled`.trim()
|
|
1230
2096
|
};
|
|
@@ -1232,12 +2098,14 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1232
2098
|
if (!isInExecutionScope) {
|
|
1233
2099
|
return {
|
|
1234
2100
|
...edge,
|
|
2101
|
+
data: enrichedData,
|
|
1235
2102
|
animated: false,
|
|
1236
2103
|
className: typeClass
|
|
1237
2104
|
};
|
|
1238
2105
|
}
|
|
1239
2106
|
return {
|
|
1240
2107
|
...edge,
|
|
2108
|
+
data: enrichedData,
|
|
1241
2109
|
animated: false,
|
|
1242
2110
|
className: `${typeClass} ${isExecutingEdge ? "executing" : "active-pipe"}`.trim()
|
|
1243
2111
|
};
|
|
@@ -1245,6 +2113,7 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1245
2113
|
if (isDisabledTarget) {
|
|
1246
2114
|
return {
|
|
1247
2115
|
...edge,
|
|
2116
|
+
data: enrichedData,
|
|
1248
2117
|
className: `${typeClass} edge-disabled`.trim()
|
|
1249
2118
|
};
|
|
1250
2119
|
}
|
|
@@ -1252,11 +2121,13 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1252
2121
|
const isConnected = highlightedSet.has(edge.source) && highlightedSet.has(edge.target);
|
|
1253
2122
|
return {
|
|
1254
2123
|
...edge,
|
|
2124
|
+
data: enrichedData,
|
|
1255
2125
|
className: `${typeClass} ${isConnected ? "highlighted" : "dimmed"}`.trim()
|
|
1256
2126
|
};
|
|
1257
2127
|
}
|
|
1258
2128
|
return {
|
|
1259
2129
|
...edge,
|
|
2130
|
+
data: enrichedData,
|
|
1260
2131
|
className: typeClass
|
|
1261
2132
|
};
|
|
1262
2133
|
});
|
|
@@ -1279,8 +2150,15 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1279
2150
|
);
|
|
1280
2151
|
const handlePaneClick = useCallback(() => {
|
|
1281
2152
|
selectNode(null);
|
|
2153
|
+
selectEdge(null);
|
|
1282
2154
|
setSelectedNodeIds([]);
|
|
1283
|
-
}, [selectNode, setSelectedNodeIds]);
|
|
2155
|
+
}, [selectNode, selectEdge, setSelectedNodeIds]);
|
|
2156
|
+
const handleEdgeClick = useCallback(
|
|
2157
|
+
(_event, edge) => {
|
|
2158
|
+
selectEdge(edge.id);
|
|
2159
|
+
},
|
|
2160
|
+
[selectEdge]
|
|
2161
|
+
);
|
|
1284
2162
|
const handleSelectionChange = useCallback(
|
|
1285
2163
|
({ nodes: selectedNodes }) => {
|
|
1286
2164
|
setSelectedNodeIds(selectedNodes.map((n) => n.id));
|
|
@@ -1290,30 +2168,56 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1290
2168
|
const handleNodeContextMenu = useCallback(
|
|
1291
2169
|
(event, node) => {
|
|
1292
2170
|
event.preventDefault();
|
|
2171
|
+
if (selectedNodeIds.length > 1 && selectedNodeIds.includes(node.id)) {
|
|
2172
|
+
openSelectionMenu(selectedNodeIds, event.clientX, event.clientY);
|
|
2173
|
+
} else {
|
|
2174
|
+
openNodeMenu(node.id, event.clientX, event.clientY);
|
|
2175
|
+
}
|
|
1293
2176
|
},
|
|
1294
|
-
[selectedNodeIds]
|
|
2177
|
+
[selectedNodeIds, openNodeMenu, openSelectionMenu]
|
|
1295
2178
|
);
|
|
1296
2179
|
const handleEdgeContextMenu = useCallback(
|
|
1297
2180
|
(event, edge) => {
|
|
1298
2181
|
event.preventDefault();
|
|
2182
|
+
openEdgeMenu(edge.id, event.clientX, event.clientY);
|
|
1299
2183
|
},
|
|
1300
|
-
[]
|
|
2184
|
+
[openEdgeMenu]
|
|
1301
2185
|
);
|
|
1302
2186
|
const handlePaneContextMenu = useCallback(
|
|
1303
2187
|
(event) => {
|
|
1304
2188
|
event.preventDefault();
|
|
2189
|
+
openPaneMenu(event.clientX, event.clientY);
|
|
1305
2190
|
},
|
|
1306
|
-
[]
|
|
2191
|
+
[openPaneMenu]
|
|
1307
2192
|
);
|
|
1308
2193
|
const handleSelectionContextMenu = useCallback(
|
|
1309
2194
|
(event, selectedNodes) => {
|
|
1310
2195
|
event.preventDefault();
|
|
2196
|
+
const nodeIds = selectedNodes.map((n) => n.id);
|
|
2197
|
+
openSelectionMenu(nodeIds, event.clientX, event.clientY);
|
|
1311
2198
|
},
|
|
1312
|
-
[]
|
|
2199
|
+
[openSelectionMenu]
|
|
1313
2200
|
);
|
|
1314
2201
|
const handleDrop = useCallback(
|
|
1315
2202
|
(event) => {
|
|
1316
2203
|
event.preventDefault();
|
|
2204
|
+
const historyData = event.dataTransfer.getData("application/history-image");
|
|
2205
|
+
if (historyData) {
|
|
2206
|
+
try {
|
|
2207
|
+
const parsed = JSON.parse(historyData);
|
|
2208
|
+
const position2 = reactFlow.screenToFlowPosition({
|
|
2209
|
+
x: event.clientX,
|
|
2210
|
+
y: event.clientY
|
|
2211
|
+
});
|
|
2212
|
+
const nodeId = addNode("imageInput", position2);
|
|
2213
|
+
if (nodeId && parsed.image) {
|
|
2214
|
+
const { updateNodeData } = useWorkflowStore.getState();
|
|
2215
|
+
updateNodeData(nodeId, { outputImage: parsed.image });
|
|
2216
|
+
}
|
|
2217
|
+
} catch {
|
|
2218
|
+
}
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
1317
2221
|
const nodeType = event.dataTransfer.getData("nodeType");
|
|
1318
2222
|
if (!nodeType) return;
|
|
1319
2223
|
const position = reactFlow.screenToFlowPosition({
|
|
@@ -1345,7 +2249,27 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1345
2249
|
if (!sourceNodeId || !sourceHandleId) return;
|
|
1346
2250
|
const target = event.target;
|
|
1347
2251
|
const nodeElement = target.closest(".react-flow__node");
|
|
1348
|
-
if (!nodeElement)
|
|
2252
|
+
if (!nodeElement) {
|
|
2253
|
+
const sourceNode = nodes.find((n) => n.id === sourceNodeId);
|
|
2254
|
+
if (!sourceNode) return;
|
|
2255
|
+
const sourceHandleType = getHandleType(
|
|
2256
|
+
sourceNode.type,
|
|
2257
|
+
sourceHandleId,
|
|
2258
|
+
"source"
|
|
2259
|
+
);
|
|
2260
|
+
if (!sourceHandleType) return;
|
|
2261
|
+
const clientX = "clientX" in event ? event.clientX : event.touches?.[0]?.clientX ?? 0;
|
|
2262
|
+
const clientY = "clientY" in event ? event.clientY : event.touches?.[0]?.clientY ?? 0;
|
|
2263
|
+
const flowPosition = reactFlow.screenToFlowPosition({ x: clientX, y: clientY });
|
|
2264
|
+
openConnectionDropMenu({
|
|
2265
|
+
position: flowPosition,
|
|
2266
|
+
screenPosition: { x: clientX, y: clientY },
|
|
2267
|
+
sourceNodeId,
|
|
2268
|
+
sourceHandleId,
|
|
2269
|
+
sourceHandleType
|
|
2270
|
+
});
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
1349
2273
|
const targetNodeId = nodeElement.getAttribute("data-id");
|
|
1350
2274
|
if (!targetNodeId || targetNodeId === sourceNodeId) return;
|
|
1351
2275
|
const droppedOnHandle = target.closest(".react-flow__handle");
|
|
@@ -1359,7 +2283,7 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1359
2283
|
targetHandle: compatibleHandle
|
|
1360
2284
|
});
|
|
1361
2285
|
},
|
|
1362
|
-
[findCompatibleHandle, onConnect]
|
|
2286
|
+
[findCompatibleHandle, onConnect, nodes, reactFlow, openConnectionDropMenu]
|
|
1363
2287
|
);
|
|
1364
2288
|
const checkValidConnection = useCallback(
|
|
1365
2289
|
(connection) => {
|
|
@@ -1427,11 +2351,16 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1427
2351
|
onNodeDragStart: handleNodeDragStart,
|
|
1428
2352
|
onNodeDrag: handleNodeDrag,
|
|
1429
2353
|
onNodeDragStop: handleNodeDragStop,
|
|
2354
|
+
onEdgeClick: handleEdgeClick,
|
|
1430
2355
|
isValidConnection: checkValidConnection,
|
|
1431
2356
|
nodeTypes: nodeTypesProp ?? nodeTypes,
|
|
2357
|
+
edgeTypes: edgeTypesMap,
|
|
1432
2358
|
fitView: true,
|
|
1433
2359
|
snapToGrid: true,
|
|
1434
2360
|
snapGrid: [16, 16],
|
|
2361
|
+
minZoom: 0.1,
|
|
2362
|
+
maxZoom: 4,
|
|
2363
|
+
nodeDragThreshold: 5,
|
|
1435
2364
|
connectionMode: ConnectionMode.Loose,
|
|
1436
2365
|
selectionMode: SelectionMode.Partial,
|
|
1437
2366
|
selectionOnDrag: true,
|
|
@@ -1474,9 +2403,24 @@ function WorkflowCanvas({ nodeTypes: nodeTypesProp } = {}) {
|
|
|
1474
2403
|
]
|
|
1475
2404
|
}
|
|
1476
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 }),
|
|
1477
2417
|
/* @__PURE__ */ jsx(NodeDetailModal, {}),
|
|
1478
2418
|
/* @__PURE__ */ jsx(ShortcutHelpModal, {}),
|
|
1479
|
-
/* @__PURE__ */ jsx(NodeSearch, {})
|
|
2419
|
+
/* @__PURE__ */ jsx(NodeSearch, {}),
|
|
2420
|
+
/* @__PURE__ */ jsx(ConnectionDropMenu, {}),
|
|
2421
|
+
/* @__PURE__ */ jsx(CostModal, {}),
|
|
2422
|
+
/* @__PURE__ */ jsx(NotificationToast, {}),
|
|
2423
|
+
/* @__PURE__ */ jsx(GlobalImageHistory, {})
|
|
1480
2424
|
] });
|
|
1481
2425
|
}
|
|
1482
2426
|
function PauseEdgeComponent({
|
|
@@ -1530,4 +2474,4 @@ function PauseEdgeComponent({
|
|
|
1530
2474
|
}
|
|
1531
2475
|
var PauseEdge = memo(PauseEdgeComponent);
|
|
1532
2476
|
|
|
1533
|
-
export { DEFAULT_GROUP_COLORS, GROUP_COLORS, GroupOverlay, HelperLines, NodeSearch, PauseEdge, ShortcutHelpModal, WorkflowCanvas };
|
|
2477
|
+
export { ConnectionDropMenu, DEFAULT_GROUP_COLORS, EdgeToolbar, EditableEdge, GROUP_COLORS, GlobalImageHistory, GroupOverlay, HelperLines, NodeSearch, NotificationToast, PauseEdge, ReferenceEdge, ShortcutHelpModal, WorkflowCanvas };
|