@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.
Files changed (50) hide show
  1. package/README.md +30 -0
  2. package/dist/{ExecutionDetailPage-HQB2SWBP.js → ExecutionDetailPage-JXGPSLS3.js} +9 -7
  3. package/dist/{ExecutionsListPage-RSLL634L.js → ExecutionsListPage-U3TC5JUI.js} +8 -6
  4. package/dist/{WorkflowBuilder-E3PNZX74.js → WorkflowBuilder-6DOD6WWH.js} +13 -10
  5. package/dist/{WorkflowsList-473FRRP2.js → WorkflowsList-XOWRQNST.js} +9 -7
  6. package/dist/{agentTools-5N3ZEZDW.js → agentTools-YQBN33WG.js} +6 -4
  7. package/dist/{chunk-XFH6SLDK.js → chunk-22U3TUKZ.js} +1 -1
  8. package/dist/{chunk-QFC6E3WD.js → chunk-2WUVF4EQ.js} +1 -1
  9. package/dist/{chunk-W5WML3OO.js → chunk-3MZOYJ5I.js} +1 -1
  10. package/dist/{chunk-3XUWPDKJ.js → chunk-3ZYRLGDF.js} +5 -5
  11. package/dist/{chunk-SALZ3LQB.js → chunk-4NNF7DIB.js} +1 -1
  12. package/dist/chunk-5DVP2UYH.js +1924 -0
  13. package/dist/{chunk-CH3YUGWR.js → chunk-EKDUYCTS.js} +4 -4
  14. package/dist/{chunk-Y6M7K4UG.js → chunk-FVWRGL7S.js} +1 -1
  15. package/dist/{chunk-2KUZBEWW.js → chunk-G33OMSW6.js} +6 -5
  16. package/dist/{chunk-6IS7EXIY.js → chunk-G774FI2E.js} +2 -2
  17. package/dist/{chunk-5CFCFA6O.js → chunk-IJ7UIDCW.js} +6 -5
  18. package/dist/chunk-JYYN3CLL.js +32 -0
  19. package/dist/{chunk-GKFMK7FF.js → chunk-KAWRHIJZ.js} +1 -1
  20. package/dist/{chunk-QPLFALVV.js → chunk-KN37RIFC.js} +4 -3
  21. package/dist/{chunk-AASHQI77.js → chunk-KZCNJAMK.js} +2 -1
  22. package/dist/{chunk-X25NCWG2.js → chunk-LDWBMCX4.js} +4 -3
  23. package/dist/{chunk-CNKTQ5MG.js → chunk-MA6I4KY5.js} +2 -2
  24. package/dist/{chunk-O7CJJNW6.js → chunk-MP44TAOO.js} +2 -2
  25. package/dist/{chunk-6ZK4MRVA.js → chunk-MY7SEUXM.js} +1 -1
  26. package/dist/{chunk-62CCBMDC.js → chunk-NMQENSPW.js} +12 -11
  27. package/dist/{chunk-HK76ZUAK.js → chunk-NZFPCTIS.js} +4 -4
  28. package/dist/chunk-SDZEAIIE.js +9 -0
  29. package/dist/{chunk-64JN2TVX.js → chunk-TJAG2DQT.js} +8 -37
  30. package/dist/{chunk-TNFBRXU7.js → chunk-TLHX4DK4.js} +6 -5
  31. package/dist/{chunk-J3YQCEP3.js → chunk-VESQH55Q.js} +1 -1
  32. package/dist/{chunk-OCJTTHTS.js → chunk-Y7FKJJMF.js} +1 -9
  33. package/dist/{chunk-QFGZXWTM.js → chunk-YGOGJDN4.js} +1 -1
  34. package/dist/{chunk-3QCRKKTI.js → chunk-ZBPRNWKY.js} +234 -2145
  35. package/dist/{chunk-AJVU5YW6.js → chunk-ZLBTUCC2.js} +3 -2
  36. package/dist/code-editor-field-WWPDMF4O.js +3 -0
  37. package/dist/executions.js +10 -8
  38. package/dist/history.js +8 -5
  39. package/dist/index.js +36 -33
  40. package/dist/{node-detail-view-WIWW5N6N.js → node-detail-view-U5HOP2WX.js} +10 -8
  41. package/dist/observability.js +6 -4
  42. package/dist/{quick-config-panel-O64TXXK3.js → quick-config-panel-XAISBW6Y.js} +8 -6
  43. package/dist/settings.js +7 -5
  44. package/dist/templates.js +12 -9
  45. package/dist/workflow-builder.js +14 -11
  46. package/dist/workflow-preview.d.ts +158 -0
  47. package/dist/workflow-preview.js +721 -0
  48. package/dist/workflows-list.js +9 -7
  49. package/package.json +5 -1
  50. 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 };