@burdenoff/fluidgrids-fe-sdk 2026.703.2 → 2026.706.1

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.
@@ -9,17 +9,17 @@ import { config, getFluidGridsConfig, validateTrustedOrigin, setFluidGridsConfig
9
9
  import { deepClone, applyTemplateInputs } from './chunk-XQP33UJR.js';
10
10
  import { cn as cn$1, Badge as Badge$1, Button as Button$1 } from './chunk-Z54K22LP.js';
11
11
  import { ThemeProvider as ThemeProvider$1 } from '@burdenoff/fe-libs/shared/providers/shell';
12
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
13
  import { useParams, useNavigate } from 'react-router-dom';
14
14
  import { useShallow, shallow } from 'zustand/shallow';
15
- import * as React2 from 'react';
16
- import React2__default, { forwardRef, useState, useImperativeHandle, useRef, useEffect, useMemo, createContext, lazy, memo, useCallback, useContext, useLayoutEffect, Suspense, useDeferredValue } from 'react';
17
- import { Handle, Position, getBezierPath, MarkerType, useStore, useReactFlow, getConnectedEdges, useNodeId, ReactFlowProvider, ReactFlow, Background, useOnSelectionChange, MiniMap, EdgeLabelRenderer, getOutgoers, useNodes, useNodesState, useEdgesState, addEdge, Controls, Panel } from '@xyflow/react';
15
+ import * as React from 'react';
16
+ import React__default, { forwardRef, useState, useImperativeHandle, useRef, useEffect, useMemo, createContext, lazy, memo, useCallback, useContext, useLayoutEffect, Suspense, useDeferredValue } from 'react';
17
+ import { Handle, Position, getBezierPath, MarkerType, useStore, useReactFlow, getConnectedEdges, useUpdateNodeInternals, useNodeId, ReactFlowProvider, ReactFlow, Background, useOnSelectionChange, MiniMap, EdgeLabelRenderer, getOutgoers, useNodes, useNodesState, useEdgesState, addEdge, Controls, Panel } from '@xyflow/react';
18
18
  import { produce } from 'immer';
19
19
  import { nanoid } from 'nanoid/non-secure';
20
+ import { Icon } from '@iconify/react';
20
21
  import { cn } from '@burdenoff/fe-libs/utils';
21
22
  import { Textarea, Input, Button, Card, Badge, CardContent as CardContent$1, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator, Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, Popover, PopoverTrigger, PopoverContent, Toaster, Tabs, TabsList, TabsTrigger, TabsContent, CardHeader as CardHeader$1, CardTitle as CardTitle$1, CardDescription as CardDescription$1, ScrollArea, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@burdenoff/fe-libs/ui';
22
- import { Icon } from '@iconify/react';
23
23
  import * as AdaptiveCards from 'adaptivecards';
24
24
  import { formatDistanceToNow } from 'date-fns';
25
25
  import { gql } from '@apollo/client';
@@ -64,6 +64,255 @@ function useDeleteNode() {
64
64
  [deleteElements, getEdges, getNode, deleteNode, deleteEdge]
65
65
  );
66
66
  }
67
+ var BRAND_ACCENTS = [
68
+ [/gmail|google/, "#ea4335"],
69
+ [/slack/, "#611f69"],
70
+ [/github/, "#334155"],
71
+ [/gitlab/, "#fc6d26"],
72
+ [/stripe/, "#635bff"],
73
+ [/discord/, "#5865f2"],
74
+ [/notion/, "#111827"],
75
+ [/telegram/, "#2aabee"],
76
+ [/whatsapp/, "#25d366"],
77
+ [/openai/, "#10a37f"],
78
+ [/anthropic|claude/, "#d97757"],
79
+ [/twilio/, "#f22f46"],
80
+ [/sheet|excel/, "#21a366"],
81
+ [/http|api|webhook|rest/, "#6366f1"],
82
+ [/db|sql|postgres|mysql|mongo/, "#0ea5e9"]
83
+ ];
84
+ var HASH_PALETTE = [
85
+ "#6366f1",
86
+ "#0ea5e9",
87
+ "#10b981",
88
+ "#f59e0b",
89
+ "#ef4444",
90
+ "#a855f7",
91
+ "#ec4899",
92
+ "#14b8a6",
93
+ "#f97316",
94
+ "#8b5cf6"
95
+ ];
96
+ function nodeAccent(key) {
97
+ const k = (key || "").toLowerCase();
98
+ for (const [re, hex] of BRAND_ACCENTS) if (re.test(k)) return hex;
99
+ let h = 0;
100
+ for (let i = 0; i < k.length; i++) h = h * 31 + k.charCodeAt(i) >>> 0;
101
+ return HASH_PALETTE[h % HASH_PALETTE.length];
102
+ }
103
+ function tint(hex, pct = 14) {
104
+ return `color-mix(in srgb, ${hex} ${pct}%, transparent)`;
105
+ }
106
+ var NodeCard = React.forwardRef(
107
+ ({
108
+ className,
109
+ selected = false,
110
+ executionStatus = "idle",
111
+ itemCount,
112
+ isTrigger = false,
113
+ accent = "#3b82f6",
114
+ onSettings,
115
+ onExecute,
116
+ onDelete,
117
+ isExecuting = false,
118
+ infoButton,
119
+ children,
120
+ ...props
121
+ }, ref) => {
122
+ const [hovered, setHovered] = React.useState(false);
123
+ 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)]" : "";
124
+ return /* @__PURE__ */ jsxs(
125
+ "div",
126
+ {
127
+ className: "group/node relative",
128
+ onMouseEnter: () => setHovered(true),
129
+ onMouseLeave: () => setHovered(false),
130
+ children: [
131
+ 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: [
132
+ itemCount,
133
+ " item",
134
+ itemCount !== 1 ? "s" : ""
135
+ ] }) }),
136
+ (onSettings || onExecute || onDelete || infoButton) && /* @__PURE__ */ jsxs(
137
+ "div",
138
+ {
139
+ className: cn(
140
+ "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",
141
+ hovered ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
142
+ ),
143
+ style: {
144
+ // Inline offset (not a `-top-*` class) so it survives the host
145
+ // app's Tailwind scan of fe-sdk source, which drops some spacing
146
+ // utilities. The toolbar is ~30px tall; top:-15px centers it on
147
+ // the card's top edge (half outside, half over the card).
148
+ top: -15,
149
+ transform: hovered ? "translateY(0)" : "translateY(4px)"
150
+ },
151
+ onDoubleClick: (e) => e.stopPropagation(),
152
+ children: [
153
+ onSettings && /* @__PURE__ */ jsx(ToolbarButton, { title: "Configure", onClick: onSettings, children: /* @__PURE__ */ jsx(Icon, { icon: "lucide:sliders-horizontal", className: "size-4" }) }),
154
+ onExecute && /* @__PURE__ */ jsx(
155
+ ToolbarButton,
156
+ {
157
+ title: "Execute step",
158
+ onClick: onExecute,
159
+ disabled: isExecuting,
160
+ children: isExecuting ? /* @__PURE__ */ jsx(
161
+ Icon,
162
+ {
163
+ icon: "svg-spinners:ring-resize",
164
+ className: "size-4 text-[var(--color-status-success-text)]"
165
+ }
166
+ ) : /* @__PURE__ */ jsx(
167
+ Icon,
168
+ {
169
+ icon: "mdi:play",
170
+ className: "size-4 text-[var(--color-status-success-text)]"
171
+ }
172
+ )
173
+ }
174
+ ),
175
+ infoButton,
176
+ onDelete && /* @__PURE__ */ jsx(ToolbarButton, { title: "Delete", onClick: onDelete, children: /* @__PURE__ */ jsx(
177
+ Icon,
178
+ {
179
+ icon: "mynaui:trash",
180
+ className: "size-4 text-[var(--color-status-error-text)]"
181
+ }
182
+ ) })
183
+ ]
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsxs(
187
+ "div",
188
+ {
189
+ ref,
190
+ "data-selected": selected,
191
+ className: cn(
192
+ "relative w-72 rounded-xl border border-border bg-card shadow-sm will-change-transform",
193
+ statusRing,
194
+ executionStatus === "success" && "animate-success-glow",
195
+ className
196
+ ),
197
+ style: {
198
+ borderTop: `3px solid ${accent}`,
199
+ transition: "transform 0.15s ease, box-shadow 0.15s ease",
200
+ transform: hovered ? "translateY(-2px)" : "none",
201
+ boxShadow: hovered ? "0 2px 4px rgba(16,24,40,0.06), 0 16px 34px -12px rgba(16,24,40,0.24)" : void 0
202
+ },
203
+ ...props,
204
+ children: [
205
+ /* @__PURE__ */ jsx(
206
+ "div",
207
+ {
208
+ className: "pointer-events-none absolute -inset-0.5 transition-opacity duration-150",
209
+ style: {
210
+ borderRadius: 14,
211
+ opacity: selected || hovered ? 1 : 0,
212
+ boxShadow: selected ? `0 0 0 2px ${accent}, 0 0 0 5px ${tint(accent, 18)}` : `0 0 0 2px ${accent}`
213
+ }
214
+ }
215
+ ),
216
+ isTrigger && /* @__PURE__ */ jsx(
217
+ "div",
218
+ {
219
+ className: "absolute -top-2 -left-2 z-10 flex size-5 items-center justify-center rounded-full shadow-md ring-2 ring-card",
220
+ style: { backgroundColor: "var(--color-status-success-text)" },
221
+ children: /* @__PURE__ */ jsx(
222
+ Icon,
223
+ {
224
+ icon: "mdi:lightning-bolt",
225
+ className: "size-3",
226
+ style: { color: "var(--color-status-success-text-inverse)" }
227
+ }
228
+ )
229
+ }
230
+ ),
231
+ children
232
+ ]
233
+ }
234
+ ),
235
+ executionStatus === "success" && /* @__PURE__ */ jsx(StatusCorner, { icon: "mdi:check", tone: "success" }),
236
+ executionStatus === "error" && /* @__PURE__ */ jsx(StatusCorner, { icon: "mdi:close", tone: "error" }),
237
+ executionStatus === "running" && /* @__PURE__ */ jsx(StatusCorner, { icon: "svg-spinners:ring-resize", tone: "success" })
238
+ ]
239
+ }
240
+ );
241
+ }
242
+ );
243
+ NodeCard.displayName = "NodeCard";
244
+ function ToolbarButton({
245
+ title,
246
+ onClick,
247
+ disabled,
248
+ children
249
+ }) {
250
+ return /* @__PURE__ */ jsx(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ title,
255
+ disabled,
256
+ onClick: (e) => {
257
+ e.stopPropagation();
258
+ onClick?.();
259
+ },
260
+ className: "flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50",
261
+ children
262
+ }
263
+ );
264
+ }
265
+ function StatusCorner({
266
+ icon,
267
+ tone
268
+ }) {
269
+ const bg = tone === "success" ? "var(--color-status-success-bg)" : "var(--color-status-error-bg)";
270
+ const fg = tone === "success" ? "var(--color-status-success-text-inverse)" : "var(--color-status-error-text-inverse)";
271
+ return /* @__PURE__ */ jsx("div", { className: "absolute -bottom-1.5 -right-1.5 z-10", children: /* @__PURE__ */ jsx(
272
+ "div",
273
+ {
274
+ className: "rounded-full p-0.5 shadow-md animate-in zoom-in duration-200",
275
+ style: { backgroundColor: bg },
276
+ children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-3", style: { color: fg } })
277
+ }
278
+ ) });
279
+ }
280
+ var NodeCardHeader = React.forwardRef(
281
+ ({ icon, appName, accentKey }, ref) => {
282
+ const accent = nodeAccent(accentKey);
283
+ return /* @__PURE__ */ jsxs("div", { ref, className: "flex items-center gap-2.5 px-3 pb-3 pt-4", children: [
284
+ /* @__PURE__ */ jsx(
285
+ "div",
286
+ {
287
+ className: "flex size-8 shrink-0 items-center justify-center rounded-lg",
288
+ style: {
289
+ backgroundColor: tint(accent, 13),
290
+ color: accent,
291
+ border: `1px solid ${tint(accent, 26)}`
292
+ },
293
+ children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-[18px]" })
294
+ }
295
+ ),
296
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: appName })
297
+ ] });
298
+ }
299
+ );
300
+ NodeCardHeader.displayName = "NodeCardHeader";
301
+ var NodeCardBody = React.forwardRef(
302
+ ({ operationName, description }, ref) => /* @__PURE__ */ jsxs(
303
+ "div",
304
+ {
305
+ ref,
306
+ className: "rounded-b-xl border-t border-border/60 px-3 pb-3 pt-2.5",
307
+ children: [
308
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Operation" }),
309
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 truncate font-mono text-[13px] font-bold text-card-foreground", children: operationName }),
310
+ description ? /* @__PURE__ */ jsx("p", { className: "mt-1 line-clamp-2 text-xs leading-snug text-muted-foreground", children: description }) : null
311
+ ]
312
+ }
313
+ )
314
+ );
315
+ NodeCardBody.displayName = "NodeCardBody";
67
316
  function CustomHandle({
68
317
  className,
69
318
  isConnectable,
@@ -105,7 +354,259 @@ function CustomHandle({
105
354
  } : void 0,
106
355
  ...props
107
356
  }
108
- ) });
357
+ ) });
358
+ }
359
+ var TONE_COLORS = {
360
+ success: "var(--color-status-success-text)",
361
+ error: "var(--color-status-error-text)",
362
+ warning: "var(--color-status-warning-text)",
363
+ info: "#3b82f6",
364
+ neutral: "var(--muted-foreground, #6b7280)"
365
+ };
366
+ function isDynamicSpec(spec) {
367
+ return !!spec && !Array.isArray(spec) && typeof spec.fromStateRecord === "string";
368
+ }
369
+ var DEFAULT_HANDLES = [{ id: "source", label: "" }];
370
+ function resolveOutputHandles(data) {
371
+ const nodeData = data?.nodeData;
372
+ const spec = nodeData?.outputHandles ?? nodeData?.operations?.outputHandles;
373
+ if (!spec) return DEFAULT_HANDLES;
374
+ if (isDynamicSpec(spec)) {
375
+ const args = data?.args;
376
+ const record = args?.state?.[spec.fromStateRecord] ?? data?.[spec.fromStateRecord] ?? {};
377
+ const dynamic = Object.entries(record).filter(([, handleId]) => typeof handleId === "string" && handleId).map(([label, handleId]) => ({ id: handleId, label }));
378
+ return [...dynamic, ...spec.extra ?? []];
379
+ }
380
+ if (spec.length === 0) return DEFAULT_HANDLES;
381
+ return spec;
382
+ }
383
+ function OutputHandles({
384
+ nodeId,
385
+ handles,
386
+ accent,
387
+ isConnectable
388
+ }) {
389
+ const updateNodeInternals = useUpdateNodeInternals();
390
+ const signature = useMemo(
391
+ () => handles.map((h) => h.id).join("|"),
392
+ [handles]
393
+ );
394
+ useEffect(() => {
395
+ updateNodeInternals(nodeId);
396
+ }, [nodeId, signature, updateNodeInternals]);
397
+ if (handles.length === 1) {
398
+ const handle = handles[0];
399
+ return /* @__PURE__ */ jsx(
400
+ CustomHandle,
401
+ {
402
+ type: "source",
403
+ id: handle.id,
404
+ position: Position.Right,
405
+ isConnectable,
406
+ accent: handle.tone ? TONE_COLORS[handle.tone] : accent
407
+ }
408
+ );
409
+ }
410
+ const total = handles.length;
411
+ return /* @__PURE__ */ jsx(Fragment, { children: handles.map((handle, index) => {
412
+ const top = `${(index + 1) / (total + 1) * 100}%`;
413
+ const color = handle.tone ? TONE_COLORS[handle.tone] : accent;
414
+ return /* @__PURE__ */ jsxs(
415
+ "div",
416
+ {
417
+ className: "absolute right-0",
418
+ style: { top, transform: "translateY(-50%)" },
419
+ children: [
420
+ /* @__PURE__ */ jsx(
421
+ CustomHandle,
422
+ {
423
+ type: "source",
424
+ id: handle.id,
425
+ position: Position.Right,
426
+ isConnectable,
427
+ accent: color
428
+ }
429
+ ),
430
+ handle.label && /* @__PURE__ */ jsx(
431
+ "span",
432
+ {
433
+ className: "pointer-events-none absolute whitespace-nowrap text-[10px] font-medium leading-none",
434
+ style: {
435
+ color,
436
+ left: "100%",
437
+ marginLeft: 16,
438
+ top: "50%",
439
+ transform: "translateY(-50%)"
440
+ },
441
+ title: handle.label,
442
+ children: handle.label
443
+ }
444
+ )
445
+ ]
446
+ },
447
+ handle.id
448
+ );
449
+ }) });
450
+ }
451
+ var DEFAULT_INPUT_HANDLES = [{ id: "target", label: "" }];
452
+ function resolveInputHandles(data) {
453
+ const nodeData = data?.nodeData;
454
+ const spec = nodeData?.inputHandles ?? nodeData?.operations?.inputHandles;
455
+ if (!Array.isArray(spec) || spec.length === 0) return DEFAULT_INPUT_HANDLES;
456
+ return spec;
457
+ }
458
+ function InputHandles({
459
+ nodeId,
460
+ handles,
461
+ accent,
462
+ isConnectable
463
+ }) {
464
+ const updateNodeInternals = useUpdateNodeInternals();
465
+ const signature = useMemo(
466
+ () => handles.map((h) => h.id).join("|"),
467
+ [handles]
468
+ );
469
+ useEffect(() => {
470
+ updateNodeInternals(nodeId);
471
+ }, [nodeId, signature, updateNodeInternals]);
472
+ if (handles.length === 1) {
473
+ const handle = handles[0];
474
+ return /* @__PURE__ */ jsx(
475
+ CustomHandle,
476
+ {
477
+ type: "target",
478
+ id: handle.id,
479
+ position: Position.Left,
480
+ isConnectable,
481
+ accent: handle.tone ? TONE_COLORS[handle.tone] : accent
482
+ }
483
+ );
484
+ }
485
+ const total = handles.length;
486
+ return /* @__PURE__ */ jsx(Fragment, { children: handles.map((handle, index) => {
487
+ const top = `${(index + 1) / (total + 1) * 100}%`;
488
+ const color = handle.tone ? TONE_COLORS[handle.tone] : accent;
489
+ return /* @__PURE__ */ jsxs(
490
+ "div",
491
+ {
492
+ className: "absolute left-0",
493
+ style: { top, transform: "translateY(-50%)" },
494
+ children: [
495
+ /* @__PURE__ */ jsx(
496
+ CustomHandle,
497
+ {
498
+ type: "target",
499
+ id: handle.id,
500
+ position: Position.Left,
501
+ isConnectable,
502
+ accent: color
503
+ }
504
+ ),
505
+ handle.label && /* @__PURE__ */ jsx(
506
+ "span",
507
+ {
508
+ className: "pointer-events-none absolute whitespace-nowrap text-[10px] font-medium leading-none",
509
+ style: {
510
+ color,
511
+ right: "100%",
512
+ marginRight: 16,
513
+ top: "50%",
514
+ transform: "translateY(-50%)"
515
+ },
516
+ title: handle.label,
517
+ children: handle.label
518
+ }
519
+ )
520
+ ]
521
+ },
522
+ handle.id
523
+ );
524
+ }) });
525
+ }
526
+ function outputHandlesSpacerHeight(count2) {
527
+ return count2 > 2 ? (count2 - 2) * 26 : 0;
528
+ }
529
+ function LogicNodeCard({
530
+ title,
531
+ subtitle,
532
+ icon,
533
+ accent,
534
+ selected,
535
+ executionStatus,
536
+ itemCount,
537
+ isTrigger,
538
+ isExecuting,
539
+ nodeId,
540
+ inputHandles,
541
+ outputHandles,
542
+ isConnectable,
543
+ onDoubleClick,
544
+ onSettings,
545
+ onExecute,
546
+ onDelete,
547
+ infoButton
548
+ }) {
549
+ const minHeight = Math.max(
550
+ 64,
551
+ Math.max(outputHandles.length, inputHandles.length) * 30 + 24
552
+ );
553
+ return /* @__PURE__ */ jsxs(
554
+ NodeCard,
555
+ {
556
+ className: "w-48",
557
+ "data-selected": selected,
558
+ selected,
559
+ onDoubleClick,
560
+ executionStatus,
561
+ itemCount,
562
+ isTrigger,
563
+ accent,
564
+ isExecuting,
565
+ onSettings,
566
+ onExecute,
567
+ onDelete,
568
+ infoButton,
569
+ children: [
570
+ /* @__PURE__ */ jsxs(
571
+ "div",
572
+ {
573
+ className: "flex flex-col items-center justify-center gap-1 px-3 py-3 text-center",
574
+ style: { minHeight },
575
+ children: [
576
+ /* @__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 } }) }),
577
+ /* @__PURE__ */ jsx("span", { className: "max-w-full truncate text-sm font-semibold leading-tight", children: title }),
578
+ subtitle && /* @__PURE__ */ jsx(
579
+ "span",
580
+ {
581
+ className: "max-w-full truncate font-mono text-[10px] text-muted-foreground",
582
+ title: subtitle,
583
+ children: subtitle
584
+ }
585
+ )
586
+ ]
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsx(
590
+ InputHandles,
591
+ {
592
+ nodeId,
593
+ handles: inputHandles,
594
+ accent,
595
+ isConnectable
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsx(
599
+ OutputHandles,
600
+ {
601
+ nodeId,
602
+ handles: outputHandles,
603
+ accent,
604
+ isConnectable
605
+ }
606
+ )
607
+ ]
608
+ }
609
+ );
109
610
  }
110
611
 
111
612
  // src/modules/ui-blocks/registry.ts
@@ -173,27 +674,27 @@ function ExpressionEditor({
173
674
  "button",
174
675
  {
175
676
  type: "button",
176
- onClick: () => insertTemplate("{{input.}}"),
677
+ onClick: () => insertTemplate("${TriggerInput.}"),
177
678
  className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
178
- children: "{{input}}"
679
+ children: "${TriggerInput}"
179
680
  }
180
681
  ),
181
682
  /* @__PURE__ */ jsx(
182
683
  "button",
183
684
  {
184
685
  type: "button",
185
- onClick: () => insertTemplate("{{output.}}"),
686
+ onClick: () => insertTemplate("${output.}"),
186
687
  className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
187
- children: "{{output}}"
688
+ children: "${output}"
188
689
  }
189
690
  ),
190
691
  /* @__PURE__ */ jsx(
191
692
  "button",
192
693
  {
193
694
  type: "button",
194
- onClick: () => insertTemplate("{{$node['']}}"),
695
+ onClick: () => insertTemplate("${WorkflowState[''].output}"),
195
696
  className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
196
- children: "{{$node}}"
697
+ children: "${WorkflowState}"
197
698
  }
198
699
  )
199
700
  ] }),
@@ -7752,271 +8253,22 @@ function NodePropertyPanel({
7752
8253
  /* @__PURE__ */ jsx("div", { className: "font-medium text-card-foreground", children: "Connection handled automatically" }),
7753
8254
  /* @__PURE__ */ jsx("div", { children: "This node uses the workspace BigConsole app connection at run time. You only need to configure the DataSink target below." })
7754
8255
  ] })
7755
- ] }),
7756
- /* @__PURE__ */ jsxs("div", { className: "flex pt-5 flex-col", children: [
7757
- /* @__PURE__ */ jsx("div", { className: "text-xs text-card-foreground/60 font-semibold", children: "Properties" }),
7758
- resolvedUi ? /* @__PURE__ */ jsx(
7759
- BlocksRenderer,
7760
- {
7761
- spec: resolvedUi.spec,
7762
- synthesized: resolvedUi.synthesized,
7763
- state: sampleData2,
7764
- onApply: submittedData
7765
- },
7766
- `${id}:${data?.operation_name ?? ""}`
7767
- ) : /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground py-2", children: "No Properties" })
7768
- ] })
7769
- ] });
7770
- }
7771
- var BRAND_ACCENTS = [
7772
- [/gmail|google/, "#ea4335"],
7773
- [/slack/, "#611f69"],
7774
- [/github/, "#334155"],
7775
- [/gitlab/, "#fc6d26"],
7776
- [/stripe/, "#635bff"],
7777
- [/discord/, "#5865f2"],
7778
- [/notion/, "#111827"],
7779
- [/telegram/, "#2aabee"],
7780
- [/whatsapp/, "#25d366"],
7781
- [/openai/, "#10a37f"],
7782
- [/anthropic|claude/, "#d97757"],
7783
- [/twilio/, "#f22f46"],
7784
- [/sheet|excel/, "#21a366"],
7785
- [/http|api|webhook|rest/, "#6366f1"],
7786
- [/db|sql|postgres|mysql|mongo/, "#0ea5e9"]
7787
- ];
7788
- var HASH_PALETTE = [
7789
- "#6366f1",
7790
- "#0ea5e9",
7791
- "#10b981",
7792
- "#f59e0b",
7793
- "#ef4444",
7794
- "#a855f7",
7795
- "#ec4899",
7796
- "#14b8a6",
7797
- "#f97316",
7798
- "#8b5cf6"
7799
- ];
7800
- function nodeAccent(key) {
7801
- const k = (key || "").toLowerCase();
7802
- for (const [re, hex] of BRAND_ACCENTS) if (re.test(k)) return hex;
7803
- let h = 0;
7804
- for (let i = 0; i < k.length; i++) h = h * 31 + k.charCodeAt(i) >>> 0;
7805
- return HASH_PALETTE[h % HASH_PALETTE.length];
7806
- }
7807
- function tint(hex, pct = 14) {
7808
- return `color-mix(in srgb, ${hex} ${pct}%, transparent)`;
7809
- }
7810
- var NodeCard = React2.forwardRef(
7811
- ({
7812
- className,
7813
- selected = false,
7814
- executionStatus = "idle",
7815
- itemCount,
7816
- isTrigger = false,
7817
- accent = "#3b82f6",
7818
- onSettings,
7819
- onExecute,
7820
- onDelete,
7821
- isExecuting = false,
7822
- infoButton,
7823
- children,
7824
- ...props
7825
- }, ref) => {
7826
- const [hovered, setHovered] = React2.useState(false);
7827
- 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)]" : "";
7828
- return /* @__PURE__ */ jsxs(
7829
- "div",
7830
- {
7831
- className: "group/node relative",
7832
- onMouseEnter: () => setHovered(true),
7833
- onMouseLeave: () => setHovered(false),
7834
- children: [
7835
- 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: [
7836
- itemCount,
7837
- " item",
7838
- itemCount !== 1 ? "s" : ""
7839
- ] }) }),
7840
- (onSettings || onExecute || onDelete || infoButton) && /* @__PURE__ */ jsxs(
7841
- "div",
7842
- {
7843
- className: cn(
7844
- "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",
7845
- hovered ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
7846
- ),
7847
- style: {
7848
- // Inline offset (not a `-top-*` class) so it survives the host
7849
- // app's Tailwind scan of fe-sdk source, which drops some spacing
7850
- // utilities. The toolbar is ~30px tall; top:-15px centers it on
7851
- // the card's top edge (half outside, half over the card).
7852
- top: -15,
7853
- transform: hovered ? "translateY(0)" : "translateY(4px)"
7854
- },
7855
- onDoubleClick: (e) => e.stopPropagation(),
7856
- children: [
7857
- onSettings && /* @__PURE__ */ jsx(ToolbarButton, { title: "Configure", onClick: onSettings, children: /* @__PURE__ */ jsx(Icon, { icon: "lucide:sliders-horizontal", className: "size-4" }) }),
7858
- onExecute && /* @__PURE__ */ jsx(
7859
- ToolbarButton,
7860
- {
7861
- title: "Execute step",
7862
- onClick: onExecute,
7863
- disabled: isExecuting,
7864
- children: isExecuting ? /* @__PURE__ */ jsx(
7865
- Icon,
7866
- {
7867
- icon: "svg-spinners:ring-resize",
7868
- className: "size-4 text-[var(--color-status-success-text)]"
7869
- }
7870
- ) : /* @__PURE__ */ jsx(
7871
- Icon,
7872
- {
7873
- icon: "mdi:play",
7874
- className: "size-4 text-[var(--color-status-success-text)]"
7875
- }
7876
- )
7877
- }
7878
- ),
7879
- infoButton,
7880
- onDelete && /* @__PURE__ */ jsx(ToolbarButton, { title: "Delete", onClick: onDelete, children: /* @__PURE__ */ jsx(
7881
- Icon,
7882
- {
7883
- icon: "mynaui:trash",
7884
- className: "size-4 text-[var(--color-status-error-text)]"
7885
- }
7886
- ) })
7887
- ]
7888
- }
7889
- ),
7890
- /* @__PURE__ */ jsxs(
7891
- "div",
7892
- {
7893
- ref,
7894
- "data-selected": selected,
7895
- className: cn(
7896
- "relative w-72 rounded-xl border border-border bg-card shadow-sm will-change-transform",
7897
- statusRing,
7898
- executionStatus === "success" && "animate-success-glow",
7899
- className
7900
- ),
7901
- style: {
7902
- borderTop: `3px solid ${accent}`,
7903
- transition: "transform 0.15s ease, box-shadow 0.15s ease",
7904
- transform: hovered ? "translateY(-2px)" : "none",
7905
- boxShadow: hovered ? "0 2px 4px rgba(16,24,40,0.06), 0 16px 34px -12px rgba(16,24,40,0.24)" : void 0
7906
- },
7907
- ...props,
7908
- children: [
7909
- /* @__PURE__ */ jsx(
7910
- "div",
7911
- {
7912
- className: "pointer-events-none absolute -inset-0.5 transition-opacity duration-150",
7913
- style: {
7914
- borderRadius: 14,
7915
- opacity: selected || hovered ? 1 : 0,
7916
- boxShadow: selected ? `0 0 0 2px ${accent}, 0 0 0 5px ${tint(accent, 18)}` : `0 0 0 2px ${accent}`
7917
- }
7918
- }
7919
- ),
7920
- isTrigger && /* @__PURE__ */ jsx(
7921
- "div",
7922
- {
7923
- className: "absolute -top-2 -left-2 z-10 flex size-5 items-center justify-center rounded-full shadow-md ring-2 ring-card",
7924
- style: { backgroundColor: "var(--color-status-success-text)" },
7925
- children: /* @__PURE__ */ jsx(
7926
- Icon,
7927
- {
7928
- icon: "mdi:lightning-bolt",
7929
- className: "size-3",
7930
- style: { color: "var(--color-status-success-text-inverse)" }
7931
- }
7932
- )
7933
- }
7934
- ),
7935
- children
7936
- ]
7937
- }
7938
- ),
7939
- executionStatus === "success" && /* @__PURE__ */ jsx(StatusCorner, { icon: "mdi:check", tone: "success" }),
7940
- executionStatus === "error" && /* @__PURE__ */ jsx(StatusCorner, { icon: "mdi:close", tone: "error" }),
7941
- executionStatus === "running" && /* @__PURE__ */ jsx(StatusCorner, { icon: "svg-spinners:ring-resize", tone: "success" })
7942
- ]
7943
- }
7944
- );
7945
- }
7946
- );
7947
- NodeCard.displayName = "NodeCard";
7948
- function ToolbarButton({
7949
- title,
7950
- onClick,
7951
- disabled,
7952
- children
7953
- }) {
7954
- return /* @__PURE__ */ jsx(
7955
- "button",
7956
- {
7957
- type: "button",
7958
- title,
7959
- disabled,
7960
- onClick: (e) => {
7961
- e.stopPropagation();
7962
- onClick?.();
7963
- },
7964
- className: "flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50",
7965
- children
7966
- }
7967
- );
7968
- }
7969
- function StatusCorner({
7970
- icon,
7971
- tone
7972
- }) {
7973
- const bg = tone === "success" ? "var(--color-status-success-bg)" : "var(--color-status-error-bg)";
7974
- const fg = tone === "success" ? "var(--color-status-success-text-inverse)" : "var(--color-status-error-text-inverse)";
7975
- return /* @__PURE__ */ jsx("div", { className: "absolute -bottom-1.5 -right-1.5 z-10", children: /* @__PURE__ */ jsx(
7976
- "div",
7977
- {
7978
- className: "rounded-full p-0.5 shadow-md animate-in zoom-in duration-200",
7979
- style: { backgroundColor: bg },
7980
- children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-3", style: { color: fg } })
7981
- }
7982
- ) });
7983
- }
7984
- var NodeCardHeader = React2.forwardRef(
7985
- ({ icon, appName, accentKey }, ref) => {
7986
- const accent = nodeAccent(accentKey);
7987
- return /* @__PURE__ */ jsxs("div", { ref, className: "flex items-center gap-2.5 px-3 pb-3 pt-4", children: [
7988
- /* @__PURE__ */ jsx(
7989
- "div",
8256
+ ] }),
8257
+ /* @__PURE__ */ jsxs("div", { className: "flex pt-5 flex-col", children: [
8258
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-card-foreground/60 font-semibold", children: "Properties" }),
8259
+ resolvedUi ? /* @__PURE__ */ jsx(
8260
+ BlocksRenderer,
7990
8261
  {
7991
- className: "flex size-8 shrink-0 items-center justify-center rounded-lg",
7992
- style: {
7993
- backgroundColor: tint(accent, 13),
7994
- color: accent,
7995
- border: `1px solid ${tint(accent, 26)}`
7996
- },
7997
- children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-[18px]" })
7998
- }
7999
- ),
8000
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: appName })
8001
- ] });
8002
- }
8003
- );
8004
- NodeCardHeader.displayName = "NodeCardHeader";
8005
- var NodeCardBody = React2.forwardRef(
8006
- ({ operationName, description }, ref) => /* @__PURE__ */ jsxs(
8007
- "div",
8008
- {
8009
- ref,
8010
- className: "rounded-b-xl border-t border-border/60 px-3 pb-3 pt-2.5",
8011
- children: [
8012
- /* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Operation" }),
8013
- /* @__PURE__ */ jsx("div", { className: "mt-0.5 truncate font-mono text-[13px] font-bold text-card-foreground", children: operationName }),
8014
- description ? /* @__PURE__ */ jsx("p", { className: "mt-1 line-clamp-2 text-xs leading-snug text-muted-foreground", children: description }) : null
8015
- ]
8016
- }
8017
- )
8018
- );
8019
- NodeCardBody.displayName = "NodeCardBody";
8262
+ spec: resolvedUi.spec,
8263
+ synthesized: resolvedUi.synthesized,
8264
+ state: sampleData2,
8265
+ onApply: submittedData
8266
+ },
8267
+ `${id}:${data?.operation_name ?? ""}`
8268
+ ) : /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground py-2", children: "No Properties" })
8269
+ ] })
8270
+ ] });
8271
+ }
8020
8272
  var nodeDetails = {
8021
8273
  node_type: "gmail_reader",
8022
8274
  version: "1.0.0",
@@ -8873,7 +9125,12 @@ var getNodeMetadata = (value) => {
8873
9125
  const nodeStatus = nodeStatuses[id] || "idle";
8874
9126
  const executionStatus = nodeStatus === "success" ? "success" : nodeStatus === "error" ? "error" : nodeStatus === "running" ? "running" : "idle";
8875
9127
  const itemCount = nodeItemCounts[id];
8876
- const isTrigger = (data?.operation_type || "").toLowerCase() === "trigger" || (value?.type || "").toLowerCase().includes("trigger");
9128
+ const nodeKinds = data?.nodeData?.kind;
9129
+ const pinnedOpType = (data?.operation_type || "").toLowerCase();
9130
+ const isTrigger = pinnedOpType === "trigger" || (value?.type || "").toLowerCase().includes("trigger") || // Kind-based detection only when NO operation is pinned: dual-role
9131
+ // nodes (e.g. Gmail: trigger kind + action operations) must follow
9132
+ // their pinned operation's role, not the manifest kind.
9133
+ Array.isArray(nodeKinds) && nodeKinds.includes("trigger") && pinnedOpType === "";
8877
9134
  const appName = (meta.title || nodeType || "Node").split(/\s+/)[0].toUpperCase();
8878
9135
  const operationDisplay = nodeType === "integration/channel" ? (() => {
8879
9136
  const explicitRole = data.args?.state?.role;
@@ -8885,6 +9142,65 @@ var getNodeMetadata = (value) => {
8885
9142
  (value?.type || "").toLowerCase()
8886
9143
  );
8887
9144
  const roleAccent = isTrigger ? "#10b981" : isLogicNode ? "#f59e0b" : "#3b82f6";
9145
+ const outputHandles = resolveOutputHandles(
9146
+ data
9147
+ );
9148
+ const inputHandles = resolveInputHandles(
9149
+ data
9150
+ );
9151
+ const handleSpacer = outputHandlesSpacerHeight(
9152
+ Math.max(outputHandles.length, inputHandles.length)
9153
+ );
9154
+ const manifestKind = data?.nodeData?.kind;
9155
+ const isControlFlowNode = (value?.type || "").startsWith("internal:") && (Array.isArray(manifestKind) ? manifestKind.includes("control") : isLogicNode);
9156
+ if (isControlFlowNode) {
9157
+ const argsState = data?.args?.state;
9158
+ const rules = argsState?.rules;
9159
+ const mode = argsState?.mode;
9160
+ const logicSubtitle = Array.isArray(rules) ? `${rules.length} rule${rules.length === 1 ? "" : "s"}` : typeof mode === "string" ? mode : void 0;
9161
+ return /* @__PURE__ */ jsxs("div", { children: [
9162
+ /* @__PURE__ */ jsx(
9163
+ LogicNodeCard,
9164
+ {
9165
+ nodeId: id,
9166
+ title: data?.label || meta.title,
9167
+ subtitle: logicSubtitle,
9168
+ icon: meta.icon,
9169
+ accent: roleAccent,
9170
+ selected: !!selected,
9171
+ executionStatus,
9172
+ itemCount,
9173
+ isTrigger,
9174
+ isExecuting: isThisNodeExecuting,
9175
+ inputHandles,
9176
+ outputHandles,
9177
+ isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0,
9178
+ onDoubleClick: handleOpenNdv,
9179
+ onSettings: handleShowNodeProperties,
9180
+ onExecute: handleExecuteStep,
9181
+ onDelete: handleDeleteNode,
9182
+ infoButton: /* @__PURE__ */ jsx(
9183
+ NodeInfoButtonWithModal,
9184
+ {
9185
+ metadata: {
9186
+ icon: meta.icon,
9187
+ title: meta.title,
9188
+ description: nodeDescription,
9189
+ gradientColor: meta.gradientColor
9190
+ }
9191
+ }
9192
+ )
9193
+ }
9194
+ ),
9195
+ /* @__PURE__ */ jsx(
9196
+ AdapterConnectors,
9197
+ {
9198
+ nodeId: id,
9199
+ adapters: data?.nodeData?.nodeAdapters
9200
+ }
9201
+ )
9202
+ ] });
9203
+ }
8888
9204
  return /* @__PURE__ */ jsxs("div", { children: [
8889
9205
  /* @__PURE__ */ jsxs(
8890
9206
  NodeCard,
@@ -8920,31 +9236,35 @@ var getNodeMetadata = (value) => {
8920
9236
  accentKey: value?.type || meta.title
8921
9237
  }
8922
9238
  ),
8923
- /* @__PURE__ */ jsx(
9239
+ operationDisplay === "execute" && (value?.type || "").startsWith("internal:") || isTrigger ? (
9240
+ // Internal single-op nodes ('execute') and triggers: the operation
9241
+ // name is an implementation detail, not a user choice — show only
9242
+ // the description (n8n-style).
9243
+ /* @__PURE__ */ jsx("div", { className: "rounded-b-xl border-t border-border/60 px-3 pb-3 pt-2.5", children: /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-snug text-muted-foreground", children: nodeDescription }) })
9244
+ ) : /* @__PURE__ */ jsx(
8924
9245
  NodeCardBody,
8925
9246
  {
8926
9247
  operationName: operationDisplay,
8927
9248
  description: nodeDescription
8928
9249
  }
8929
9250
  ),
8930
- /* @__PURE__ */ jsx(
8931
- CustomHandle,
9251
+ handleSpacer > 0 && /* @__PURE__ */ jsx("div", { style: { height: handleSpacer } }),
9252
+ !isTrigger && /* @__PURE__ */ jsx(
9253
+ InputHandles,
8932
9254
  {
8933
- type: "target",
8934
- id: "target",
8935
- position: Position.Left,
8936
- isConnectable,
8937
- accent: roleAccent
9255
+ nodeId: id,
9256
+ handles: inputHandles,
9257
+ accent: roleAccent,
9258
+ isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0
8938
9259
  }
8939
9260
  ),
8940
9261
  /* @__PURE__ */ jsx(
8941
- CustomHandle,
9262
+ OutputHandles,
8942
9263
  {
8943
- type: "source",
8944
- id: "source",
8945
- position: Position.Right,
8946
- isConnectable,
8947
- accent: roleAccent
9264
+ nodeId: id,
9265
+ handles: outputHandles,
9266
+ accent: roleAccent,
9267
+ isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0
8948
9268
  }
8949
9269
  )
8950
9270
  ]
@@ -12490,7 +12810,7 @@ function TemplateCard({
12490
12810
  onClick,
12491
12811
  onUseTemplate
12492
12812
  }) {
12493
- const [imageError, setImageError] = React2__default.useState(false);
12813
+ const [imageError, setImageError] = React__default.useState(false);
12494
12814
  const previewImage = template.screenshots?.[0] || template.icon || void 0;
12495
12815
  const hasPreview = Boolean(previewImage) && !imageError;
12496
12816
  const handleUseTemplate = (e) => {
@@ -13350,7 +13670,7 @@ function ThemeProvider2({
13350
13670
  defaultTheme = "system",
13351
13671
  storageKey = "vite-ui-theme"
13352
13672
  }) {
13353
- React2.useEffect(() => {
13673
+ React.useEffect(() => {
13354
13674
  if (typeof window !== "undefined") {
13355
13675
  try {
13356
13676
  localStorage.removeItem(storageKey);
@@ -14270,8 +14590,8 @@ function toast5({ ...props }) {
14270
14590
  };
14271
14591
  }
14272
14592
  function useToast() {
14273
- const [state, setState] = React2.useState(memoryState);
14274
- React2.useEffect(() => {
14593
+ const [state, setState] = React.useState(memoryState);
14594
+ React.useEffect(() => {
14275
14595
  listeners.push(setState);
14276
14596
  return () => {
14277
14597
  const index = listeners.indexOf(setState);
@@ -19290,6 +19610,9 @@ function buildOperationSeed(type, operationName, existingData) {
19290
19610
  if (!op) return null;
19291
19611
  const manifest = config2.defaultData?.nodeData;
19292
19612
  const prevArgs = existingData.args ?? {};
19613
+ const manifestOps = manifest?.operations;
19614
+ const outputHandles = manifest?.outputHandles ?? manifestOps?.outputHandles;
19615
+ const inputHandles = manifest?.inputHandles ?? manifestOps?.inputHandles;
19293
19616
  return {
19294
19617
  ...existingData,
19295
19618
  operation_name: op.name,
@@ -19300,7 +19623,12 @@ function buildOperationSeed(type, operationName, existingData) {
19300
19623
  state: { ...op.adaptive_card?.defaultValue ?? {} }
19301
19624
  },
19302
19625
  // Slim manifest: keep node-level metadata but only the pinned operation.
19303
- nodeData: { ...manifest ?? {}, operations: [op] }
19626
+ nodeData: {
19627
+ ...manifest ?? {},
19628
+ ...outputHandles !== void 0 ? { outputHandles } : {},
19629
+ ...inputHandles !== void 0 ? { inputHandles } : {},
19630
+ operations: [op]
19631
+ }
19304
19632
  };
19305
19633
  }
19306
19634
  function useInsertNode() {
@@ -19320,10 +19648,18 @@ function useInsertNode() {
19320
19648
  position: _pos,
19321
19649
  selected: true
19322
19650
  });
19323
- if (operationName) {
19651
+ let pinTo = operationName;
19652
+ if (!pinTo) {
19653
+ const config2 = NODE_CONFIGS.find((c) => c.type === type);
19654
+ const ops = config2?.operations ?? [];
19655
+ if (ops.length === 1 && ops[0]?.name) {
19656
+ pinTo = ops[0].name;
19657
+ }
19658
+ }
19659
+ if (pinTo) {
19324
19660
  const seeded = buildOperationSeed(
19325
19661
  type,
19326
- operationName,
19662
+ pinTo,
19327
19663
  newNode.data ?? {}
19328
19664
  );
19329
19665
  if (seeded) newNode.data = seeded;
@@ -20907,6 +21243,449 @@ function FieldTree({
20907
21243
  field.path
20908
21244
  )) });
20909
21245
  }
21246
+ function emptyGroup() {
21247
+ return { type: "group", combinator: "AND", children: [] };
21248
+ }
21249
+ function emptyRule(field = "") {
21250
+ return { type: "rule", field, operator: "==", value: "" };
21251
+ }
21252
+ var OPERATORS2 = {
21253
+ string: [
21254
+ { value: "==", label: "equals", needsValue: true },
21255
+ { value: "!=", label: "not equals", needsValue: true },
21256
+ { value: "contains", label: "contains", needsValue: true },
21257
+ { value: "notContains", label: "does not contain", needsValue: true },
21258
+ { value: "startsWith", label: "starts with", needsValue: true },
21259
+ { value: "endsWith", label: "ends with", needsValue: true },
21260
+ { value: "regex", label: "matches regex", needsValue: true },
21261
+ { value: "isEmpty", label: "is empty", needsValue: false },
21262
+ { value: "isNotEmpty", label: "is not empty", needsValue: false },
21263
+ { value: "isNull", label: "is null", needsValue: false },
21264
+ { value: "isNotNull", label: "is not null", needsValue: false }
21265
+ ],
21266
+ number: [
21267
+ { value: "==", label: "equals", needsValue: true },
21268
+ { value: "!=", label: "not equals", needsValue: true },
21269
+ { value: ">", label: "greater than", needsValue: true },
21270
+ { value: ">=", label: "greater or equal", needsValue: true },
21271
+ { value: "<", label: "less than", needsValue: true },
21272
+ { value: "<=", label: "less or equal", needsValue: true },
21273
+ { value: "isNull", label: "is null", needsValue: false },
21274
+ { value: "isNotNull", label: "is not null", needsValue: false }
21275
+ ],
21276
+ boolean: [
21277
+ { value: "isTrue", label: "is true", needsValue: false },
21278
+ { value: "isFalse", label: "is false", needsValue: false },
21279
+ { value: "isNull", label: "is null", needsValue: false },
21280
+ { value: "isNotNull", label: "is not null", needsValue: false }
21281
+ ],
21282
+ array: [
21283
+ { value: "contains", label: "contains", needsValue: true },
21284
+ { value: "notContains", label: "does not contain", needsValue: true },
21285
+ { value: "isEmpty", label: "is empty", needsValue: false },
21286
+ { value: "isNotEmpty", label: "is not empty", needsValue: false },
21287
+ { value: "isNull", label: "is null", needsValue: false },
21288
+ { value: "isNotNull", label: "is not null", needsValue: false }
21289
+ ],
21290
+ object: [
21291
+ { value: "hasProperty", label: "has property", needsValue: true },
21292
+ { value: "isEmpty", label: "is empty", needsValue: false },
21293
+ { value: "isNotEmpty", label: "is not empty", needsValue: false },
21294
+ { value: "isNull", label: "is null", needsValue: false },
21295
+ { value: "isNotNull", label: "is not null", needsValue: false }
21296
+ ],
21297
+ any: [
21298
+ { value: "==", label: "equals", needsValue: true },
21299
+ { value: "!=", label: "not equals", needsValue: true },
21300
+ { value: ">", label: "greater than", needsValue: true },
21301
+ { value: "<", label: "less than", needsValue: true },
21302
+ { value: "contains", label: "contains", needsValue: true },
21303
+ { value: "isEmpty", label: "is empty", needsValue: false },
21304
+ { value: "isNotEmpty", label: "is not empty", needsValue: false },
21305
+ { value: "isNull", label: "is null", needsValue: false },
21306
+ { value: "isNotNull", label: "is not null", needsValue: false }
21307
+ ]
21308
+ };
21309
+ function operatorsFor(type) {
21310
+ return OPERATORS2[type ?? "any"] ?? OPERATORS2.any;
21311
+ }
21312
+ function flattenFields(nodes) {
21313
+ const out = [];
21314
+ const walk = (list) => {
21315
+ for (const node of list) {
21316
+ if (node.path) out.push({ path: node.path, type: node.type ?? "any" });
21317
+ if (Array.isArray(node.children)) walk(node.children);
21318
+ }
21319
+ };
21320
+ walk(nodes);
21321
+ return out.sort((a, b) => a.path.localeCompare(b.path));
21322
+ }
21323
+ var rowInput = "rounded-md border border-input bg-background px-2 py-1 text-xs outline-none focus:border-primary";
21324
+ function RuleRow({
21325
+ rule,
21326
+ fields,
21327
+ onChange,
21328
+ onRemove
21329
+ }) {
21330
+ const fieldType = fields.find((f) => f.path === rule.field)?.type ?? "any";
21331
+ const ops = operatorsFor(fieldType);
21332
+ const currentOp = ops.find((o) => o.value === rule.operator) ?? ops[0];
21333
+ const knownField = fields.some((f) => f.path === rule.field);
21334
+ return /* @__PURE__ */ jsxs(
21335
+ "div",
21336
+ {
21337
+ className: "flex flex-wrap items-center gap-1.5",
21338
+ "data-testid": "condition-rule",
21339
+ children: [
21340
+ fields.length > 0 && (knownField || rule.field === "") ? /* @__PURE__ */ jsxs(
21341
+ "select",
21342
+ {
21343
+ value: rule.field,
21344
+ onChange: (e) => {
21345
+ const nextField = e.target.value;
21346
+ const nextType = fields.find((f) => f.path === nextField)?.type ?? "any";
21347
+ const nextOps = operatorsFor(nextType);
21348
+ const operator = nextOps.some((o) => o.value === rule.operator) ? rule.operator : nextOps[0].value;
21349
+ onChange({ ...rule, field: nextField, operator });
21350
+ },
21351
+ className: cn(rowInput, "max-w-[180px] flex-1"),
21352
+ "data-testid": "condition-field",
21353
+ children: [
21354
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select field\u2026" }),
21355
+ fields.map((f) => /* @__PURE__ */ jsxs("option", { value: f.path, children: [
21356
+ f.path,
21357
+ " (",
21358
+ f.type,
21359
+ ")"
21360
+ ] }, f.path)),
21361
+ /* @__PURE__ */ jsx("option", { value: "__custom__", children: "Custom path\u2026" })
21362
+ ]
21363
+ }
21364
+ ) : /* @__PURE__ */ jsx(
21365
+ "input",
21366
+ {
21367
+ type: "text",
21368
+ value: rule.field === "__custom__" ? "" : rule.field,
21369
+ placeholder: "field.path",
21370
+ onChange: (e) => onChange({ ...rule, field: e.target.value }),
21371
+ className: cn(rowInput, "max-w-[180px] flex-1 font-mono"),
21372
+ "data-testid": "condition-field"
21373
+ }
21374
+ ),
21375
+ /* @__PURE__ */ jsx(
21376
+ "select",
21377
+ {
21378
+ value: currentOp.value,
21379
+ onChange: (e) => onChange({ ...rule, operator: e.target.value }),
21380
+ className: rowInput,
21381
+ "data-testid": "condition-operator",
21382
+ children: ops.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
21383
+ }
21384
+ ),
21385
+ currentOp.needsValue && /* @__PURE__ */ jsx(
21386
+ "input",
21387
+ {
21388
+ type: fieldType === "number" ? "number" : "text",
21389
+ value: rule.value === null || rule.value === void 0 ? "" : String(rule.value),
21390
+ placeholder: "value",
21391
+ onChange: (e) => {
21392
+ const raw = e.target.value;
21393
+ const value = fieldType === "number" && raw.trim() !== "" && !Number.isNaN(Number(raw)) ? Number(raw) : raw;
21394
+ onChange({ ...rule, value });
21395
+ },
21396
+ className: cn(rowInput, "min-w-[90px] flex-1"),
21397
+ "data-testid": "condition-value"
21398
+ }
21399
+ ),
21400
+ /* @__PURE__ */ jsx(
21401
+ "button",
21402
+ {
21403
+ type: "button",
21404
+ onClick: onRemove,
21405
+ title: "Remove condition",
21406
+ className: "rounded p-1 text-muted-foreground hover:text-[var(--color-status-error-text)]",
21407
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mynaui:trash", className: "size-3.5" })
21408
+ }
21409
+ )
21410
+ ]
21411
+ }
21412
+ );
21413
+ }
21414
+ function GroupEditor({
21415
+ group,
21416
+ fields,
21417
+ depth,
21418
+ onChange,
21419
+ onRemove
21420
+ }) {
21421
+ const setChild = (index, child) => {
21422
+ const children = group.children.slice();
21423
+ children[index] = child;
21424
+ onChange({ ...group, children });
21425
+ };
21426
+ const removeChild = (index) => {
21427
+ onChange({
21428
+ ...group,
21429
+ children: group.children.filter((_, i) => i !== index)
21430
+ });
21431
+ };
21432
+ return /* @__PURE__ */ jsxs(
21433
+ "div",
21434
+ {
21435
+ className: cn(
21436
+ "flex flex-col gap-1.5 rounded-md",
21437
+ depth > 0 && "border border-border/70 bg-muted/20 p-2"
21438
+ ),
21439
+ "data-testid": `condition-group-depth-${depth}`,
21440
+ children: [
21441
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
21442
+ /* @__PURE__ */ jsx("div", { className: "flex overflow-hidden rounded-md border border-input text-[10px] font-semibold", children: ["AND", "OR"].map((c) => /* @__PURE__ */ jsx(
21443
+ "button",
21444
+ {
21445
+ type: "button",
21446
+ onClick: () => onChange({ ...group, combinator: c }),
21447
+ className: cn(
21448
+ "px-2 py-0.5",
21449
+ group.combinator === c ? "bg-primary text-primary-foreground" : "bg-background text-muted-foreground hover:text-foreground"
21450
+ ),
21451
+ "data-testid": `combinator-${c}`,
21452
+ children: c
21453
+ },
21454
+ c
21455
+ )) }),
21456
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: group.combinator === "AND" ? "all conditions must match" : "any condition may match" }),
21457
+ onRemove && /* @__PURE__ */ jsx(
21458
+ "button",
21459
+ {
21460
+ type: "button",
21461
+ onClick: onRemove,
21462
+ title: "Remove group",
21463
+ className: "ml-auto rounded p-1 text-muted-foreground hover:text-[var(--color-status-error-text)]",
21464
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mynaui:trash", className: "size-3.5" })
21465
+ }
21466
+ )
21467
+ ] }),
21468
+ group.children.map(
21469
+ (child, index) => child.type === "rule" ? /* @__PURE__ */ jsx(
21470
+ RuleRow,
21471
+ {
21472
+ rule: child,
21473
+ fields,
21474
+ onChange: (next) => setChild(index, next),
21475
+ onRemove: () => removeChild(index)
21476
+ },
21477
+ index
21478
+ ) : /* @__PURE__ */ jsx(
21479
+ GroupEditor,
21480
+ {
21481
+ group: child,
21482
+ fields,
21483
+ depth: depth + 1,
21484
+ onChange: (next) => setChild(index, next),
21485
+ onRemove: () => removeChild(index)
21486
+ },
21487
+ index
21488
+ )
21489
+ ),
21490
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
21491
+ /* @__PURE__ */ jsxs(
21492
+ "button",
21493
+ {
21494
+ type: "button",
21495
+ onClick: () => onChange({
21496
+ ...group,
21497
+ children: [...group.children, emptyRule(fields[0]?.path ?? "")]
21498
+ }),
21499
+ className: "flex items-center gap-1 text-[11px] font-medium text-primary hover:underline",
21500
+ "data-testid": "add-condition",
21501
+ children: [
21502
+ /* @__PURE__ */ jsx(Icon, { icon: "basil:add-solid", className: "size-3.5" }),
21503
+ " Add condition"
21504
+ ]
21505
+ }
21506
+ ),
21507
+ depth < 2 && /* @__PURE__ */ jsxs(
21508
+ "button",
21509
+ {
21510
+ type: "button",
21511
+ onClick: () => onChange({
21512
+ ...group,
21513
+ children: [...group.children, emptyGroup()]
21514
+ }),
21515
+ className: "flex items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground",
21516
+ "data-testid": "add-group",
21517
+ children: [
21518
+ /* @__PURE__ */ jsx(Icon, { icon: "basil:add-solid", className: "size-3.5" }),
21519
+ " Add group"
21520
+ ]
21521
+ }
21522
+ )
21523
+ ] })
21524
+ ]
21525
+ }
21526
+ );
21527
+ }
21528
+ function ConditionTreeBuilder({
21529
+ value,
21530
+ fields,
21531
+ onChange
21532
+ }) {
21533
+ const tree = value && typeof value === "object" && value.type === "group" ? value : emptyGroup();
21534
+ return /* @__PURE__ */ jsx("div", { "data-testid": "condition-tree-builder", children: /* @__PURE__ */ jsx(
21535
+ GroupEditor,
21536
+ {
21537
+ group: tree,
21538
+ fields,
21539
+ depth: 0,
21540
+ onChange: (next) => onChange(next.children.length === 0 ? void 0 : next)
21541
+ }
21542
+ ) });
21543
+ }
21544
+ function newRuleHandle(existing) {
21545
+ let n = existing.length;
21546
+ const taken = new Set(existing.map((r) => r.handle));
21547
+ let handle = `rule-${n}`;
21548
+ while (taken.has(handle)) handle = `rule-${++n}`;
21549
+ return handle;
21550
+ }
21551
+ function casesFromRules(rules) {
21552
+ const cases = {};
21553
+ rules.forEach((rule, index) => {
21554
+ cases[rule.name || `Rule ${index + 1}`] = rule.handle;
21555
+ });
21556
+ return cases;
21557
+ }
21558
+ function SwitchRulesEditor({
21559
+ rules,
21560
+ fallbackHandle,
21561
+ fields,
21562
+ onChange
21563
+ }) {
21564
+ const setRule = (index, rule) => {
21565
+ const next = rules.slice();
21566
+ next[index] = rule;
21567
+ onChange({ rules: next, fallbackHandle });
21568
+ };
21569
+ const move = (index, dir) => {
21570
+ const target = index + dir;
21571
+ if (target < 0 || target >= rules.length) return;
21572
+ const next = rules.slice();
21573
+ const tmp = next[index];
21574
+ next[index] = next[target];
21575
+ next[target] = tmp;
21576
+ onChange({ rules: next, fallbackHandle });
21577
+ };
21578
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-testid": "switch-rules-editor", children: [
21579
+ rules.map((rule, index) => /* @__PURE__ */ jsxs(
21580
+ "div",
21581
+ {
21582
+ className: "rounded-md border border-border/70 bg-muted/10 p-2",
21583
+ "data-testid": `switch-rule-${index}`,
21584
+ children: [
21585
+ /* @__PURE__ */ jsxs("div", { className: "mb-1.5 flex items-center gap-1.5", children: [
21586
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-semibold text-muted-foreground", children: [
21587
+ index + 1,
21588
+ "."
21589
+ ] }),
21590
+ /* @__PURE__ */ jsx(
21591
+ "input",
21592
+ {
21593
+ type: "text",
21594
+ value: rule.name,
21595
+ placeholder: `Rule ${index + 1}`,
21596
+ onChange: (e) => setRule(index, { ...rule, name: e.target.value }),
21597
+ className: cn(rowInput, "flex-1 font-medium"),
21598
+ "data-testid": "switch-rule-name"
21599
+ }
21600
+ ),
21601
+ /* @__PURE__ */ jsx(
21602
+ "button",
21603
+ {
21604
+ type: "button",
21605
+ onClick: () => move(index, -1),
21606
+ disabled: index === 0,
21607
+ title: "Move up",
21608
+ className: "rounded p-0.5 text-muted-foreground hover:text-foreground disabled:opacity-30",
21609
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:chevron-up", className: "size-4" })
21610
+ }
21611
+ ),
21612
+ /* @__PURE__ */ jsx(
21613
+ "button",
21614
+ {
21615
+ type: "button",
21616
+ onClick: () => move(index, 1),
21617
+ disabled: index === rules.length - 1,
21618
+ title: "Move down",
21619
+ className: "rounded p-0.5 text-muted-foreground hover:text-foreground disabled:opacity-30",
21620
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:chevron-down", className: "size-4" })
21621
+ }
21622
+ ),
21623
+ /* @__PURE__ */ jsx(
21624
+ "button",
21625
+ {
21626
+ type: "button",
21627
+ onClick: () => onChange({
21628
+ rules: rules.filter((_, i) => i !== index),
21629
+ fallbackHandle
21630
+ }),
21631
+ title: "Remove rule",
21632
+ className: "rounded p-0.5 text-muted-foreground hover:text-[var(--color-status-error-text)]",
21633
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mynaui:trash", className: "size-3.5" })
21634
+ }
21635
+ )
21636
+ ] }),
21637
+ /* @__PURE__ */ jsx(
21638
+ ConditionTreeBuilder,
21639
+ {
21640
+ value: rule.conditionTree,
21641
+ fields,
21642
+ onChange: (tree) => setRule(index, { ...rule, conditionTree: tree ?? emptyGroup() })
21643
+ }
21644
+ )
21645
+ ]
21646
+ },
21647
+ rule.handle
21648
+ )),
21649
+ /* @__PURE__ */ jsxs(
21650
+ "button",
21651
+ {
21652
+ type: "button",
21653
+ onClick: () => onChange({
21654
+ rules: [
21655
+ ...rules,
21656
+ {
21657
+ name: `Rule ${rules.length + 1}`,
21658
+ handle: newRuleHandle(rules),
21659
+ conditionTree: emptyGroup()
21660
+ }
21661
+ ],
21662
+ fallbackHandle
21663
+ }),
21664
+ className: "flex w-fit items-center gap-1 text-[11px] font-medium text-primary hover:underline",
21665
+ "data-testid": "add-switch-rule",
21666
+ children: [
21667
+ /* @__PURE__ */ jsx(Icon, { icon: "basil:add-solid", className: "size-3.5" }),
21668
+ " Add rule"
21669
+ ]
21670
+ }
21671
+ ),
21672
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
21673
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Fallback output (no rule matches):" }),
21674
+ /* @__PURE__ */ jsx(
21675
+ "input",
21676
+ {
21677
+ type: "text",
21678
+ value: fallbackHandle,
21679
+ placeholder: "default",
21680
+ onChange: (e) => onChange({ rules, fallbackHandle: e.target.value }),
21681
+ className: cn(rowInput, "w-28"),
21682
+ "data-testid": "switch-fallback"
21683
+ }
21684
+ )
21685
+ ] }),
21686
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] leading-snug text-muted-foreground", children: "Rules are checked top-down; the first matching rule routes the item to its output. Each rule renders as an output handle on the node." })
21687
+ ] });
21688
+ }
20910
21689
  function coerceNumber(type, raw) {
20911
21690
  if (raw.trim() === "") return void 0;
20912
21691
  const n = type === "integer" ? parseInt(raw, 10) : parseFloat(raw);
@@ -21036,6 +21815,7 @@ function NodeDetailView() {
21036
21815
  [currentOp]
21037
21816
  );
21038
21817
  const params = useMemo(() => paramsFromOperation(currentOp), [currentOp]);
21818
+ const pickerFields = useMemo(() => flattenFields(inputFields), [inputFields]);
21039
21819
  const mapping = currentEdge?.mapping?.definition ?? {};
21040
21820
  const argsState = useMemo(() => {
21041
21821
  const data = asRecord(node?.data);
@@ -21284,6 +22064,40 @@ function NodeDetailView() {
21284
22064
  ] }),
21285
22065
  !currentEdge && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: "No upstream node connected \u2014 set fixed values below, or connect a node to map fields with expressions." }),
21286
22066
  params.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-xs text-muted-foreground", children: "This operation has no input parameters." }) : params.map((p) => {
22067
+ if (p.type === "condition") {
22068
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
22069
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name === "conditionTree" ? "Conditions" : p.name }),
22070
+ /* @__PURE__ */ jsx(
22071
+ ConditionTreeBuilder,
22072
+ {
22073
+ value: argsState[p.name],
22074
+ fields: pickerFields,
22075
+ onChange: (tree) => writeFixed(p.name, tree)
22076
+ }
22077
+ ),
22078
+ p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
22079
+ ] }, p.name);
22080
+ }
22081
+ if (p.type === "switch-rules") {
22082
+ const rules = Array.isArray(argsState[p.name]) ? argsState[p.name] : [];
22083
+ const fallbackHandle = typeof argsState.fallbackHandle === "string" ? argsState.fallbackHandle : "default";
22084
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
22085
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: "Routing rules" }),
22086
+ /* @__PURE__ */ jsx(
22087
+ SwitchRulesEditor,
22088
+ {
22089
+ rules,
22090
+ fallbackHandle,
22091
+ fields: pickerFields,
22092
+ onChange: (next) => {
22093
+ writeFixed(p.name, next.rules);
22094
+ writeFixed("cases", casesFromRules(next.rules));
22095
+ writeFixed("fallbackHandle", next.fallbackHandle);
22096
+ }
22097
+ }
22098
+ )
22099
+ ] }, p.name);
22100
+ }
21287
22101
  const mode = modeByField[p.name] ?? (mapping[p.name] !== void 0 ? "expression" : "fixed");
21288
22102
  const exprValue = mapping[p.name] ?? "";
21289
22103
  const fixedValue = argsState[p.name];
@@ -22221,5 +23035,5 @@ function WorkflowBuilder() {
22221
23035
  }
22222
23036
 
22223
23037
  export { Card2 as Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ConfigSchemaForm, ConnectionFormDialog, ConnectionSelector, FluidGridsProvider, ProviderPicker, TemplateCard, WorkflowBuilder, getProviderForm, listCustomBlocks, refetchStoreNodes, registerCustomBlock, resolveUiSpec, stripSubmitActions, useConnection, useConnectionDialog, useConnections, useConnectionsByProvider, useCreateConnection, useFluidGridsContext, useImportWorkflowTemplate, useInitiateOAuth, useNodeExecution, useProviderByKey, useProviderCatalog, useProviders, useWorkflowTemplates };
22224
- //# sourceMappingURL=chunk-5GIC24CM.js.map
22225
- //# sourceMappingURL=chunk-5GIC24CM.js.map
23038
+ //# sourceMappingURL=chunk-DPD5ILMD.js.map
23039
+ //# sourceMappingURL=chunk-DPD5ILMD.js.map