@burdenoff/fluidgrids-fe-sdk 2026.1105.2 → 2026.1105.3
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 +30 -0
- package/dist/{ExecutionDetailPage-HQB2SWBP.js → ExecutionDetailPage-JXGPSLS3.js} +9 -7
- package/dist/{ExecutionsListPage-RSLL634L.js → ExecutionsListPage-U3TC5JUI.js} +8 -6
- package/dist/{WorkflowBuilder-E3PNZX74.js → WorkflowBuilder-6DOD6WWH.js} +13 -10
- package/dist/{WorkflowsList-473FRRP2.js → WorkflowsList-XOWRQNST.js} +9 -7
- package/dist/{agentTools-5N3ZEZDW.js → agentTools-YQBN33WG.js} +6 -4
- package/dist/{chunk-XFH6SLDK.js → chunk-22U3TUKZ.js} +1 -1
- package/dist/{chunk-QFC6E3WD.js → chunk-2WUVF4EQ.js} +1 -1
- package/dist/{chunk-W5WML3OO.js → chunk-3MZOYJ5I.js} +1 -1
- package/dist/{chunk-3XUWPDKJ.js → chunk-3ZYRLGDF.js} +5 -5
- package/dist/{chunk-SALZ3LQB.js → chunk-4NNF7DIB.js} +1 -1
- package/dist/chunk-5DVP2UYH.js +1924 -0
- package/dist/{chunk-CH3YUGWR.js → chunk-EKDUYCTS.js} +4 -4
- package/dist/{chunk-Y6M7K4UG.js → chunk-FVWRGL7S.js} +1 -1
- package/dist/{chunk-2KUZBEWW.js → chunk-G33OMSW6.js} +6 -5
- package/dist/{chunk-6IS7EXIY.js → chunk-G774FI2E.js} +2 -2
- package/dist/{chunk-5CFCFA6O.js → chunk-IJ7UIDCW.js} +6 -5
- package/dist/chunk-JYYN3CLL.js +32 -0
- package/dist/{chunk-GKFMK7FF.js → chunk-KAWRHIJZ.js} +1 -1
- package/dist/{chunk-QPLFALVV.js → chunk-KN37RIFC.js} +4 -3
- package/dist/{chunk-AASHQI77.js → chunk-KZCNJAMK.js} +2 -1
- package/dist/{chunk-X25NCWG2.js → chunk-LDWBMCX4.js} +4 -3
- package/dist/{chunk-CNKTQ5MG.js → chunk-MA6I4KY5.js} +2 -2
- package/dist/{chunk-O7CJJNW6.js → chunk-MP44TAOO.js} +2 -2
- package/dist/{chunk-6ZK4MRVA.js → chunk-MY7SEUXM.js} +1 -1
- package/dist/{chunk-62CCBMDC.js → chunk-NMQENSPW.js} +12 -11
- package/dist/{chunk-HK76ZUAK.js → chunk-NZFPCTIS.js} +4 -4
- package/dist/chunk-SDZEAIIE.js +9 -0
- package/dist/{chunk-64JN2TVX.js → chunk-TJAG2DQT.js} +8 -37
- package/dist/{chunk-TNFBRXU7.js → chunk-TLHX4DK4.js} +6 -5
- package/dist/{chunk-J3YQCEP3.js → chunk-VESQH55Q.js} +1 -1
- package/dist/{chunk-OCJTTHTS.js → chunk-Y7FKJJMF.js} +1 -9
- package/dist/{chunk-QFGZXWTM.js → chunk-YGOGJDN4.js} +1 -1
- package/dist/{chunk-3QCRKKTI.js → chunk-ZBPRNWKY.js} +234 -2145
- package/dist/{chunk-AJVU5YW6.js → chunk-ZLBTUCC2.js} +3 -2
- package/dist/code-editor-field-WWPDMF4O.js +3 -0
- package/dist/executions.js +10 -8
- package/dist/history.js +8 -5
- package/dist/index.js +36 -33
- package/dist/{node-detail-view-WIWW5N6N.js → node-detail-view-U5HOP2WX.js} +10 -8
- package/dist/observability.js +6 -4
- package/dist/{quick-config-panel-O64TXXK3.js → quick-config-panel-XAISBW6Y.js} +8 -6
- package/dist/settings.js +7 -5
- package/dist/templates.js +12 -9
- package/dist/workflow-builder.js +14 -11
- package/dist/workflow-preview.d.ts +158 -0
- package/dist/workflow-preview.js +721 -0
- package/dist/workflows-list.js +9 -7
- package/package.json +5 -1
- package/dist/code-editor-field-XWUVOKTA.js +0 -2
|
@@ -0,0 +1,1924 @@
|
|
|
1
|
+
import { editorSeverity } from './chunk-Y7FKJJMF.js';
|
|
2
|
+
import { useTr } from './chunk-UULVIT25.js';
|
|
3
|
+
import { nanoid } from 'nanoid/non-secure';
|
|
4
|
+
import { addEdge, applyEdgeChanges, applyNodeChanges, useUpdateNodeInternals, Position, useStore, useNodeId, getConnectedEdges, Handle } from '@xyflow/react';
|
|
5
|
+
import { create } from 'zustand';
|
|
6
|
+
import { produce } from 'immer';
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { useCallback, useMemo, useEffect } from 'react';
|
|
9
|
+
import { Icon } from '@iconify/react';
|
|
10
|
+
import { cn } from '@burdenoff/fe-libs/utils';
|
|
11
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
|
+
import { nodeCategoryColor, chartSeriesColor, chartSeriesIndex } from '@burdenoff/fe-libs/shared/utils';
|
|
13
|
+
|
|
14
|
+
// src/builder/lib/node-role.ts
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function readRecord(value) {
|
|
19
|
+
return isRecord(value) ? value : void 0;
|
|
20
|
+
}
|
|
21
|
+
function readString(value) {
|
|
22
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
23
|
+
}
|
|
24
|
+
function operationRole(operation) {
|
|
25
|
+
const metadata = readRecord(operation);
|
|
26
|
+
const role = readString(metadata?.role);
|
|
27
|
+
if (role) return role === "trigger" ? "trigger" : "action";
|
|
28
|
+
const type = readString(metadata?.type);
|
|
29
|
+
if (type === "trigger" || type === "subscription" || readString(metadata?.delivery)) {
|
|
30
|
+
return "trigger";
|
|
31
|
+
}
|
|
32
|
+
if (type === "action" || type === "query" || type === "mutation") {
|
|
33
|
+
return "action";
|
|
34
|
+
}
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
function isTriggerOperation(operation) {
|
|
38
|
+
return operationRole(operation) === "trigger";
|
|
39
|
+
}
|
|
40
|
+
function isLegacyTriggerType(type) {
|
|
41
|
+
return type === "trigger" || type.startsWith("trigger:") || type.startsWith("internal:schedule") || type.startsWith("internal:webhook") || type.startsWith("internal:api") || type.startsWith("internal:form") || type.startsWith("internal:mcp") || type.endsWith("_trigger") || type.endsWith("Trigger") || type.endsWith("-trigger");
|
|
42
|
+
}
|
|
43
|
+
function getSelectedOperationRole(nodeType, nodeData) {
|
|
44
|
+
const data = readRecord(nodeData);
|
|
45
|
+
const operationName = data?.operation_name;
|
|
46
|
+
if (typeof operationName !== "string" || !operationName.trim())
|
|
47
|
+
return void 0;
|
|
48
|
+
const manifest = readRecord(data?.nodeData);
|
|
49
|
+
const declared = manifest?.operations;
|
|
50
|
+
const operations = Array.isArray(declared) ? declared : readRecord(declared)?.operations;
|
|
51
|
+
const operation = Array.isArray(operations) ? operations.find(
|
|
52
|
+
(entry) => readRecord(entry)?.name === operationName
|
|
53
|
+
) : void 0;
|
|
54
|
+
const selectedRole = operationRole(operation) ?? operationRole({ type: data?.operation_type });
|
|
55
|
+
if (selectedRole) return selectedRole;
|
|
56
|
+
if (nodeType === "integration/channel" || nodeType?.startsWith("store/")) {
|
|
57
|
+
return operationName.startsWith("on") ? "trigger" : "action";
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function isNodeTrigger(node) {
|
|
62
|
+
const data = readRecord(node.data);
|
|
63
|
+
const selectedRole = getSelectedOperationRole(node.type, data);
|
|
64
|
+
if (selectedRole) return selectedRole === "trigger";
|
|
65
|
+
const state = readRecord(readRecord(data?.args)?.state);
|
|
66
|
+
for (const legacyRole of [data?.role, state?.role]) {
|
|
67
|
+
const explicitRole = readString(legacyRole);
|
|
68
|
+
if (explicitRole === "trigger" || explicitRole === "action") {
|
|
69
|
+
return explicitRole === "trigger";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const savedRole = operationRole({ type: data?.operation_type });
|
|
73
|
+
if (savedRole) return savedRole === "trigger";
|
|
74
|
+
const type = node.type ?? "";
|
|
75
|
+
if (isLegacyTriggerType(type)) return true;
|
|
76
|
+
const manifest = readRecord(data?.nodeData);
|
|
77
|
+
if (!data?.operation_name && Array.isArray(manifest?.kind) && manifest.kind.includes("trigger")) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/builder/hooks/use-is-valid-connection.ts
|
|
84
|
+
function isLoopController(source) {
|
|
85
|
+
const type = source?.type?.toLowerCase();
|
|
86
|
+
const disabled = source?.data?.disabled === true || source !== void 0 && "disabled" in source && source.disabled === true;
|
|
87
|
+
return (type === "internal:loop" || type === "loop") && !disabled;
|
|
88
|
+
}
|
|
89
|
+
function isRecord2(value) {
|
|
90
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
function readRecord2(value) {
|
|
93
|
+
return isRecord2(value) ? value : void 0;
|
|
94
|
+
}
|
|
95
|
+
function loopBodyHandle(source) {
|
|
96
|
+
const node = readRecord2(source);
|
|
97
|
+
const data = readRecord2(node?.data);
|
|
98
|
+
const parameters = {
|
|
99
|
+
...data,
|
|
100
|
+
...readRecord2(node?.config),
|
|
101
|
+
...readRecord2(readRecord2(data?.args)?.state),
|
|
102
|
+
...readRecord2(readRecord2(node?.args)?.state)
|
|
103
|
+
};
|
|
104
|
+
return typeof parameters.loopHandle === "string" && parameters.loopHandle ? parameters.loopHandle : "loop";
|
|
105
|
+
}
|
|
106
|
+
function isLoopBodyEdge(edge, nodes) {
|
|
107
|
+
const source = nodes.find((node) => node.id === edge.source);
|
|
108
|
+
return source !== void 0 && isLoopController(source) && edge.sourceHandle === loopBodyHandle(source);
|
|
109
|
+
}
|
|
110
|
+
function adjacency(edges) {
|
|
111
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const edge of edges) {
|
|
113
|
+
const targets = outgoing.get(edge.source) ?? /* @__PURE__ */ new Set();
|
|
114
|
+
targets.add(edge.target);
|
|
115
|
+
outgoing.set(edge.source, targets);
|
|
116
|
+
}
|
|
117
|
+
return outgoing;
|
|
118
|
+
}
|
|
119
|
+
function reachable(starts, outgoing, blocked = /* @__PURE__ */ new Set()) {
|
|
120
|
+
const pending = [...starts];
|
|
121
|
+
const visited = /* @__PURE__ */ new Set();
|
|
122
|
+
while (pending.length > 0) {
|
|
123
|
+
const current = pending.pop();
|
|
124
|
+
if (current === void 0 || visited.has(current) || blocked.has(current))
|
|
125
|
+
continue;
|
|
126
|
+
visited.add(current);
|
|
127
|
+
pending.push(...outgoing.get(current) ?? []);
|
|
128
|
+
}
|
|
129
|
+
return visited;
|
|
130
|
+
}
|
|
131
|
+
function controlledReturns(nodes, edges) {
|
|
132
|
+
const outgoing = adjacency(edges);
|
|
133
|
+
const incomingTargets = new Set(edges.map((edge) => edge.target));
|
|
134
|
+
const entries = nodes.filter((node) => !incomingTargets.has(node.id) || isNodeTrigger(node)).map((node) => node.id);
|
|
135
|
+
const returns = /* @__PURE__ */ new Set();
|
|
136
|
+
for (const controller of nodes.filter(isLoopController)) {
|
|
137
|
+
const bodyHandle = loopBodyHandle(controller);
|
|
138
|
+
const controllerEdges = edges.filter(
|
|
139
|
+
(edge) => edge.source === controller.id
|
|
140
|
+
);
|
|
141
|
+
const outside = reachable(
|
|
142
|
+
[
|
|
143
|
+
...entries,
|
|
144
|
+
...controllerEdges.filter((edge) => edge.sourceHandle !== bodyHandle).map((edge) => edge.target)
|
|
145
|
+
],
|
|
146
|
+
outgoing,
|
|
147
|
+
/* @__PURE__ */ new Set([controller.id])
|
|
148
|
+
);
|
|
149
|
+
const body = reachable(
|
|
150
|
+
controllerEdges.filter((edge) => edge.sourceHandle === bodyHandle).map((edge) => edge.target),
|
|
151
|
+
outgoing,
|
|
152
|
+
/* @__PURE__ */ new Set([controller.id, ...outside])
|
|
153
|
+
);
|
|
154
|
+
for (const edge of edges) {
|
|
155
|
+
if (edge.target === controller.id && body.has(edge.source))
|
|
156
|
+
returns.add(edge);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return returns;
|
|
160
|
+
}
|
|
161
|
+
function isValidWorkflowConnection(nodes, edges, connection) {
|
|
162
|
+
if (connection.target === connection.source) return false;
|
|
163
|
+
const candidates = [...edges, connection];
|
|
164
|
+
const previousReturns = controlledReturns(nodes, edges);
|
|
165
|
+
const nextReturns = controlledReturns(nodes, candidates);
|
|
166
|
+
const outgoing = adjacency(
|
|
167
|
+
candidates.filter((edge) => !nextReturns.has(edge))
|
|
168
|
+
);
|
|
169
|
+
const invalidated = [connection, ...previousReturns].filter(
|
|
170
|
+
(edge) => !nextReturns.has(edge)
|
|
171
|
+
);
|
|
172
|
+
return invalidated.every(
|
|
173
|
+
(edge) => !reachable([edge.target], outgoing).has(edge.source)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function useIsValidConnection(nodes, edges) {
|
|
177
|
+
return useCallback(
|
|
178
|
+
(connection) => isValidWorkflowConnection(nodes, edges, connection),
|
|
179
|
+
[nodes, edges]
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/builder/stores/flow-store.ts
|
|
184
|
+
var HISTORY_LIMIT = 100;
|
|
185
|
+
var CLONE_OFFSET = 48;
|
|
186
|
+
function cloneValue(value) {
|
|
187
|
+
try {
|
|
188
|
+
return structuredClone(value);
|
|
189
|
+
} catch {
|
|
190
|
+
return JSON.parse(JSON.stringify(value));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
var TAGS = [
|
|
194
|
+
{ value: "marketing", label: "Marketing", color: "var(--color-chart-5)" },
|
|
195
|
+
{ value: "support", label: "Support", color: "var(--color-chart-5)" },
|
|
196
|
+
{ value: "lead", label: "Lead", color: "var(--color-chart-4)" },
|
|
197
|
+
{ value: "new", label: "New", color: "var(--color-chart-7)" }
|
|
198
|
+
];
|
|
199
|
+
var INITIAL_EXECUTION = {
|
|
200
|
+
isExecuting: false,
|
|
201
|
+
executingNodeId: null,
|
|
202
|
+
currentRunId: null,
|
|
203
|
+
sourceRun: null,
|
|
204
|
+
stepPlan: null,
|
|
205
|
+
logs: [],
|
|
206
|
+
selectedLogId: null,
|
|
207
|
+
panelExpanded: false,
|
|
208
|
+
nodeStatuses: {},
|
|
209
|
+
nodeItemCounts: {},
|
|
210
|
+
followRequest: null
|
|
211
|
+
};
|
|
212
|
+
var INITIAL_HISTORY = { past: [], future: [] };
|
|
213
|
+
var useFlowStore = create()((set, get) => {
|
|
214
|
+
let lastCheckpointKind = null;
|
|
215
|
+
let lastCheckpointAt = 0;
|
|
216
|
+
function pushCheckpoint(kind, coalesce) {
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
const isCoalesced = coalesce && kind === lastCheckpointKind && now - lastCheckpointAt < 800;
|
|
219
|
+
if (!isCoalesced) {
|
|
220
|
+
const state = get();
|
|
221
|
+
const snapshot = {
|
|
222
|
+
nodes: cloneValue(state.workflow.nodes),
|
|
223
|
+
edges: cloneValue(state.workflow.edges)
|
|
224
|
+
};
|
|
225
|
+
set((s) => ({
|
|
226
|
+
history: {
|
|
227
|
+
past: [...s.history.past, snapshot].slice(-HISTORY_LIMIT),
|
|
228
|
+
future: []
|
|
229
|
+
}
|
|
230
|
+
}));
|
|
231
|
+
} else if (get().history.future.length > 0) {
|
|
232
|
+
set((s) => ({ history: { ...s.history, future: [] } }));
|
|
233
|
+
}
|
|
234
|
+
lastCheckpointKind = kind;
|
|
235
|
+
lastCheckpointAt = now;
|
|
236
|
+
noteEdit();
|
|
237
|
+
}
|
|
238
|
+
function noteEdit() {
|
|
239
|
+
set((s) => ({ editRevision: s.editRevision + 1 }));
|
|
240
|
+
}
|
|
241
|
+
const REMOVAL_KINDS = /* @__PURE__ */ new Set(["nodes:remove", "edges:remove"]);
|
|
242
|
+
const SAME_ACTION_WINDOW_MS = 150;
|
|
243
|
+
function joinsPrecedingRemoval(kind) {
|
|
244
|
+
return lastCheckpointKind !== null && lastCheckpointKind !== kind && REMOVAL_KINDS.has(kind) && REMOVAL_KINDS.has(lastCheckpointKind) && Date.now() - lastCheckpointAt < SAME_ACTION_WINDOW_MS;
|
|
245
|
+
}
|
|
246
|
+
function resetCoalescing() {
|
|
247
|
+
lastCheckpointKind = null;
|
|
248
|
+
lastCheckpointAt = 0;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
tags: TAGS,
|
|
252
|
+
history: { ...INITIAL_HISTORY },
|
|
253
|
+
editRevision: 0,
|
|
254
|
+
loadGeneration: 0,
|
|
255
|
+
workflow: {
|
|
256
|
+
id: nanoid(),
|
|
257
|
+
workflowKey: "",
|
|
258
|
+
name: "",
|
|
259
|
+
version: "",
|
|
260
|
+
alias: "",
|
|
261
|
+
edges: [],
|
|
262
|
+
nodes: [],
|
|
263
|
+
ndv: null,
|
|
264
|
+
quickConfig: null,
|
|
265
|
+
pendingConnection: null,
|
|
266
|
+
renamingNodeId: null,
|
|
267
|
+
sidebar: {
|
|
268
|
+
active: "none",
|
|
269
|
+
panels: {
|
|
270
|
+
nodeProperties: {
|
|
271
|
+
selectedNode: null
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
execution: { ...INITIAL_EXECUTION },
|
|
277
|
+
actions: {
|
|
278
|
+
saveWorkflow: () => {
|
|
279
|
+
const { workflow } = get();
|
|
280
|
+
const { id, name, version, alias, dbId, status, nodes, edges } = workflow;
|
|
281
|
+
set({ workflow });
|
|
282
|
+
return { id, name, version, alias, dbId, status, nodes, edges };
|
|
283
|
+
},
|
|
284
|
+
setWorkflow: (workflow) => {
|
|
285
|
+
const current = get().workflow;
|
|
286
|
+
if (workflow.name !== void 0 && workflow.name !== current.name || workflow.alias !== void 0 && workflow.alias !== current.alias) {
|
|
287
|
+
noteEdit();
|
|
288
|
+
}
|
|
289
|
+
const nodes = workflow.nodes?.map(
|
|
290
|
+
(node) => node.data === void 0 || node.data === null ? { ...node, data: {} } : node
|
|
291
|
+
);
|
|
292
|
+
set((state) => ({
|
|
293
|
+
workflow: {
|
|
294
|
+
...state.workflow,
|
|
295
|
+
...workflow,
|
|
296
|
+
...nodes ? { nodes } : {}
|
|
297
|
+
}
|
|
298
|
+
}));
|
|
299
|
+
},
|
|
300
|
+
resetWorkflow: () => {
|
|
301
|
+
resetCoalescing();
|
|
302
|
+
set((s) => ({
|
|
303
|
+
loadGeneration: s.loadGeneration + 1,
|
|
304
|
+
workflow: {
|
|
305
|
+
id: nanoid(),
|
|
306
|
+
workflowKey: "",
|
|
307
|
+
name: "",
|
|
308
|
+
version: "",
|
|
309
|
+
alias: "",
|
|
310
|
+
dbId: void 0,
|
|
311
|
+
status: void 0,
|
|
312
|
+
codeBuildId: void 0,
|
|
313
|
+
edges: [],
|
|
314
|
+
nodes: [],
|
|
315
|
+
ndv: null,
|
|
316
|
+
quickConfig: null,
|
|
317
|
+
pendingConnection: null,
|
|
318
|
+
renamingNodeId: null,
|
|
319
|
+
sidebar: {
|
|
320
|
+
active: "none",
|
|
321
|
+
panels: {
|
|
322
|
+
nodeProperties: {
|
|
323
|
+
selectedNode: null
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
// Wipe run state too — resetWorkflow fires when the builder loads a
|
|
329
|
+
// (different) workflow, so the previous workflow's logs must not carry
|
|
330
|
+
// over into this one's Logs panel.
|
|
331
|
+
execution: { ...INITIAL_EXECUTION },
|
|
332
|
+
// A freshly loaded workflow must not be undo-able back to empty —
|
|
333
|
+
// undo/redo only ever applies within the currently open workflow.
|
|
334
|
+
history: { ...INITIAL_HISTORY }
|
|
335
|
+
}));
|
|
336
|
+
},
|
|
337
|
+
history: {
|
|
338
|
+
undo: () => {
|
|
339
|
+
const { past, future } = get().history;
|
|
340
|
+
const previous = past[past.length - 1];
|
|
341
|
+
if (!previous) return;
|
|
342
|
+
const { nodes, edges } = get().workflow;
|
|
343
|
+
const current = {
|
|
344
|
+
nodes: cloneValue(nodes),
|
|
345
|
+
edges: cloneValue(edges)
|
|
346
|
+
};
|
|
347
|
+
resetCoalescing();
|
|
348
|
+
noteEdit();
|
|
349
|
+
set((s) => ({
|
|
350
|
+
workflow: {
|
|
351
|
+
...s.workflow,
|
|
352
|
+
nodes: previous.nodes,
|
|
353
|
+
edges: previous.edges
|
|
354
|
+
},
|
|
355
|
+
history: {
|
|
356
|
+
past: past.slice(0, -1),
|
|
357
|
+
future: [current, ...future].slice(0, HISTORY_LIMIT)
|
|
358
|
+
}
|
|
359
|
+
}));
|
|
360
|
+
},
|
|
361
|
+
redo: () => {
|
|
362
|
+
const { past, future } = get().history;
|
|
363
|
+
const next = future[0];
|
|
364
|
+
if (!next) return;
|
|
365
|
+
const { nodes, edges } = get().workflow;
|
|
366
|
+
const current = {
|
|
367
|
+
nodes: cloneValue(nodes),
|
|
368
|
+
edges: cloneValue(edges)
|
|
369
|
+
};
|
|
370
|
+
resetCoalescing();
|
|
371
|
+
noteEdit();
|
|
372
|
+
set((s) => ({
|
|
373
|
+
workflow: {
|
|
374
|
+
...s.workflow,
|
|
375
|
+
nodes: next.nodes,
|
|
376
|
+
edges: next.edges
|
|
377
|
+
},
|
|
378
|
+
history: {
|
|
379
|
+
past: [...past, current].slice(-HISTORY_LIMIT),
|
|
380
|
+
future: future.slice(1)
|
|
381
|
+
}
|
|
382
|
+
}));
|
|
383
|
+
},
|
|
384
|
+
clear: () => {
|
|
385
|
+
resetCoalescing();
|
|
386
|
+
set({ history: { ...INITIAL_HISTORY } });
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
ndv: {
|
|
390
|
+
// Opening the full NDV supersedes the floating quick-config panel — the
|
|
391
|
+
// two edit the same node data, so only one should be visible at a time.
|
|
392
|
+
open: (node) => set((state) => ({
|
|
393
|
+
workflow: { ...state.workflow, ndv: node, quickConfig: null }
|
|
394
|
+
})),
|
|
395
|
+
close: () => set((state) => ({
|
|
396
|
+
workflow: { ...state.workflow, ndv: null }
|
|
397
|
+
}))
|
|
398
|
+
},
|
|
399
|
+
quickConfig: {
|
|
400
|
+
open: (node) => set((state) => ({
|
|
401
|
+
workflow: { ...state.workflow, quickConfig: node }
|
|
402
|
+
})),
|
|
403
|
+
close: () => set((state) => ({
|
|
404
|
+
workflow: { ...state.workflow, quickConfig: null }
|
|
405
|
+
}))
|
|
406
|
+
},
|
|
407
|
+
pendingConnection: {
|
|
408
|
+
start: (connection) => set((state) => ({
|
|
409
|
+
workflow: {
|
|
410
|
+
...state.workflow,
|
|
411
|
+
pendingConnection: connection,
|
|
412
|
+
sidebar: { ...state.workflow.sidebar, active: "store-browse" }
|
|
413
|
+
}
|
|
414
|
+
})),
|
|
415
|
+
clear: () => set((state) => ({
|
|
416
|
+
workflow: { ...state.workflow, pendingConnection: null }
|
|
417
|
+
}))
|
|
418
|
+
},
|
|
419
|
+
sidebar: {
|
|
420
|
+
// Leaving the store panel abandons any armed "+" attach mode — the
|
|
421
|
+
// pending handle would otherwise silently capture a later insert.
|
|
422
|
+
setActivePanel: (panel) => set((state) => ({
|
|
423
|
+
workflow: {
|
|
424
|
+
...state.workflow,
|
|
425
|
+
sidebar: { ...state.workflow.sidebar, active: panel },
|
|
426
|
+
...panel !== "store-browse" ? { pendingConnection: null } : {}
|
|
427
|
+
}
|
|
428
|
+
})),
|
|
429
|
+
showNodePropertiesOf: (node) => {
|
|
430
|
+
set((state) => ({
|
|
431
|
+
workflow: {
|
|
432
|
+
...state.workflow,
|
|
433
|
+
pendingConnection: null,
|
|
434
|
+
sidebar: {
|
|
435
|
+
...state.workflow.sidebar,
|
|
436
|
+
active: "node-properties",
|
|
437
|
+
panels: {
|
|
438
|
+
...state.workflow.sidebar.panels,
|
|
439
|
+
nodeProperties: {
|
|
440
|
+
...state.workflow.sidebar.panels.nodeProperties,
|
|
441
|
+
selectedNode: node
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}));
|
|
447
|
+
},
|
|
448
|
+
panels: {
|
|
449
|
+
nodeProperties: {
|
|
450
|
+
setSelectedNode: (node) => set((state) => ({
|
|
451
|
+
workflow: {
|
|
452
|
+
...state.workflow,
|
|
453
|
+
sidebar: {
|
|
454
|
+
...state.workflow.sidebar,
|
|
455
|
+
panels: {
|
|
456
|
+
...state.workflow.sidebar.panels,
|
|
457
|
+
nodeProperties: {
|
|
458
|
+
...state.workflow.sidebar.panels.nodeProperties,
|
|
459
|
+
selectedNode: node
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}))
|
|
465
|
+
},
|
|
466
|
+
tags: {
|
|
467
|
+
setTags: (tags) => set({ tags }),
|
|
468
|
+
createTag: (tag) => set((state) => ({
|
|
469
|
+
tags: [...state.tags, tag]
|
|
470
|
+
})),
|
|
471
|
+
updateTag: (tag, newTag) => set((state) => ({
|
|
472
|
+
tags: state.tags.map(
|
|
473
|
+
(f) => f.value === tag.value ? newTag : f
|
|
474
|
+
)
|
|
475
|
+
})),
|
|
476
|
+
deleteTag: (tag) => set((state) => ({
|
|
477
|
+
tags: state.tags.filter((f) => f.value !== tag.value)
|
|
478
|
+
}))
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
nodes: {
|
|
483
|
+
onNodesChange: (changes) => {
|
|
484
|
+
const removedIds = new Set(
|
|
485
|
+
changes.filter(
|
|
486
|
+
(c) => c.type === "remove"
|
|
487
|
+
).map((c) => c.id)
|
|
488
|
+
);
|
|
489
|
+
const addedCount = changes.filter((c) => c.type === "add").length;
|
|
490
|
+
if (removedIds.size > 0) {
|
|
491
|
+
const stillPresent = get().workflow.nodes.some(
|
|
492
|
+
(n) => removedIds.has(n.id)
|
|
493
|
+
);
|
|
494
|
+
if (stillPresent && !joinsPrecedingRemoval("nodes:remove")) {
|
|
495
|
+
pushCheckpoint("nodes:remove", false);
|
|
496
|
+
}
|
|
497
|
+
} else if (addedCount > 0) {
|
|
498
|
+
pushCheckpoint("nodes:add", false);
|
|
499
|
+
} else {
|
|
500
|
+
const startsDrag = changes.some(
|
|
501
|
+
(c) => c.type === "position" && c.dragging === true && !get().workflow.nodes.find((n) => n.id === c.id)?.dragging
|
|
502
|
+
);
|
|
503
|
+
if (startsDrag) noteEdit();
|
|
504
|
+
const finishedDragIds = changes.filter(
|
|
505
|
+
(c) => c.type === "position" && c.dragging === false
|
|
506
|
+
);
|
|
507
|
+
const actuallyMoved = finishedDragIds.some((c) => {
|
|
508
|
+
const current = get().workflow.nodes.find((n) => n.id === c.id);
|
|
509
|
+
return current && c.position && (current.position.x !== c.position.x || current.position.y !== c.position.y);
|
|
510
|
+
});
|
|
511
|
+
if (actuallyMoved) pushCheckpoint("nodes:move", false);
|
|
512
|
+
}
|
|
513
|
+
set(
|
|
514
|
+
(state) => produce(state, (draft) => {
|
|
515
|
+
const updatedNodes = applyNodeChanges(
|
|
516
|
+
changes,
|
|
517
|
+
draft.workflow.nodes
|
|
518
|
+
);
|
|
519
|
+
draft.workflow.nodes = updatedNodes;
|
|
520
|
+
})
|
|
521
|
+
);
|
|
522
|
+
},
|
|
523
|
+
setNodes: (nodes, opts) => {
|
|
524
|
+
if (!opts?.skipHistory) {
|
|
525
|
+
pushCheckpoint(
|
|
526
|
+
opts?.historyKind ?? "nodes:set",
|
|
527
|
+
!!opts?.historyKind
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
set({ workflow: { ...get().workflow, nodes } });
|
|
531
|
+
},
|
|
532
|
+
deleteNode: (node) => {
|
|
533
|
+
const exists = get().workflow.nodes.some((n) => n.id === node.id);
|
|
534
|
+
if (exists) pushCheckpoint("nodes:delete", false);
|
|
535
|
+
set((state) => ({
|
|
536
|
+
workflow: {
|
|
537
|
+
...state.workflow,
|
|
538
|
+
nodes: state.workflow.nodes.filter((n) => n.id !== node.id),
|
|
539
|
+
// Drop any UI state pointing at the removed node.
|
|
540
|
+
...state.workflow.quickConfig?.nodeId === node.id ? { quickConfig: null } : {},
|
|
541
|
+
...state.workflow.pendingConnection?.nodeId === node.id ? { pendingConnection: null } : {}
|
|
542
|
+
}
|
|
543
|
+
}));
|
|
544
|
+
},
|
|
545
|
+
deleteNodes: (nodes) => {
|
|
546
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
547
|
+
if (ids.size === 0) return;
|
|
548
|
+
const anyExist = get().workflow.nodes.some((n) => ids.has(n.id));
|
|
549
|
+
if (anyExist) pushCheckpoint("nodes:delete", false);
|
|
550
|
+
set((state) => ({
|
|
551
|
+
workflow: {
|
|
552
|
+
...state.workflow,
|
|
553
|
+
nodes: state.workflow.nodes.filter((n) => !ids.has(n.id)),
|
|
554
|
+
edges: state.workflow.edges.filter(
|
|
555
|
+
(e) => !ids.has(e.source) && !ids.has(e.target)
|
|
556
|
+
),
|
|
557
|
+
...state.workflow.quickConfig && ids.has(state.workflow.quickConfig.nodeId) ? { quickConfig: null } : {},
|
|
558
|
+
...state.workflow.pendingConnection && ids.has(state.workflow.pendingConnection.nodeId) ? { pendingConnection: null } : {}
|
|
559
|
+
}
|
|
560
|
+
}));
|
|
561
|
+
},
|
|
562
|
+
duplicateNodes: (nodeIds) => {
|
|
563
|
+
const { nodes, edges } = get().workflow;
|
|
564
|
+
const idSet = new Set(nodeIds);
|
|
565
|
+
const selected = nodes.filter((n) => idSet.has(n.id));
|
|
566
|
+
if (selected.length === 0) return;
|
|
567
|
+
pushCheckpoint("nodes:duplicate", false);
|
|
568
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
569
|
+
const clones = selected.map((n) => {
|
|
570
|
+
const newId = nanoid();
|
|
571
|
+
idMap.set(n.id, newId);
|
|
572
|
+
const cloned = cloneValue(n);
|
|
573
|
+
return {
|
|
574
|
+
...cloned,
|
|
575
|
+
id: newId,
|
|
576
|
+
position: {
|
|
577
|
+
x: cloned.position.x + CLONE_OFFSET,
|
|
578
|
+
y: cloned.position.y + CLONE_OFFSET
|
|
579
|
+
},
|
|
580
|
+
selected: true
|
|
581
|
+
};
|
|
582
|
+
});
|
|
583
|
+
const clonedEdges = edges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((e) => {
|
|
584
|
+
const cloned = cloneValue(e);
|
|
585
|
+
return {
|
|
586
|
+
...cloned,
|
|
587
|
+
id: nanoid(),
|
|
588
|
+
source: idMap.get(e.source),
|
|
589
|
+
target: idMap.get(e.target),
|
|
590
|
+
selected: false
|
|
591
|
+
};
|
|
592
|
+
});
|
|
593
|
+
set((state) => ({
|
|
594
|
+
workflow: {
|
|
595
|
+
...state.workflow,
|
|
596
|
+
nodes: [
|
|
597
|
+
...state.workflow.nodes.map(
|
|
598
|
+
(n) => idSet.has(n.id) ? { ...n, selected: false } : n
|
|
599
|
+
),
|
|
600
|
+
...clones
|
|
601
|
+
],
|
|
602
|
+
edges: [...state.workflow.edges, ...clonedEdges]
|
|
603
|
+
}
|
|
604
|
+
}));
|
|
605
|
+
},
|
|
606
|
+
pasteNodes: (payload) => {
|
|
607
|
+
const pasteNodesIn = payload.nodes ?? [];
|
|
608
|
+
if (pasteNodesIn.length === 0) return;
|
|
609
|
+
pushCheckpoint("nodes:paste", false);
|
|
610
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
611
|
+
const clones = pasteNodesIn.map((n) => {
|
|
612
|
+
const newId = nanoid();
|
|
613
|
+
idMap.set(n.id, newId);
|
|
614
|
+
const cloned = cloneValue(n);
|
|
615
|
+
return {
|
|
616
|
+
...cloned,
|
|
617
|
+
id: newId,
|
|
618
|
+
position: {
|
|
619
|
+
x: (cloned.position?.x ?? 0) + CLONE_OFFSET,
|
|
620
|
+
y: (cloned.position?.y ?? 0) + CLONE_OFFSET
|
|
621
|
+
},
|
|
622
|
+
selected: true
|
|
623
|
+
};
|
|
624
|
+
});
|
|
625
|
+
const clonedEdges = (payload.edges ?? []).filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((e) => {
|
|
626
|
+
const cloned = cloneValue(e);
|
|
627
|
+
return {
|
|
628
|
+
...cloned,
|
|
629
|
+
id: nanoid(),
|
|
630
|
+
source: idMap.get(e.source),
|
|
631
|
+
target: idMap.get(e.target),
|
|
632
|
+
selected: false
|
|
633
|
+
};
|
|
634
|
+
});
|
|
635
|
+
set((state) => ({
|
|
636
|
+
workflow: {
|
|
637
|
+
...state.workflow,
|
|
638
|
+
nodes: [
|
|
639
|
+
...state.workflow.nodes.map((n) => ({ ...n, selected: false })),
|
|
640
|
+
...clones
|
|
641
|
+
],
|
|
642
|
+
edges: [...state.workflow.edges, ...clonedEdges]
|
|
643
|
+
}
|
|
644
|
+
}));
|
|
645
|
+
},
|
|
646
|
+
toggleDisabled: (nodeId) => {
|
|
647
|
+
const { nodes, edges } = get().workflow;
|
|
648
|
+
const updatedNodes = nodes.map((node) => {
|
|
649
|
+
if (node.id !== nodeId) return node;
|
|
650
|
+
const data = node.data ?? {};
|
|
651
|
+
return {
|
|
652
|
+
...node,
|
|
653
|
+
data: { ...data, disabled: !(data.disabled === true) }
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
const breaksLoop = edges.some(
|
|
657
|
+
(edge) => edge.source === nodeId && isLoopBodyEdge(edge, nodes) && !isValidWorkflowConnection(updatedNodes, edges, edge)
|
|
658
|
+
);
|
|
659
|
+
if (breaksLoop) return false;
|
|
660
|
+
pushCheckpoint("nodes:disable", false);
|
|
661
|
+
set((state) => ({
|
|
662
|
+
workflow: {
|
|
663
|
+
...state.workflow,
|
|
664
|
+
nodes: updatedNodes
|
|
665
|
+
}
|
|
666
|
+
}));
|
|
667
|
+
return true;
|
|
668
|
+
},
|
|
669
|
+
renameNode: (nodeId, label) => {
|
|
670
|
+
pushCheckpoint("nodes:rename", false);
|
|
671
|
+
set((state) => ({
|
|
672
|
+
workflow: {
|
|
673
|
+
...state.workflow,
|
|
674
|
+
nodes: state.workflow.nodes.map(
|
|
675
|
+
(n) => n.id === nodeId ? { ...n, data: { ...n.data ?? {}, label } } : n
|
|
676
|
+
),
|
|
677
|
+
renamingNodeId: null
|
|
678
|
+
}
|
|
679
|
+
}));
|
|
680
|
+
},
|
|
681
|
+
startRename: (nodeId) => {
|
|
682
|
+
set((state) => ({
|
|
683
|
+
workflow: { ...state.workflow, renamingNodeId: nodeId }
|
|
684
|
+
}));
|
|
685
|
+
},
|
|
686
|
+
endRename: () => {
|
|
687
|
+
set((state) => ({
|
|
688
|
+
workflow: { ...state.workflow, renamingNodeId: null }
|
|
689
|
+
}));
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
edges: {
|
|
693
|
+
onEdgesChange: (changes) => {
|
|
694
|
+
const removedIds = new Set(
|
|
695
|
+
changes.filter(
|
|
696
|
+
(c) => c.type === "remove"
|
|
697
|
+
).map((c) => c.id)
|
|
698
|
+
);
|
|
699
|
+
if (removedIds.size > 0) {
|
|
700
|
+
const stillPresent = get().workflow.edges.some(
|
|
701
|
+
(e) => removedIds.has(e.id)
|
|
702
|
+
);
|
|
703
|
+
if (stillPresent && !joinsPrecedingRemoval("edges:remove")) {
|
|
704
|
+
pushCheckpoint("edges:remove", false);
|
|
705
|
+
}
|
|
706
|
+
} else if (changes.some((c) => c.type === "add")) {
|
|
707
|
+
pushCheckpoint("edges:add", false);
|
|
708
|
+
}
|
|
709
|
+
set(
|
|
710
|
+
(state) => produce(state, (draft) => {
|
|
711
|
+
const updatedEdges = applyEdgeChanges(
|
|
712
|
+
changes,
|
|
713
|
+
draft.workflow.edges
|
|
714
|
+
);
|
|
715
|
+
draft.workflow.edges = updatedEdges;
|
|
716
|
+
})
|
|
717
|
+
);
|
|
718
|
+
},
|
|
719
|
+
onConnect: (connection) => {
|
|
720
|
+
pushCheckpoint("edges:connect", false);
|
|
721
|
+
const edge = {
|
|
722
|
+
...connection,
|
|
723
|
+
id: nanoid(),
|
|
724
|
+
type: "deletable"
|
|
725
|
+
};
|
|
726
|
+
set({
|
|
727
|
+
workflow: {
|
|
728
|
+
...get().workflow,
|
|
729
|
+
edges: addEdge(edge, get().workflow.edges)
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
setEdges: (edges, opts) => {
|
|
734
|
+
if (!opts?.skipHistory) {
|
|
735
|
+
pushCheckpoint(
|
|
736
|
+
opts?.historyKind ?? "edges:set",
|
|
737
|
+
!!opts?.historyKind
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
set({ workflow: { ...get().workflow, edges } });
|
|
741
|
+
},
|
|
742
|
+
deleteEdge: (edge) => {
|
|
743
|
+
const exists = get().workflow.edges.some((e) => e.id === edge.id);
|
|
744
|
+
if (exists) pushCheckpoint("edges:delete", false);
|
|
745
|
+
set((state) => ({
|
|
746
|
+
workflow: {
|
|
747
|
+
...state.workflow,
|
|
748
|
+
edges: state.workflow.edges.filter((e) => e.id !== edge.id)
|
|
749
|
+
}
|
|
750
|
+
}));
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
execution: {
|
|
754
|
+
startExecution: (nodeId, runId, metadata) => {
|
|
755
|
+
set((state) => ({
|
|
756
|
+
execution: {
|
|
757
|
+
...state.execution,
|
|
758
|
+
isExecuting: true,
|
|
759
|
+
executingNodeId: nodeId,
|
|
760
|
+
currentRunId: runId,
|
|
761
|
+
sourceRun: metadata ? { runId, ...metadata } : { runId, workflowKey: "", version: "" },
|
|
762
|
+
panelExpanded: true,
|
|
763
|
+
nodeStatuses: {
|
|
764
|
+
...state.execution.nodeStatuses,
|
|
765
|
+
[nodeId]: "running"
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}));
|
|
769
|
+
},
|
|
770
|
+
setRunMetadata: (metadata) => {
|
|
771
|
+
set((state) => {
|
|
772
|
+
if (state.execution.currentRunId !== metadata.runId && state.execution.sourceRun?.runId !== metadata.runId) {
|
|
773
|
+
return state;
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
execution: {
|
|
777
|
+
...state.execution,
|
|
778
|
+
currentRunId: metadata.runId,
|
|
779
|
+
sourceRun: metadata
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
});
|
|
783
|
+
},
|
|
784
|
+
setStepPlan: (plan) => {
|
|
785
|
+
set((state) => ({
|
|
786
|
+
execution: {
|
|
787
|
+
...state.execution,
|
|
788
|
+
stepPlan: plan
|
|
789
|
+
}
|
|
790
|
+
}));
|
|
791
|
+
},
|
|
792
|
+
addLog: (log) => {
|
|
793
|
+
set((state) => ({
|
|
794
|
+
execution: {
|
|
795
|
+
...state.execution,
|
|
796
|
+
logs: [...state.execution.logs, log],
|
|
797
|
+
selectedLogId: log.id
|
|
798
|
+
}
|
|
799
|
+
}));
|
|
800
|
+
},
|
|
801
|
+
updateLog: (logId, updates) => {
|
|
802
|
+
set((state) => ({
|
|
803
|
+
execution: {
|
|
804
|
+
...state.execution,
|
|
805
|
+
logs: state.execution.logs.map(
|
|
806
|
+
(log) => log.id === logId ? { ...log, ...updates } : log
|
|
807
|
+
)
|
|
808
|
+
}
|
|
809
|
+
}));
|
|
810
|
+
},
|
|
811
|
+
setNodeStatus: (nodeId, status) => {
|
|
812
|
+
set((state) => ({
|
|
813
|
+
execution: {
|
|
814
|
+
...state.execution,
|
|
815
|
+
nodeStatuses: {
|
|
816
|
+
...state.execution.nodeStatuses,
|
|
817
|
+
[nodeId]: status
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}));
|
|
821
|
+
},
|
|
822
|
+
setNodeItemCount: (nodeId, count) => {
|
|
823
|
+
set((state) => ({
|
|
824
|
+
execution: {
|
|
825
|
+
...state.execution,
|
|
826
|
+
nodeItemCounts: {
|
|
827
|
+
...state.execution.nodeItemCounts,
|
|
828
|
+
[nodeId]: count
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}));
|
|
832
|
+
},
|
|
833
|
+
selectLog: (logId) => {
|
|
834
|
+
set((state) => ({
|
|
835
|
+
execution: {
|
|
836
|
+
...state.execution,
|
|
837
|
+
selectedLogId: logId
|
|
838
|
+
}
|
|
839
|
+
}));
|
|
840
|
+
},
|
|
841
|
+
togglePanel: () => {
|
|
842
|
+
set((state) => ({
|
|
843
|
+
execution: {
|
|
844
|
+
...state.execution,
|
|
845
|
+
panelExpanded: !state.execution.panelExpanded
|
|
846
|
+
}
|
|
847
|
+
}));
|
|
848
|
+
},
|
|
849
|
+
clearLogs: () => {
|
|
850
|
+
set((state) => ({
|
|
851
|
+
execution: {
|
|
852
|
+
...state.execution,
|
|
853
|
+
logs: [],
|
|
854
|
+
stepPlan: null,
|
|
855
|
+
currentRunId: null,
|
|
856
|
+
sourceRun: null,
|
|
857
|
+
selectedLogId: null,
|
|
858
|
+
nodeStatuses: {},
|
|
859
|
+
nodeItemCounts: {}
|
|
860
|
+
}
|
|
861
|
+
}));
|
|
862
|
+
},
|
|
863
|
+
completeExecution: () => {
|
|
864
|
+
set((state) => ({
|
|
865
|
+
execution: {
|
|
866
|
+
...state.execution,
|
|
867
|
+
isExecuting: false,
|
|
868
|
+
executingNodeId: null
|
|
869
|
+
}
|
|
870
|
+
}));
|
|
871
|
+
},
|
|
872
|
+
requestFollowRun: (runId, triggerNodeId) => {
|
|
873
|
+
set((state) => ({
|
|
874
|
+
execution: {
|
|
875
|
+
...state.execution,
|
|
876
|
+
followRequest: { runId, triggerNodeId }
|
|
877
|
+
}
|
|
878
|
+
}));
|
|
879
|
+
},
|
|
880
|
+
clearFollowRequest: () => {
|
|
881
|
+
set((state) => ({
|
|
882
|
+
execution: { ...state.execution, followRequest: null }
|
|
883
|
+
}));
|
|
884
|
+
},
|
|
885
|
+
loadDebugRun: (runId, logs, metadata) => {
|
|
886
|
+
const nodeStatuses = {};
|
|
887
|
+
for (const log of logs) {
|
|
888
|
+
nodeStatuses[log.nodeId] = log.status === "completed" ? "success" : log.status === "failed" ? "error" : log.status === "running" ? "running" : "idle";
|
|
889
|
+
}
|
|
890
|
+
set(() => ({
|
|
891
|
+
execution: {
|
|
892
|
+
...INITIAL_EXECUTION,
|
|
893
|
+
currentRunId: runId,
|
|
894
|
+
sourceRun: metadata ? { runId, ...metadata } : { runId, workflowKey: "", version: "" },
|
|
895
|
+
logs,
|
|
896
|
+
nodeStatuses,
|
|
897
|
+
panelExpanded: true
|
|
898
|
+
}
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
});
|
|
905
|
+
if (typeof window !== "undefined" && import.meta.env?.DEV) {
|
|
906
|
+
window.__fgFlowStore = useFlowStore;
|
|
907
|
+
}
|
|
908
|
+
function isImageIcon(icon) {
|
|
909
|
+
return !!icon && (/^https?:\/\//.test(icon) || icon.startsWith("data:"));
|
|
910
|
+
}
|
|
911
|
+
function NodeTypeIcon({
|
|
912
|
+
icon,
|
|
913
|
+
alt,
|
|
914
|
+
className,
|
|
915
|
+
fallbackIcon = "mynaui:puzzle"
|
|
916
|
+
}) {
|
|
917
|
+
if (isImageIcon(icon)) {
|
|
918
|
+
return /* @__PURE__ */ jsx(
|
|
919
|
+
"img",
|
|
920
|
+
{
|
|
921
|
+
src: icon,
|
|
922
|
+
alt: alt ?? "",
|
|
923
|
+
className: cn("object-contain", className)
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
return /* @__PURE__ */ jsx(Icon, { icon: icon || fallbackIcon, className });
|
|
928
|
+
}
|
|
929
|
+
var PROVIDER_ACCENTS = [
|
|
930
|
+
[/gmail|google/, "var(--color-chart-5)"],
|
|
931
|
+
[/slack/, "var(--color-chart-3)"],
|
|
932
|
+
[/github|gitlab/, nodeCategoryColor("utility")],
|
|
933
|
+
[/stripe/, "var(--color-chart-12)"],
|
|
934
|
+
[/discord|telegram/, "var(--color-chart-1)"],
|
|
935
|
+
[/notion/, "var(--color-text-primary)"],
|
|
936
|
+
[/whatsapp|sheet|excel/, "var(--color-chart-7)"],
|
|
937
|
+
[/openai|anthropic|claude/, nodeCategoryColor("ai")],
|
|
938
|
+
[/twilio/, "var(--color-chart-10)"],
|
|
939
|
+
[/http|api|webhook|rest/, nodeCategoryColor("integration")],
|
|
940
|
+
[/db|sql|postgres|mysql|mongo/, nodeCategoryColor("data")]
|
|
941
|
+
];
|
|
942
|
+
function nodeAccent(key) {
|
|
943
|
+
const k = (key || "").toLowerCase();
|
|
944
|
+
for (const [re, color] of PROVIDER_ACCENTS) if (re.test(k)) return color;
|
|
945
|
+
return chartSeriesColor(chartSeriesIndex(k));
|
|
946
|
+
}
|
|
947
|
+
function tint(color, pct = 14) {
|
|
948
|
+
return `color-mix(in srgb, ${color} ${pct}%, transparent)`;
|
|
949
|
+
}
|
|
950
|
+
function InlineEditableLabel({
|
|
951
|
+
value,
|
|
952
|
+
placeholder,
|
|
953
|
+
editing,
|
|
954
|
+
onStartEdit,
|
|
955
|
+
onCommit,
|
|
956
|
+
onCancel,
|
|
957
|
+
className,
|
|
958
|
+
inputClassName
|
|
959
|
+
}) {
|
|
960
|
+
const tr = useTr();
|
|
961
|
+
const [draft, setDraft] = React.useState(value);
|
|
962
|
+
const inputRef = React.useRef(null);
|
|
963
|
+
React.useEffect(() => {
|
|
964
|
+
if (!editing) return;
|
|
965
|
+
setDraft(value);
|
|
966
|
+
const raf = requestAnimationFrame(() => {
|
|
967
|
+
inputRef.current?.focus();
|
|
968
|
+
inputRef.current?.select();
|
|
969
|
+
});
|
|
970
|
+
return () => cancelAnimationFrame(raf);
|
|
971
|
+
}, [editing, value]);
|
|
972
|
+
if (editing) {
|
|
973
|
+
return /* @__PURE__ */ jsx(
|
|
974
|
+
"input",
|
|
975
|
+
{
|
|
976
|
+
ref: inputRef,
|
|
977
|
+
value: draft,
|
|
978
|
+
onChange: (e) => setDraft(e.target.value),
|
|
979
|
+
onClick: (e) => e.stopPropagation(),
|
|
980
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
981
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
982
|
+
onKeyDown: (e) => {
|
|
983
|
+
e.stopPropagation();
|
|
984
|
+
if (e.key === "Enter") {
|
|
985
|
+
e.preventDefault();
|
|
986
|
+
onCommit(draft.trim());
|
|
987
|
+
} else if (e.key === "Escape") {
|
|
988
|
+
e.preventDefault();
|
|
989
|
+
onCancel();
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
onBlur: () => onCommit(draft.trim()),
|
|
993
|
+
className: cn(
|
|
994
|
+
"min-w-0 flex-1 rounded border border-primary/50 bg-card px-1 text-inherit outline-none",
|
|
995
|
+
inputClassName
|
|
996
|
+
)
|
|
997
|
+
}
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
return /* @__PURE__ */ jsx(
|
|
1001
|
+
"span",
|
|
1002
|
+
{
|
|
1003
|
+
className: cn("cursor-text truncate", className),
|
|
1004
|
+
title: tr(
|
|
1005
|
+
"fluidgrids.builder.nodeCard.renameTooltip",
|
|
1006
|
+
"Double-click or press F2 to rename"
|
|
1007
|
+
),
|
|
1008
|
+
onDoubleClick: (e) => {
|
|
1009
|
+
e.stopPropagation();
|
|
1010
|
+
onStartEdit();
|
|
1011
|
+
},
|
|
1012
|
+
children: value || placeholder
|
|
1013
|
+
}
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
var NodeCard = React.forwardRef(
|
|
1017
|
+
({
|
|
1018
|
+
className,
|
|
1019
|
+
selected = false,
|
|
1020
|
+
executionStatus = "idle",
|
|
1021
|
+
itemCount,
|
|
1022
|
+
isTrigger = false,
|
|
1023
|
+
accent = nodeCategoryColor("action"),
|
|
1024
|
+
onSettings,
|
|
1025
|
+
onExecute,
|
|
1026
|
+
onDelete,
|
|
1027
|
+
isExecuting = false,
|
|
1028
|
+
infoButton,
|
|
1029
|
+
disabled = false,
|
|
1030
|
+
onToggleDisabled,
|
|
1031
|
+
children,
|
|
1032
|
+
...props
|
|
1033
|
+
}, ref) => {
|
|
1034
|
+
const tr = useTr();
|
|
1035
|
+
const [hovered, setHovered] = React.useState(false);
|
|
1036
|
+
const statusRing = executionStatus === "running" ? "ring-2 ring-[var(--color-status-success-border)] animate-pulse-border" : executionStatus === "success" ? "ring-2 ring-[var(--color-status-success-border)]" : executionStatus === "error" ? "ring-2 ring-[var(--color-status-error-border)]" : "";
|
|
1037
|
+
return /* @__PURE__ */ jsxs(
|
|
1038
|
+
"div",
|
|
1039
|
+
{
|
|
1040
|
+
className: "group/node relative",
|
|
1041
|
+
onMouseEnter: () => setHovered(true),
|
|
1042
|
+
onMouseLeave: () => setHovered(false),
|
|
1043
|
+
children: [
|
|
1044
|
+
itemCount !== void 0 && itemCount > 0 && /* @__PURE__ */ jsx("div", { className: "absolute -top-7 left-1/2 -translate-x-1/2 z-10", children: /* @__PURE__ */ jsxs("div", { className: "rounded-full border border-border bg-card px-2 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm", children: [
|
|
1045
|
+
itemCount,
|
|
1046
|
+
" ",
|
|
1047
|
+
itemCount !== 1 ? tr("fluidgrids.builder.nodeCard.itemCountOther", "items") : tr("fluidgrids.builder.nodeCard.itemCountOne", "item")
|
|
1048
|
+
] }) }),
|
|
1049
|
+
(onSettings || onExecute || onDelete || onToggleDisabled || infoButton) && /* @__PURE__ */ jsxs(
|
|
1050
|
+
"div",
|
|
1051
|
+
{
|
|
1052
|
+
className: cn(
|
|
1053
|
+
"absolute right-1 z-20 flex items-center gap-0.5 rounded-lg border border-border bg-card p-0.5 shadow-md transition-all duration-150",
|
|
1054
|
+
hovered ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
|
1055
|
+
),
|
|
1056
|
+
style: {
|
|
1057
|
+
// Inline offset (not a `-top-*` class) so it survives the host
|
|
1058
|
+
// app's Tailwind scan of fe-sdk source, which drops some spacing
|
|
1059
|
+
// utilities. The toolbar is ~30px tall; top:-15px centers it on
|
|
1060
|
+
// the card's top edge (half outside, half over the card).
|
|
1061
|
+
top: -15,
|
|
1062
|
+
transform: hovered ? "translateY(0)" : "translateY(4px)"
|
|
1063
|
+
},
|
|
1064
|
+
onClick: (e) => e.stopPropagation(),
|
|
1065
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
1066
|
+
children: [
|
|
1067
|
+
onSettings && /* @__PURE__ */ jsx(
|
|
1068
|
+
ToolbarButton,
|
|
1069
|
+
{
|
|
1070
|
+
title: tr(
|
|
1071
|
+
"fluidgrids.builder.nodeCard.configureTooltip",
|
|
1072
|
+
"Configure"
|
|
1073
|
+
),
|
|
1074
|
+
onClick: onSettings,
|
|
1075
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "lucide:sliders-horizontal", className: "size-4" })
|
|
1076
|
+
}
|
|
1077
|
+
),
|
|
1078
|
+
onExecute && /* @__PURE__ */ jsx(
|
|
1079
|
+
ToolbarButton,
|
|
1080
|
+
{
|
|
1081
|
+
"data-agent": "confirm",
|
|
1082
|
+
title: tr(
|
|
1083
|
+
"fluidgrids.builder.nodeCard.executeTooltip",
|
|
1084
|
+
"Execute step"
|
|
1085
|
+
),
|
|
1086
|
+
onClick: onExecute,
|
|
1087
|
+
disabled: isExecuting,
|
|
1088
|
+
children: isExecuting ? /* @__PURE__ */ jsx(
|
|
1089
|
+
Icon,
|
|
1090
|
+
{
|
|
1091
|
+
icon: "svg-spinners:ring-resize",
|
|
1092
|
+
className: "size-4 text-[var(--color-status-success-text)]"
|
|
1093
|
+
}
|
|
1094
|
+
) : /* @__PURE__ */ jsx(
|
|
1095
|
+
Icon,
|
|
1096
|
+
{
|
|
1097
|
+
icon: "mdi:play",
|
|
1098
|
+
className: "size-4 text-[var(--color-status-success-text)]"
|
|
1099
|
+
}
|
|
1100
|
+
)
|
|
1101
|
+
}
|
|
1102
|
+
),
|
|
1103
|
+
infoButton,
|
|
1104
|
+
onToggleDisabled && /* @__PURE__ */ jsx(
|
|
1105
|
+
ToolbarButton,
|
|
1106
|
+
{
|
|
1107
|
+
title: disabled ? tr(
|
|
1108
|
+
"fluidgrids.builder.nodeCard.enableTooltip",
|
|
1109
|
+
"Enable node"
|
|
1110
|
+
) : tr(
|
|
1111
|
+
"fluidgrids.builder.nodeCard.disableTooltip",
|
|
1112
|
+
"Disable node (skip on run)"
|
|
1113
|
+
),
|
|
1114
|
+
onClick: onToggleDisabled,
|
|
1115
|
+
children: /* @__PURE__ */ jsx(
|
|
1116
|
+
Icon,
|
|
1117
|
+
{
|
|
1118
|
+
icon: disabled ? "mdi:power" : "mdi:power-off",
|
|
1119
|
+
className: cn(
|
|
1120
|
+
"size-4",
|
|
1121
|
+
disabled ? "text-[var(--color-status-warning-text)]" : "text-muted-foreground"
|
|
1122
|
+
)
|
|
1123
|
+
}
|
|
1124
|
+
)
|
|
1125
|
+
}
|
|
1126
|
+
),
|
|
1127
|
+
onDelete && /* @__PURE__ */ jsx(
|
|
1128
|
+
ToolbarButton,
|
|
1129
|
+
{
|
|
1130
|
+
title: tr(
|
|
1131
|
+
"fluidgrids.builder.nodeCard.deleteTooltip",
|
|
1132
|
+
"Delete"
|
|
1133
|
+
),
|
|
1134
|
+
onClick: onDelete,
|
|
1135
|
+
children: /* @__PURE__ */ jsx(
|
|
1136
|
+
Icon,
|
|
1137
|
+
{
|
|
1138
|
+
icon: "mynaui:trash",
|
|
1139
|
+
className: "size-4 text-[var(--color-status-error-text)]"
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
}
|
|
1143
|
+
)
|
|
1144
|
+
]
|
|
1145
|
+
}
|
|
1146
|
+
),
|
|
1147
|
+
disabled && /* @__PURE__ */ jsx("div", { className: "absolute -top-2 right-2 z-10", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-full border border-[var(--color-status-warning-border)] bg-[var(--color-status-warning-bg)] px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-[var(--color-status-warning-text)] shadow-sm", children: [
|
|
1148
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:power-off", className: "size-2.5" }),
|
|
1149
|
+
tr("fluidgrids.builder.nodeCard.disabledBadge", "Disabled")
|
|
1150
|
+
] }) }),
|
|
1151
|
+
/* @__PURE__ */ jsxs(
|
|
1152
|
+
"div",
|
|
1153
|
+
{
|
|
1154
|
+
ref,
|
|
1155
|
+
"data-selected": selected,
|
|
1156
|
+
className: cn(
|
|
1157
|
+
"relative w-72 rounded-xl border border-border bg-card shadow-sm will-change-transform",
|
|
1158
|
+
statusRing,
|
|
1159
|
+
executionStatus === "success" && "animate-success-glow",
|
|
1160
|
+
disabled && "opacity-50 grayscale-[0.6]",
|
|
1161
|
+
className
|
|
1162
|
+
),
|
|
1163
|
+
style: {
|
|
1164
|
+
borderTop: `3px solid ${accent}`,
|
|
1165
|
+
transition: "transform 0.15s ease, box-shadow 0.15s ease",
|
|
1166
|
+
transform: hovered ? "translateY(-2px)" : "none",
|
|
1167
|
+
boxShadow: hovered ? "var(--shadow-float)" : void 0
|
|
1168
|
+
},
|
|
1169
|
+
...props,
|
|
1170
|
+
children: [
|
|
1171
|
+
/* @__PURE__ */ jsx(
|
|
1172
|
+
"div",
|
|
1173
|
+
{
|
|
1174
|
+
className: "pointer-events-none absolute -inset-0.5 transition-opacity duration-150",
|
|
1175
|
+
style: {
|
|
1176
|
+
borderRadius: 14,
|
|
1177
|
+
opacity: selected || hovered ? 1 : 0,
|
|
1178
|
+
boxShadow: selected ? `0 0 0 2px ${accent}, 0 0 0 5px ${tint(accent, 18)}` : `0 0 0 2px ${accent}`
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
),
|
|
1182
|
+
isTrigger && /* @__PURE__ */ jsx(
|
|
1183
|
+
"div",
|
|
1184
|
+
{
|
|
1185
|
+
className: "absolute -top-2 -left-2 z-10 flex size-5 items-center justify-center rounded-full shadow-md ring-2 ring-card",
|
|
1186
|
+
style: { backgroundColor: "var(--color-status-success-text)" },
|
|
1187
|
+
children: /* @__PURE__ */ jsx(
|
|
1188
|
+
Icon,
|
|
1189
|
+
{
|
|
1190
|
+
icon: "mdi:lightning-bolt",
|
|
1191
|
+
className: "size-3",
|
|
1192
|
+
style: { color: "var(--color-status-success-text-inverse)" }
|
|
1193
|
+
}
|
|
1194
|
+
)
|
|
1195
|
+
}
|
|
1196
|
+
),
|
|
1197
|
+
children
|
|
1198
|
+
]
|
|
1199
|
+
}
|
|
1200
|
+
),
|
|
1201
|
+
executionStatus === "success" && /* @__PURE__ */ jsx(StatusCorner, { icon: "mdi:check", tone: "success" }),
|
|
1202
|
+
executionStatus === "error" && /* @__PURE__ */ jsx(StatusCorner, { icon: "mdi:close", tone: "error" }),
|
|
1203
|
+
executionStatus === "running" && /* @__PURE__ */ jsx(StatusCorner, { icon: "svg-spinners:ring-resize", tone: "success" })
|
|
1204
|
+
]
|
|
1205
|
+
}
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
NodeCard.displayName = "NodeCard";
|
|
1210
|
+
function ToolbarButton({
|
|
1211
|
+
title,
|
|
1212
|
+
onClick,
|
|
1213
|
+
disabled,
|
|
1214
|
+
children,
|
|
1215
|
+
"data-agent": agent
|
|
1216
|
+
}) {
|
|
1217
|
+
return /* @__PURE__ */ jsx(
|
|
1218
|
+
"button",
|
|
1219
|
+
{
|
|
1220
|
+
type: "button",
|
|
1221
|
+
"data-agent": agent,
|
|
1222
|
+
title,
|
|
1223
|
+
disabled,
|
|
1224
|
+
onClick: (e) => {
|
|
1225
|
+
e.stopPropagation();
|
|
1226
|
+
onClick?.();
|
|
1227
|
+
},
|
|
1228
|
+
className: "flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50",
|
|
1229
|
+
children
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
function StatusCorner({
|
|
1234
|
+
icon,
|
|
1235
|
+
tone
|
|
1236
|
+
}) {
|
|
1237
|
+
const bg = tone === "success" ? "var(--color-status-success-bg)" : "var(--color-status-error-bg)";
|
|
1238
|
+
const fg = tone === "success" ? "var(--color-status-success-text-inverse)" : "var(--color-status-error-text-inverse)";
|
|
1239
|
+
return /* @__PURE__ */ jsx("div", { className: "absolute -bottom-1.5 -right-1.5 z-10", children: /* @__PURE__ */ jsx(
|
|
1240
|
+
"div",
|
|
1241
|
+
{
|
|
1242
|
+
className: "rounded-full p-0.5 shadow-md animate-in zoom-in duration-200",
|
|
1243
|
+
style: { backgroundColor: bg },
|
|
1244
|
+
children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-3", style: { color: fg } })
|
|
1245
|
+
}
|
|
1246
|
+
) });
|
|
1247
|
+
}
|
|
1248
|
+
var NodeCardHeader = React.forwardRef(
|
|
1249
|
+
({
|
|
1250
|
+
icon,
|
|
1251
|
+
appName,
|
|
1252
|
+
accentKey,
|
|
1253
|
+
missingCredentials = false,
|
|
1254
|
+
label,
|
|
1255
|
+
isRenaming = false,
|
|
1256
|
+
onStartRename,
|
|
1257
|
+
onCommitRename,
|
|
1258
|
+
onCancelRename
|
|
1259
|
+
}, ref) => {
|
|
1260
|
+
const tr = useTr();
|
|
1261
|
+
const accent = nodeAccent(accentKey);
|
|
1262
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "flex items-center gap-2.5 px-3 pb-3 pt-4", children: [
|
|
1263
|
+
/* @__PURE__ */ jsx(
|
|
1264
|
+
"div",
|
|
1265
|
+
{
|
|
1266
|
+
className: "flex size-8 shrink-0 items-center justify-center rounded-lg",
|
|
1267
|
+
style: {
|
|
1268
|
+
backgroundColor: tint(accent, 13),
|
|
1269
|
+
color: accent,
|
|
1270
|
+
border: `1px solid ${tint(accent, 26)}`
|
|
1271
|
+
},
|
|
1272
|
+
children: /* @__PURE__ */ jsx(NodeTypeIcon, { icon, alt: appName, className: "size-[18px]" })
|
|
1273
|
+
}
|
|
1274
|
+
),
|
|
1275
|
+
onStartRename && onCommitRename && onCancelRename ? /* @__PURE__ */ jsx(
|
|
1276
|
+
InlineEditableLabel,
|
|
1277
|
+
{
|
|
1278
|
+
value: label ?? "",
|
|
1279
|
+
placeholder: appName,
|
|
1280
|
+
editing: isRenaming,
|
|
1281
|
+
onStartEdit: onStartRename,
|
|
1282
|
+
onCommit: onCommitRename,
|
|
1283
|
+
onCancel: onCancelRename,
|
|
1284
|
+
className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground",
|
|
1285
|
+
inputClassName: "text-[11px] font-semibold uppercase tracking-wide"
|
|
1286
|
+
}
|
|
1287
|
+
) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: label || appName }),
|
|
1288
|
+
missingCredentials && /* @__PURE__ */ jsx(
|
|
1289
|
+
"span",
|
|
1290
|
+
{
|
|
1291
|
+
title: tr(
|
|
1292
|
+
"fluidgrids.builder.nodeCard.missingCredentialsTooltip",
|
|
1293
|
+
"Credentials not configured"
|
|
1294
|
+
),
|
|
1295
|
+
className: "flex items-center text-[var(--color-status-warning-text)]",
|
|
1296
|
+
"aria-label": tr(
|
|
1297
|
+
"fluidgrids.builder.nodeCard.missingCredentialsAriaLabel",
|
|
1298
|
+
"Credentials not configured"
|
|
1299
|
+
),
|
|
1300
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "lucide:alert-triangle", className: "size-4" })
|
|
1301
|
+
}
|
|
1302
|
+
)
|
|
1303
|
+
] });
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
NodeCardHeader.displayName = "NodeCardHeader";
|
|
1307
|
+
var NodeCardBody = React.forwardRef(
|
|
1308
|
+
({ operationName, description }, ref) => {
|
|
1309
|
+
const tr = useTr();
|
|
1310
|
+
return /* @__PURE__ */ jsxs(
|
|
1311
|
+
"div",
|
|
1312
|
+
{
|
|
1313
|
+
ref,
|
|
1314
|
+
className: "rounded-b-xl border-t border-border/60 px-3 pb-3 pt-2.5",
|
|
1315
|
+
children: [
|
|
1316
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: tr("fluidgrids.builder.nodeCard.operationLabel", "Operation") }),
|
|
1317
|
+
/* @__PURE__ */ jsx("div", { className: "mt-0.5 truncate font-mono text-[13px] font-bold text-card-foreground", children: operationName }),
|
|
1318
|
+
description ? /* @__PURE__ */ jsx("p", { className: "mt-1 line-clamp-2 text-xs leading-snug text-muted-foreground", children: description }) : null
|
|
1319
|
+
]
|
|
1320
|
+
}
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
);
|
|
1324
|
+
NodeCardBody.displayName = "NodeCardBody";
|
|
1325
|
+
|
|
1326
|
+
// src/builder/components/flow-builder/components/blocks/nodes/node/handle-spec.ts
|
|
1327
|
+
function isDynamicSpec(spec) {
|
|
1328
|
+
return !!spec && !Array.isArray(spec) && typeof spec.fromStateRecord === "string";
|
|
1329
|
+
}
|
|
1330
|
+
var DEFAULT_HANDLES = [{ id: "source", label: "" }];
|
|
1331
|
+
var BUILTIN_OUTPUT_HANDLES = {
|
|
1332
|
+
"internal:wait-for-signal": [
|
|
1333
|
+
{ id: "received", label: "Received", tone: "success" },
|
|
1334
|
+
{ id: "timedOut", label: "Timed out", tone: "warning" }
|
|
1335
|
+
]
|
|
1336
|
+
};
|
|
1337
|
+
function resolveOutputHandles(data, nodeType) {
|
|
1338
|
+
const nodeData = data?.nodeData;
|
|
1339
|
+
const operationsBlob = Array.isArray(nodeData?.operations) ? void 0 : nodeData?.operations;
|
|
1340
|
+
const operationList = Array.isArray(nodeData?.operations) ? nodeData.operations : operationsBlob?.operations ?? [];
|
|
1341
|
+
const operationName = typeof data?.operation_name === "string" ? data.operation_name : void 0;
|
|
1342
|
+
const operationSpec = operationName ? operationList.find((op) => op?.name === operationName)?.outputHandles : void 0;
|
|
1343
|
+
const spec = operationSpec ?? nodeData?.outputHandles ?? operationsBlob?.outputHandles;
|
|
1344
|
+
if (!spec)
|
|
1345
|
+
return nodeType && BUILTIN_OUTPUT_HANDLES[nodeType] || DEFAULT_HANDLES;
|
|
1346
|
+
if (isDynamicSpec(spec)) {
|
|
1347
|
+
const args = data?.args;
|
|
1348
|
+
const record = args?.state?.[spec.fromStateRecord] ?? data?.[spec.fromStateRecord] ?? {};
|
|
1349
|
+
const dynamic = Object.entries(record).filter(([, handleId]) => typeof handleId === "string" && handleId).map(([label, handleId]) => ({ id: handleId, label }));
|
|
1350
|
+
return [...dynamic, ...spec.extra ?? []];
|
|
1351
|
+
}
|
|
1352
|
+
if (spec.length === 0) return DEFAULT_HANDLES;
|
|
1353
|
+
return spec;
|
|
1354
|
+
}
|
|
1355
|
+
var DEFAULT_INPUT_HANDLES = [{ id: "target", label: "" }];
|
|
1356
|
+
function resolveInputHandles(data) {
|
|
1357
|
+
const nodeData = data?.nodeData;
|
|
1358
|
+
const spec = nodeData?.inputHandles ?? nodeData?.operations?.inputHandles;
|
|
1359
|
+
if (!Array.isArray(spec) || spec.length === 0) return DEFAULT_INPUT_HANDLES;
|
|
1360
|
+
return spec;
|
|
1361
|
+
}
|
|
1362
|
+
function outputHandlesSpacerHeight(count) {
|
|
1363
|
+
return count > 2 ? (count - 2) * 26 : 0;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// src/builder/components/code-workflow/code-workflow-state.ts
|
|
1367
|
+
var CODE_WORKFLOW_NODE_TYPE = "internal:code-workflow";
|
|
1368
|
+
var WAIT_FOR_SIGNAL_NODE_TYPE = "internal:wait-for-signal";
|
|
1369
|
+
function isRecord3(value) {
|
|
1370
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1371
|
+
}
|
|
1372
|
+
function str(value) {
|
|
1373
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
1374
|
+
}
|
|
1375
|
+
function readCodeWorkflowState(state) {
|
|
1376
|
+
const s = isRecord3(state) ? state : {};
|
|
1377
|
+
const waitForCompletion = s.waitForCompletion !== false;
|
|
1378
|
+
const policy = s.parentClosePolicy;
|
|
1379
|
+
return {
|
|
1380
|
+
workflowFn: str(s.workflowFn),
|
|
1381
|
+
taskQueue: str(s.taskQueue) ?? "managed",
|
|
1382
|
+
waitForCompletion,
|
|
1383
|
+
parentClosePolicy: policy === "TERMINATE" || policy === "ABANDON" || policy === "REQUEST_CANCEL" ? policy : waitForCompletion ? "TERMINATE" : "ABANDON",
|
|
1384
|
+
timeout: typeof s.timeout === "number" && s.timeout > 0 ? s.timeout : 0
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function readSignalName(state) {
|
|
1388
|
+
return isRecord3(state) ? str(state.signalName) : void 0;
|
|
1389
|
+
}
|
|
1390
|
+
function typeLabel(schema) {
|
|
1391
|
+
if (!isRecord3(schema)) return "any";
|
|
1392
|
+
if (Array.isArray(schema.enum)) return schema.enum.map(String).join(" | ");
|
|
1393
|
+
if (Array.isArray(schema.type)) return schema.type.map(String).join(" | ");
|
|
1394
|
+
if (schema.type === "array") return `${typeLabel(schema.items)}[]`;
|
|
1395
|
+
return typeof schema.type === "string" ? schema.type : "any";
|
|
1396
|
+
}
|
|
1397
|
+
function interfaceFields(schema) {
|
|
1398
|
+
if (!isRecord3(schema) || !isRecord3(schema.properties)) return [];
|
|
1399
|
+
const required = new Set(
|
|
1400
|
+
Array.isArray(schema.required) ? schema.required.map(String) : []
|
|
1401
|
+
);
|
|
1402
|
+
return Object.entries(schema.properties).map(([name, property]) => ({
|
|
1403
|
+
name,
|
|
1404
|
+
type: typeLabel(property),
|
|
1405
|
+
required: required.has(name),
|
|
1406
|
+
description: isRecord3(property) ? str(property.description) : void 0
|
|
1407
|
+
}));
|
|
1408
|
+
}
|
|
1409
|
+
function interfaceHandlers(iface) {
|
|
1410
|
+
const of = (key) => isRecord3(iface) && isRecord3(iface[key]) ? Object.keys(iface[key]).sort() : [];
|
|
1411
|
+
return {
|
|
1412
|
+
signals: of("signals"),
|
|
1413
|
+
queries: of("queries"),
|
|
1414
|
+
updates: of("updates")
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function childRunIdOf(nodeStates, nodeId) {
|
|
1418
|
+
const states = nodeStates.filter((state) => state.nodeId === nodeId);
|
|
1419
|
+
for (let i = states.length - 1; i >= 0; i -= 1) {
|
|
1420
|
+
const state = states[i];
|
|
1421
|
+
const fromColumn = str(state.childRunId);
|
|
1422
|
+
if (fromColumn) return fromColumn;
|
|
1423
|
+
if (isRecord3(state.output)) {
|
|
1424
|
+
const fromOutput = str(state.output._childRunId);
|
|
1425
|
+
if (fromOutput) return fromOutput;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
return void 0;
|
|
1429
|
+
}
|
|
1430
|
+
function sourceLanguageOf(path) {
|
|
1431
|
+
if (/\.(ts|tsx|mts|cts)$/.test(path)) return "typescript";
|
|
1432
|
+
if (/\.py$/.test(path)) return "python";
|
|
1433
|
+
if (/\.json$/.test(path)) return "json";
|
|
1434
|
+
return "text";
|
|
1435
|
+
}
|
|
1436
|
+
function isEditableSourcePath(path) {
|
|
1437
|
+
if (path.startsWith(".fluidgrids/")) return false;
|
|
1438
|
+
const language = sourceLanguageOf(path);
|
|
1439
|
+
return language === "typescript" || language === "json";
|
|
1440
|
+
}
|
|
1441
|
+
function projectLanguage(files) {
|
|
1442
|
+
return files.some(
|
|
1443
|
+
(file) => file.path.startsWith("workflows/") && file.path.endsWith(".py")
|
|
1444
|
+
) ? "python" : "typescript";
|
|
1445
|
+
}
|
|
1446
|
+
function changedSourceFiles(loaded, current) {
|
|
1447
|
+
return loaded.filter((file) => {
|
|
1448
|
+
const now = current[file.path];
|
|
1449
|
+
return now !== void 0 && now !== file.content;
|
|
1450
|
+
}).map((file) => ({ path: file.path, content: current[file.path] })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
1451
|
+
}
|
|
1452
|
+
function defaultSourcePath(files, entry) {
|
|
1453
|
+
if (entry && files.some((file) => file.path === entry)) return entry;
|
|
1454
|
+
return files.find(
|
|
1455
|
+
(file) => file.path.startsWith("workflows/") && isEditableSourcePath(file.path)
|
|
1456
|
+
)?.path ?? files[0]?.path;
|
|
1457
|
+
}
|
|
1458
|
+
function diagnosticsByPath(diagnostics) {
|
|
1459
|
+
const result = /* @__PURE__ */ new Map();
|
|
1460
|
+
for (const diagnostic of diagnostics) {
|
|
1461
|
+
const path = diagnostic.path?.replace(/^\.\//, "") ?? "";
|
|
1462
|
+
const list = result.get(path) ?? [];
|
|
1463
|
+
list.push({
|
|
1464
|
+
line: diagnostic.line,
|
|
1465
|
+
column: diagnostic.column,
|
|
1466
|
+
severity: editorSeverity(diagnostic.severity),
|
|
1467
|
+
code: diagnostic.code,
|
|
1468
|
+
message: diagnostic.message
|
|
1469
|
+
});
|
|
1470
|
+
result.set(path, list);
|
|
1471
|
+
}
|
|
1472
|
+
return result;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// src/builder/contants/symbols.ts
|
|
1476
|
+
var NODE_TYPE_DRAG_DATA_FORMAT = "application/flow-builder.node-type";
|
|
1477
|
+
var NODE_OPERATION_DRAG_DATA_FORMAT = "application/flow-builder.node-operation";
|
|
1478
|
+
var STICKY_NOTE_TYPE = "sticky-note";
|
|
1479
|
+
|
|
1480
|
+
// src/builder/lib/sticky-notes.ts
|
|
1481
|
+
function isStickyNode(node) {
|
|
1482
|
+
return node?.type === STICKY_NOTE_TYPE;
|
|
1483
|
+
}
|
|
1484
|
+
function partitionStickyNotes(nodes) {
|
|
1485
|
+
return {
|
|
1486
|
+
nodes: nodes.filter((n) => !isStickyNode(n)),
|
|
1487
|
+
stickyNotes: nodes.filter((n) => isStickyNode(n))
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function mergeStickyNotes(def) {
|
|
1491
|
+
const nodes = Array.isArray(def.nodes) ? def.nodes : [];
|
|
1492
|
+
const stickies = Array.isArray(def.stickyNotes) ? def.stickyNotes : [];
|
|
1493
|
+
const seen = new Set(nodes.map((n) => n.id));
|
|
1494
|
+
return [...nodes, ...stickies.filter((s) => s?.id && !seen.has(s.id))];
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/builder/lib/node-operation-selection.ts
|
|
1498
|
+
function resolveOperationName(selectedOperation, fallback) {
|
|
1499
|
+
return typeof selectedOperation === "string" && selectedOperation.trim() ? selectedOperation : fallback;
|
|
1500
|
+
}
|
|
1501
|
+
function canRenderOperationPanel(selectedOperation, supportedOperations) {
|
|
1502
|
+
const operationName = resolveOperationName(selectedOperation, "");
|
|
1503
|
+
return operationName === "" || supportedOperations.includes(operationName);
|
|
1504
|
+
}
|
|
1505
|
+
function isRecord4(value) {
|
|
1506
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1507
|
+
}
|
|
1508
|
+
function readRecord3(value) {
|
|
1509
|
+
return isRecord4(value) ? value : void 0;
|
|
1510
|
+
}
|
|
1511
|
+
function readOperations(value) {
|
|
1512
|
+
const operations = Array.isArray(value) ? value : readRecord3(value)?.operations;
|
|
1513
|
+
return Array.isArray(operations) ? operations : [];
|
|
1514
|
+
}
|
|
1515
|
+
function synchronizeOperationUpdate(currentData, updates, registeredOperations) {
|
|
1516
|
+
const operationName = resolveOperationName(updates.operation_name, "");
|
|
1517
|
+
if (!operationName || operationName === currentData.operation_name)
|
|
1518
|
+
return updates;
|
|
1519
|
+
const manifest = readRecord3(updates.nodeData ?? currentData.nodeData);
|
|
1520
|
+
const operations = [
|
|
1521
|
+
...readOperations(manifest?.operations),
|
|
1522
|
+
...readOperations(registeredOperations)
|
|
1523
|
+
];
|
|
1524
|
+
const selected = readRecord3(
|
|
1525
|
+
operations.find(
|
|
1526
|
+
(operation) => readRecord3(operation)?.name === operationName
|
|
1527
|
+
)
|
|
1528
|
+
);
|
|
1529
|
+
return {
|
|
1530
|
+
...updates,
|
|
1531
|
+
operation_type: typeof selected?.type === "string" ? selected.type : updates.operation_type,
|
|
1532
|
+
nodeData: { ...manifest, operations: selected ? [selected] : [] }
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function CustomHandle({
|
|
1536
|
+
className,
|
|
1537
|
+
isConnectable,
|
|
1538
|
+
accent,
|
|
1539
|
+
...props
|
|
1540
|
+
}) {
|
|
1541
|
+
const { nodeLookup, edges } = useStore(({ nodeLookup: nodeLookup2, edges: edges2 }) => ({
|
|
1542
|
+
nodeLookup: nodeLookup2,
|
|
1543
|
+
edges: edges2
|
|
1544
|
+
}));
|
|
1545
|
+
const nodeId = useNodeId();
|
|
1546
|
+
const isHandleConnectable = useMemo(() => {
|
|
1547
|
+
if (!nodeId) return false;
|
|
1548
|
+
const node = nodeLookup.get(nodeId);
|
|
1549
|
+
if (!node) return false;
|
|
1550
|
+
const connectedEdges = getConnectedEdges([node], edges);
|
|
1551
|
+
if (typeof isConnectable === "function")
|
|
1552
|
+
return isConnectable({ node, connectedEdges });
|
|
1553
|
+
if (typeof isConnectable === "number")
|
|
1554
|
+
return connectedEdges.length < isConnectable;
|
|
1555
|
+
return isConnectable;
|
|
1556
|
+
}, [edges, isConnectable, nodeId, nodeLookup]);
|
|
1557
|
+
return /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
|
|
1558
|
+
Handle,
|
|
1559
|
+
{
|
|
1560
|
+
className: cn(
|
|
1561
|
+
"cursor-crosshair transition-transform hover:scale-110",
|
|
1562
|
+
!accent && "size-3 border-2 border-card-foreground/40 hover:border-primary/50",
|
|
1563
|
+
className
|
|
1564
|
+
),
|
|
1565
|
+
isConnectable: isHandleConnectable,
|
|
1566
|
+
style: accent ? {
|
|
1567
|
+
width: 14,
|
|
1568
|
+
height: 14,
|
|
1569
|
+
borderRadius: "50%",
|
|
1570
|
+
background: "var(--color-node-bg)",
|
|
1571
|
+
border: `2.5px solid ${accent}`,
|
|
1572
|
+
boxShadow: `0 0 0 3px color-mix(in srgb, ${accent} 12%, transparent)`
|
|
1573
|
+
} : void 0,
|
|
1574
|
+
...props
|
|
1575
|
+
}
|
|
1576
|
+
) });
|
|
1577
|
+
}
|
|
1578
|
+
var TONE_COLORS = {
|
|
1579
|
+
success: "var(--color-status-success-text)",
|
|
1580
|
+
error: "var(--color-status-error-text)",
|
|
1581
|
+
warning: "var(--color-status-warning-text)",
|
|
1582
|
+
info: "var(--color-status-info-text)",
|
|
1583
|
+
neutral: "var(--color-text-muted)"
|
|
1584
|
+
};
|
|
1585
|
+
function HandlePlusStub({
|
|
1586
|
+
nodeId,
|
|
1587
|
+
handleId,
|
|
1588
|
+
color,
|
|
1589
|
+
isConnectable,
|
|
1590
|
+
isOnlyHandle,
|
|
1591
|
+
anchor
|
|
1592
|
+
}) {
|
|
1593
|
+
const tr = useTr();
|
|
1594
|
+
const startAttach = useFlowStore((s) => s.actions.pendingConnection.start);
|
|
1595
|
+
const isArmedHere = useFlowStore(
|
|
1596
|
+
(s) => s.workflow.pendingConnection?.nodeId === nodeId && s.workflow.pendingConnection?.handleId === handleId
|
|
1597
|
+
);
|
|
1598
|
+
const hasOutgoing = useStore(
|
|
1599
|
+
(s) => s.edges.some(
|
|
1600
|
+
(e) => e.source === nodeId && (e.sourceHandle === handleId || isOnlyHandle && (e.sourceHandle === null || e.sourceHandle === void 0))
|
|
1601
|
+
)
|
|
1602
|
+
);
|
|
1603
|
+
if (hasOutgoing || isConnectable === false) return null;
|
|
1604
|
+
const containerStyle = anchor === "card" ? {
|
|
1605
|
+
left: "100%",
|
|
1606
|
+
top: "50%",
|
|
1607
|
+
transform: "translateY(-50%)",
|
|
1608
|
+
marginLeft: 10
|
|
1609
|
+
} : { left: 10, top: 0, transform: "translateY(-50%)" };
|
|
1610
|
+
return /* @__PURE__ */ jsxs(
|
|
1611
|
+
"div",
|
|
1612
|
+
{
|
|
1613
|
+
className: "nodrag nopan absolute flex items-center",
|
|
1614
|
+
style: { ...containerStyle, pointerEvents: "none", zIndex: 5 },
|
|
1615
|
+
children: [
|
|
1616
|
+
/* @__PURE__ */ jsx(
|
|
1617
|
+
"div",
|
|
1618
|
+
{
|
|
1619
|
+
style: {
|
|
1620
|
+
width: 16,
|
|
1621
|
+
borderTop: `1.5px dashed ${color}`,
|
|
1622
|
+
opacity: 0.5
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
),
|
|
1626
|
+
/* @__PURE__ */ jsx(
|
|
1627
|
+
"button",
|
|
1628
|
+
{
|
|
1629
|
+
type: "button",
|
|
1630
|
+
title: tr(
|
|
1631
|
+
"fluidgrids.builder.outputHandles.addNextNodeTooltip",
|
|
1632
|
+
"Add next node"
|
|
1633
|
+
),
|
|
1634
|
+
"aria-label": `Add next node from ${handleId}`,
|
|
1635
|
+
"data-testid": `handle-plus-${nodeId}-${handleId}`,
|
|
1636
|
+
onClick: (e) => {
|
|
1637
|
+
e.stopPropagation();
|
|
1638
|
+
startAttach({ nodeId, handleId });
|
|
1639
|
+
},
|
|
1640
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
1641
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1642
|
+
className: "cursor-pointer transition-transform hover:scale-110",
|
|
1643
|
+
style: {
|
|
1644
|
+
pointerEvents: "all",
|
|
1645
|
+
width: 16,
|
|
1646
|
+
height: 16,
|
|
1647
|
+
borderRadius: "50%",
|
|
1648
|
+
display: "flex",
|
|
1649
|
+
alignItems: "center",
|
|
1650
|
+
justifyContent: "center",
|
|
1651
|
+
border: `1.5px solid ${color}`,
|
|
1652
|
+
background: isArmedHere ? color : "var(--color-node-bg)",
|
|
1653
|
+
color: isArmedHere ? "var(--color-node-bg)" : color,
|
|
1654
|
+
boxShadow: isArmedHere ? `0 0 0 3px color-mix(in srgb, ${color} 22%, transparent)` : `0 0 0 2px color-mix(in srgb, ${color} 10%, transparent)`,
|
|
1655
|
+
transition: "transform .12s ease, background .12s ease, color .12s ease"
|
|
1656
|
+
},
|
|
1657
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", className: "size-2.5" })
|
|
1658
|
+
}
|
|
1659
|
+
)
|
|
1660
|
+
]
|
|
1661
|
+
}
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
function OutputHandles({
|
|
1665
|
+
nodeId,
|
|
1666
|
+
handles,
|
|
1667
|
+
accent,
|
|
1668
|
+
isConnectable
|
|
1669
|
+
}) {
|
|
1670
|
+
const updateNodeInternals = useUpdateNodeInternals();
|
|
1671
|
+
const signature = useMemo(
|
|
1672
|
+
() => handles.map((h) => h.id).join("|"),
|
|
1673
|
+
[handles]
|
|
1674
|
+
);
|
|
1675
|
+
useEffect(() => {
|
|
1676
|
+
updateNodeInternals(nodeId);
|
|
1677
|
+
}, [nodeId, signature, updateNodeInternals]);
|
|
1678
|
+
if (handles.length === 1) {
|
|
1679
|
+
const handle = handles[0];
|
|
1680
|
+
const color = handle.tone ? TONE_COLORS[handle.tone] : accent;
|
|
1681
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1682
|
+
/* @__PURE__ */ jsx(
|
|
1683
|
+
CustomHandle,
|
|
1684
|
+
{
|
|
1685
|
+
type: "source",
|
|
1686
|
+
id: handle.id,
|
|
1687
|
+
position: Position.Right,
|
|
1688
|
+
isConnectable,
|
|
1689
|
+
accent: color
|
|
1690
|
+
}
|
|
1691
|
+
),
|
|
1692
|
+
/* @__PURE__ */ jsx(
|
|
1693
|
+
HandlePlusStub,
|
|
1694
|
+
{
|
|
1695
|
+
nodeId,
|
|
1696
|
+
handleId: handle.id,
|
|
1697
|
+
color,
|
|
1698
|
+
isConnectable,
|
|
1699
|
+
isOnlyHandle: true,
|
|
1700
|
+
anchor: "card"
|
|
1701
|
+
}
|
|
1702
|
+
)
|
|
1703
|
+
] });
|
|
1704
|
+
}
|
|
1705
|
+
const total = handles.length;
|
|
1706
|
+
return /* @__PURE__ */ jsx(Fragment, { children: handles.map((handle, index) => {
|
|
1707
|
+
const top = `${(index + 1) / (total + 1) * 100}%`;
|
|
1708
|
+
const color = handle.tone ? TONE_COLORS[handle.tone] : accent;
|
|
1709
|
+
return /* @__PURE__ */ jsxs(
|
|
1710
|
+
"div",
|
|
1711
|
+
{
|
|
1712
|
+
className: "absolute right-0",
|
|
1713
|
+
style: { top, transform: "translateY(-50%)" },
|
|
1714
|
+
children: [
|
|
1715
|
+
/* @__PURE__ */ jsx(
|
|
1716
|
+
CustomHandle,
|
|
1717
|
+
{
|
|
1718
|
+
type: "source",
|
|
1719
|
+
id: handle.id,
|
|
1720
|
+
position: Position.Right,
|
|
1721
|
+
isConnectable,
|
|
1722
|
+
accent: color
|
|
1723
|
+
}
|
|
1724
|
+
),
|
|
1725
|
+
handle.label && /* @__PURE__ */ jsx(
|
|
1726
|
+
"span",
|
|
1727
|
+
{
|
|
1728
|
+
className: "pointer-events-none absolute whitespace-nowrap text-[10px] font-medium leading-none",
|
|
1729
|
+
style: { color, left: 12, bottom: 8 },
|
|
1730
|
+
title: handle.label,
|
|
1731
|
+
children: handle.label
|
|
1732
|
+
}
|
|
1733
|
+
),
|
|
1734
|
+
/* @__PURE__ */ jsx(
|
|
1735
|
+
HandlePlusStub,
|
|
1736
|
+
{
|
|
1737
|
+
nodeId,
|
|
1738
|
+
handleId: handle.id,
|
|
1739
|
+
color,
|
|
1740
|
+
isConnectable,
|
|
1741
|
+
isOnlyHandle: false,
|
|
1742
|
+
anchor: "point"
|
|
1743
|
+
}
|
|
1744
|
+
)
|
|
1745
|
+
]
|
|
1746
|
+
},
|
|
1747
|
+
handle.id
|
|
1748
|
+
);
|
|
1749
|
+
}) });
|
|
1750
|
+
}
|
|
1751
|
+
function InputHandles({
|
|
1752
|
+
nodeId,
|
|
1753
|
+
handles,
|
|
1754
|
+
accent,
|
|
1755
|
+
isConnectable
|
|
1756
|
+
}) {
|
|
1757
|
+
const updateNodeInternals = useUpdateNodeInternals();
|
|
1758
|
+
const signature = useMemo(
|
|
1759
|
+
() => handles.map((h) => h.id).join("|"),
|
|
1760
|
+
[handles]
|
|
1761
|
+
);
|
|
1762
|
+
useEffect(() => {
|
|
1763
|
+
updateNodeInternals(nodeId);
|
|
1764
|
+
}, [nodeId, signature, updateNodeInternals]);
|
|
1765
|
+
if (handles.length === 1) {
|
|
1766
|
+
const handle = handles[0];
|
|
1767
|
+
return /* @__PURE__ */ jsx(
|
|
1768
|
+
CustomHandle,
|
|
1769
|
+
{
|
|
1770
|
+
type: "target",
|
|
1771
|
+
id: handle.id,
|
|
1772
|
+
position: Position.Left,
|
|
1773
|
+
isConnectable,
|
|
1774
|
+
accent: handle.tone ? TONE_COLORS[handle.tone] : accent
|
|
1775
|
+
}
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
const total = handles.length;
|
|
1779
|
+
return /* @__PURE__ */ jsx(Fragment, { children: handles.map((handle, index) => {
|
|
1780
|
+
const top = `${(index + 1) / (total + 1) * 100}%`;
|
|
1781
|
+
const color = handle.tone ? TONE_COLORS[handle.tone] : accent;
|
|
1782
|
+
return /* @__PURE__ */ jsxs(
|
|
1783
|
+
"div",
|
|
1784
|
+
{
|
|
1785
|
+
className: "absolute left-0",
|
|
1786
|
+
style: { top, transform: "translateY(-50%)" },
|
|
1787
|
+
children: [
|
|
1788
|
+
/* @__PURE__ */ jsx(
|
|
1789
|
+
CustomHandle,
|
|
1790
|
+
{
|
|
1791
|
+
type: "target",
|
|
1792
|
+
id: handle.id,
|
|
1793
|
+
position: Position.Left,
|
|
1794
|
+
isConnectable,
|
|
1795
|
+
accent: color
|
|
1796
|
+
}
|
|
1797
|
+
),
|
|
1798
|
+
handle.label && /* @__PURE__ */ jsx(
|
|
1799
|
+
"span",
|
|
1800
|
+
{
|
|
1801
|
+
className: "pointer-events-none absolute whitespace-nowrap text-[10px] font-medium leading-none",
|
|
1802
|
+
style: {
|
|
1803
|
+
color,
|
|
1804
|
+
right: "100%",
|
|
1805
|
+
marginRight: 16,
|
|
1806
|
+
top: "50%",
|
|
1807
|
+
transform: "translateY(-50%)"
|
|
1808
|
+
},
|
|
1809
|
+
title: handle.label,
|
|
1810
|
+
children: handle.label
|
|
1811
|
+
}
|
|
1812
|
+
)
|
|
1813
|
+
]
|
|
1814
|
+
},
|
|
1815
|
+
handle.id
|
|
1816
|
+
);
|
|
1817
|
+
}) });
|
|
1818
|
+
}
|
|
1819
|
+
function LogicNodeCard({
|
|
1820
|
+
title,
|
|
1821
|
+
subtitle,
|
|
1822
|
+
icon,
|
|
1823
|
+
accent,
|
|
1824
|
+
selected,
|
|
1825
|
+
executionStatus,
|
|
1826
|
+
itemCount,
|
|
1827
|
+
isTrigger,
|
|
1828
|
+
isExecuting,
|
|
1829
|
+
nodeId,
|
|
1830
|
+
inputHandles,
|
|
1831
|
+
outputHandles,
|
|
1832
|
+
isConnectable,
|
|
1833
|
+
onClick,
|
|
1834
|
+
onDoubleClick,
|
|
1835
|
+
onSettings,
|
|
1836
|
+
onExecute,
|
|
1837
|
+
onDelete,
|
|
1838
|
+
infoButton,
|
|
1839
|
+
disabled = false,
|
|
1840
|
+
onToggleDisabled,
|
|
1841
|
+
isRenaming = false,
|
|
1842
|
+
onStartRename,
|
|
1843
|
+
onCommitRename,
|
|
1844
|
+
onCancelRename
|
|
1845
|
+
}) {
|
|
1846
|
+
const minHeight = Math.max(
|
|
1847
|
+
64,
|
|
1848
|
+
Math.max(outputHandles.length, inputHandles.length) * 30 + 24
|
|
1849
|
+
);
|
|
1850
|
+
return /* @__PURE__ */ jsxs(
|
|
1851
|
+
NodeCard,
|
|
1852
|
+
{
|
|
1853
|
+
className: "w-48",
|
|
1854
|
+
"data-selected": selected,
|
|
1855
|
+
selected,
|
|
1856
|
+
onClick,
|
|
1857
|
+
onDoubleClick,
|
|
1858
|
+
executionStatus,
|
|
1859
|
+
itemCount,
|
|
1860
|
+
isTrigger,
|
|
1861
|
+
accent,
|
|
1862
|
+
isExecuting,
|
|
1863
|
+
onSettings,
|
|
1864
|
+
onExecute,
|
|
1865
|
+
onDelete,
|
|
1866
|
+
infoButton,
|
|
1867
|
+
disabled,
|
|
1868
|
+
onToggleDisabled,
|
|
1869
|
+
children: [
|
|
1870
|
+
/* @__PURE__ */ jsxs(
|
|
1871
|
+
"div",
|
|
1872
|
+
{
|
|
1873
|
+
className: "flex flex-col items-center justify-center gap-1 px-3 py-3 text-center",
|
|
1874
|
+
style: { minHeight },
|
|
1875
|
+
children: [
|
|
1876
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-9 items-center justify-center rounded-lg bg-muted", children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-5", style: { color: accent } }) }),
|
|
1877
|
+
onStartRename && onCommitRename && onCancelRename ? /* @__PURE__ */ jsx(
|
|
1878
|
+
InlineEditableLabel,
|
|
1879
|
+
{
|
|
1880
|
+
value: title,
|
|
1881
|
+
placeholder: title,
|
|
1882
|
+
editing: isRenaming,
|
|
1883
|
+
onStartEdit: onStartRename,
|
|
1884
|
+
onCommit: onCommitRename,
|
|
1885
|
+
onCancel: onCancelRename,
|
|
1886
|
+
className: "max-w-full truncate text-sm font-semibold leading-tight",
|
|
1887
|
+
inputClassName: "text-sm font-semibold text-center"
|
|
1888
|
+
}
|
|
1889
|
+
) : /* @__PURE__ */ jsx("span", { className: "max-w-full truncate text-sm font-semibold leading-tight", children: title }),
|
|
1890
|
+
subtitle && /* @__PURE__ */ jsx(
|
|
1891
|
+
"span",
|
|
1892
|
+
{
|
|
1893
|
+
className: "max-w-full truncate font-mono text-[10px] text-muted-foreground",
|
|
1894
|
+
title: subtitle,
|
|
1895
|
+
children: subtitle
|
|
1896
|
+
}
|
|
1897
|
+
)
|
|
1898
|
+
]
|
|
1899
|
+
}
|
|
1900
|
+
),
|
|
1901
|
+
/* @__PURE__ */ jsx(
|
|
1902
|
+
InputHandles,
|
|
1903
|
+
{
|
|
1904
|
+
nodeId,
|
|
1905
|
+
handles: inputHandles,
|
|
1906
|
+
accent,
|
|
1907
|
+
isConnectable
|
|
1908
|
+
}
|
|
1909
|
+
),
|
|
1910
|
+
/* @__PURE__ */ jsx(
|
|
1911
|
+
OutputHandles,
|
|
1912
|
+
{
|
|
1913
|
+
nodeId,
|
|
1914
|
+
handles: outputHandles,
|
|
1915
|
+
accent,
|
|
1916
|
+
isConnectable
|
|
1917
|
+
}
|
|
1918
|
+
)
|
|
1919
|
+
]
|
|
1920
|
+
}
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
export { CODE_WORKFLOW_NODE_TYPE, InputHandles, LogicNodeCard, NODE_OPERATION_DRAG_DATA_FORMAT, NODE_TYPE_DRAG_DATA_FORMAT, NodeCard, NodeCardBody, NodeCardHeader, NodeTypeIcon, OutputHandles, STICKY_NOTE_TYPE, WAIT_FOR_SIGNAL_NODE_TYPE, canRenderOperationPanel, changedSourceFiles, childRunIdOf, defaultSourcePath, diagnosticsByPath, interfaceFields, interfaceHandlers, isEditableSourcePath, isImageIcon, isNodeTrigger, isTriggerOperation, mergeStickyNotes, nodeAccent, outputHandlesSpacerHeight, partitionStickyNotes, projectLanguage, readCodeWorkflowState, readSignalName, resolveInputHandles, resolveOperationName, resolveOutputHandles, sourceLanguageOf, synchronizeOperationUpdate, useFlowStore, useIsValidConnection };
|