@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.
- package/README.md +11 -33
- package/dist/canvas.d.ts +22 -22
- package/dist/canvas.mjs +16 -17
- package/dist/{chunk-WBR34V4L.mjs → chunk-2FUPL67V.mjs} +1593 -1045
- package/dist/{chunk-4VEN4UN7.mjs → chunk-53XDE62A.mjs} +818 -623
- package/dist/{chunk-PCIWWD37.mjs → chunk-7LV4UAUS.mjs} +19 -19
- package/dist/{chunk-7SKSRSS7.mjs → chunk-B4EAAKYF.mjs} +16 -16
- package/dist/{chunk-ZJD5WMR3.mjs → chunk-C6MQBJFC.mjs} +45 -13
- package/dist/{chunk-7H3WJJYS.mjs → chunk-ESVULCFY.mjs} +12 -6
- package/dist/{chunk-GWBGK3KL.mjs → chunk-FWJIAW2E.mjs} +82 -47
- package/dist/{chunk-R727OFBR.mjs → chunk-GPYIIWD5.mjs} +404 -350
- package/dist/{chunk-OQREHJXK.mjs → chunk-IYFWAJBB.mjs} +208 -203
- package/dist/{chunk-2JQSKIWR.mjs → chunk-MGLAKMDP.mjs} +24 -23
- package/dist/{chunk-LT3ZJJL6.mjs → chunk-OJWVEEMM.mjs} +497 -399
- package/dist/{chunk-ZD2BADZO.mjs → chunk-ORVDYXDP.mjs} +221 -175
- package/dist/{chunk-CV4M7CNU.mjs → chunk-QQVHGJ2G.mjs} +149 -142
- package/dist/{chunk-6PSJTBNV.mjs → chunk-U4QPE4CY.mjs} +387 -347
- package/dist/{chunk-EFXQT23N.mjs → chunk-VVQ4CH77.mjs} +5 -5
- package/dist/{chunk-VRN3UWE5.mjs → chunk-XRC3O5GK.mjs} +73 -73
- package/dist/{chunk-FT33LFII.mjs → chunk-YUIK4AHM.mjs} +1 -1
- package/dist/{chunk-JT4Y5H3U.mjs → chunk-ZSITTZ4S.mjs} +630 -569
- package/dist/hooks.d.ts +37 -37
- package/dist/hooks.mjs +10 -10
- package/dist/index.d.ts +26 -11
- package/dist/index.mjs +99 -20
- package/dist/lib.d.ts +203 -203
- package/dist/lib.mjs +228 -199
- package/dist/nodes.d.ts +2 -2
- package/dist/nodes.mjs +12 -13
- package/dist/panels.d.ts +2 -3
- package/dist/panels.mjs +3 -3
- package/dist/provider.d.ts +2 -2
- package/dist/provider.mjs +2 -2
- package/dist/stores.d.ts +5 -5
- package/dist/stores.mjs +5 -5
- package/dist/toolbar.d.ts +42 -24
- package/dist/toolbar.mjs +4 -5
- package/dist/ui.d.ts +2 -2
- package/dist/ui.mjs +2 -2
- package/dist/{useCommentNavigation-BakbiiIc.d.ts → useRequiredInputs-ByoIS-fT.d.ts} +160 -160
- package/dist/{promptLibraryStore-Dl3Q3cP6.d.ts → workflowStore-Bsz0nd5c.d.ts} +368 -368
- package/dist/workflowStore-N2F7WIG3.mjs +2 -0
- package/package.json +79 -77
- package/src/styles/workflow-ui.css +56 -19
- package/dist/chunk-OY7BRSGG.mjs +0 -60
- package/dist/workflowStore-UAAKOOIK.mjs +0 -2
- package/dist/{types-IEKYuYhu.d.ts → types-CRXJnajq.d.ts} +1 -1
|
@@ -1,59 +1,382 @@
|
|
|
1
|
-
import { calculateWorkflowCost, formatCost } from './chunk-
|
|
2
|
-
import {
|
|
3
|
-
import { useWorkflowStore } from './chunk-
|
|
4
|
-
import {
|
|
5
|
-
import { useState, useRef,
|
|
6
|
-
import {
|
|
1
|
+
import { calculateWorkflowCost, formatCost } from './chunk-MGLAKMDP.mjs';
|
|
2
|
+
import { useExecutionStore, useUIStore, useSettingsStore } from './chunk-OJWVEEMM.mjs';
|
|
3
|
+
import { useWorkflowStore } from './chunk-GPYIIWD5.mjs';
|
|
4
|
+
import { Minus, Plus, Square, Play, ChevronUp, PlayCircle, RotateCcw, DollarSign, MoreVertical, X, CloudOff, Loader2, Cloud, Check, ChevronDown, SaveAll, Save, FolderOpen, Bug, LayoutGrid, Undo2, Redo2, HelpCircle, Settings, AlertCircle } from 'lucide-react';
|
|
5
|
+
import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
|
|
6
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
var MIN_BATCH = 1;
|
|
9
|
+
var MAX_BATCH = 10;
|
|
10
|
+
function BottomBar() {
|
|
11
|
+
const [batchCount, setBatchCount] = useState(1);
|
|
12
|
+
const [currentBatchRun, setCurrentBatchRun] = useState(0);
|
|
13
|
+
const [isBatchRunning, setIsBatchRunning] = useState(false);
|
|
14
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
15
|
+
const batchCancelledRef = useRef(false);
|
|
16
|
+
const dropdownRef = useRef(null);
|
|
17
|
+
const isRunning = useExecutionStore((s) => s.isRunning);
|
|
18
|
+
const executeWorkflow = useExecutionStore((s) => s.executeWorkflow);
|
|
19
|
+
const executeSelectedNodes = useExecutionStore((s) => s.executeSelectedNodes);
|
|
20
|
+
const resumeFromFailed = useExecutionStore((s) => s.resumeFromFailed);
|
|
21
|
+
const canResumeFromFailed = useExecutionStore((s) => s.canResumeFromFailed);
|
|
22
|
+
const stopExecution = useExecutionStore((s) => s.stopExecution);
|
|
23
|
+
useExecutionStore((s) => s.lastFailedNodeId);
|
|
24
|
+
const selectedNodeIds = useWorkflowStore((s) => s.selectedNodeIds);
|
|
25
|
+
const nodes = useWorkflowStore((s) => s.nodes);
|
|
26
|
+
const validateWorkflow = useWorkflowStore((s) => s.validateWorkflow);
|
|
27
|
+
const canRunWorkflow = useMemo(() => {
|
|
28
|
+
if (nodes.length === 0) return false;
|
|
29
|
+
const validation = validateWorkflow();
|
|
30
|
+
return validation.isValid;
|
|
31
|
+
}, [nodes, validateWorkflow]);
|
|
32
|
+
const hasSelection = selectedNodeIds.length > 0;
|
|
33
|
+
const showResume = canResumeFromFailed();
|
|
34
|
+
const decrementBatch = useCallback(() => {
|
|
35
|
+
setBatchCount((prev) => Math.max(MIN_BATCH, prev - 1));
|
|
36
|
+
}, []);
|
|
37
|
+
const incrementBatch = useCallback(() => {
|
|
38
|
+
setBatchCount((prev) => Math.min(MAX_BATCH, prev + 1));
|
|
39
|
+
}, []);
|
|
40
|
+
const waitForExecutionEnd = useCallback(() => {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
if (!useExecutionStore.getState().isRunning) {
|
|
43
|
+
resolve();
|
|
44
|
+
return;
|
|
23
45
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
const unsubscribe = useExecutionStore.subscribe((state) => {
|
|
47
|
+
if (!state.isRunning) {
|
|
48
|
+
unsubscribe();
|
|
49
|
+
resolve();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}, []);
|
|
54
|
+
const runBatch = useCallback(async () => {
|
|
55
|
+
batchCancelledRef.current = false;
|
|
56
|
+
setIsBatchRunning(true);
|
|
57
|
+
const accumulatedImages = /* @__PURE__ */ new Map();
|
|
58
|
+
for (let i = 0; i < batchCount; i++) {
|
|
59
|
+
if (batchCancelledRef.current) break;
|
|
60
|
+
setCurrentBatchRun(i + 1);
|
|
61
|
+
executeWorkflow();
|
|
62
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
63
|
+
if (!useExecutionStore.getState().isRunning) break;
|
|
64
|
+
await waitForExecutionEnd();
|
|
65
|
+
if (useExecutionStore.getState().lastFailedNodeId) break;
|
|
66
|
+
const { nodes: currentNodes, updateNodeData } = useWorkflowStore.getState();
|
|
67
|
+
for (const node of currentNodes) {
|
|
68
|
+
if (node.type !== "imageGen") continue;
|
|
69
|
+
const nodeData = node.data;
|
|
70
|
+
const newImages = nodeData.outputImages;
|
|
71
|
+
if (!newImages?.length) continue;
|
|
72
|
+
const existing = accumulatedImages.get(node.id) || [];
|
|
73
|
+
const merged = [...existing, ...newImages];
|
|
74
|
+
accumulatedImages.set(node.id, merged);
|
|
75
|
+
updateNodeData(node.id, { outputImages: merged });
|
|
31
76
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
}
|
|
78
|
+
setIsBatchRunning(false);
|
|
79
|
+
setCurrentBatchRun(0);
|
|
80
|
+
}, [batchCount, executeWorkflow, waitForExecutionEnd]);
|
|
81
|
+
const handlePrimaryClick = useCallback(() => {
|
|
82
|
+
if (isRunning || isBatchRunning) {
|
|
83
|
+
batchCancelledRef.current = true;
|
|
84
|
+
stopExecution();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (batchCount > 1) {
|
|
88
|
+
runBatch();
|
|
89
|
+
} else {
|
|
90
|
+
executeWorkflow();
|
|
91
|
+
}
|
|
92
|
+
}, [
|
|
93
|
+
isRunning,
|
|
94
|
+
isBatchRunning,
|
|
95
|
+
batchCount,
|
|
96
|
+
runBatch,
|
|
97
|
+
executeWorkflow,
|
|
98
|
+
stopExecution
|
|
99
|
+
]);
|
|
100
|
+
const handleRunSelected = useCallback(() => {
|
|
101
|
+
if (!isRunning && hasSelection) {
|
|
102
|
+
executeSelectedNodes();
|
|
103
|
+
setDropdownOpen(false);
|
|
104
|
+
}
|
|
105
|
+
}, [isRunning, hasSelection, executeSelectedNodes]);
|
|
106
|
+
const handleResume = useCallback(() => {
|
|
107
|
+
if (canResumeFromFailed()) {
|
|
108
|
+
resumeFromFailed();
|
|
109
|
+
setDropdownOpen(false);
|
|
110
|
+
}
|
|
111
|
+
}, [canResumeFromFailed, resumeFromFailed]);
|
|
112
|
+
const isActive = isRunning || isBatchRunning;
|
|
113
|
+
return /* @__PURE__ */ jsx(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
className: "fixed bottom-5 left-1/2 z-50 -translate-x-1/2",
|
|
117
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
118
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-md border border-neutral-700/80 bg-neutral-800/95 px-2 py-1 shadow-lg backdrop-blur-sm", children: [
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
|
|
120
|
+
/* @__PURE__ */ jsx("span", { className: "mr-0.5 text-[11px] text-neutral-400", children: "Batch" }),
|
|
121
|
+
/* @__PURE__ */ jsx(
|
|
122
|
+
"button",
|
|
123
|
+
{
|
|
124
|
+
onClick: decrementBatch,
|
|
125
|
+
disabled: batchCount <= MIN_BATCH || isActive,
|
|
126
|
+
className: "flex h-5 w-5 items-center justify-center rounded text-neutral-400 transition hover:bg-neutral-700 hover:text-white disabled:opacity-40 disabled:hover:bg-transparent",
|
|
127
|
+
children: /* @__PURE__ */ jsx(Minus, { className: "h-2.5 w-2.5" })
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
/* @__PURE__ */ jsx("span", { className: "w-4 text-center text-xs font-medium tabular-nums text-white", children: batchCount }),
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
"button",
|
|
133
|
+
{
|
|
134
|
+
onClick: incrementBatch,
|
|
135
|
+
disabled: batchCount >= MAX_BATCH || isActive,
|
|
136
|
+
className: "flex h-5 w-5 items-center justify-center rounded text-neutral-400 transition hover:bg-neutral-700 hover:text-white disabled:opacity-40 disabled:hover:bg-transparent",
|
|
137
|
+
children: /* @__PURE__ */ jsx(Plus, { className: "h-2.5 w-2.5" })
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
] }),
|
|
141
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-neutral-600" }),
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"button",
|
|
145
|
+
{
|
|
146
|
+
onClick: (e) => {
|
|
147
|
+
e.stopPropagation();
|
|
148
|
+
handlePrimaryClick();
|
|
149
|
+
},
|
|
150
|
+
disabled: !isActive && !canRunWorkflow,
|
|
151
|
+
className: `flex h-7 items-center gap-1.5 rounded-l px-3 text-xs font-medium transition ${isActive ? "bg-red-500/90 text-white hover:bg-red-500" : canRunWorkflow ? "bg-white text-black hover:bg-neutral-200" : "bg-neutral-600 text-neutral-400"} disabled:cursor-not-allowed`,
|
|
152
|
+
children: isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
153
|
+
/* @__PURE__ */ jsx(Square, { className: "h-3.5 w-3.5" }),
|
|
154
|
+
"Stop"
|
|
155
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
156
|
+
/* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5 fill-current" }),
|
|
157
|
+
"Run"
|
|
158
|
+
] })
|
|
159
|
+
}
|
|
160
|
+
),
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
"button",
|
|
163
|
+
{
|
|
164
|
+
onPointerDown: (e) => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
},
|
|
168
|
+
onClick: (e) => {
|
|
169
|
+
e.stopPropagation();
|
|
170
|
+
setDropdownOpen((prev) => !prev);
|
|
171
|
+
},
|
|
172
|
+
disabled: isActive,
|
|
173
|
+
className: `flex h-7 items-center rounded-r border-l px-1.5 transition ${isActive ? "border-red-400/30 bg-red-500/90 text-white" : canRunWorkflow ? "border-neutral-300 bg-white text-black hover:bg-neutral-200" : "border-neutral-500 bg-neutral-600 text-neutral-400"} disabled:cursor-not-allowed`,
|
|
174
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
dropdownOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
46
178
|
/* @__PURE__ */ jsx(
|
|
47
|
-
"
|
|
179
|
+
"div",
|
|
48
180
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
181
|
+
className: "fixed inset-0 z-40",
|
|
182
|
+
onClick: () => setDropdownOpen(false)
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
/* @__PURE__ */ jsxs(
|
|
186
|
+
"div",
|
|
187
|
+
{
|
|
188
|
+
ref: dropdownRef,
|
|
189
|
+
onClick: (e) => e.stopPropagation(),
|
|
190
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
191
|
+
className: "absolute bottom-full left-0 z-50 mb-1.5 min-w-[180px] rounded-md border border-neutral-700 bg-neutral-800 py-0.5 shadow-xl",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ jsxs(
|
|
194
|
+
"button",
|
|
195
|
+
{
|
|
196
|
+
onClick: () => {
|
|
197
|
+
executeWorkflow();
|
|
198
|
+
setDropdownOpen(false);
|
|
199
|
+
},
|
|
200
|
+
disabled: !canRunWorkflow,
|
|
201
|
+
className: "flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-xs text-neutral-200 transition hover:bg-neutral-700 disabled:text-neutral-500 disabled:hover:bg-transparent",
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
|
|
204
|
+
"Run Workflow"
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
),
|
|
208
|
+
/* @__PURE__ */ jsxs(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
onClick: handleRunSelected,
|
|
212
|
+
disabled: !hasSelection || isRunning,
|
|
213
|
+
className: "flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-xs text-neutral-200 transition hover:bg-neutral-700 disabled:text-neutral-500 disabled:hover:bg-transparent",
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ jsx(PlayCircle, { className: "h-3 w-3" }),
|
|
216
|
+
"Run Selected (",
|
|
217
|
+
selectedNodeIds.length,
|
|
218
|
+
")"
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
showResume && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
223
|
+
/* @__PURE__ */ jsx("div", { className: "mx-2 my-0.5 h-px bg-neutral-700" }),
|
|
224
|
+
/* @__PURE__ */ jsxs(
|
|
225
|
+
"button",
|
|
226
|
+
{
|
|
227
|
+
onClick: handleResume,
|
|
228
|
+
className: "flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-xs text-neutral-200 transition hover:bg-neutral-700",
|
|
229
|
+
children: [
|
|
230
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "h-3 w-3" }),
|
|
231
|
+
"Resume from Failed"
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
] })
|
|
236
|
+
]
|
|
52
237
|
}
|
|
53
238
|
)
|
|
54
|
-
] })
|
|
55
|
-
|
|
56
|
-
|
|
239
|
+
] })
|
|
240
|
+
] }),
|
|
241
|
+
isBatchRunning && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
242
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-neutral-600" }),
|
|
243
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[11px] tabular-nums text-neutral-400", children: [
|
|
244
|
+
currentBatchRun,
|
|
245
|
+
"/",
|
|
246
|
+
batchCount
|
|
247
|
+
] })
|
|
248
|
+
] })
|
|
249
|
+
] })
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
function CostIndicator() {
|
|
254
|
+
const nodes = useWorkflowStore((state) => state.nodes);
|
|
255
|
+
const isRunning = useExecutionStore((state) => state.isRunning);
|
|
256
|
+
const actualCost = useExecutionStore((state) => state.actualCost);
|
|
257
|
+
const { openModal } = useUIStore();
|
|
258
|
+
const breakdown = useMemo(() => calculateWorkflowCost(nodes), [nodes]);
|
|
259
|
+
const displayCost = isRunning && actualCost > 0 ? actualCost : breakdown.total;
|
|
260
|
+
if (breakdown.items.length === 0) return null;
|
|
261
|
+
return /* @__PURE__ */ jsxs(
|
|
262
|
+
"button",
|
|
263
|
+
{
|
|
264
|
+
onClick: () => openModal("cost"),
|
|
265
|
+
title: "View cost breakdown",
|
|
266
|
+
className: "flex items-center gap-1.5 rounded-md border border-[var(--border)] px-2 py-1 text-sm text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
267
|
+
children: [
|
|
268
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3.5 w-3.5" }),
|
|
269
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: formatCost(displayCost) }),
|
|
270
|
+
isRunning && actualCost > 0 && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" })
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
function OverflowMenu({ items }) {
|
|
276
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
277
|
+
const menuRef = useRef(null);
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (!isOpen) return;
|
|
280
|
+
const handleClickOutside = (event) => {
|
|
281
|
+
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
282
|
+
setIsOpen(false);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
286
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
287
|
+
}, [isOpen]);
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (!isOpen) return;
|
|
290
|
+
const handleKeyDown = (event) => {
|
|
291
|
+
if (event.key === "Escape") {
|
|
292
|
+
setIsOpen(false);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
296
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
297
|
+
}, [isOpen]);
|
|
298
|
+
return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "relative", children: [
|
|
299
|
+
/* @__PURE__ */ jsx(
|
|
300
|
+
"button",
|
|
301
|
+
{
|
|
302
|
+
onClick: () => setIsOpen(!isOpen),
|
|
303
|
+
title: "More options",
|
|
304
|
+
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
305
|
+
children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" })
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
isOpen && /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-full z-50 mt-1 min-w-[180px] rounded-lg border border-border bg-card py-1 shadow-lg", children: items.map((item) => /* @__PURE__ */ jsxs(
|
|
309
|
+
"button",
|
|
310
|
+
{
|
|
311
|
+
onClick: () => {
|
|
312
|
+
item.onClick?.();
|
|
313
|
+
setIsOpen(false);
|
|
314
|
+
},
|
|
315
|
+
className: "flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground transition hover:bg-secondary",
|
|
316
|
+
children: [
|
|
317
|
+
/* @__PURE__ */ jsx("span", { className: "h-4 w-4 shrink-0", children: item.icon }),
|
|
318
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
319
|
+
item.external && /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs text-muted-foreground", children: "\u2197" })
|
|
320
|
+
]
|
|
321
|
+
},
|
|
322
|
+
item.id
|
|
323
|
+
)) })
|
|
324
|
+
] });
|
|
325
|
+
}
|
|
326
|
+
function SaveAsDialog({
|
|
327
|
+
isOpen,
|
|
328
|
+
currentName,
|
|
329
|
+
onSave,
|
|
330
|
+
onClose
|
|
331
|
+
}) {
|
|
332
|
+
const [name, setName] = useState("");
|
|
333
|
+
const inputRef = useRef(null);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (isOpen) {
|
|
336
|
+
setName(`${currentName} (copy)`);
|
|
337
|
+
setTimeout(() => inputRef.current?.select(), 0);
|
|
338
|
+
}
|
|
339
|
+
}, [isOpen, currentName]);
|
|
340
|
+
const handleSubmit = useCallback(
|
|
341
|
+
(e) => {
|
|
342
|
+
e.preventDefault();
|
|
343
|
+
const trimmed = name.trim();
|
|
344
|
+
if (trimmed) {
|
|
345
|
+
onSave(trimmed);
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
[name, onSave]
|
|
349
|
+
);
|
|
350
|
+
const handleKeyDown = useCallback(
|
|
351
|
+
(e) => {
|
|
352
|
+
if (e.key === "Escape") {
|
|
353
|
+
onClose();
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
[onClose]
|
|
357
|
+
);
|
|
358
|
+
if (!isOpen) return null;
|
|
359
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
360
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/60", onClick: onClose }),
|
|
361
|
+
/* @__PURE__ */ jsxs(
|
|
362
|
+
"div",
|
|
363
|
+
{
|
|
364
|
+
className: "relative z-10 w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl",
|
|
365
|
+
onKeyDown: handleKeyDown,
|
|
366
|
+
children: [
|
|
367
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between", children: [
|
|
368
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Save As" }),
|
|
369
|
+
/* @__PURE__ */ jsx(
|
|
370
|
+
"button",
|
|
371
|
+
{
|
|
372
|
+
onClick: onClose,
|
|
373
|
+
className: "flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
374
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
] }),
|
|
378
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
|
|
379
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
57
380
|
/* @__PURE__ */ jsx(
|
|
58
381
|
"label",
|
|
59
382
|
{
|
|
@@ -102,18 +425,25 @@ function SaveAsDialog({ isOpen, currentName, onSave, onClose }) {
|
|
|
102
425
|
)
|
|
103
426
|
] });
|
|
104
427
|
}
|
|
105
|
-
function SaveIndicator(
|
|
106
|
-
|
|
107
|
-
|
|
428
|
+
function SaveIndicator({
|
|
429
|
+
isDirty: isDirtyProp,
|
|
430
|
+
isSaving: isSavingProp,
|
|
431
|
+
variant = "default"
|
|
432
|
+
}) {
|
|
433
|
+
const storeIsDirty = useWorkflowStore((state) => state.isDirty);
|
|
434
|
+
const storeIsSaving = useWorkflowStore((state) => state.isSaving);
|
|
108
435
|
const autoSaveEnabled = useSettingsStore((state) => state.autoSaveEnabled);
|
|
109
436
|
const toggleAutoSave = useSettingsStore((state) => state.toggleAutoSave);
|
|
437
|
+
const isDirty = isDirtyProp ?? storeIsDirty;
|
|
438
|
+
const isSaving = isSavingProp ?? storeIsSaving;
|
|
439
|
+
const isPill = variant === "pill";
|
|
110
440
|
if (!autoSaveEnabled) {
|
|
111
441
|
return /* @__PURE__ */ jsxs(
|
|
112
442
|
"button",
|
|
113
443
|
{
|
|
114
444
|
onClick: toggleAutoSave,
|
|
115
445
|
title: "Click to enable auto-save",
|
|
116
|
-
className: "flex items-center gap-1.5 text-muted-foreground
|
|
446
|
+
className: "flex items-center gap-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground",
|
|
117
447
|
children: [
|
|
118
448
|
/* @__PURE__ */ jsx(CloudOff, { className: "h-3.5 w-3.5" }),
|
|
119
449
|
/* @__PURE__ */ jsx("span", { children: "Auto-save off" })
|
|
@@ -122,23 +452,35 @@ function SaveIndicator() {
|
|
|
122
452
|
);
|
|
123
453
|
}
|
|
124
454
|
if (isSaving) {
|
|
125
|
-
return /* @__PURE__ */ jsxs(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
455
|
+
return /* @__PURE__ */ jsxs(
|
|
456
|
+
"div",
|
|
457
|
+
{
|
|
458
|
+
className: isPill ? "flex items-center gap-1.5 rounded-full border border-blue-500/20 bg-blue-500/10 px-2.5 py-1 text-xs text-blue-400" : "flex items-center gap-1.5 text-xs text-blue-500",
|
|
459
|
+
children: [
|
|
460
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
461
|
+
/* @__PURE__ */ jsx("span", { children: "Saving..." })
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
);
|
|
129
465
|
}
|
|
130
466
|
if (isDirty) {
|
|
131
|
-
return /* @__PURE__ */ jsxs(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
467
|
+
return /* @__PURE__ */ jsxs(
|
|
468
|
+
"div",
|
|
469
|
+
{
|
|
470
|
+
className: isPill ? "flex items-center gap-1.5 rounded-full border border-border bg-secondary/40 px-2.5 py-1 text-xs text-muted-foreground" : "flex items-center gap-1.5 text-xs text-muted-foreground",
|
|
471
|
+
children: [
|
|
472
|
+
/* @__PURE__ */ jsx(Cloud, { className: "h-3.5 w-3.5" }),
|
|
473
|
+
/* @__PURE__ */ jsx("span", { children: "Unsaved" })
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
);
|
|
135
477
|
}
|
|
136
478
|
return /* @__PURE__ */ jsxs(
|
|
137
479
|
"button",
|
|
138
480
|
{
|
|
139
481
|
onClick: toggleAutoSave,
|
|
140
482
|
title: "Click to disable auto-save",
|
|
141
|
-
className: "flex items-center gap-1.5
|
|
483
|
+
className: isPill ? "flex items-center gap-1.5 rounded-full border border-emerald-500/20 bg-emerald-500/10 px-2.5 py-1 text-xs text-emerald-400 transition-colors hover:text-emerald-300" : "flex items-center gap-1.5 text-xs text-green-500 transition-colors hover:text-green-400",
|
|
142
484
|
children: [
|
|
143
485
|
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
144
486
|
/* @__PURE__ */ jsx("span", { children: "Saved" })
|
|
@@ -234,526 +576,245 @@ function Toolbar({
|
|
|
234
576
|
fileMenuItemsPrepend,
|
|
235
577
|
fileMenuItemsAppend,
|
|
236
578
|
additionalMenus,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
useEffect(() => {
|
|
256
|
-
const unsubscribe = useWorkflowStore.temporal.subscribe((state) => {
|
|
257
|
-
setCanUndo(state.pastStates.length > 0);
|
|
258
|
-
setCanRedo(state.futureStates.length > 0);
|
|
259
|
-
});
|
|
260
|
-
const temporal = useWorkflowStore.temporal.getState();
|
|
261
|
-
setCanUndo(temporal.pastStates.length > 0);
|
|
262
|
-
setCanRedo(temporal.futureStates.length > 0);
|
|
263
|
-
return unsubscribe;
|
|
264
|
-
}, []);
|
|
265
|
-
const handleExport = useCallback(() => {
|
|
266
|
-
const workflow = exportWorkflow();
|
|
267
|
-
const blob = new Blob([JSON.stringify(workflow, null, 2)], {
|
|
268
|
-
type: "application/json"
|
|
269
|
-
});
|
|
270
|
-
const url = URL.createObjectURL(blob);
|
|
271
|
-
const link = document.createElement("a");
|
|
272
|
-
link.href = url;
|
|
273
|
-
link.download = `${workflow.name.toLowerCase().replace(/\s+/g, "-")}.json`;
|
|
274
|
-
document.body.appendChild(link);
|
|
275
|
-
link.click();
|
|
276
|
-
document.body.removeChild(link);
|
|
277
|
-
URL.revokeObjectURL(url);
|
|
278
|
-
}, [exportWorkflow]);
|
|
279
|
-
const handleImport = useCallback(() => {
|
|
280
|
-
const input = document.createElement("input");
|
|
281
|
-
input.type = "file";
|
|
282
|
-
input.accept = ".json";
|
|
283
|
-
input.onchange = (e) => {
|
|
284
|
-
const file = e.target.files?.[0];
|
|
285
|
-
if (!file) return;
|
|
286
|
-
const reader = new FileReader();
|
|
287
|
-
reader.onload = (event) => {
|
|
288
|
-
try {
|
|
289
|
-
const data = JSON.parse(event.target?.result);
|
|
290
|
-
if (!isValidWorkflow(data)) {
|
|
291
|
-
console.warn("[Toolbar] Invalid workflow file structure");
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
useWorkflowStore.getState().loadWorkflow(data);
|
|
295
|
-
} catch {
|
|
296
|
-
console.warn("[Toolbar] Failed to parse workflow file");
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
reader.readAsText(file);
|
|
300
|
-
};
|
|
301
|
-
input.click();
|
|
302
|
-
}, []);
|
|
303
|
-
const handleSaveAs = useCallback(
|
|
304
|
-
(newName) => {
|
|
305
|
-
if (onSaveAs) {
|
|
306
|
-
onSaveAs(newName);
|
|
307
|
-
}
|
|
308
|
-
setShowSaveAsDialog(false);
|
|
309
|
-
},
|
|
310
|
-
[onSaveAs]
|
|
311
|
-
);
|
|
312
|
-
const fileMenuItems = useMemo(() => {
|
|
313
|
-
const items = [];
|
|
314
|
-
if (fileMenuItemsPrepend?.length) {
|
|
315
|
-
items.push(...fileMenuItemsPrepend);
|
|
316
|
-
items.push({ id: "separator-prepend", separator: true });
|
|
317
|
-
}
|
|
318
|
-
if (onSaveAs) {
|
|
319
|
-
items.push({
|
|
320
|
-
id: "saveAs",
|
|
321
|
-
label: "Save As...",
|
|
322
|
-
icon: /* @__PURE__ */ jsx(SaveAll, { className: "h-4 w-4" }),
|
|
323
|
-
onClick: () => setShowSaveAsDialog(true)
|
|
324
|
-
});
|
|
325
|
-
items.push({ id: "separator-saveas", separator: true });
|
|
326
|
-
}
|
|
327
|
-
items.push(
|
|
328
|
-
{
|
|
329
|
-
id: "export",
|
|
330
|
-
label: "Export Workflow",
|
|
331
|
-
icon: /* @__PURE__ */ jsx(Save, { className: "h-4 w-4" }),
|
|
332
|
-
onClick: handleExport
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
id: "import",
|
|
336
|
-
label: "Import Workflow",
|
|
337
|
-
icon: /* @__PURE__ */ jsx(FolderOpen, { className: "h-4 w-4" }),
|
|
338
|
-
onClick: handleImport
|
|
339
|
-
}
|
|
340
|
-
);
|
|
341
|
-
if (fileMenuItemsAppend?.length) {
|
|
342
|
-
items.push({ id: "separator-append", separator: true });
|
|
343
|
-
items.push(...fileMenuItemsAppend);
|
|
344
|
-
}
|
|
345
|
-
return items;
|
|
346
|
-
}, [handleExport, handleImport, onSaveAs, fileMenuItemsPrepend, fileMenuItemsAppend]);
|
|
347
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex h-14 items-center gap-3 border-b border-border bg-card px-4", children: [
|
|
348
|
-
/* @__PURE__ */ jsx(
|
|
349
|
-
"a",
|
|
350
|
-
{
|
|
351
|
-
href: logoHref,
|
|
352
|
-
title: "Go to Dashboard",
|
|
353
|
-
className: "flex h-6 w-6 items-center justify-center hover:opacity-90 transition",
|
|
354
|
-
children: /* @__PURE__ */ jsx(
|
|
355
|
-
"img",
|
|
356
|
-
{
|
|
357
|
-
src: logoSrc,
|
|
358
|
-
alt: "Genfeed",
|
|
359
|
-
className: "h-6 w-6 object-contain"
|
|
360
|
-
}
|
|
361
|
-
)
|
|
362
|
-
}
|
|
363
|
-
),
|
|
364
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 w-px bg-border" }),
|
|
365
|
-
/* @__PURE__ */ jsx(ToolbarDropdown, { label: "File", items: fileMenuItems }),
|
|
366
|
-
additionalMenus?.map((menu) => /* @__PURE__ */ jsx(ToolbarDropdown, { label: menu.label, items: menu.items }, menu.label)),
|
|
367
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 w-px bg-border" }),
|
|
368
|
-
debugMode && /* @__PURE__ */ jsxs(
|
|
369
|
-
"button",
|
|
370
|
-
{
|
|
371
|
-
onClick: () => openModal("settings"),
|
|
372
|
-
title: "Debug mode active - API calls are mocked",
|
|
373
|
-
className: "flex items-center gap-1.5 rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1 text-sm text-amber-500 transition hover:bg-amber-500/20",
|
|
374
|
-
children: [
|
|
375
|
-
/* @__PURE__ */ jsx(Bug, { className: "h-4 w-4" }),
|
|
376
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Debug" })
|
|
377
|
-
]
|
|
378
|
-
}
|
|
379
|
-
),
|
|
380
|
-
onAutoLayout && /* @__PURE__ */ jsx(
|
|
381
|
-
"button",
|
|
382
|
-
{
|
|
383
|
-
onClick: () => onAutoLayout("LR"),
|
|
384
|
-
title: "Auto-layout nodes",
|
|
385
|
-
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
386
|
-
children: /* @__PURE__ */ jsx(LayoutGrid, { className: "h-4 w-4" })
|
|
387
|
-
}
|
|
388
|
-
),
|
|
389
|
-
/* @__PURE__ */ jsx(
|
|
390
|
-
"button",
|
|
391
|
-
{
|
|
392
|
-
onClick: () => undo(),
|
|
393
|
-
disabled: !canUndo,
|
|
394
|
-
title: "Undo (Ctrl+Z)",
|
|
395
|
-
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
396
|
-
children: /* @__PURE__ */ jsx(Undo2, { className: "h-4 w-4" })
|
|
397
|
-
}
|
|
398
|
-
),
|
|
399
|
-
/* @__PURE__ */ jsx(
|
|
400
|
-
"button",
|
|
401
|
-
{
|
|
402
|
-
onClick: () => redo(),
|
|
403
|
-
disabled: !canRedo,
|
|
404
|
-
title: "Redo (Ctrl+Shift+Z)",
|
|
405
|
-
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
406
|
-
children: /* @__PURE__ */ jsx(Redo2, { className: "h-4 w-4" })
|
|
407
|
-
}
|
|
408
|
-
),
|
|
409
|
-
/* @__PURE__ */ jsx(SaveIndicator, {}),
|
|
410
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
411
|
-
rightContent,
|
|
412
|
-
showSettings && /* @__PURE__ */ jsx(
|
|
413
|
-
"button",
|
|
414
|
-
{
|
|
415
|
-
onClick: () => openModal("settings"),
|
|
416
|
-
title: "Settings",
|
|
417
|
-
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
418
|
-
children: /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" })
|
|
419
|
-
}
|
|
420
|
-
),
|
|
421
|
-
uniqueErrorMessages.length > 0 && /* @__PURE__ */ jsx("div", { className: "fixed right-4 top-20 z-50 max-w-sm rounded-lg border border-destructive/30 bg-destructive/10 p-4 shadow-xl", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
422
|
-
/* @__PURE__ */ jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 shrink-0 text-destructive" }),
|
|
423
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
424
|
-
/* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-medium text-destructive", children: "Cannot run workflow" }),
|
|
425
|
-
/* @__PURE__ */ jsxs("ul", { className: "space-y-1", children: [
|
|
426
|
-
uniqueErrorMessages.slice(0, 5).map((message) => /* @__PURE__ */ jsx("li", { className: "text-xs text-destructive/80", children: message }, message)),
|
|
427
|
-
uniqueErrorMessages.length > 5 && /* @__PURE__ */ jsxs("li", { className: "text-xs text-destructive/60", children: [
|
|
428
|
-
"+",
|
|
429
|
-
uniqueErrorMessages.length - 5,
|
|
430
|
-
" more errors"
|
|
431
|
-
] })
|
|
432
|
-
] })
|
|
433
|
-
] }),
|
|
434
|
-
/* @__PURE__ */ jsx(
|
|
435
|
-
"button",
|
|
436
|
-
{
|
|
437
|
-
onClick: clearValidationErrors,
|
|
438
|
-
className: "flex h-7 w-7 items-center justify-center rounded-md text-destructive transition hover:bg-destructive/20",
|
|
439
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
440
|
-
}
|
|
441
|
-
)
|
|
442
|
-
] }) }),
|
|
443
|
-
/* @__PURE__ */ jsx(
|
|
444
|
-
SaveAsDialog,
|
|
445
|
-
{
|
|
446
|
-
isOpen: showSaveAsDialog,
|
|
447
|
-
currentName: workflowName,
|
|
448
|
-
onSave: handleSaveAs,
|
|
449
|
-
onClose: () => setShowSaveAsDialog(false)
|
|
450
|
-
}
|
|
451
|
-
)
|
|
452
|
-
] });
|
|
453
|
-
}
|
|
454
|
-
var MIN_BATCH = 1;
|
|
455
|
-
var MAX_BATCH = 10;
|
|
456
|
-
function BottomBar() {
|
|
457
|
-
const [batchCount, setBatchCount] = useState(1);
|
|
458
|
-
const [currentBatchRun, setCurrentBatchRun] = useState(0);
|
|
459
|
-
const [isBatchRunning, setIsBatchRunning] = useState(false);
|
|
460
|
-
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
461
|
-
const batchCancelledRef = useRef(false);
|
|
462
|
-
const dropdownRef = useRef(null);
|
|
463
|
-
const isRunning = useExecutionStore((s) => s.isRunning);
|
|
464
|
-
const executeWorkflow = useExecutionStore((s) => s.executeWorkflow);
|
|
465
|
-
const executeSelectedNodes = useExecutionStore((s) => s.executeSelectedNodes);
|
|
466
|
-
const resumeFromFailed = useExecutionStore((s) => s.resumeFromFailed);
|
|
467
|
-
const canResumeFromFailed = useExecutionStore((s) => s.canResumeFromFailed);
|
|
468
|
-
const stopExecution = useExecutionStore((s) => s.stopExecution);
|
|
469
|
-
useExecutionStore((s) => s.lastFailedNodeId);
|
|
470
|
-
const selectedNodeIds = useWorkflowStore((s) => s.selectedNodeIds);
|
|
471
|
-
const nodes = useWorkflowStore((s) => s.nodes);
|
|
472
|
-
const validateWorkflow = useWorkflowStore((s) => s.validateWorkflow);
|
|
473
|
-
const canRunWorkflow = useMemo(() => {
|
|
474
|
-
if (nodes.length === 0) return false;
|
|
475
|
-
const validation = validateWorkflow();
|
|
476
|
-
return validation.isValid;
|
|
477
|
-
}, [nodes, validateWorkflow]);
|
|
478
|
-
const hasSelection = selectedNodeIds.length > 0;
|
|
479
|
-
const showResume = canResumeFromFailed();
|
|
480
|
-
const decrementBatch = useCallback(() => {
|
|
481
|
-
setBatchCount((prev) => Math.max(MIN_BATCH, prev - 1));
|
|
482
|
-
}, []);
|
|
483
|
-
const incrementBatch = useCallback(() => {
|
|
484
|
-
setBatchCount((prev) => Math.min(MAX_BATCH, prev + 1));
|
|
485
|
-
}, []);
|
|
486
|
-
const waitForExecutionEnd = useCallback(() => {
|
|
487
|
-
return new Promise((resolve) => {
|
|
488
|
-
if (!useExecutionStore.getState().isRunning) {
|
|
489
|
-
resolve();
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
const unsubscribe = useExecutionStore.subscribe((state) => {
|
|
493
|
-
if (!state.isRunning) {
|
|
494
|
-
unsubscribe();
|
|
495
|
-
resolve();
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
}, []);
|
|
500
|
-
const runBatch = useCallback(async () => {
|
|
501
|
-
batchCancelledRef.current = false;
|
|
502
|
-
setIsBatchRunning(true);
|
|
503
|
-
const accumulatedImages = /* @__PURE__ */ new Map();
|
|
504
|
-
for (let i = 0; i < batchCount; i++) {
|
|
505
|
-
if (batchCancelledRef.current) break;
|
|
506
|
-
setCurrentBatchRun(i + 1);
|
|
507
|
-
executeWorkflow();
|
|
508
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
509
|
-
if (!useExecutionStore.getState().isRunning) break;
|
|
510
|
-
await waitForExecutionEnd();
|
|
511
|
-
if (useExecutionStore.getState().lastFailedNodeId) break;
|
|
512
|
-
const { nodes: currentNodes, updateNodeData } = useWorkflowStore.getState();
|
|
513
|
-
for (const node of currentNodes) {
|
|
514
|
-
if (node.type !== "imageGen") continue;
|
|
515
|
-
const nodeData = node.data;
|
|
516
|
-
const newImages = nodeData.outputImages;
|
|
517
|
-
if (!newImages?.length) continue;
|
|
518
|
-
const existing = accumulatedImages.get(node.id) || [];
|
|
519
|
-
const merged = [...existing, ...newImages];
|
|
520
|
-
accumulatedImages.set(node.id, merged);
|
|
521
|
-
updateNodeData(node.id, { outputImages: merged });
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
setIsBatchRunning(false);
|
|
525
|
-
setCurrentBatchRun(0);
|
|
526
|
-
}, [batchCount, executeWorkflow, waitForExecutionEnd]);
|
|
527
|
-
const handlePrimaryClick = useCallback(() => {
|
|
528
|
-
if (isRunning || isBatchRunning) {
|
|
529
|
-
batchCancelledRef.current = true;
|
|
530
|
-
stopExecution();
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
if (batchCount > 1) {
|
|
534
|
-
runBatch();
|
|
535
|
-
} else {
|
|
536
|
-
executeWorkflow();
|
|
537
|
-
}
|
|
538
|
-
}, [isRunning, isBatchRunning, batchCount, runBatch, executeWorkflow, stopExecution]);
|
|
539
|
-
const handleRunSelected = useCallback(() => {
|
|
540
|
-
if (!isRunning && hasSelection) {
|
|
541
|
-
executeSelectedNodes();
|
|
542
|
-
setDropdownOpen(false);
|
|
543
|
-
}
|
|
544
|
-
}, [isRunning, hasSelection, executeSelectedNodes]);
|
|
545
|
-
const handleResume = useCallback(() => {
|
|
546
|
-
if (canResumeFromFailed()) {
|
|
547
|
-
resumeFromFailed();
|
|
548
|
-
setDropdownOpen(false);
|
|
549
|
-
}
|
|
550
|
-
}, [canResumeFromFailed, resumeFromFailed]);
|
|
551
|
-
const isActive = isRunning || isBatchRunning;
|
|
552
|
-
return /* @__PURE__ */ jsx(
|
|
553
|
-
"div",
|
|
554
|
-
{
|
|
555
|
-
className: "fixed bottom-5 left-1/2 z-50 -translate-x-1/2",
|
|
556
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
557
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-md border border-neutral-700/80 bg-neutral-800/95 px-2 py-1 shadow-lg backdrop-blur-sm", children: [
|
|
558
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
|
|
559
|
-
/* @__PURE__ */ jsx("span", { className: "mr-0.5 text-[11px] text-neutral-400", children: "Batch" }),
|
|
560
|
-
/* @__PURE__ */ jsx(
|
|
561
|
-
"button",
|
|
562
|
-
{
|
|
563
|
-
onClick: decrementBatch,
|
|
564
|
-
disabled: batchCount <= MIN_BATCH || isActive,
|
|
565
|
-
className: "flex h-5 w-5 items-center justify-center rounded text-neutral-400 transition hover:bg-neutral-700 hover:text-white disabled:opacity-40 disabled:hover:bg-transparent",
|
|
566
|
-
children: /* @__PURE__ */ jsx(Minus, { className: "h-2.5 w-2.5" })
|
|
567
|
-
}
|
|
568
|
-
),
|
|
569
|
-
/* @__PURE__ */ jsx("span", { className: "w-4 text-center text-xs font-medium tabular-nums text-white", children: batchCount }),
|
|
570
|
-
/* @__PURE__ */ jsx(
|
|
571
|
-
"button",
|
|
572
|
-
{
|
|
573
|
-
onClick: incrementBatch,
|
|
574
|
-
disabled: batchCount >= MAX_BATCH || isActive,
|
|
575
|
-
className: "flex h-5 w-5 items-center justify-center rounded text-neutral-400 transition hover:bg-neutral-700 hover:text-white disabled:opacity-40 disabled:hover:bg-transparent",
|
|
576
|
-
children: /* @__PURE__ */ jsx(Plus, { className: "h-2.5 w-2.5" })
|
|
577
|
-
}
|
|
578
|
-
)
|
|
579
|
-
] }),
|
|
580
|
-
/* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-neutral-600" }),
|
|
581
|
-
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
582
|
-
/* @__PURE__ */ jsx(
|
|
583
|
-
"button",
|
|
584
|
-
{
|
|
585
|
-
onClick: (e) => {
|
|
586
|
-
e.stopPropagation();
|
|
587
|
-
handlePrimaryClick();
|
|
588
|
-
},
|
|
589
|
-
disabled: !isActive && !canRunWorkflow,
|
|
590
|
-
className: `flex h-7 items-center gap-1.5 rounded-l px-3 text-xs font-medium transition ${isActive ? "bg-red-500/90 text-white hover:bg-red-500" : canRunWorkflow ? "bg-white text-black hover:bg-neutral-200" : "bg-neutral-600 text-neutral-400"} disabled:cursor-not-allowed`,
|
|
591
|
-
children: isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
592
|
-
/* @__PURE__ */ jsx(Square, { className: "h-3.5 w-3.5" }),
|
|
593
|
-
"Stop"
|
|
594
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
595
|
-
/* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5 fill-current" }),
|
|
596
|
-
"Run"
|
|
597
|
-
] })
|
|
598
|
-
}
|
|
599
|
-
),
|
|
600
|
-
/* @__PURE__ */ jsx(
|
|
601
|
-
"button",
|
|
602
|
-
{
|
|
603
|
-
onPointerDown: (e) => {
|
|
604
|
-
e.stopPropagation();
|
|
605
|
-
e.preventDefault();
|
|
606
|
-
},
|
|
607
|
-
onClick: (e) => {
|
|
608
|
-
e.stopPropagation();
|
|
609
|
-
setDropdownOpen((prev) => !prev);
|
|
610
|
-
},
|
|
611
|
-
disabled: isActive,
|
|
612
|
-
className: `flex h-7 items-center rounded-r border-l px-1.5 transition ${isActive ? "border-red-400/30 bg-red-500/90 text-white" : canRunWorkflow ? "border-neutral-300 bg-white text-black hover:bg-neutral-200" : "border-neutral-500 bg-neutral-600 text-neutral-400"} disabled:cursor-not-allowed`,
|
|
613
|
-
children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
614
|
-
}
|
|
615
|
-
),
|
|
616
|
-
dropdownOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
617
|
-
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: () => setDropdownOpen(false) }),
|
|
618
|
-
/* @__PURE__ */ jsxs(
|
|
619
|
-
"div",
|
|
620
|
-
{
|
|
621
|
-
ref: dropdownRef,
|
|
622
|
-
onClick: (e) => e.stopPropagation(),
|
|
623
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
624
|
-
className: "absolute bottom-full left-0 z-50 mb-1.5 min-w-[180px] rounded-md border border-neutral-700 bg-neutral-800 py-0.5 shadow-xl",
|
|
625
|
-
children: [
|
|
626
|
-
/* @__PURE__ */ jsxs(
|
|
627
|
-
"button",
|
|
628
|
-
{
|
|
629
|
-
onClick: () => {
|
|
630
|
-
executeWorkflow();
|
|
631
|
-
setDropdownOpen(false);
|
|
632
|
-
},
|
|
633
|
-
disabled: !canRunWorkflow,
|
|
634
|
-
className: "flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-xs text-neutral-200 transition hover:bg-neutral-700 disabled:text-neutral-500 disabled:hover:bg-transparent",
|
|
635
|
-
children: [
|
|
636
|
-
/* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
|
|
637
|
-
"Run Workflow"
|
|
638
|
-
]
|
|
639
|
-
}
|
|
640
|
-
),
|
|
641
|
-
/* @__PURE__ */ jsxs(
|
|
642
|
-
"button",
|
|
643
|
-
{
|
|
644
|
-
onClick: handleRunSelected,
|
|
645
|
-
disabled: !hasSelection || isRunning,
|
|
646
|
-
className: "flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-xs text-neutral-200 transition hover:bg-neutral-700 disabled:text-neutral-500 disabled:hover:bg-transparent",
|
|
647
|
-
children: [
|
|
648
|
-
/* @__PURE__ */ jsx(PlayCircle, { className: "h-3 w-3" }),
|
|
649
|
-
"Run Selected (",
|
|
650
|
-
selectedNodeIds.length,
|
|
651
|
-
")"
|
|
652
|
-
]
|
|
653
|
-
}
|
|
654
|
-
),
|
|
655
|
-
showResume && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
656
|
-
/* @__PURE__ */ jsx("div", { className: "mx-2 my-0.5 h-px bg-neutral-700" }),
|
|
657
|
-
/* @__PURE__ */ jsxs(
|
|
658
|
-
"button",
|
|
659
|
-
{
|
|
660
|
-
onClick: handleResume,
|
|
661
|
-
className: "flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left text-xs text-neutral-200 transition hover:bg-neutral-700",
|
|
662
|
-
children: [
|
|
663
|
-
/* @__PURE__ */ jsx(RotateCcw, { className: "h-3 w-3" }),
|
|
664
|
-
"Resume from Failed"
|
|
665
|
-
]
|
|
666
|
-
}
|
|
667
|
-
)
|
|
668
|
-
] })
|
|
669
|
-
]
|
|
670
|
-
}
|
|
671
|
-
)
|
|
672
|
-
] })
|
|
673
|
-
] }),
|
|
674
|
-
isBatchRunning && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
675
|
-
/* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-neutral-600" }),
|
|
676
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[11px] tabular-nums text-neutral-400", children: [
|
|
677
|
-
currentBatchRun,
|
|
678
|
-
"/",
|
|
679
|
-
batchCount
|
|
680
|
-
] })
|
|
681
|
-
] })
|
|
682
|
-
] })
|
|
683
|
-
}
|
|
579
|
+
branding,
|
|
580
|
+
leftContent,
|
|
581
|
+
middleContent,
|
|
582
|
+
saveIndicator,
|
|
583
|
+
logoHref = "/",
|
|
584
|
+
logoSrc = "https://cdn.genfeed.ai/assets/branding/logo-white.png",
|
|
585
|
+
showSettings = true,
|
|
586
|
+
showShortcutHelp = false,
|
|
587
|
+
rightContent
|
|
588
|
+
}) {
|
|
589
|
+
const { exportWorkflow, workflowName } = useWorkflowStore();
|
|
590
|
+
const { undo, redo } = useWorkflowStore.temporal.getState();
|
|
591
|
+
const [canUndo, setCanUndo] = useState(false);
|
|
592
|
+
const [canRedo, setCanRedo] = useState(false);
|
|
593
|
+
const [showSaveAsDialog, setShowSaveAsDialog] = useState(false);
|
|
594
|
+
const validationErrors = useExecutionStore((state) => state.validationErrors);
|
|
595
|
+
const clearValidationErrors = useExecutionStore(
|
|
596
|
+
(state) => state.clearValidationErrors
|
|
684
597
|
);
|
|
685
|
-
}
|
|
686
|
-
function CostIndicator() {
|
|
687
|
-
const nodes = useWorkflowStore((state) => state.nodes);
|
|
688
|
-
const isRunning = useExecutionStore((state) => state.isRunning);
|
|
689
|
-
const actualCost = useExecutionStore((state) => state.actualCost);
|
|
690
598
|
const { openModal } = useUIStore();
|
|
691
|
-
const
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
{
|
|
697
|
-
onClick: () => openModal("cost"),
|
|
698
|
-
title: "View cost breakdown",
|
|
699
|
-
className: "flex items-center gap-1.5 rounded-md border border-[var(--border)] px-2 py-1 text-sm text-[var(--muted-foreground)] transition hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",
|
|
700
|
-
children: [
|
|
701
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3.5 w-3.5" }),
|
|
702
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: formatCost(displayCost) }),
|
|
703
|
-
isRunning && actualCost > 0 && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" })
|
|
704
|
-
]
|
|
705
|
-
}
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
function OverflowMenu({ items }) {
|
|
709
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
710
|
-
const menuRef = useRef(null);
|
|
599
|
+
const debugMode = useSettingsStore((s) => s.debugMode);
|
|
600
|
+
const uniqueErrorMessages = useMemo(() => {
|
|
601
|
+
if (!validationErrors?.errors.length) return [];
|
|
602
|
+
return [...new Set(validationErrors.errors.map((e) => e.message))];
|
|
603
|
+
}, [validationErrors]);
|
|
711
604
|
useEffect(() => {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
605
|
+
const unsubscribe = useWorkflowStore.temporal.subscribe((state) => {
|
|
606
|
+
setCanUndo(state.pastStates.length > 0);
|
|
607
|
+
setCanRedo(state.futureStates.length > 0);
|
|
608
|
+
});
|
|
609
|
+
const temporal = useWorkflowStore.temporal.getState();
|
|
610
|
+
setCanUndo(temporal.pastStates.length > 0);
|
|
611
|
+
setCanRedo(temporal.futureStates.length > 0);
|
|
612
|
+
return unsubscribe;
|
|
613
|
+
}, []);
|
|
614
|
+
const handleExport = useCallback(() => {
|
|
615
|
+
const workflow = exportWorkflow();
|
|
616
|
+
const blob = new Blob([JSON.stringify(workflow, null, 2)], {
|
|
617
|
+
type: "application/json"
|
|
618
|
+
});
|
|
619
|
+
const url = URL.createObjectURL(blob);
|
|
620
|
+
const link = document.createElement("a");
|
|
621
|
+
link.href = url;
|
|
622
|
+
link.download = `${workflow.name.toLowerCase().replace(/\s+/g, "-")}.json`;
|
|
623
|
+
document.body.appendChild(link);
|
|
624
|
+
link.click();
|
|
625
|
+
document.body.removeChild(link);
|
|
626
|
+
URL.revokeObjectURL(url);
|
|
627
|
+
}, [exportWorkflow]);
|
|
628
|
+
const handleImport = useCallback(() => {
|
|
629
|
+
const input = document.createElement("input");
|
|
630
|
+
input.type = "file";
|
|
631
|
+
input.accept = ".json";
|
|
632
|
+
input.onchange = (e) => {
|
|
633
|
+
const file = e.target.files?.[0];
|
|
634
|
+
if (!file) return;
|
|
635
|
+
const reader = new FileReader();
|
|
636
|
+
reader.onload = (event) => {
|
|
637
|
+
try {
|
|
638
|
+
const data = JSON.parse(event.target?.result);
|
|
639
|
+
if (!isValidWorkflow(data)) {
|
|
640
|
+
console.warn("[Toolbar] Invalid workflow file structure");
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
useWorkflowStore.getState().loadWorkflow(data);
|
|
644
|
+
} catch {
|
|
645
|
+
console.warn("[Toolbar] Failed to parse workflow file");
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
reader.readAsText(file);
|
|
717
649
|
};
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (event.key === "Escape") {
|
|
725
|
-
setIsOpen(false);
|
|
650
|
+
input.click();
|
|
651
|
+
}, []);
|
|
652
|
+
const handleSaveAs = useCallback(
|
|
653
|
+
(newName) => {
|
|
654
|
+
if (onSaveAs) {
|
|
655
|
+
onSaveAs(newName);
|
|
726
656
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
657
|
+
setShowSaveAsDialog(false);
|
|
658
|
+
},
|
|
659
|
+
[onSaveAs]
|
|
660
|
+
);
|
|
661
|
+
const fileMenuItems = useMemo(() => {
|
|
662
|
+
const items = [];
|
|
663
|
+
if (fileMenuItemsPrepend?.length) {
|
|
664
|
+
items.push(...fileMenuItemsPrepend);
|
|
665
|
+
items.push({ id: "separator-prepend", separator: true });
|
|
666
|
+
}
|
|
667
|
+
if (onSaveAs) {
|
|
668
|
+
items.push({
|
|
669
|
+
icon: /* @__PURE__ */ jsx(SaveAll, { className: "h-4 w-4" }),
|
|
670
|
+
id: "saveAs",
|
|
671
|
+
label: "Save As...",
|
|
672
|
+
onClick: () => setShowSaveAsDialog(true)
|
|
673
|
+
});
|
|
674
|
+
items.push({ id: "separator-saveas", separator: true });
|
|
675
|
+
}
|
|
676
|
+
items.push(
|
|
677
|
+
{
|
|
678
|
+
icon: /* @__PURE__ */ jsx(Save, { className: "h-4 w-4" }),
|
|
679
|
+
id: "export",
|
|
680
|
+
label: "Export Workflow",
|
|
681
|
+
onClick: handleExport
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
icon: /* @__PURE__ */ jsx(FolderOpen, { className: "h-4 w-4" }),
|
|
685
|
+
id: "import",
|
|
686
|
+
label: "Import Workflow",
|
|
687
|
+
onClick: handleImport
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
if (fileMenuItemsAppend?.length) {
|
|
691
|
+
items.push({ id: "separator-append", separator: true });
|
|
692
|
+
items.push(...fileMenuItemsAppend);
|
|
693
|
+
}
|
|
694
|
+
return items;
|
|
695
|
+
}, [
|
|
696
|
+
handleExport,
|
|
697
|
+
handleImport,
|
|
698
|
+
onSaveAs,
|
|
699
|
+
fileMenuItemsPrepend,
|
|
700
|
+
fileMenuItemsAppend
|
|
701
|
+
]);
|
|
702
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-14 items-center gap-3 border-b border-border bg-card px-4", children: [
|
|
703
|
+
branding ?? /* @__PURE__ */ jsx(
|
|
704
|
+
"a",
|
|
705
|
+
{
|
|
706
|
+
href: logoHref,
|
|
707
|
+
title: "Go to Dashboard",
|
|
708
|
+
className: "flex h-6 w-6 items-center justify-center transition hover:opacity-90",
|
|
709
|
+
children: /* @__PURE__ */ jsx("img", { src: logoSrc, alt: "Genfeed", className: "h-6 w-6 object-contain" })
|
|
710
|
+
}
|
|
711
|
+
),
|
|
712
|
+
leftContent,
|
|
713
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-px bg-border" }),
|
|
714
|
+
/* @__PURE__ */ jsx(ToolbarDropdown, { label: "File", items: fileMenuItems }),
|
|
715
|
+
additionalMenus?.map((menu) => /* @__PURE__ */ jsx(
|
|
716
|
+
ToolbarDropdown,
|
|
717
|
+
{
|
|
718
|
+
label: menu.label,
|
|
719
|
+
items: menu.items
|
|
720
|
+
},
|
|
721
|
+
menu.label
|
|
722
|
+
)),
|
|
723
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-px bg-border" }),
|
|
724
|
+
debugMode && /* @__PURE__ */ jsxs(
|
|
725
|
+
"button",
|
|
726
|
+
{
|
|
727
|
+
onClick: () => openModal("settings"),
|
|
728
|
+
title: "Debug mode active - API calls are mocked",
|
|
729
|
+
className: "flex items-center gap-1.5 rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1 text-sm text-amber-500 transition hover:bg-amber-500/20",
|
|
730
|
+
children: [
|
|
731
|
+
/* @__PURE__ */ jsx(Bug, { className: "h-4 w-4" }),
|
|
732
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Debug" })
|
|
733
|
+
]
|
|
734
|
+
}
|
|
735
|
+
),
|
|
736
|
+
onAutoLayout && /* @__PURE__ */ jsx(
|
|
737
|
+
"button",
|
|
738
|
+
{
|
|
739
|
+
onClick: () => onAutoLayout("LR"),
|
|
740
|
+
title: "Auto-layout nodes",
|
|
741
|
+
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
742
|
+
children: /* @__PURE__ */ jsx(LayoutGrid, { className: "h-4 w-4" })
|
|
743
|
+
}
|
|
744
|
+
),
|
|
732
745
|
/* @__PURE__ */ jsx(
|
|
733
746
|
"button",
|
|
734
747
|
{
|
|
735
|
-
onClick: () =>
|
|
736
|
-
|
|
748
|
+
onClick: () => undo(),
|
|
749
|
+
disabled: !canUndo,
|
|
750
|
+
title: "Undo (Ctrl+Z)",
|
|
751
|
+
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
752
|
+
children: /* @__PURE__ */ jsx(Undo2, { className: "h-4 w-4" })
|
|
753
|
+
}
|
|
754
|
+
),
|
|
755
|
+
/* @__PURE__ */ jsx(
|
|
756
|
+
"button",
|
|
757
|
+
{
|
|
758
|
+
onClick: () => redo(),
|
|
759
|
+
disabled: !canRedo,
|
|
760
|
+
title: "Redo (Ctrl+Shift+Z)",
|
|
761
|
+
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
762
|
+
children: /* @__PURE__ */ jsx(Redo2, { className: "h-4 w-4" })
|
|
763
|
+
}
|
|
764
|
+
),
|
|
765
|
+
saveIndicator ?? /* @__PURE__ */ jsx(SaveIndicator, {}),
|
|
766
|
+
middleContent,
|
|
767
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
768
|
+
rightContent,
|
|
769
|
+
showShortcutHelp && /* @__PURE__ */ jsx(
|
|
770
|
+
"button",
|
|
771
|
+
{
|
|
772
|
+
onClick: () => openModal("shortcutHelp"),
|
|
773
|
+
title: "Keyboard shortcuts",
|
|
737
774
|
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
738
|
-
children: /* @__PURE__ */ jsx(
|
|
775
|
+
children: /* @__PURE__ */ jsx(HelpCircle, { className: "h-4 w-4" })
|
|
739
776
|
}
|
|
740
777
|
),
|
|
741
|
-
|
|
778
|
+
showSettings && /* @__PURE__ */ jsx(
|
|
742
779
|
"button",
|
|
743
780
|
{
|
|
744
|
-
onClick: () =>
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
781
|
+
onClick: () => openModal("settings"),
|
|
782
|
+
title: "Settings",
|
|
783
|
+
className: "flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:bg-secondary hover:text-foreground",
|
|
784
|
+
children: /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" })
|
|
785
|
+
}
|
|
786
|
+
),
|
|
787
|
+
uniqueErrorMessages.length > 0 && /* @__PURE__ */ jsx("div", { className: "fixed right-4 top-20 z-50 max-w-sm rounded-lg border border-destructive/30 bg-destructive/10 p-4 shadow-xl", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
788
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 shrink-0 text-destructive" }),
|
|
789
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
790
|
+
/* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-medium text-destructive", children: "Cannot run workflow" }),
|
|
791
|
+
/* @__PURE__ */ jsxs("ul", { className: "space-y-1", children: [
|
|
792
|
+
uniqueErrorMessages.slice(0, 5).map((message) => /* @__PURE__ */ jsx("li", { className: "text-xs text-destructive/80", children: message }, message)),
|
|
793
|
+
uniqueErrorMessages.length > 5 && /* @__PURE__ */ jsxs("li", { className: "text-xs text-destructive/60", children: [
|
|
794
|
+
"+",
|
|
795
|
+
uniqueErrorMessages.length - 5,
|
|
796
|
+
" more errors"
|
|
797
|
+
] })
|
|
798
|
+
] })
|
|
799
|
+
] }),
|
|
800
|
+
/* @__PURE__ */ jsx(
|
|
801
|
+
"button",
|
|
802
|
+
{
|
|
803
|
+
onClick: clearValidationErrors,
|
|
804
|
+
className: "flex h-7 w-7 items-center justify-center rounded-md text-destructive transition hover:bg-destructive/20",
|
|
805
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
] }) }),
|
|
809
|
+
/* @__PURE__ */ jsx(
|
|
810
|
+
SaveAsDialog,
|
|
811
|
+
{
|
|
812
|
+
isOpen: showSaveAsDialog,
|
|
813
|
+
currentName: workflowName,
|
|
814
|
+
onSave: handleSaveAs,
|
|
815
|
+
onClose: () => setShowSaveAsDialog(false)
|
|
816
|
+
}
|
|
817
|
+
)
|
|
757
818
|
] });
|
|
758
819
|
}
|
|
759
820
|
|