@tangle-network/sandbox-ui 0.92.0 → 0.93.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.
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
fmtDuration,
|
|
11
11
|
fmtTokens,
|
|
12
12
|
providerLabel,
|
|
13
|
-
shortModel
|
|
14
|
-
|
|
13
|
+
shortModel,
|
|
14
|
+
triggerNodeIndex
|
|
15
|
+
} from "./chunk-YOSNN3QU.js";
|
|
15
16
|
import {
|
|
16
17
|
ModelBrandStack,
|
|
17
18
|
modelBrandFor
|
|
@@ -22,8 +23,11 @@ import "./chunk-CZ7CSFO4.js";
|
|
|
22
23
|
// src/workflows/WorkflowGraph.tsx
|
|
23
24
|
import {
|
|
24
25
|
Background,
|
|
26
|
+
BaseEdge,
|
|
25
27
|
Controls,
|
|
28
|
+
EdgeLabelRenderer,
|
|
26
29
|
getNodesBounds,
|
|
30
|
+
getSmoothStepPath,
|
|
27
31
|
getViewportForBounds,
|
|
28
32
|
Handle,
|
|
29
33
|
MarkerType,
|
|
@@ -47,6 +51,7 @@ import {
|
|
|
47
51
|
|
|
48
52
|
// src/workflows/flow-graph.ts
|
|
49
53
|
import { Position } from "@xyflow/react";
|
|
54
|
+
var WF_EDGE_TYPE = "wfEdge";
|
|
50
55
|
function isNodeStateMap(arg) {
|
|
51
56
|
const values = Object.values(arg);
|
|
52
57
|
return values.length > 0 && values.every(
|
|
@@ -55,7 +60,7 @@ function isNodeStateMap(arg) {
|
|
|
55
60
|
}
|
|
56
61
|
function normalizeFlowGraphOptions(arg) {
|
|
57
62
|
if (!arg) return {};
|
|
58
|
-
if ("nodeState" in arg || "direction" in arg || "compact" in arg) {
|
|
63
|
+
if ("nodeState" in arg || "direction" in arg || "compact" in arg || "edges" in arg) {
|
|
59
64
|
return arg;
|
|
60
65
|
}
|
|
61
66
|
return isNodeStateMap(arg) ? { nodeState: arg } : {};
|
|
@@ -68,7 +73,8 @@ function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
|
68
73
|
const graph = buildWorkflowGraph(yaml, {
|
|
69
74
|
reserveRunState: nodeState !== void 0,
|
|
70
75
|
direction,
|
|
71
|
-
compact
|
|
76
|
+
compact,
|
|
77
|
+
...options.edges ? { edges: options.edges } : {}
|
|
72
78
|
});
|
|
73
79
|
const isLR = direction === "LR";
|
|
74
80
|
const sourcePosition = isLR ? Position.Right : Position.Bottom;
|
|
@@ -82,6 +88,13 @@ function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
|
82
88
|
position: n.position,
|
|
83
89
|
width: n.width,
|
|
84
90
|
height: n.height,
|
|
91
|
+
// A node is a `do` entry, and removing one is a list edit — never a
|
|
92
|
+
// canvas gesture. This matters most on an EDITABLE canvas, where the
|
|
93
|
+
// delete key is armed for edges: React Flow deletes a selected node
|
|
94
|
+
// together with every edge touching it, so a node left deletable would
|
|
95
|
+
// vanish from the canvas AND report each of its edges as removed,
|
|
96
|
+
// asking the host to drop declared edges nobody touched.
|
|
97
|
+
deletable: false,
|
|
85
98
|
// Fix the DOM box to the laid-out size so the card can't grow past its
|
|
86
99
|
// reserved space and overlap a neighbour (see model.ts nodeHeight).
|
|
87
100
|
style: { width: n.width, height: n.height },
|
|
@@ -90,12 +103,20 @@ function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
|
90
103
|
data: nodeState?.[n.id] ? { ...n.data, state: nodeState[n.id] } : n.data
|
|
91
104
|
})
|
|
92
105
|
),
|
|
93
|
-
edges: graph.edges.map((e) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
edges: graph.edges.map((e) => {
|
|
107
|
+
const decorated = e.whenLabel !== void 0 || e.backEdge === true;
|
|
108
|
+
return {
|
|
109
|
+
id: e.id,
|
|
110
|
+
source: e.source,
|
|
111
|
+
target: e.target,
|
|
112
|
+
type: decorated ? WF_EDGE_TYPE : "smoothstep",
|
|
113
|
+
data: {
|
|
114
|
+
kind: e.kind,
|
|
115
|
+
...e.whenLabel !== void 0 ? { whenLabel: e.whenLabel } : {},
|
|
116
|
+
...e.backEdge === true ? { backEdge: true } : {}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
})
|
|
99
120
|
};
|
|
100
121
|
}
|
|
101
122
|
function sameRunState(a, b) {
|
|
@@ -248,10 +269,13 @@ import {
|
|
|
248
269
|
Bell,
|
|
249
270
|
Box,
|
|
250
271
|
Cable,
|
|
272
|
+
Camera,
|
|
251
273
|
Circle,
|
|
252
274
|
Clock,
|
|
275
|
+
Code,
|
|
253
276
|
GitBranch,
|
|
254
277
|
Repeat,
|
|
278
|
+
ScanSearch,
|
|
255
279
|
Sparkles,
|
|
256
280
|
UserCheck,
|
|
257
281
|
Webhook,
|
|
@@ -335,7 +359,11 @@ function progressFill(status) {
|
|
|
335
359
|
}
|
|
336
360
|
var KIND_ICON = {
|
|
337
361
|
schedule: Clock,
|
|
362
|
+
// Both wake the workflow on an inbound POST, so they share the mark. A
|
|
363
|
+
// `provider_event` normally shows its provider's LOGO instead and reaches this
|
|
364
|
+
// glyph only when the connector has none.
|
|
338
365
|
provider_event: Webhook,
|
|
366
|
+
webhook: Webhook,
|
|
339
367
|
trigger: Zap,
|
|
340
368
|
"agent.run": Sparkles,
|
|
341
369
|
"integration.invoke": Cable,
|
|
@@ -343,7 +371,10 @@ var KIND_ICON = {
|
|
|
343
371
|
parallel: GitBranch,
|
|
344
372
|
foreach: Repeat,
|
|
345
373
|
decision: UserCheck,
|
|
346
|
-
"sandbox.spawn": Box
|
|
374
|
+
"sandbox.spawn": Box,
|
|
375
|
+
"sandbox.snapshot": Camera,
|
|
376
|
+
"script.run": Code,
|
|
377
|
+
"trace.analyze": ScanSearch
|
|
347
378
|
};
|
|
348
379
|
function NodeMark({
|
|
349
380
|
kind,
|
|
@@ -421,6 +452,15 @@ function useColorMode() {
|
|
|
421
452
|
}
|
|
422
453
|
var DirectionContext = createContext("LR");
|
|
423
454
|
var DensityContext = createContext(false);
|
|
455
|
+
var SelectedNodeContext = createContext(void 0);
|
|
456
|
+
var SELECTION_OUTLINE = {
|
|
457
|
+
outline: "2px solid hsl(var(--primary))",
|
|
458
|
+
outlineOffset: 2
|
|
459
|
+
};
|
|
460
|
+
var ConnectableContext = createContext(false);
|
|
461
|
+
function isEditableEdge(edge) {
|
|
462
|
+
return edge.data?.kind !== "fork" && triggerNodeIndex(edge.source) === null;
|
|
463
|
+
}
|
|
424
464
|
function StatusFooter({
|
|
425
465
|
status,
|
|
426
466
|
rounds,
|
|
@@ -475,6 +515,7 @@ function StatusPill({ status }) {
|
|
|
475
515
|
);
|
|
476
516
|
}
|
|
477
517
|
var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
518
|
+
var HANDLE_CONNECTABLE_CLASS = "!h-2.5 !w-2.5 !min-w-0 !rounded-full !border !border-border !bg-muted-foreground/70 opacity-100 transition-opacity hover:!bg-primary";
|
|
478
519
|
function compactHandleStyle(position, isLR) {
|
|
479
520
|
const tileLeft = isLR ? (COMPACT_NODE_SIZE.width - COMPACT_TILE) / 2 : 0;
|
|
480
521
|
const center = COMPACT_TILE / 2;
|
|
@@ -496,11 +537,14 @@ function compactHandleStyle(position, isLR) {
|
|
|
496
537
|
return anchor(tileLeft + center, COMPACT_TILE);
|
|
497
538
|
}
|
|
498
539
|
}
|
|
499
|
-
function WorkflowNode({ data }) {
|
|
540
|
+
function WorkflowNode({ id, data }) {
|
|
500
541
|
const d = data;
|
|
501
542
|
const state = d.state;
|
|
502
543
|
const direction = useContext(DirectionContext);
|
|
503
544
|
const compact = useContext(DensityContext);
|
|
545
|
+
const connectable = useContext(ConnectableContext);
|
|
546
|
+
const hostSelection = useContext(SelectedNodeContext);
|
|
547
|
+
const selected = hostSelection !== void 0 && hostSelection === id;
|
|
504
548
|
const isLR = direction === "LR";
|
|
505
549
|
const targetPos = isLR ? Position2.Left : Position2.Top;
|
|
506
550
|
const sourcePos = isLR ? Position2.Right : Position2.Bottom;
|
|
@@ -527,13 +571,14 @@ function WorkflowNode({ data }) {
|
|
|
527
571
|
);
|
|
528
572
|
return shape.kind === "empty" ? null : { shape, tone: source.tone, label: source.label };
|
|
529
573
|
})();
|
|
574
|
+
const handleClass = connectable ? HANDLE_CONNECTABLE_CLASS : HANDLE_CLASS;
|
|
530
575
|
const handles = /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
531
576
|
!d.isRoot && /* @__PURE__ */ jsx3(
|
|
532
577
|
Handle,
|
|
533
578
|
{
|
|
534
579
|
type: "target",
|
|
535
580
|
position: targetPos,
|
|
536
|
-
className:
|
|
581
|
+
className: handleClass,
|
|
537
582
|
style: compact ? compactHandleStyle(targetPos, isLR) : void 0
|
|
538
583
|
}
|
|
539
584
|
),
|
|
@@ -542,7 +587,7 @@ function WorkflowNode({ data }) {
|
|
|
542
587
|
{
|
|
543
588
|
type: "source",
|
|
544
589
|
position: sourcePos,
|
|
545
|
-
className:
|
|
590
|
+
className: handleClass,
|
|
546
591
|
style: compact ? compactHandleStyle(sourcePos, isLR) : void 0
|
|
547
592
|
}
|
|
548
593
|
)
|
|
@@ -563,7 +608,10 @@ function WorkflowNode({ data }) {
|
|
|
563
608
|
height: COMPACT_TILE,
|
|
564
609
|
...state ? statusBorder(state.status) : {
|
|
565
610
|
borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
|
|
566
|
-
}
|
|
611
|
+
},
|
|
612
|
+
// The TILE is the compact node (the name beside it is unboxed), so
|
|
613
|
+
// the selection ring belongs to it and not to the wider box.
|
|
614
|
+
...selected ? SELECTION_OUTLINE : {}
|
|
567
615
|
},
|
|
568
616
|
children: [
|
|
569
617
|
/* @__PURE__ */ jsx3("span", { className: "wf-node-body-in flex items-center justify-center", children: /* @__PURE__ */ jsx3(
|
|
@@ -624,8 +672,11 @@ function WorkflowNode({ data }) {
|
|
|
624
672
|
"div",
|
|
625
673
|
{
|
|
626
674
|
className: "relative flex h-full w-full flex-col overflow-hidden rounded-xl border bg-card shadow-sm transition-colors",
|
|
627
|
-
style:
|
|
628
|
-
|
|
675
|
+
style: {
|
|
676
|
+
...state ? statusBorder(state.status) : {
|
|
677
|
+
borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
|
|
678
|
+
},
|
|
679
|
+
...selected ? SELECTION_OUTLINE : {}
|
|
629
680
|
},
|
|
630
681
|
children: [
|
|
631
682
|
handles,
|
|
@@ -695,22 +746,88 @@ function WorkflowNode({ data }) {
|
|
|
695
746
|
}
|
|
696
747
|
);
|
|
697
748
|
}
|
|
749
|
+
var EDGE_CHIP_CLASS = "rounded-full border border-border bg-card/90 px-1.5 py-[1px] text-[10px] leading-tight backdrop-blur";
|
|
750
|
+
function WfEdgeRenderer({
|
|
751
|
+
id,
|
|
752
|
+
sourceX,
|
|
753
|
+
sourceY,
|
|
754
|
+
targetX,
|
|
755
|
+
targetY,
|
|
756
|
+
sourcePosition,
|
|
757
|
+
targetPosition,
|
|
758
|
+
data,
|
|
759
|
+
markerEnd,
|
|
760
|
+
style
|
|
761
|
+
}) {
|
|
762
|
+
const [path, labelX, labelY] = getSmoothStepPath({
|
|
763
|
+
sourceX,
|
|
764
|
+
sourceY,
|
|
765
|
+
sourcePosition,
|
|
766
|
+
targetX,
|
|
767
|
+
targetY,
|
|
768
|
+
targetPosition
|
|
769
|
+
});
|
|
770
|
+
const backEdge = data?.backEdge === true;
|
|
771
|
+
const whenLabel = data?.whenLabel;
|
|
772
|
+
const visits = data?.maxNodeVisits;
|
|
773
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
774
|
+
/* @__PURE__ */ jsx3(BaseEdge, { id, path, markerEnd, style }),
|
|
775
|
+
/* @__PURE__ */ jsx3(EdgeLabelRenderer, { children: /* @__PURE__ */ jsxs2(
|
|
776
|
+
"div",
|
|
777
|
+
{
|
|
778
|
+
className: "nodrag nopan absolute flex flex-col items-center gap-0.5",
|
|
779
|
+
style: {
|
|
780
|
+
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`
|
|
781
|
+
},
|
|
782
|
+
children: [
|
|
783
|
+
whenLabel && /* @__PURE__ */ jsx3(
|
|
784
|
+
"span",
|
|
785
|
+
{
|
|
786
|
+
title: whenLabel,
|
|
787
|
+
className: `${EDGE_CHIP_CLASS} max-w-40 truncate text-muted-foreground`,
|
|
788
|
+
children: whenLabel
|
|
789
|
+
}
|
|
790
|
+
),
|
|
791
|
+
backEdge && /* @__PURE__ */ jsx3(
|
|
792
|
+
"span",
|
|
793
|
+
{
|
|
794
|
+
title: visits !== void 0 ? `Loops back \u2014 each node runs at most ${visits} time${visits === 1 ? "" : "s"} per run` : "Loops back to a step that has already run",
|
|
795
|
+
className: `${EDGE_CHIP_CLASS} shrink-0 text-foreground`,
|
|
796
|
+
children: visits !== void 0 ? `\u21BA \u2264${visits}` : "\u21BA"
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
]
|
|
800
|
+
}
|
|
801
|
+
) })
|
|
802
|
+
] });
|
|
803
|
+
}
|
|
698
804
|
var NODE_TYPES = { wfNode: WorkflowNode };
|
|
699
|
-
|
|
805
|
+
var EDGE_TYPES = { [WF_EDGE_TYPE]: WfEdgeRenderer };
|
|
806
|
+
function buildStyledEdges(base, nodeState, maxNodeVisits) {
|
|
700
807
|
return base.map((e) => {
|
|
701
808
|
const status = nodeState?.[e.target]?.status;
|
|
702
809
|
const color = edgeColor(nodeState ? status : void 0);
|
|
810
|
+
const backEdge = e.data?.backEdge === true;
|
|
703
811
|
return {
|
|
704
812
|
...e,
|
|
705
|
-
// The active hop flows; everything else is static.
|
|
706
|
-
|
|
707
|
-
|
|
813
|
+
// The active hop flows; everything else is static. A cycle-closing edge
|
|
814
|
+
// never flows even into a running target: it is the loop's RETURN path,
|
|
815
|
+
// and animating it would read as the run travelling backwards.
|
|
816
|
+
animated: !backEdge && status === "running",
|
|
817
|
+
style: {
|
|
818
|
+
strokeWidth: 1.75,
|
|
819
|
+
stroke: color,
|
|
820
|
+
// Dashed, so a return path is distinguishable from a forward hop before
|
|
821
|
+
// its badge is read — and at a zoom where the badge isn't legible.
|
|
822
|
+
...backEdge ? { strokeDasharray: "6 3" } : {}
|
|
823
|
+
},
|
|
708
824
|
markerEnd: {
|
|
709
825
|
type: MarkerType.ArrowClosed,
|
|
710
826
|
width: 15,
|
|
711
827
|
height: 15,
|
|
712
828
|
color
|
|
713
|
-
}
|
|
829
|
+
},
|
|
830
|
+
...backEdge && maxNodeVisits !== void 0 && e.data ? { data: { ...e.data, maxNodeVisits } } : {}
|
|
714
831
|
};
|
|
715
832
|
});
|
|
716
833
|
}
|
|
@@ -721,28 +838,41 @@ function WorkflowGraph({
|
|
|
721
838
|
defaultCompact = true,
|
|
722
839
|
className,
|
|
723
840
|
nodeState,
|
|
724
|
-
|
|
841
|
+
edges: declaredEdges,
|
|
842
|
+
maxNodeVisits,
|
|
843
|
+
selectedNodeId,
|
|
844
|
+
onNodeClick,
|
|
845
|
+
onEdgeConnect,
|
|
846
|
+
onEdgeDelete,
|
|
847
|
+
onEdgeClick
|
|
725
848
|
}) {
|
|
849
|
+
const editable = onEdgeConnect !== void 0;
|
|
726
850
|
const colorMode = useColorMode();
|
|
727
851
|
const isPreview = variant === "preview";
|
|
728
852
|
const [userCompact, setUserCompact] = useState(defaultCompact);
|
|
729
853
|
const compact = isPreview || userCompact;
|
|
730
854
|
const [displayCompact, setDisplayCompact] = useState(compact);
|
|
731
|
-
const rfRef = useRef(
|
|
855
|
+
const rfRef = useRef(
|
|
856
|
+
null
|
|
857
|
+
);
|
|
732
858
|
const wrapperRef = useRef(null);
|
|
733
859
|
const hasRunOverlay = nodeState !== void 0;
|
|
734
860
|
const structural = useMemo(
|
|
735
861
|
() => buildFlowGraph(yaml, {
|
|
736
862
|
nodeState: hasRunOverlay ? {} : void 0,
|
|
737
863
|
direction,
|
|
738
|
-
compact
|
|
864
|
+
compact,
|
|
865
|
+
...declaredEdges ? { edges: declaredEdges } : {}
|
|
739
866
|
}),
|
|
740
|
-
[yaml, hasRunOverlay, direction, compact]
|
|
741
|
-
);
|
|
742
|
-
const styledEdges = useMemo(
|
|
743
|
-
() => buildStyledEdges(structural.edges, nodeState),
|
|
744
|
-
[structural.edges, nodeState]
|
|
867
|
+
[yaml, hasRunOverlay, direction, compact, declaredEdges]
|
|
745
868
|
);
|
|
869
|
+
const styledEdges = useMemo(() => {
|
|
870
|
+
const styled = buildStyledEdges(structural.edges, nodeState, maxNodeVisits);
|
|
871
|
+
return styled.map((e) => ({
|
|
872
|
+
...e,
|
|
873
|
+
deletable: editable ? isEditableEdge(e) : false
|
|
874
|
+
}));
|
|
875
|
+
}, [structural.edges, nodeState, maxNodeVisits, editable]);
|
|
746
876
|
const [nodes, setNodes, onNodesChange] = useNodesState(structural.nodes);
|
|
747
877
|
const [edges, setEdges, onEdgesChange] = useEdgesState(styledEdges);
|
|
748
878
|
const nodesRef = useRef(nodes);
|
|
@@ -844,6 +974,20 @@ function WorkflowGraph({
|
|
|
844
974
|
},
|
|
845
975
|
[onNodeClick]
|
|
846
976
|
);
|
|
977
|
+
const handleNodeKeyDown = useCallback(
|
|
978
|
+
(event) => {
|
|
979
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
980
|
+
if (!(event.target instanceof HTMLElement)) return;
|
|
981
|
+
const nodeId = event.target.closest(".react-flow__node")?.getAttribute("data-id");
|
|
982
|
+
if (!nodeId) return;
|
|
983
|
+
const node = nodesRef.current.find((n) => n.id === nodeId);
|
|
984
|
+
if (!node) return;
|
|
985
|
+
event.preventDefault();
|
|
986
|
+
event.stopPropagation();
|
|
987
|
+
onNodeClick?.(node.id, node.data);
|
|
988
|
+
},
|
|
989
|
+
[onNodeClick]
|
|
990
|
+
);
|
|
847
991
|
if (structural.error || structural.nodes.length === 0) {
|
|
848
992
|
return /* @__PURE__ */ jsx3(
|
|
849
993
|
"div",
|
|
@@ -853,7 +997,7 @@ function WorkflowGraph({
|
|
|
853
997
|
}
|
|
854
998
|
);
|
|
855
999
|
}
|
|
856
|
-
return /* @__PURE__ */ jsx3(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx3(DensityContext.Provider, { value: displayCompact, children: /* @__PURE__ */ jsx3("div", { ref: wrapperRef, className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs2(
|
|
1000
|
+
return /* @__PURE__ */ jsx3(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx3(DensityContext.Provider, { value: displayCompact, children: /* @__PURE__ */ jsx3(SelectedNodeContext.Provider, { value: selectedNodeId, children: /* @__PURE__ */ jsx3(ConnectableContext.Provider, { value: editable, children: /* @__PURE__ */ jsx3("div", { ref: wrapperRef, className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs2(
|
|
857
1001
|
ReactFlow,
|
|
858
1002
|
{
|
|
859
1003
|
nodes,
|
|
@@ -861,20 +1005,39 @@ function WorkflowGraph({
|
|
|
861
1005
|
onNodesChange,
|
|
862
1006
|
onEdgesChange,
|
|
863
1007
|
nodeTypes: NODE_TYPES,
|
|
1008
|
+
edgeTypes: EDGE_TYPES,
|
|
864
1009
|
colorMode,
|
|
865
1010
|
onInit: (inst) => {
|
|
866
1011
|
rfRef.current = inst;
|
|
867
1012
|
},
|
|
868
1013
|
onNodeClick: onNodeClick ? handleNodeClick : void 0,
|
|
1014
|
+
onKeyDown: onNodeClick ? handleNodeKeyDown : void 0,
|
|
869
1015
|
fitView: true,
|
|
870
1016
|
fitViewOptions: { ...FIT_VIEW, maxZoom: fitZoomCeiling(compact) },
|
|
871
1017
|
proOptions: { hideAttribution: true },
|
|
872
1018
|
nodesDraggable: !isPreview,
|
|
873
|
-
nodesConnectable:
|
|
874
|
-
deleteKeyCode: null,
|
|
1019
|
+
nodesConnectable: editable,
|
|
1020
|
+
deleteKeyCode: editable ? ["Backspace", "Delete"] : null,
|
|
875
1021
|
edgesReconnectable: false,
|
|
876
1022
|
elementsSelectable: !isPreview,
|
|
877
|
-
edgesFocusable:
|
|
1023
|
+
edgesFocusable: editable,
|
|
1024
|
+
onConnect: editable ? (connection) => {
|
|
1025
|
+
if (connection.source && connection.target) {
|
|
1026
|
+
onEdgeConnect(connection.source, connection.target);
|
|
1027
|
+
}
|
|
1028
|
+
} : void 0,
|
|
1029
|
+
onEdgesDelete: editable ? (deleted) => {
|
|
1030
|
+
for (const edge of deleted) {
|
|
1031
|
+
if (isEditableEdge(edge)) {
|
|
1032
|
+
onEdgeDelete?.(edge.source, edge.target);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
} : void 0,
|
|
1036
|
+
onEdgeClick: editable ? (_event, edge) => {
|
|
1037
|
+
if (isEditableEdge(edge)) {
|
|
1038
|
+
onEdgeClick?.(edge.source, edge.target);
|
|
1039
|
+
}
|
|
1040
|
+
} : void 0,
|
|
878
1041
|
zoomOnScroll: false,
|
|
879
1042
|
zoomOnPinch: true,
|
|
880
1043
|
zoomOnDoubleClick: !isPreview,
|
|
@@ -903,14 +1066,17 @@ function WorkflowGraph({
|
|
|
903
1066
|
] })
|
|
904
1067
|
]
|
|
905
1068
|
}
|
|
906
|
-
) }) }) });
|
|
1069
|
+
) }) }) }) }) });
|
|
907
1070
|
}
|
|
908
1071
|
export {
|
|
1072
|
+
ConnectableContext,
|
|
909
1073
|
DensityContext,
|
|
910
1074
|
DirectionContext,
|
|
911
1075
|
LAYOUT_TRANSITION_MS,
|
|
1076
|
+
SelectedNodeContext,
|
|
912
1077
|
WorkflowGraph,
|
|
913
1078
|
WorkflowNode,
|
|
914
1079
|
buildStyledEdges,
|
|
915
|
-
fitZoomCeiling
|
|
1080
|
+
fitZoomCeiling,
|
|
1081
|
+
isEditableEdge
|
|
916
1082
|
};
|
|
@@ -214,9 +214,26 @@ function providerLabel(provider) {
|
|
|
214
214
|
|
|
215
215
|
// src/workflows/model.ts
|
|
216
216
|
import { parse as parseYaml } from "yaml";
|
|
217
|
+
var TRIGGER_NODE_ID = "trigger";
|
|
218
|
+
var TRIGGER_NODE_ID_PATTERN = new RegExp(`^${TRIGGER_NODE_ID}:(\\d+)$`);
|
|
219
|
+
function triggerNodeId(index) {
|
|
220
|
+
return index === 0 ? TRIGGER_NODE_ID : `${TRIGGER_NODE_ID}:${index}`;
|
|
221
|
+
}
|
|
222
|
+
function triggerNodeIndex(nodeId) {
|
|
223
|
+
if (nodeId === TRIGGER_NODE_ID) return 0;
|
|
224
|
+
const match = TRIGGER_NODE_ID_PATTERN.exec(nodeId);
|
|
225
|
+
return match ? Number(match[1]) : null;
|
|
226
|
+
}
|
|
227
|
+
function actionNodeId(index) {
|
|
228
|
+
return `a${index}`;
|
|
229
|
+
}
|
|
230
|
+
function branchNodeId(actionIndex, branchIndex) {
|
|
231
|
+
return `${actionNodeId(actionIndex)}-b${branchIndex}`;
|
|
232
|
+
}
|
|
217
233
|
var NODE_W = 292;
|
|
218
234
|
var RANK_SEP = 72;
|
|
219
235
|
var CROSS_SEP = 24;
|
|
236
|
+
var EDGE_LABEL_LANE = 180;
|
|
220
237
|
var CARD_CHROME = 20;
|
|
221
238
|
var HEADER_ROW = 34;
|
|
222
239
|
var DESCRIPTION_ROW = 40;
|
|
@@ -372,6 +389,50 @@ function describeActionBase(action) {
|
|
|
372
389
|
tone: "structural"
|
|
373
390
|
};
|
|
374
391
|
}
|
|
392
|
+
case "script.run": {
|
|
393
|
+
const connections = Array.isArray(cfg.connections) ? cfg.connections.filter((c) => typeof c === "string") : [];
|
|
394
|
+
return {
|
|
395
|
+
title: "Script",
|
|
396
|
+
kind,
|
|
397
|
+
// What the box is GRANTED is the thing that distinguishes one script
|
|
398
|
+
// step from another at a glance; the timeout is a bound, and bounds read
|
|
399
|
+
// in the detail view with the rest of the config.
|
|
400
|
+
subtitle: connections.length > 0 ? `TypeScript \xB7 ${connections.length} connection${connections.length === 1 ? "" : "s"}` : "TypeScript",
|
|
401
|
+
// The head of the module — the same role the prompt plays on an agent:
|
|
402
|
+
// the text that says what this particular step actually does.
|
|
403
|
+
description: describeText(cfg.source),
|
|
404
|
+
isRoot: false,
|
|
405
|
+
tone: "action"
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
case "sandbox.snapshot":
|
|
409
|
+
return {
|
|
410
|
+
title: "Snapshot",
|
|
411
|
+
kind,
|
|
412
|
+
// WHICH sandbox is the only thing separating one snapshot step from
|
|
413
|
+
// another, and it is usually a `${steps…}` reference — so the subtitle
|
|
414
|
+
// reads as the step whose disk is being captured.
|
|
415
|
+
subtitle: str(cfg.sandbox) ?? "Capture a sandbox",
|
|
416
|
+
isRoot: false,
|
|
417
|
+
tone: "action"
|
|
418
|
+
};
|
|
419
|
+
case "trace.analyze": {
|
|
420
|
+
const kinds = Array.isArray(cfg.kinds) ? cfg.kinds.filter((k) => typeof k === "string") : [];
|
|
421
|
+
return {
|
|
422
|
+
title: "Trace analysis",
|
|
423
|
+
kind,
|
|
424
|
+
// The analysts that run ARE the step. The model it runs them on is
|
|
425
|
+
// incidental here (unlike agent.run, where the model IS the identity),
|
|
426
|
+
// so it stays in the config for the detail view and the node keeps its
|
|
427
|
+
// own glyph rather than borrowing a lab's brand mark.
|
|
428
|
+
subtitle: kinds.length > 0 ? describeText(
|
|
429
|
+
kinds.map((k) => humanizeIdentifier(k, "lower")).join(", ")
|
|
430
|
+
) : "Default analysts",
|
|
431
|
+
description: describeText(cfg.trace),
|
|
432
|
+
isRoot: false,
|
|
433
|
+
tone: "action"
|
|
434
|
+
};
|
|
435
|
+
}
|
|
375
436
|
default:
|
|
376
437
|
return {
|
|
377
438
|
// An action kind this version doesn't model yet still gets a readable
|
|
@@ -465,6 +526,18 @@ function describeTrigger(on) {
|
|
|
465
526
|
sch
|
|
466
527
|
);
|
|
467
528
|
}
|
|
529
|
+
if (rec.webhook !== void 0) {
|
|
530
|
+
return withConfig(
|
|
531
|
+
{
|
|
532
|
+
title: "Webhook",
|
|
533
|
+
kind: "webhook",
|
|
534
|
+
subtitle: "On an inbound POST",
|
|
535
|
+
isRoot: true,
|
|
536
|
+
tone: "trigger"
|
|
537
|
+
},
|
|
538
|
+
asRecord(rec.webhook)
|
|
539
|
+
);
|
|
540
|
+
}
|
|
468
541
|
const [kind] = Object.keys(rec);
|
|
469
542
|
return withConfig(
|
|
470
543
|
{
|
|
@@ -519,10 +592,74 @@ function layoutLayers(nodes, direction, rankSep, crossSep) {
|
|
|
519
592
|
}
|
|
520
593
|
return out;
|
|
521
594
|
}
|
|
595
|
+
function classifyBackEdges(orderedIds, edges) {
|
|
596
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
597
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
598
|
+
for (const e of edges) {
|
|
599
|
+
const list = outgoing.get(e.source);
|
|
600
|
+
if (list) list.push(e);
|
|
601
|
+
else outgoing.set(e.source, [e]);
|
|
602
|
+
hasIncoming.add(e.target);
|
|
603
|
+
}
|
|
604
|
+
const walkOrder = [
|
|
605
|
+
...orderedIds.filter((id) => !hasIncoming.has(id)),
|
|
606
|
+
...orderedIds.filter((id) => hasIncoming.has(id))
|
|
607
|
+
];
|
|
608
|
+
const back = /* @__PURE__ */ new Set();
|
|
609
|
+
const color = new Map(orderedIds.map((id) => [id, 0]));
|
|
610
|
+
for (const start of walkOrder) {
|
|
611
|
+
if (color.get(start) !== 0) continue;
|
|
612
|
+
color.set(start, 1);
|
|
613
|
+
const stack = [{ id: start, next: 0 }];
|
|
614
|
+
while (stack.length > 0) {
|
|
615
|
+
const frame = stack[stack.length - 1];
|
|
616
|
+
const out = outgoing.get(frame.id);
|
|
617
|
+
if (!out || frame.next >= out.length) {
|
|
618
|
+
color.set(frame.id, 2);
|
|
619
|
+
stack.pop();
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
const edge = out[frame.next];
|
|
623
|
+
frame.next += 1;
|
|
624
|
+
const target = color.get(edge.target);
|
|
625
|
+
if (target === 1) {
|
|
626
|
+
back.add(edge.id);
|
|
627
|
+
} else if (target === 0) {
|
|
628
|
+
color.set(edge.target, 1);
|
|
629
|
+
stack.push({ id: edge.target, next: 0 });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return back;
|
|
634
|
+
}
|
|
635
|
+
function rankByTopology(orderedIds, edges, backEdgeIds) {
|
|
636
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
637
|
+
const indegree = new Map(orderedIds.map((id) => [id, 0]));
|
|
638
|
+
for (const e of edges) {
|
|
639
|
+
if (backEdgeIds.has(e.id)) continue;
|
|
640
|
+
const list = outgoing.get(e.source);
|
|
641
|
+
if (list) list.push(e);
|
|
642
|
+
else outgoing.set(e.source, [e]);
|
|
643
|
+
indegree.set(e.target, (indegree.get(e.target) ?? 0) + 1);
|
|
644
|
+
}
|
|
645
|
+
const rank = new Map(orderedIds.map((id) => [id, 0]));
|
|
646
|
+
const queue = orderedIds.filter((id) => indegree.get(id) === 0);
|
|
647
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
648
|
+
const id = queue[head];
|
|
649
|
+
for (const e of outgoing.get(id) ?? []) {
|
|
650
|
+
rank.set(e.target, Math.max(rank.get(e.target) ?? 0, (rank.get(id) ?? 0) + 1));
|
|
651
|
+
const remaining = (indegree.get(e.target) ?? 0) - 1;
|
|
652
|
+
indegree.set(e.target, remaining);
|
|
653
|
+
if (remaining === 0) queue.push(e.target);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return rank;
|
|
657
|
+
}
|
|
522
658
|
function buildWorkflowGraph(yaml, options) {
|
|
523
659
|
const reserveRunState = options?.reserveRunState ?? false;
|
|
524
660
|
const direction = options?.direction ?? "LR";
|
|
525
661
|
const compact = options?.compact ?? false;
|
|
662
|
+
const declared = options?.edges;
|
|
526
663
|
if (!yaml || yaml.trim() === "") {
|
|
527
664
|
return { nodes: [], edges: [], error: "No definition" };
|
|
528
665
|
}
|
|
@@ -534,7 +671,8 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
534
671
|
}
|
|
535
672
|
const def = asRecord(parsed);
|
|
536
673
|
const actions = Array.isArray(def.do) ? def.do : [];
|
|
537
|
-
|
|
674
|
+
const triggers = !def.on ? [] : Array.isArray(def.on) ? def.on : [def.on];
|
|
675
|
+
if (triggers.length === 0 && actions.length === 0) {
|
|
538
676
|
return { nodes: [], edges: [], error: "Empty workflow" };
|
|
539
677
|
}
|
|
540
678
|
const logical = [];
|
|
@@ -547,24 +685,39 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
547
685
|
};
|
|
548
686
|
logical.push({ id, data, width: dims.width, height: dims.height, rank: rank2 });
|
|
549
687
|
};
|
|
550
|
-
const
|
|
551
|
-
|
|
688
|
+
const edgeIds = /* @__PURE__ */ new Set();
|
|
689
|
+
const addEdge = (source, target, kind, whenLabel) => {
|
|
690
|
+
const id = `${source}->${target}`;
|
|
691
|
+
if (edgeIds.has(id)) return;
|
|
692
|
+
edgeIds.add(id);
|
|
693
|
+
edges.push({
|
|
694
|
+
id,
|
|
695
|
+
source,
|
|
696
|
+
target,
|
|
697
|
+
kind,
|
|
698
|
+
...whenLabel ? { whenLabel } : {}
|
|
699
|
+
});
|
|
552
700
|
};
|
|
553
701
|
let rank = 0;
|
|
554
702
|
let prevExits = [];
|
|
555
703
|
const branchLeafIds = /* @__PURE__ */ new Set();
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
704
|
+
const triggerIds = triggers.map((_, i) => triggerNodeId(i));
|
|
705
|
+
triggers.forEach((trigger, i) => {
|
|
706
|
+
addNode(triggerNodeId(i), describeTrigger(trigger), rank, false);
|
|
707
|
+
});
|
|
708
|
+
if (triggers.length > 0) {
|
|
709
|
+
prevExits = [...triggerIds];
|
|
559
710
|
rank += 1;
|
|
560
711
|
}
|
|
561
712
|
actions.forEach((action, i) => {
|
|
562
|
-
const id =
|
|
713
|
+
const id = actionNodeId(i);
|
|
563
714
|
const data = describeAction(action);
|
|
564
|
-
if (i === 0 &&
|
|
715
|
+
if (i === 0 && triggers.length === 0) data.isRoot = true;
|
|
565
716
|
addNode(id, data, rank, reserveRunState);
|
|
566
|
-
|
|
567
|
-
|
|
717
|
+
if (!declared) {
|
|
718
|
+
for (const from of prevExits) {
|
|
719
|
+
addEdge(from, id, branchLeafIds.has(from) ? "join" : "spine");
|
|
720
|
+
}
|
|
568
721
|
}
|
|
569
722
|
const rec = asRecord(action);
|
|
570
723
|
const children = "parallel" in rec ? Array.isArray(asRecord(rec.parallel).branches) ? asRecord(rec.parallel).branches : [] : "foreach" in rec ? (
|
|
@@ -575,7 +728,7 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
575
728
|
const branchRank = rank + 1;
|
|
576
729
|
const branchIds = [];
|
|
577
730
|
children.forEach((child, j) => {
|
|
578
|
-
const cid =
|
|
731
|
+
const cid = branchNodeId(i, j);
|
|
579
732
|
addNode(cid, describeAction(child), branchRank, reserveRunState);
|
|
580
733
|
addEdge(id, cid, "fork");
|
|
581
734
|
branchIds.push(cid);
|
|
@@ -588,8 +741,43 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
588
741
|
rank += 1;
|
|
589
742
|
}
|
|
590
743
|
});
|
|
744
|
+
if (declared) {
|
|
745
|
+
const known = new Set(logical.map((n) => n.id));
|
|
746
|
+
for (const spec of declared) {
|
|
747
|
+
const missing = !known.has(spec.from) ? spec.from : !known.has(spec.to) ? spec.to : null;
|
|
748
|
+
if (missing !== null) {
|
|
749
|
+
return {
|
|
750
|
+
nodes: [],
|
|
751
|
+
edges: [],
|
|
752
|
+
error: `Declared topology names "${missing}", which this definition has no step for`
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
addEdge(spec.from, spec.to, "spine", spec.whenLabel);
|
|
756
|
+
}
|
|
757
|
+
const rootward = new Set(edges.map((e) => e.target));
|
|
758
|
+
for (const n of logical) {
|
|
759
|
+
if (triggerNodeIndex(n.id) !== null) continue;
|
|
760
|
+
if (rootward.has(n.id)) continue;
|
|
761
|
+
for (const triggerId of triggerIds) addEdge(triggerId, n.id, "spine");
|
|
762
|
+
}
|
|
763
|
+
const orderedIds = logical.map((n) => n.id);
|
|
764
|
+
const backEdgeIds = classifyBackEdges(orderedIds, edges);
|
|
765
|
+
for (const e of edges) {
|
|
766
|
+
if (backEdgeIds.has(e.id)) e.backEdge = true;
|
|
767
|
+
}
|
|
768
|
+
const rankById = rankByTopology(orderedIds, edges, backEdgeIds);
|
|
769
|
+
const incoming = new Set(edges.map((e) => e.target));
|
|
770
|
+
for (const n of logical) {
|
|
771
|
+
n.rank = rankById.get(n.id) ?? 0;
|
|
772
|
+
n.data = { ...n.data, isRoot: !incoming.has(n.id) };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
const labelled = edges.some(
|
|
776
|
+
(e) => e.whenLabel !== void 0 || e.backEdge === true
|
|
777
|
+
);
|
|
778
|
+
const rankSep = labelled ? Math.max(geo.rankSep, EDGE_LABEL_LANE) : geo.rankSep;
|
|
591
779
|
return {
|
|
592
|
-
nodes: layoutLayers(logical, direction,
|
|
780
|
+
nodes: layoutLayers(logical, direction, rankSep, geo.crossSep),
|
|
593
781
|
edges,
|
|
594
782
|
error: null
|
|
595
783
|
};
|
|
@@ -602,6 +790,11 @@ export {
|
|
|
602
790
|
fmtTokens,
|
|
603
791
|
shortModel,
|
|
604
792
|
providerLabel,
|
|
793
|
+
TRIGGER_NODE_ID,
|
|
794
|
+
triggerNodeId,
|
|
795
|
+
triggerNodeIndex,
|
|
796
|
+
actionNodeId,
|
|
797
|
+
branchNodeId,
|
|
605
798
|
COMPACT_TILE,
|
|
606
799
|
COMPACT_NODE_SIZE,
|
|
607
800
|
buildWorkflowGraph
|
package/dist/globals.css
CHANGED
|
@@ -718,6 +718,9 @@
|
|
|
718
718
|
.\!h-2 {
|
|
719
719
|
height: calc(var(--spacing) * 2) !important;
|
|
720
720
|
}
|
|
721
|
+
.\!h-2\.5 {
|
|
722
|
+
height: calc(var(--spacing) * 2.5) !important;
|
|
723
|
+
}
|
|
721
724
|
.h-1 {
|
|
722
725
|
height: var(--spacing);
|
|
723
726
|
}
|
|
@@ -796,6 +799,9 @@
|
|
|
796
799
|
.h-\[24rem\] {
|
|
797
800
|
height: 24rem;
|
|
798
801
|
}
|
|
802
|
+
.h-\[26rem\] {
|
|
803
|
+
height: 26rem;
|
|
804
|
+
}
|
|
799
805
|
.h-\[28rem\] {
|
|
800
806
|
height: 28rem;
|
|
801
807
|
}
|
|
@@ -967,6 +973,9 @@
|
|
|
967
973
|
.\!w-2 {
|
|
968
974
|
width: calc(var(--spacing) * 2) !important;
|
|
969
975
|
}
|
|
976
|
+
.\!w-2\.5 {
|
|
977
|
+
width: calc(var(--spacing) * 2.5) !important;
|
|
978
|
+
}
|
|
970
979
|
.w-0 {
|
|
971
980
|
width: 0px;
|
|
972
981
|
}
|
|
@@ -1210,6 +1219,9 @@
|
|
|
1210
1219
|
.max-w-6xl {
|
|
1211
1220
|
max-width: var(--container-6xl);
|
|
1212
1221
|
}
|
|
1222
|
+
.max-w-40 {
|
|
1223
|
+
max-width: calc(var(--spacing) * 40);
|
|
1224
|
+
}
|
|
1213
1225
|
.max-w-\[14rem\] {
|
|
1214
1226
|
max-width: 14rem;
|
|
1215
1227
|
}
|
|
@@ -1698,6 +1710,9 @@
|
|
|
1698
1710
|
.overscroll-x-contain {
|
|
1699
1711
|
overscroll-behavior-x: contain;
|
|
1700
1712
|
}
|
|
1713
|
+
.\!rounded-full {
|
|
1714
|
+
border-radius: calc(infinity * 1px) !important;
|
|
1715
|
+
}
|
|
1701
1716
|
.rounded {
|
|
1702
1717
|
border-radius: 0.25rem;
|
|
1703
1718
|
}
|
|
@@ -1781,6 +1796,10 @@
|
|
|
1781
1796
|
.rounded-bl-lg {
|
|
1782
1797
|
border-bottom-left-radius: var(--radius-lg);
|
|
1783
1798
|
}
|
|
1799
|
+
.\!border {
|
|
1800
|
+
border-style: var(--tw-border-style) !important;
|
|
1801
|
+
border-width: 1px !important;
|
|
1802
|
+
}
|
|
1784
1803
|
.\!border-0 {
|
|
1785
1804
|
border-style: var(--tw-border-style) !important;
|
|
1786
1805
|
border-width: 0px !important;
|
|
@@ -1841,6 +1860,9 @@
|
|
|
1841
1860
|
--tw-border-style: none;
|
|
1842
1861
|
border-style: none;
|
|
1843
1862
|
}
|
|
1863
|
+
.\!border-border {
|
|
1864
|
+
border-color: var(--color-border) !important;
|
|
1865
|
+
}
|
|
1844
1866
|
.border-\[var\(--accent-text\)\] {
|
|
1845
1867
|
border-color: var(--accent-text);
|
|
1846
1868
|
}
|
|
@@ -2081,6 +2103,12 @@
|
|
|
2081
2103
|
.border-b-primary {
|
|
2082
2104
|
border-bottom-color: var(--color-primary);
|
|
2083
2105
|
}
|
|
2106
|
+
.\!bg-muted-foreground\/70 {
|
|
2107
|
+
background-color: color-mix(in srgb, hsl(var(--muted-foreground)) 70%, transparent) !important;
|
|
2108
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2109
|
+
background-color: color-mix(in oklab, var(--color-muted-foreground) 70%, transparent) !important;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2084
2112
|
.\!bg-transparent {
|
|
2085
2113
|
background-color: transparent !important;
|
|
2086
2114
|
}
|
|
@@ -2300,6 +2328,12 @@
|
|
|
2300
2328
|
background-color: color-mix(in oklab, var(--color-card) 80%, transparent);
|
|
2301
2329
|
}
|
|
2302
2330
|
}
|
|
2331
|
+
.bg-card\/90 {
|
|
2332
|
+
background-color: color-mix(in srgb, hsl(var(--card)) 90%, transparent);
|
|
2333
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2334
|
+
background-color: color-mix(in oklab, var(--color-card) 90%, transparent);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2303
2337
|
.bg-destructive {
|
|
2304
2338
|
background-color: var(--color-destructive);
|
|
2305
2339
|
}
|
|
@@ -3893,6 +3927,9 @@
|
|
|
3893
3927
|
border-color: color-mix(in oklab, var(--color-primary) 50%, transparent);
|
|
3894
3928
|
}
|
|
3895
3929
|
}
|
|
3930
|
+
.hover\:\!bg-primary:hover {
|
|
3931
|
+
background-color: var(--color-primary) !important;
|
|
3932
|
+
}
|
|
3896
3933
|
.hover\:bg-\[var\(--accent-surface-soft\)\]:hover {
|
|
3897
3934
|
background-color: var(--accent-surface-soft);
|
|
3898
3935
|
}
|
package/dist/styles.css
CHANGED
|
@@ -718,6 +718,9 @@
|
|
|
718
718
|
.\!h-2 {
|
|
719
719
|
height: calc(var(--spacing) * 2) !important;
|
|
720
720
|
}
|
|
721
|
+
.\!h-2\.5 {
|
|
722
|
+
height: calc(var(--spacing) * 2.5) !important;
|
|
723
|
+
}
|
|
721
724
|
.h-1 {
|
|
722
725
|
height: var(--spacing);
|
|
723
726
|
}
|
|
@@ -796,6 +799,9 @@
|
|
|
796
799
|
.h-\[24rem\] {
|
|
797
800
|
height: 24rem;
|
|
798
801
|
}
|
|
802
|
+
.h-\[26rem\] {
|
|
803
|
+
height: 26rem;
|
|
804
|
+
}
|
|
799
805
|
.h-\[28rem\] {
|
|
800
806
|
height: 28rem;
|
|
801
807
|
}
|
|
@@ -967,6 +973,9 @@
|
|
|
967
973
|
.\!w-2 {
|
|
968
974
|
width: calc(var(--spacing) * 2) !important;
|
|
969
975
|
}
|
|
976
|
+
.\!w-2\.5 {
|
|
977
|
+
width: calc(var(--spacing) * 2.5) !important;
|
|
978
|
+
}
|
|
970
979
|
.w-0 {
|
|
971
980
|
width: 0px;
|
|
972
981
|
}
|
|
@@ -1210,6 +1219,9 @@
|
|
|
1210
1219
|
.max-w-6xl {
|
|
1211
1220
|
max-width: var(--container-6xl);
|
|
1212
1221
|
}
|
|
1222
|
+
.max-w-40 {
|
|
1223
|
+
max-width: calc(var(--spacing) * 40);
|
|
1224
|
+
}
|
|
1213
1225
|
.max-w-\[14rem\] {
|
|
1214
1226
|
max-width: 14rem;
|
|
1215
1227
|
}
|
|
@@ -1698,6 +1710,9 @@
|
|
|
1698
1710
|
.overscroll-x-contain {
|
|
1699
1711
|
overscroll-behavior-x: contain;
|
|
1700
1712
|
}
|
|
1713
|
+
.\!rounded-full {
|
|
1714
|
+
border-radius: calc(infinity * 1px) !important;
|
|
1715
|
+
}
|
|
1701
1716
|
.rounded {
|
|
1702
1717
|
border-radius: 0.25rem;
|
|
1703
1718
|
}
|
|
@@ -1781,6 +1796,10 @@
|
|
|
1781
1796
|
.rounded-bl-lg {
|
|
1782
1797
|
border-bottom-left-radius: var(--radius-lg);
|
|
1783
1798
|
}
|
|
1799
|
+
.\!border {
|
|
1800
|
+
border-style: var(--tw-border-style) !important;
|
|
1801
|
+
border-width: 1px !important;
|
|
1802
|
+
}
|
|
1784
1803
|
.\!border-0 {
|
|
1785
1804
|
border-style: var(--tw-border-style) !important;
|
|
1786
1805
|
border-width: 0px !important;
|
|
@@ -1841,6 +1860,9 @@
|
|
|
1841
1860
|
--tw-border-style: none;
|
|
1842
1861
|
border-style: none;
|
|
1843
1862
|
}
|
|
1863
|
+
.\!border-border {
|
|
1864
|
+
border-color: var(--color-border) !important;
|
|
1865
|
+
}
|
|
1844
1866
|
.border-\[var\(--accent-text\)\] {
|
|
1845
1867
|
border-color: var(--accent-text);
|
|
1846
1868
|
}
|
|
@@ -2081,6 +2103,12 @@
|
|
|
2081
2103
|
.border-b-primary {
|
|
2082
2104
|
border-bottom-color: var(--color-primary);
|
|
2083
2105
|
}
|
|
2106
|
+
.\!bg-muted-foreground\/70 {
|
|
2107
|
+
background-color: color-mix(in srgb, hsl(var(--muted-foreground)) 70%, transparent) !important;
|
|
2108
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2109
|
+
background-color: color-mix(in oklab, var(--color-muted-foreground) 70%, transparent) !important;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2084
2112
|
.\!bg-transparent {
|
|
2085
2113
|
background-color: transparent !important;
|
|
2086
2114
|
}
|
|
@@ -2300,6 +2328,12 @@
|
|
|
2300
2328
|
background-color: color-mix(in oklab, var(--color-card) 80%, transparent);
|
|
2301
2329
|
}
|
|
2302
2330
|
}
|
|
2331
|
+
.bg-card\/90 {
|
|
2332
|
+
background-color: color-mix(in srgb, hsl(var(--card)) 90%, transparent);
|
|
2333
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2334
|
+
background-color: color-mix(in oklab, var(--color-card) 90%, transparent);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2303
2337
|
.bg-destructive {
|
|
2304
2338
|
background-color: var(--color-destructive);
|
|
2305
2339
|
}
|
|
@@ -3893,6 +3927,9 @@
|
|
|
3893
3927
|
border-color: color-mix(in oklab, var(--color-primary) 50%, transparent);
|
|
3894
3928
|
}
|
|
3895
3929
|
}
|
|
3930
|
+
.hover\:\!bg-primary:hover {
|
|
3931
|
+
background-color: var(--color-primary) !important;
|
|
3932
|
+
}
|
|
3896
3933
|
.hover\:bg-\[var\(--accent-surface-soft\)\]:hover {
|
|
3897
3934
|
background-color: var(--accent-surface-soft);
|
|
3898
3935
|
}
|
package/dist/workflows.d.ts
CHANGED
|
@@ -119,14 +119,80 @@ interface WfNode {
|
|
|
119
119
|
height: number;
|
|
120
120
|
data: WfNodeData;
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Node ids are PUBLIC contract, not an internal detail. A host keys its live
|
|
124
|
+
* `nodeState` by them, points the graph's `selectedNodeId` at one, and — once it
|
|
125
|
+
* supplies a declared topology — names its own edge endpoints with them. They
|
|
126
|
+
* are bare strings, so a host that re-derives the format at the call site gets
|
|
127
|
+
* no type error when the format changes here; the graph simply renders without
|
|
128
|
+
* edges. The format is therefore written in exactly one place (these helpers)
|
|
129
|
+
* and read everywhere else through them.
|
|
130
|
+
*/
|
|
131
|
+
/** The workflow's first (or only) trigger. */
|
|
132
|
+
declare const TRIGGER_NODE_ID = "trigger";
|
|
133
|
+
/** The node for the `index`th entry of a list-form `on:`. Entry 0 IS
|
|
134
|
+
* {@link TRIGGER_NODE_ID}, so a single-trigger graph — the overwhelmingly
|
|
135
|
+
* common one — keeps the plain id a host may already have persisted.
|
|
136
|
+
*
|
|
137
|
+
* `index` is a position in the definition's `on:` list: a non-negative
|
|
138
|
+
* integer. Anything else formats an id no node bears, which a declared
|
|
139
|
+
* topology then rejects by name ("…names "trigger:NaN", which this definition
|
|
140
|
+
* has no step for") — reported there rather than thrown from here, because
|
|
141
|
+
* {@link buildWorkflowGraph} calls this and must never throw. */
|
|
142
|
+
declare function triggerNodeId(index: number): string;
|
|
143
|
+
/** The `on:` entry a trigger node stands for, or null when the id names
|
|
144
|
+
* anything else — so a host can tell a trigger from an action without
|
|
145
|
+
* matching the id format itself. */
|
|
146
|
+
declare function triggerNodeIndex(nodeId: string): number | null;
|
|
147
|
+
/** The node for the `do` entry at `index`. */
|
|
148
|
+
declare function actionNodeId(index: number): string;
|
|
149
|
+
/** The node for the `branchIndex`th fan-out leaf of the `do` entry at
|
|
150
|
+
* `actionIndex` — a `parallel` branch or a `foreach` template. */
|
|
151
|
+
declare function branchNodeId(actionIndex: number, branchIndex: number): string;
|
|
122
152
|
type WfEdgeKind = "spine" | "fork" | "join";
|
|
123
153
|
interface WfEdge {
|
|
124
154
|
id: string;
|
|
125
155
|
source: string;
|
|
126
156
|
target: string;
|
|
127
157
|
/** Spine = trigger→action→action; fork = fan-out into a branch leaf; join =
|
|
128
|
-
* a branch leaf reconverging onto the next spine node. Drives edge styling.
|
|
158
|
+
* a branch leaf reconverging onto the next spine node. Drives edge styling.
|
|
159
|
+
*
|
|
160
|
+
* A DECLARED edge ({@link BuildWorkflowGraphOptions.edges}) is a `spine`
|
|
161
|
+
* edge: it is the flow. Fork edges survive a declared topology unchanged (a
|
|
162
|
+
* branch leaf is this module's own node, which no declared spec addresses);
|
|
163
|
+
* join edges do not exist under one, because what follows a fan-out is then
|
|
164
|
+
* declared rather than inferred from list position. */
|
|
129
165
|
kind: WfEdgeKind;
|
|
166
|
+
/** Short, already-human summary of the edge's guard, when it carries one.
|
|
167
|
+
* Supplied by {@link WfEdgeSpec.whenLabel} — this module never interprets a
|
|
168
|
+
* condition, it only places the label its host wrote. */
|
|
169
|
+
whenLabel?: string;
|
|
170
|
+
/** True when this edge closes a cycle: it points back at a node that is
|
|
171
|
+
* already on the path reaching it. Rendered distinctly (dashed, with the
|
|
172
|
+
* visit budget) because such an edge is the one that can run a node twice. */
|
|
173
|
+
backEdge?: boolean;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* One edge of a DECLARED topology — the caller's answer to "what actually
|
|
177
|
+
* connects to what", replacing the positional spine this module would otherwise
|
|
178
|
+
* infer from `do`-list order.
|
|
179
|
+
*
|
|
180
|
+
* Endpoints are node ids from THIS graph, so name them with {@link actionNodeId}
|
|
181
|
+
* / {@link branchNodeId} / {@link TRIGGER_NODE_ID} rather than by hand. Edges
|
|
182
|
+
* into a node that has none are how a root is identified: every trigger node
|
|
183
|
+
* gets an edge to every root, so the caller declares only the topology it owns
|
|
184
|
+
* and never has to restate what the trigger connects to.
|
|
185
|
+
*
|
|
186
|
+
* The guard arrives PRE-SUMMARIZED (`whenLabel`). A condition's schema belongs
|
|
187
|
+
* to the system that compiles and evaluates it — this library renders graphs and
|
|
188
|
+
* has no business owning a second, drifting interpretation of one. The same
|
|
189
|
+
* summary the host writes here is then the one it can show elsewhere (a skipped
|
|
190
|
+
* step's row), which is what keeps the two readings identical.
|
|
191
|
+
*/
|
|
192
|
+
interface WfEdgeSpec {
|
|
193
|
+
from: string;
|
|
194
|
+
to: string;
|
|
195
|
+
whenLabel?: string;
|
|
130
196
|
}
|
|
131
197
|
interface WfGraph {
|
|
132
198
|
nodes: WfNode[];
|
|
@@ -151,12 +217,29 @@ interface BuildWorkflowGraphOptions {
|
|
|
151
217
|
/** Collapse every node to the fixed icon-tile size, and pitch the layers for
|
|
152
218
|
* it. Defaults to `false` (the full, expanded card). */
|
|
153
219
|
compact?: boolean;
|
|
220
|
+
/**
|
|
221
|
+
* The graph's DECLARED topology. Omit — the default — and edges are inferred
|
|
222
|
+
* from `do`-list order: a linear spine, which is exactly right for a workflow
|
|
223
|
+
* that runs as one, and wrong for any workflow whose definition declares its
|
|
224
|
+
* own edges (`needs`, guards, cycles). Supply it and the inferred spine is
|
|
225
|
+
* replaced wholesale by these edges, the layout is re-ranked to the shape they
|
|
226
|
+
* describe (so a diamond reads as a diamond rather than a chain drawn over
|
|
227
|
+
* one), and cycle-closing edges are marked {@link WfEdge.backEdge}.
|
|
228
|
+
*
|
|
229
|
+
* An edge naming a node this graph has no slot for is an ERROR
|
|
230
|
+
* ({@link WfGraph.error}), never a quiet fall back to the positional spine:
|
|
231
|
+
* the two disagreeing means the topology and the definition came from
|
|
232
|
+
* different places, and a graph that draws edges the run will not take is
|
|
233
|
+
* worse than one that says it cannot be drawn.
|
|
234
|
+
*/
|
|
235
|
+
edges?: readonly WfEdgeSpec[];
|
|
154
236
|
}
|
|
155
237
|
/** Build a positioned graph from a workflow YAML string. Never throws —
|
|
156
238
|
* malformed YAML or an empty definition returns an `error` the UI can fall
|
|
157
239
|
* back on (e.g. show the raw YAML while authoring). `reserveRunState` leaves
|
|
158
240
|
* room for the rows live run state adds (see {@link nodeHeight}); `direction`
|
|
159
|
-
* picks the flow axis (default "LR"); `compact` collapses nodes to icon tiles
|
|
241
|
+
* picks the flow axis (default "LR"); `compact` collapses nodes to icon tiles;
|
|
242
|
+
* `edges` replaces the inferred positional spine with a declared topology. */
|
|
160
243
|
declare function buildWorkflowGraph(yaml: string, options?: BuildWorkflowGraphOptions): WfGraph;
|
|
161
244
|
|
|
162
245
|
interface WorkflowGraphProps {
|
|
@@ -187,11 +270,46 @@ interface WorkflowGraphProps {
|
|
|
187
270
|
* fresh record each update — e.g. from a poll/SSE tick — satisfies this.
|
|
188
271
|
*/
|
|
189
272
|
nodeState?: Record<string, WfNodeState>;
|
|
273
|
+
/**
|
|
274
|
+
* The graph's DECLARED topology, replacing the spine inferred from `do`-list
|
|
275
|
+
* order — see {@link WfEdgeSpec}. Name endpoints with the exported id helpers
|
|
276
|
+
* (`actionNodeId`, `branchNodeId`, `TRIGGER_NODE_ID`).
|
|
277
|
+
*
|
|
278
|
+
* Immutability contract, as for `nodeState`: the layout memo keys on this
|
|
279
|
+
* array's reference, so pass a stable one (a `useMemo`, or a value derived
|
|
280
|
+
* once per fetch) rather than a fresh literal each render.
|
|
281
|
+
*/
|
|
282
|
+
edges?: readonly WfEdgeSpec[];
|
|
283
|
+
/** Per-node visit budget for a cyclic graph, shown on cycle-closing edges.
|
|
284
|
+
* Meaningless without `edges` (an inferred spine cannot loop). */
|
|
285
|
+
maxNodeVisits?: number;
|
|
286
|
+
/** The node to ring as selected — e.g. the one whose detail panel is open.
|
|
287
|
+
* Selection is the host's state; the graph only reflects it. */
|
|
288
|
+
selectedNodeId?: string;
|
|
190
289
|
/** Click handler for a node (e.g. open a detail drawer). Absent ⇒ nodes are
|
|
191
290
|
* non-interactive on click. */
|
|
192
291
|
onNodeClick?: (nodeId: string, data: WfNodeData) => void;
|
|
292
|
+
/**
|
|
293
|
+
* Editing gestures. Supplying `onEdgeConnect` turns the canvas from a diagram
|
|
294
|
+
* into an EDITOR: node handles become visible and draggable, an edge can be
|
|
295
|
+
* selected and removed with Delete/Backspace, and clicking one asks to edit
|
|
296
|
+
* its guard. Omit all three — the default — and the graph stays the read-only
|
|
297
|
+
* visualisation it has always been.
|
|
298
|
+
*
|
|
299
|
+
* Every callback speaks node ids (`actionNodeId`, `branchNodeId`), never
|
|
300
|
+
* positions: this component reports the gesture, and turning it into a
|
|
301
|
+
* definition edit is the host's job — it owns the YAML, and only it knows
|
|
302
|
+
* what a `needs` row is. Fan-out and trigger edges never fire any of these
|
|
303
|
+
* ({@link isEditableEdge}), because neither is a row in anyone's topology.
|
|
304
|
+
*
|
|
305
|
+
* The canvas holds no pending state: an accepted edit comes back as new
|
|
306
|
+
* `yaml` + `edges`, and a rejected one simply never arrives.
|
|
307
|
+
*/
|
|
308
|
+
onEdgeConnect?: (sourceId: string, targetId: string) => void;
|
|
309
|
+
onEdgeDelete?: (sourceId: string, targetId: string) => void;
|
|
310
|
+
onEdgeClick?: (sourceId: string, targetId: string) => void;
|
|
193
311
|
}
|
|
194
312
|
|
|
195
313
|
declare function WorkflowGraph(props: WorkflowGraphProps): React.JSX.Element;
|
|
196
314
|
|
|
197
|
-
export { type BuildWorkflowGraphOptions, type WfEdge, type WfGraph, type WfNode, type WfNodeData, type WfNodeState, type WfNodeStatus, type WfNodeTone, WorkflowGraph as WorkflowGraphLazy, type WorkflowGraphProps, buildWorkflowGraph };
|
|
315
|
+
export { type BuildWorkflowGraphOptions, TRIGGER_NODE_ID, type WfEdge, type WfEdgeKind, type WfEdgeSpec, type WfGraph, type WfNode, type WfNodeData, type WfNodeState, type WfNodeStatus, type WfNodeTone, WorkflowGraph as WorkflowGraphLazy, type WorkflowGraphProps, actionNodeId, branchNodeId, buildWorkflowGraph, triggerNodeId, triggerNodeIndex };
|
package/dist/workflows.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
TRIGGER_NODE_ID,
|
|
3
|
+
actionNodeId,
|
|
4
|
+
branchNodeId,
|
|
5
|
+
buildWorkflowGraph,
|
|
6
|
+
triggerNodeId,
|
|
7
|
+
triggerNodeIndex
|
|
8
|
+
} from "./chunk-YOSNN3QU.js";
|
|
4
9
|
import {
|
|
5
10
|
ModelBrandStack,
|
|
6
11
|
modelBrandFor
|
|
@@ -31,7 +36,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
|
|
|
31
36
|
function lazyWorkflowGraph() {
|
|
32
37
|
return lazy(
|
|
33
38
|
() => retryImport(
|
|
34
|
-
() => import("./WorkflowGraph-
|
|
39
|
+
() => import("./WorkflowGraph-YII62JEW.js").then((m) => ({ default: m.WorkflowGraph }))
|
|
35
40
|
)
|
|
36
41
|
);
|
|
37
42
|
}
|
|
@@ -103,7 +108,12 @@ function WorkflowGraph(props) {
|
|
|
103
108
|
}
|
|
104
109
|
export {
|
|
105
110
|
ModelBrandStack,
|
|
111
|
+
TRIGGER_NODE_ID,
|
|
106
112
|
WorkflowGraph as WorkflowGraphLazy,
|
|
113
|
+
actionNodeId,
|
|
114
|
+
branchNodeId,
|
|
107
115
|
buildWorkflowGraph,
|
|
108
|
-
modelBrandFor
|
|
116
|
+
modelBrandFor,
|
|
117
|
+
triggerNodeId,
|
|
118
|
+
triggerNodeIndex
|
|
109
119
|
};
|
package/package.json
CHANGED