@tangle-network/sandbox-ui 0.94.3 → 0.95.0
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.
|
@@ -2,19 +2,22 @@ import {
|
|
|
2
2
|
ProviderIcon
|
|
3
3
|
} from "./chunk-GTJ5PSK5.js";
|
|
4
4
|
import {
|
|
5
|
+
ACTION_OUTPUT_ROWS,
|
|
6
|
+
AGENT_BODY_ROWS,
|
|
5
7
|
COMPACT_NODE_SIZE,
|
|
6
8
|
COMPACT_TILE,
|
|
7
9
|
OUTPUT_PREVIEW_CHARS,
|
|
8
|
-
|
|
10
|
+
STRUCTURAL_OUTPUT_ROWS,
|
|
9
11
|
buildWorkflowGraph,
|
|
10
12
|
clampPreview,
|
|
11
13
|
fmtCost,
|
|
12
14
|
fmtDuration,
|
|
13
15
|
fmtTokens,
|
|
16
|
+
isStructuralKind,
|
|
14
17
|
providerLabel,
|
|
15
18
|
shortModel,
|
|
16
19
|
triggerNodeIndex
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-O7MHUR4U.js";
|
|
18
21
|
import {
|
|
19
22
|
ModelBrandStack,
|
|
20
23
|
modelBrandFor
|
|
@@ -28,7 +31,6 @@ import {
|
|
|
28
31
|
BaseEdge,
|
|
29
32
|
Controls,
|
|
30
33
|
EdgeLabelRenderer,
|
|
31
|
-
getNodesBounds,
|
|
32
34
|
getSmoothStepPath,
|
|
33
35
|
getViewportForBounds,
|
|
34
36
|
Handle,
|
|
@@ -54,6 +56,12 @@ import {
|
|
|
54
56
|
// src/workflows/flow-graph.ts
|
|
55
57
|
import { Position } from "@xyflow/react";
|
|
56
58
|
var WF_EDGE_TYPE = "wfEdge";
|
|
59
|
+
var SIDE_POSITION = {
|
|
60
|
+
left: Position.Left,
|
|
61
|
+
right: Position.Right,
|
|
62
|
+
top: Position.Top,
|
|
63
|
+
bottom: Position.Bottom
|
|
64
|
+
};
|
|
57
65
|
function isNodeStateMap(arg) {
|
|
58
66
|
const values = Object.values(arg);
|
|
59
67
|
return values.length > 0 && values.every(
|
|
@@ -62,7 +70,7 @@ function isNodeStateMap(arg) {
|
|
|
62
70
|
}
|
|
63
71
|
function normalizeFlowGraphOptions(arg) {
|
|
64
72
|
if (!arg) return {};
|
|
65
|
-
if ("nodeState" in arg || "direction" in arg || "compact" in arg || "edges" in arg) {
|
|
73
|
+
if ("nodeState" in arg || "direction" in arg || "compact" in arg || "wrap" in arg || "edges" in arg) {
|
|
66
74
|
return arg;
|
|
67
75
|
}
|
|
68
76
|
return isNodeStateMap(arg) ? { nodeState: arg } : {};
|
|
@@ -76,6 +84,7 @@ function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
|
76
84
|
reserveRunState: nodeState !== void 0,
|
|
77
85
|
direction,
|
|
78
86
|
compact,
|
|
87
|
+
...options.wrap ? { wrap: true } : {},
|
|
79
88
|
...options.edges ? { edges: options.edges } : {}
|
|
80
89
|
});
|
|
81
90
|
const isLR = direction === "LR";
|
|
@@ -100,8 +109,11 @@ function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
|
100
109
|
// Fix the DOM box to the laid-out size so the card can't grow past its
|
|
101
110
|
// reserved space and overlap a neighbour (see model.ts nodeHeight).
|
|
102
111
|
style: { width: n.width, height: n.height },
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
// A folded layout names each node's own sides (a mirrored row, a corner
|
|
113
|
+
// that leaves downward); a straight one names none and takes the
|
|
114
|
+
// direction's answer for every node.
|
|
115
|
+
sourcePosition: n.sourceSide ? SIDE_POSITION[n.sourceSide] : sourcePosition,
|
|
116
|
+
targetPosition: n.targetSide ? SIDE_POSITION[n.targetSide] : targetPosition,
|
|
105
117
|
data: nodeState?.[n.id] ? { ...n.data, state: nodeState[n.id] } : n.data
|
|
106
118
|
})
|
|
107
119
|
),
|
|
@@ -203,19 +215,23 @@ function classifyOutput(preview, condenseMarkdown = false) {
|
|
|
203
215
|
var CLAMP_BY_ROWS = {
|
|
204
216
|
1: "truncate",
|
|
205
217
|
2: "line-clamp-2",
|
|
206
|
-
3: "line-clamp-3"
|
|
218
|
+
3: "line-clamp-3",
|
|
219
|
+
4: "line-clamp-4",
|
|
220
|
+
5: "line-clamp-5",
|
|
221
|
+
6: "line-clamp-6"
|
|
207
222
|
};
|
|
208
223
|
function NodeOutputBody({
|
|
209
224
|
shape,
|
|
210
225
|
rows = 2,
|
|
211
226
|
tone = "default"
|
|
212
227
|
}) {
|
|
213
|
-
const
|
|
228
|
+
const unclamped = rows === "none";
|
|
229
|
+
const clampClass = unclamped ? "" : CLAMP_BY_ROWS[rows] ?? "line-clamp-2";
|
|
214
230
|
const codeWrap = rows === 1 ? "" : "whitespace-pre-wrap break-all";
|
|
215
231
|
if (shape.kind === "empty") return null;
|
|
216
232
|
if (shape.kind === "json") {
|
|
217
|
-
const overflow = shape.truncated || shape.entries.length > rows;
|
|
218
|
-
const visible = shape.entries.slice(0, overflow ? Math.max(0, rows - 1) : rows);
|
|
233
|
+
const overflow = unclamped ? shape.truncated : shape.truncated || shape.entries.length > rows;
|
|
234
|
+
const visible = unclamped ? shape.entries : shape.entries.slice(0, overflow ? Math.max(0, rows - 1) : rows);
|
|
219
235
|
const isError = tone === "error";
|
|
220
236
|
const keyStyle = isError ? ERROR_TEXT : void 0;
|
|
221
237
|
const keyClass = isError ? "" : "text-muted-foreground";
|
|
@@ -283,7 +299,8 @@ import {
|
|
|
283
299
|
Webhook,
|
|
284
300
|
Zap
|
|
285
301
|
} from "lucide-react";
|
|
286
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
302
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
303
|
+
var MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
|
|
287
304
|
var TONE_ACCENT = {
|
|
288
305
|
trigger: "hsl(var(--primary))",
|
|
289
306
|
structural: "var(--surface-warning-text)",
|
|
@@ -378,6 +395,62 @@ var KIND_ICON = {
|
|
|
378
395
|
"script.run": Code,
|
|
379
396
|
"trace.analyze": ScanSearch
|
|
380
397
|
};
|
|
398
|
+
function StatusPill({ status }) {
|
|
399
|
+
return /* @__PURE__ */ jsx2(
|
|
400
|
+
"span",
|
|
401
|
+
{
|
|
402
|
+
className: "shrink-0 whitespace-nowrap rounded-full border px-2 py-[1px] font-medium text-[10px]",
|
|
403
|
+
style: STATUS_PILL[status],
|
|
404
|
+
children: STATUS_LABEL[status]
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
function StatusFooter({
|
|
409
|
+
status,
|
|
410
|
+
rounds,
|
|
411
|
+
cost,
|
|
412
|
+
tokens,
|
|
413
|
+
elapsed
|
|
414
|
+
}) {
|
|
415
|
+
const roundsLabel = rounds !== void 0 ? `${rounds} round${rounds === 1 ? "" : "s"}` : void 0;
|
|
416
|
+
const caption = [roundsLabel, cost, tokens].filter(Boolean).join(" \xB7 ");
|
|
417
|
+
return /* @__PURE__ */ jsxs2("div", { className: "wf-node-body-in mt-auto border-border border-t", children: [
|
|
418
|
+
/* @__PURE__ */ jsx2(
|
|
419
|
+
"div",
|
|
420
|
+
{
|
|
421
|
+
className: "h-1 w-full overflow-hidden",
|
|
422
|
+
style: { background: MUTED_TRACK },
|
|
423
|
+
children: /* @__PURE__ */ jsx2(
|
|
424
|
+
"div",
|
|
425
|
+
{
|
|
426
|
+
"data-testid": "wf-node-progress",
|
|
427
|
+
className: `h-full ${status === "running" ? "animate-pulse" : ""}`,
|
|
428
|
+
style: {
|
|
429
|
+
width: progressFill(status),
|
|
430
|
+
background: STATUS_COLOR[status]
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
),
|
|
436
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3.5 py-1 text-[10px] text-muted-foreground leading-[15px]", children: [
|
|
437
|
+
/* @__PURE__ */ jsx2("span", { className: "min-h-[15px] truncate tabular-nums", children: caption }),
|
|
438
|
+
/* @__PURE__ */ jsx2("span", { className: "min-h-[15px] shrink-0 tabular-nums", children: elapsed ?? "" })
|
|
439
|
+
] })
|
|
440
|
+
] });
|
|
441
|
+
}
|
|
442
|
+
function emptySlotLabel(status) {
|
|
443
|
+
switch (status) {
|
|
444
|
+
case "queued":
|
|
445
|
+
return "Not run yet";
|
|
446
|
+
case "succeeded":
|
|
447
|
+
return "No output";
|
|
448
|
+
case "failed":
|
|
449
|
+
return "No error reported";
|
|
450
|
+
default:
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
381
454
|
function NodeMark({
|
|
382
455
|
kind,
|
|
383
456
|
provider,
|
|
@@ -425,12 +498,44 @@ function NodeMark({
|
|
|
425
498
|
}
|
|
426
499
|
|
|
427
500
|
// src/workflows/WorkflowGraph.tsx
|
|
428
|
-
import { Fragment, jsx as jsx3, jsxs as
|
|
429
|
-
var
|
|
430
|
-
var FIT_VIEW = { padding: 0.16, minZoom: 0.55 };
|
|
501
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
502
|
+
var FIT_VIEW = { padding: 0.16 };
|
|
431
503
|
function fitZoomCeiling(compact) {
|
|
432
504
|
return compact ? 1.5 : 1;
|
|
433
505
|
}
|
|
506
|
+
function fitZoomFloor(compact) {
|
|
507
|
+
return compact ? 0.55 : 0.35;
|
|
508
|
+
}
|
|
509
|
+
function layoutBounds(nodes) {
|
|
510
|
+
if (nodes.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
|
|
511
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
512
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
513
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
514
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
515
|
+
for (const node of nodes) {
|
|
516
|
+
minX = Math.min(minX, node.position.x);
|
|
517
|
+
minY = Math.min(minY, node.position.y);
|
|
518
|
+
maxX = Math.max(maxX, node.position.x + (node.width ?? 0));
|
|
519
|
+
maxY = Math.max(maxY, node.position.y + (node.height ?? 0));
|
|
520
|
+
}
|
|
521
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
522
|
+
}
|
|
523
|
+
function framingViewport(bounds, width, height, compact) {
|
|
524
|
+
const fit = getViewportForBounds(
|
|
525
|
+
bounds,
|
|
526
|
+
width,
|
|
527
|
+
height,
|
|
528
|
+
fitZoomFloor(compact),
|
|
529
|
+
fitZoomCeiling(compact),
|
|
530
|
+
FIT_VIEW.padding
|
|
531
|
+
);
|
|
532
|
+
const anchored = (offset, origin) => offset + Math.max(0, -(offset + origin * fit.zoom));
|
|
533
|
+
return {
|
|
534
|
+
zoom: fit.zoom,
|
|
535
|
+
x: anchored(fit.x, bounds.x),
|
|
536
|
+
y: anchored(fit.y, bounds.y)
|
|
537
|
+
};
|
|
538
|
+
}
|
|
434
539
|
var LAYOUT_TRANSITION_MS = 220;
|
|
435
540
|
var DENSITY_FLIP_AT = { grow: 0.12, shrink: 0.75 };
|
|
436
541
|
function motionAllowed() {
|
|
@@ -455,6 +560,7 @@ function useColorMode() {
|
|
|
455
560
|
var DirectionContext = createContext("LR");
|
|
456
561
|
var DensityContext = createContext(false);
|
|
457
562
|
var SelectedNodeContext = createContext(void 0);
|
|
563
|
+
var RunModeContext = createContext(false);
|
|
458
564
|
var SELECTION_OUTLINE = {
|
|
459
565
|
outline: "2px solid hsl(var(--primary))",
|
|
460
566
|
outlineOffset: 2
|
|
@@ -463,59 +569,6 @@ var ConnectableContext = createContext(false);
|
|
|
463
569
|
function isEditableEdge(edge) {
|
|
464
570
|
return edge.data?.kind !== "fork" && triggerNodeIndex(edge.source) === null;
|
|
465
571
|
}
|
|
466
|
-
function StatusFooter({
|
|
467
|
-
status,
|
|
468
|
-
rounds,
|
|
469
|
-
elapsed
|
|
470
|
-
}) {
|
|
471
|
-
const roundsLabel = rounds !== void 0 ? `${rounds} round${rounds === 1 ? "" : "s"}` : void 0;
|
|
472
|
-
return /* @__PURE__ */ jsxs2("div", { className: "wf-node-body-in mt-auto border-border border-t", children: [
|
|
473
|
-
/* @__PURE__ */ jsx3(
|
|
474
|
-
"div",
|
|
475
|
-
{
|
|
476
|
-
className: "h-1 w-full overflow-hidden",
|
|
477
|
-
style: { background: MUTED_TRACK },
|
|
478
|
-
children: /* @__PURE__ */ jsx3(
|
|
479
|
-
"div",
|
|
480
|
-
{
|
|
481
|
-
"data-testid": "wf-node-progress",
|
|
482
|
-
className: `h-full ${status === "running" ? "animate-pulse" : ""}`,
|
|
483
|
-
style: {
|
|
484
|
-
width: progressFill(status),
|
|
485
|
-
background: STATUS_COLOR[status]
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
)
|
|
489
|
-
}
|
|
490
|
-
),
|
|
491
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3.5 py-1 text-[10px] text-muted-foreground leading-[15px]", children: [
|
|
492
|
-
/* @__PURE__ */ jsx3("span", { className: "min-h-[15px] truncate", children: roundsLabel ?? "" }),
|
|
493
|
-
/* @__PURE__ */ jsx3("span", { className: "min-h-[15px] shrink-0 tabular-nums", children: elapsed ?? "" })
|
|
494
|
-
] })
|
|
495
|
-
] });
|
|
496
|
-
}
|
|
497
|
-
function emptySlotLabel(status) {
|
|
498
|
-
switch (status) {
|
|
499
|
-
case "queued":
|
|
500
|
-
return "Not run yet";
|
|
501
|
-
case "succeeded":
|
|
502
|
-
return "No output";
|
|
503
|
-
case "failed":
|
|
504
|
-
return "No error reported";
|
|
505
|
-
default:
|
|
506
|
-
return void 0;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
function StatusPill({ status }) {
|
|
510
|
-
return /* @__PURE__ */ jsx3(
|
|
511
|
-
"span",
|
|
512
|
-
{
|
|
513
|
-
className: "shrink-0 rounded-full border px-2 py-[1px] font-medium text-[10px]",
|
|
514
|
-
style: STATUS_PILL[status],
|
|
515
|
-
children: STATUS_LABEL[status]
|
|
516
|
-
}
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
572
|
var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
520
573
|
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";
|
|
521
574
|
function compactHandleStyle(position, isLR) {
|
|
@@ -539,22 +592,32 @@ function compactHandleStyle(position, isLR) {
|
|
|
539
592
|
return anchor(tileLeft + center, COMPACT_TILE);
|
|
540
593
|
}
|
|
541
594
|
}
|
|
542
|
-
function WorkflowNode({
|
|
595
|
+
function WorkflowNode({
|
|
596
|
+
id,
|
|
597
|
+
data,
|
|
598
|
+
// Set only where the LAYOUT decides a node's own sides — a folded row that
|
|
599
|
+
// runs right-to-left, or the step that turns the corner and leaves downward.
|
|
600
|
+
// A straight layer flow leaves them to the direction, below.
|
|
601
|
+
sourcePosition,
|
|
602
|
+
targetPosition
|
|
603
|
+
}) {
|
|
543
604
|
const d = data;
|
|
544
605
|
const state = d.state;
|
|
545
606
|
const direction = useContext(DirectionContext);
|
|
546
607
|
const compact = useContext(DensityContext);
|
|
608
|
+
const runMode = useContext(RunModeContext);
|
|
547
609
|
const connectable = useContext(ConnectableContext);
|
|
548
610
|
const hostSelection = useContext(SelectedNodeContext);
|
|
549
611
|
const selected = hostSelection !== void 0 && hostSelection === id;
|
|
550
612
|
const isLR = direction === "LR";
|
|
551
|
-
const targetPos = isLR ? Position2.Left : Position2.Top;
|
|
552
|
-
const sourcePos = isLR ? Position2.Right : Position2.Bottom;
|
|
613
|
+
const targetPos = targetPosition ?? (isLR ? Position2.Left : Position2.Top);
|
|
614
|
+
const sourcePos = sourcePosition ?? (isLR ? Position2.Right : Position2.Bottom);
|
|
553
615
|
const accent = TONE_ACCENT[d.tone];
|
|
554
616
|
const isAgent = d.kind === "agent.run";
|
|
555
617
|
const subtitle = isAgent && state?.model ? shortModel(state.model) : d.subtitle;
|
|
556
618
|
const markModel = isAgent && state?.model ? state.model : d.model;
|
|
557
|
-
const runBands =
|
|
619
|
+
const runBands = runMode && d.tone !== "trigger";
|
|
620
|
+
const runStatus = runBands ? state?.status ?? "queued" : state?.status;
|
|
558
621
|
const duration = fmtDuration(state?.durationMs);
|
|
559
622
|
const cost = fmtCost(state?.costUsd);
|
|
560
623
|
const tokens = isAgent ? fmtTokens(state?.inputTokens, state?.outputTokens) : void 0;
|
|
@@ -574,7 +637,7 @@ function WorkflowNode({ id, data }) {
|
|
|
574
637
|
return shape.kind === "empty" ? null : { shape, tone: source.tone, label: source.label };
|
|
575
638
|
})();
|
|
576
639
|
const handleClass = connectable ? HANDLE_CONNECTABLE_CLASS : HANDLE_CLASS;
|
|
577
|
-
const handles = /* @__PURE__ */
|
|
640
|
+
const handles = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
578
641
|
!d.isRoot && /* @__PURE__ */ jsx3(
|
|
579
642
|
Handle,
|
|
580
643
|
{
|
|
@@ -595,13 +658,13 @@ function WorkflowNode({ id, data }) {
|
|
|
595
658
|
)
|
|
596
659
|
] });
|
|
597
660
|
if (compact) {
|
|
598
|
-
return /* @__PURE__ */
|
|
661
|
+
return /* @__PURE__ */ jsxs3(
|
|
599
662
|
"div",
|
|
600
663
|
{
|
|
601
664
|
className: `relative flex h-full w-full ${isLR ? "flex-col items-center" : "flex-row items-center"}`,
|
|
602
665
|
children: [
|
|
603
666
|
handles,
|
|
604
|
-
/* @__PURE__ */
|
|
667
|
+
/* @__PURE__ */ jsxs3(
|
|
605
668
|
"span",
|
|
606
669
|
{
|
|
607
670
|
className: "relative flex items-center justify-center rounded-xl border bg-card shadow-sm transition-colors",
|
|
@@ -641,7 +704,7 @@ function WorkflowNode({ id, data }) {
|
|
|
641
704
|
]
|
|
642
705
|
}
|
|
643
706
|
),
|
|
644
|
-
/* @__PURE__ */
|
|
707
|
+
/* @__PURE__ */ jsxs3(
|
|
645
708
|
"div",
|
|
646
709
|
{
|
|
647
710
|
className: `wf-node-body-in min-w-0 ${isLR ? "mt-2 w-full px-1 text-center" : "ml-2 flex-1 text-left"}`,
|
|
@@ -670,82 +733,250 @@ function WorkflowNode({ id, data }) {
|
|
|
670
733
|
}
|
|
671
734
|
);
|
|
672
735
|
}
|
|
673
|
-
|
|
736
|
+
const cardShell = (children) => /* @__PURE__ */ jsxs3(
|
|
674
737
|
"div",
|
|
675
738
|
{
|
|
676
739
|
className: "relative flex h-full w-full flex-col overflow-hidden rounded-xl border bg-card shadow-sm transition-colors",
|
|
677
740
|
style: {
|
|
678
|
-
...
|
|
741
|
+
...runStatus ? statusBorder(runStatus) : {
|
|
679
742
|
borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
|
|
680
743
|
},
|
|
681
744
|
...selected ? SELECTION_OUTLINE : {}
|
|
682
745
|
},
|
|
683
746
|
children: [
|
|
684
747
|
handles,
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
748
|
+
children
|
|
749
|
+
]
|
|
750
|
+
}
|
|
751
|
+
);
|
|
752
|
+
const identityRow = /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-2.5", children: [
|
|
753
|
+
/* @__PURE__ */ jsx3(
|
|
754
|
+
NodeMark,
|
|
755
|
+
{
|
|
756
|
+
kind: d.kind,
|
|
757
|
+
provider: d.provider,
|
|
758
|
+
model: markModel,
|
|
759
|
+
accent,
|
|
760
|
+
tile: 34
|
|
761
|
+
}
|
|
762
|
+
),
|
|
763
|
+
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
764
|
+
/* @__PURE__ */ jsx3(
|
|
765
|
+
"div",
|
|
766
|
+
{
|
|
767
|
+
className: "truncate font-semibold text-[13px] text-foreground leading-tight",
|
|
768
|
+
title: d.title,
|
|
769
|
+
children: d.title
|
|
770
|
+
}
|
|
771
|
+
),
|
|
772
|
+
subtitle && /* @__PURE__ */ jsx3(
|
|
773
|
+
"div",
|
|
774
|
+
{
|
|
775
|
+
className: "truncate text-[11px] text-muted-foreground leading-tight",
|
|
776
|
+
title: subtitle,
|
|
777
|
+
children: subtitle
|
|
778
|
+
}
|
|
779
|
+
)
|
|
780
|
+
] }),
|
|
781
|
+
runStatus ? /* @__PURE__ */ jsx3(StatusPill, { status: runStatus }) : d.badge && /* @__PURE__ */ jsx3("span", { className: "shrink-0 rounded-full border border-border bg-surface-container-high px-2 py-[1px] font-medium text-[10px] text-muted-foreground", children: d.badge })
|
|
782
|
+
] });
|
|
783
|
+
const footer = runStatus ? /* @__PURE__ */ jsx3(
|
|
784
|
+
StatusFooter,
|
|
785
|
+
{
|
|
786
|
+
status: runStatus,
|
|
787
|
+
rounds: isAgent ? state?.rounds : void 0,
|
|
788
|
+
cost,
|
|
789
|
+
tokens,
|
|
790
|
+
elapsed: duration
|
|
791
|
+
}
|
|
792
|
+
) : null;
|
|
793
|
+
const descriptionBand = (emphasized) => d.description ? /* @__PURE__ */ jsx3(
|
|
794
|
+
"p",
|
|
795
|
+
{
|
|
796
|
+
className: `mt-2 line-clamp-2 shrink-0 text-[11px] leading-snug ${emphasized ? "text-foreground" : "text-muted-foreground"}`,
|
|
797
|
+
title: d.description,
|
|
798
|
+
children: d.description
|
|
799
|
+
}
|
|
800
|
+
) : null;
|
|
801
|
+
const emptySlot = runStatus && !runOutput && emptySlotLabel(runStatus) ? /* @__PURE__ */ jsx3("div", { className: "mt-2 flex flex-1 items-center text-[11px] text-muted-foreground italic", children: emptySlotLabel(runStatus) }) : null;
|
|
802
|
+
const outputWell = (rows) => runOutput ? /* @__PURE__ */ jsxs3("div", { className: "mt-2 min-h-0 grow shrink-0 rounded-lg border border-border bg-surface-container-high/60 px-2 py-1.5", children: [
|
|
803
|
+
/* @__PURE__ */ jsx3("div", { className: "mb-1 font-semibold text-[9px] text-muted-foreground uppercase tracking-[0.09em]", children: runOutput.label }),
|
|
804
|
+
/* @__PURE__ */ jsx3(
|
|
805
|
+
NodeOutputBody,
|
|
806
|
+
{
|
|
807
|
+
shape: runOutput.shape,
|
|
808
|
+
tone: runOutput.tone,
|
|
809
|
+
rows
|
|
810
|
+
}
|
|
811
|
+
)
|
|
812
|
+
] }) : emptySlot;
|
|
813
|
+
const body = (children, justify = "") => (
|
|
814
|
+
/* The content region owns its overflow: each band is `shrink-0`, so a band
|
|
815
|
+
that renders taller than its reservation (a consumer's font metrics) is
|
|
816
|
+
CLIPPED here rather than squeezed — a squeezed band cuts a line of text
|
|
817
|
+
in half, and pushes the pinned footer off the card. Fades in when the
|
|
818
|
+
density swap lands mid layout-morph; the card's border/surface (the
|
|
819
|
+
root) stays opaque so the node never blinks out. */
|
|
820
|
+
/* @__PURE__ */ jsx3(
|
|
821
|
+
"div",
|
|
822
|
+
{
|
|
823
|
+
className: `wf-node-body-in flex min-h-0 flex-1 flex-col overflow-hidden px-3.5 pt-2.5 pb-2 ${justify}`,
|
|
824
|
+
children
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
);
|
|
828
|
+
if (!runBands) {
|
|
829
|
+
return cardShell(body(
|
|
830
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
831
|
+
identityRow,
|
|
832
|
+
descriptionBand(false)
|
|
833
|
+
] })
|
|
834
|
+
));
|
|
835
|
+
}
|
|
836
|
+
if (d.kind === "decision") {
|
|
837
|
+
return cardShell(
|
|
838
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
839
|
+
body(
|
|
840
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
841
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-start gap-2.5", children: [
|
|
842
|
+
/* @__PURE__ */ jsx3(
|
|
843
|
+
NodeMark,
|
|
844
|
+
{
|
|
845
|
+
kind: d.kind,
|
|
846
|
+
provider: d.provider,
|
|
847
|
+
model: markModel,
|
|
848
|
+
accent,
|
|
849
|
+
tile: 34
|
|
850
|
+
}
|
|
851
|
+
),
|
|
852
|
+
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
853
|
+
/* @__PURE__ */ jsx3(
|
|
854
|
+
"div",
|
|
855
|
+
{
|
|
856
|
+
className: "truncate font-semibold text-[13px] text-foreground leading-tight",
|
|
857
|
+
title: d.title,
|
|
858
|
+
children: d.title
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsxs3("div", { className: "mt-0.5 flex items-center gap-2", children: [
|
|
862
|
+
subtitle && /* @__PURE__ */ jsx3(
|
|
863
|
+
"span",
|
|
864
|
+
{
|
|
865
|
+
className: "truncate text-[11px] text-muted-foreground leading-tight",
|
|
866
|
+
title: subtitle,
|
|
867
|
+
children: subtitle
|
|
868
|
+
}
|
|
869
|
+
),
|
|
870
|
+
runStatus && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0", children: /* @__PURE__ */ jsx3(StatusPill, { status: runStatus }) })
|
|
871
|
+
] })
|
|
872
|
+
] })
|
|
873
|
+
] }),
|
|
874
|
+
runOutput && runStatus === "failed" ? /* @__PURE__ */ jsx3("div", { className: "mt-2 min-h-0 shrink-0", children: /* @__PURE__ */ jsx3(
|
|
875
|
+
NodeOutputBody,
|
|
689
876
|
{
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
accent,
|
|
694
|
-
tile: 34
|
|
877
|
+
shape: runOutput.shape,
|
|
878
|
+
tone: runOutput.tone,
|
|
879
|
+
rows: 2
|
|
695
880
|
}
|
|
696
|
-
)
|
|
697
|
-
|
|
881
|
+
) }) : descriptionBand(true)
|
|
882
|
+
] })
|
|
883
|
+
),
|
|
884
|
+
footer
|
|
885
|
+
] })
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (isStructuralKind(d.kind)) {
|
|
889
|
+
return cardShell(
|
|
890
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
891
|
+
body(
|
|
892
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
893
|
+
identityRow,
|
|
894
|
+
/* @__PURE__ */ jsx3("div", { className: "mt-1.5 min-h-0 shrink-0", children: runOutput && /* @__PURE__ */ jsx3(
|
|
895
|
+
NodeOutputBody,
|
|
896
|
+
{
|
|
897
|
+
shape: runOutput.shape,
|
|
898
|
+
tone: runOutput.tone,
|
|
899
|
+
rows: STRUCTURAL_OUTPUT_ROWS
|
|
900
|
+
}
|
|
901
|
+
) })
|
|
902
|
+
] }),
|
|
903
|
+
// Centered, because the failure slot is empty on every run that does
|
|
904
|
+
// NOT fail — which is most of them. Reserved space it cannot give back
|
|
905
|
+
// (the layout is fixed before the run) reads as a gap under a
|
|
906
|
+
// top-aligned row, and as breathing room around a centered one.
|
|
907
|
+
"justify-center"
|
|
908
|
+
),
|
|
909
|
+
footer
|
|
910
|
+
] })
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
if (isAgent) {
|
|
914
|
+
const strip = [d.title, subtitle].filter(Boolean).join(" \xB7 ");
|
|
915
|
+
return cardShell(
|
|
916
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
917
|
+
body(
|
|
918
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
919
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-1.5", children: [
|
|
698
920
|
/* @__PURE__ */ jsx3(
|
|
699
|
-
|
|
921
|
+
NodeMark,
|
|
700
922
|
{
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
923
|
+
kind: d.kind,
|
|
924
|
+
provider: d.provider,
|
|
925
|
+
model: markModel,
|
|
926
|
+
accent,
|
|
927
|
+
tile: 18
|
|
704
928
|
}
|
|
705
929
|
),
|
|
706
|
-
|
|
707
|
-
"
|
|
930
|
+
/* @__PURE__ */ jsx3(
|
|
931
|
+
"span",
|
|
708
932
|
{
|
|
709
|
-
className: "truncate text-[11px] text-muted-foreground
|
|
710
|
-
title:
|
|
711
|
-
children:
|
|
933
|
+
className: "truncate font-medium text-[11px] text-muted-foreground",
|
|
934
|
+
title: strip,
|
|
935
|
+
children: strip
|
|
712
936
|
}
|
|
713
|
-
)
|
|
937
|
+
),
|
|
938
|
+
/* @__PURE__ */ jsx3("span", { className: "flex-1" }),
|
|
939
|
+
runStatus && /* @__PURE__ */ jsx3(StatusPill, { status: runStatus })
|
|
714
940
|
] }),
|
|
715
|
-
|
|
716
|
-
] }),
|
|
717
|
-
d.description && /* @__PURE__ */ jsx3(
|
|
718
|
-
"p",
|
|
719
|
-
{
|
|
720
|
-
className: "mt-2 line-clamp-2 shrink-0 text-[11px] text-muted-foreground leading-snug",
|
|
721
|
-
title: d.description,
|
|
722
|
-
children: d.description
|
|
723
|
-
}
|
|
724
|
-
),
|
|
725
|
-
runBands && (cost || tokens) && /* @__PURE__ */ jsx3("div", { className: "mt-2 shrink-0 truncate text-[11px] text-muted-foreground tabular-nums", children: [cost, tokens].filter(Boolean).join(" \xB7 ") }),
|
|
726
|
-
runBands && runOutput && /* @__PURE__ */ jsxs2("div", { className: "mt-2 min-h-0 shrink-0 rounded-lg border border-border bg-surface-container-high/60 px-2 py-1.5", children: [
|
|
727
|
-
/* @__PURE__ */ jsx3("div", { className: "mb-1 font-semibold text-[9px] text-muted-foreground uppercase tracking-[0.09em]", children: runOutput.label }),
|
|
728
|
-
/* @__PURE__ */ jsx3(
|
|
941
|
+
/* @__PURE__ */ jsx3("div", { className: "mt-2 min-h-0 flex-1 text-foreground leading-snug", children: runOutput ? /* @__PURE__ */ jsx3(
|
|
729
942
|
NodeOutputBody,
|
|
730
943
|
{
|
|
731
944
|
shape: runOutput.shape,
|
|
732
945
|
tone: runOutput.tone,
|
|
733
|
-
rows:
|
|
946
|
+
rows: AGENT_BODY_ROWS
|
|
734
947
|
}
|
|
735
|
-
)
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
948
|
+
) : runStatus && runStatus !== "succeeded" && runStatus !== "failed" && d.description ? (
|
|
949
|
+
// What it is ABOUT to do, in the muted token — the answer, when it
|
|
950
|
+
// arrives, is the thing set in the foreground.
|
|
951
|
+
//
|
|
952
|
+
// Only while the node is still going to produce one. A FINISHED
|
|
953
|
+
// node that produced nothing must say so: showing its prompt
|
|
954
|
+
// there reads as the result it never returned.
|
|
955
|
+
/* @__PURE__ */ jsx3(
|
|
956
|
+
"p",
|
|
957
|
+
{
|
|
958
|
+
className: "line-clamp-5 text-[11px] text-muted-foreground leading-snug",
|
|
959
|
+
title: d.description,
|
|
960
|
+
children: d.description
|
|
961
|
+
}
|
|
962
|
+
)
|
|
963
|
+
) : runStatus && /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground italic", children: emptySlotLabel(runStatus) }) })
|
|
964
|
+
] })
|
|
965
|
+
),
|
|
966
|
+
footer
|
|
967
|
+
] })
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
return cardShell(
|
|
971
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
972
|
+
body(
|
|
973
|
+
/* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
974
|
+
identityRow,
|
|
975
|
+
outputWell(ACTION_OUTPUT_ROWS)
|
|
976
|
+
] })
|
|
977
|
+
),
|
|
978
|
+
footer
|
|
979
|
+
] })
|
|
749
980
|
);
|
|
750
981
|
}
|
|
751
982
|
var EDGE_CHIP_CLASS = "rounded-full border border-border bg-card/90 px-1.5 py-[1px] text-[10px] leading-tight backdrop-blur";
|
|
@@ -772,9 +1003,9 @@ function WfEdgeRenderer({
|
|
|
772
1003
|
const backEdge = data?.backEdge === true;
|
|
773
1004
|
const whenLabel = data?.whenLabel;
|
|
774
1005
|
const visits = data?.maxNodeVisits;
|
|
775
|
-
return /* @__PURE__ */
|
|
1006
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
776
1007
|
/* @__PURE__ */ jsx3(BaseEdge, { id, path, markerEnd, style }),
|
|
777
|
-
/* @__PURE__ */ jsx3(EdgeLabelRenderer, { children: /* @__PURE__ */
|
|
1008
|
+
/* @__PURE__ */ jsx3(EdgeLabelRenderer, { children: /* @__PURE__ */ jsxs3(
|
|
778
1009
|
"div",
|
|
779
1010
|
{
|
|
780
1011
|
className: "nodrag nopan absolute flex flex-col items-center gap-0.5",
|
|
@@ -837,8 +1068,9 @@ function WorkflowGraph({
|
|
|
837
1068
|
yaml,
|
|
838
1069
|
variant = "full",
|
|
839
1070
|
direction = "LR",
|
|
840
|
-
defaultCompact
|
|
1071
|
+
defaultCompact,
|
|
841
1072
|
className,
|
|
1073
|
+
wrap = false,
|
|
842
1074
|
nodeState,
|
|
843
1075
|
edges: declaredEdges,
|
|
844
1076
|
maxNodeVisits,
|
|
@@ -851,22 +1083,27 @@ function WorkflowGraph({
|
|
|
851
1083
|
const editable = onEdgeConnect !== void 0;
|
|
852
1084
|
const colorMode = useColorMode();
|
|
853
1085
|
const isPreview = variant === "preview";
|
|
854
|
-
const [userCompact, setUserCompact] = useState(
|
|
1086
|
+
const [userCompact, setUserCompact] = useState(
|
|
1087
|
+
defaultCompact ?? nodeState === void 0
|
|
1088
|
+
);
|
|
855
1089
|
const compact = isPreview || userCompact;
|
|
856
1090
|
const [displayCompact, setDisplayCompact] = useState(compact);
|
|
857
1091
|
const rfRef = useRef(
|
|
858
1092
|
null
|
|
859
1093
|
);
|
|
860
1094
|
const wrapperRef = useRef(null);
|
|
1095
|
+
const framedRef = useRef(false);
|
|
1096
|
+
const [framingCue, setFramingCue] = useState(0);
|
|
861
1097
|
const hasRunOverlay = nodeState !== void 0;
|
|
862
1098
|
const structural = useMemo(
|
|
863
1099
|
() => buildFlowGraph(yaml, {
|
|
864
1100
|
nodeState: hasRunOverlay ? {} : void 0,
|
|
865
1101
|
direction,
|
|
866
1102
|
compact,
|
|
1103
|
+
wrap,
|
|
867
1104
|
...declaredEdges ? { edges: declaredEdges } : {}
|
|
868
1105
|
}),
|
|
869
|
-
[yaml, hasRunOverlay, direction, compact, declaredEdges]
|
|
1106
|
+
[yaml, hasRunOverlay, direction, compact, wrap, declaredEdges]
|
|
870
1107
|
);
|
|
871
1108
|
const styledEdges = useMemo(() => {
|
|
872
1109
|
const styled = buildStyledEdges(structural.edges, nodeState, maxNodeVisits);
|
|
@@ -910,14 +1147,13 @@ function WorkflowGraph({
|
|
|
910
1147
|
setDisplayCompact(compact);
|
|
911
1148
|
return;
|
|
912
1149
|
}
|
|
913
|
-
const endViewport =
|
|
914
|
-
|
|
1150
|
+
const endViewport = framingViewport(
|
|
1151
|
+
layoutBounds(structural.nodes),
|
|
915
1152
|
frame.width,
|
|
916
1153
|
frame.height,
|
|
917
|
-
|
|
918
|
-
fitZoomCeiling(compact),
|
|
919
|
-
FIT_VIEW.padding
|
|
1154
|
+
compact
|
|
920
1155
|
);
|
|
1156
|
+
framedRef.current = true;
|
|
921
1157
|
const prev = nodesRef.current;
|
|
922
1158
|
const prevById = new Map(prev.map((n) => [n.id, n]));
|
|
923
1159
|
const sameNodeSet = prev.length === structural.nodes.length && structural.nodes.every((n) => prevById.has(n.id));
|
|
@@ -970,6 +1206,34 @@ function WorkflowGraph({
|
|
|
970
1206
|
clearTimeout(flipTimer);
|
|
971
1207
|
};
|
|
972
1208
|
}, [structural, compact, setNodes]);
|
|
1209
|
+
useEffect(() => {
|
|
1210
|
+
if (framedRef.current) return;
|
|
1211
|
+
const inst = rfRef.current;
|
|
1212
|
+
const frame = wrapperRef.current?.getBoundingClientRect();
|
|
1213
|
+
if (!inst || !frame || frame.width === 0 || frame.height === 0) return;
|
|
1214
|
+
inst.setViewport(
|
|
1215
|
+
framingViewport(
|
|
1216
|
+
layoutBounds(structural.nodes),
|
|
1217
|
+
frame.width,
|
|
1218
|
+
frame.height,
|
|
1219
|
+
compact
|
|
1220
|
+
)
|
|
1221
|
+
);
|
|
1222
|
+
framedRef.current = true;
|
|
1223
|
+
}, [framingCue, structural, compact]);
|
|
1224
|
+
useEffect(() => {
|
|
1225
|
+
const el = wrapperRef.current;
|
|
1226
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
1227
|
+
const observer = new ResizeObserver(() => {
|
|
1228
|
+
if (framedRef.current) {
|
|
1229
|
+
observer.disconnect();
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
setFramingCue((cue) => cue + 1);
|
|
1233
|
+
});
|
|
1234
|
+
observer.observe(el);
|
|
1235
|
+
return () => observer.disconnect();
|
|
1236
|
+
}, []);
|
|
973
1237
|
const handleNodeClick = useCallback(
|
|
974
1238
|
(_event, node) => {
|
|
975
1239
|
onNodeClick?.(node.id, node.data);
|
|
@@ -999,7 +1263,7 @@ function WorkflowGraph({
|
|
|
999
1263
|
}
|
|
1000
1264
|
);
|
|
1001
1265
|
}
|
|
1002
|
-
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__ */
|
|
1266
|
+
return /* @__PURE__ */ jsx3(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx3(DensityContext.Provider, { value: displayCompact, children: /* @__PURE__ */ jsx3(RunModeContext.Provider, { value: hasRunOverlay, 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__ */ jsxs3(
|
|
1003
1267
|
ReactFlow,
|
|
1004
1268
|
{
|
|
1005
1269
|
nodes,
|
|
@@ -1011,11 +1275,16 @@ function WorkflowGraph({
|
|
|
1011
1275
|
colorMode,
|
|
1012
1276
|
onInit: (inst) => {
|
|
1013
1277
|
rfRef.current = inst;
|
|
1278
|
+
if (!framedRef.current) setFramingCue((cue) => cue + 1);
|
|
1014
1279
|
},
|
|
1015
1280
|
onNodeClick: onNodeClick ? handleNodeClick : void 0,
|
|
1016
1281
|
onKeyDown: onNodeClick ? handleNodeKeyDown : void 0,
|
|
1017
1282
|
fitView: true,
|
|
1018
|
-
fitViewOptions: {
|
|
1283
|
+
fitViewOptions: {
|
|
1284
|
+
...FIT_VIEW,
|
|
1285
|
+
minZoom: fitZoomFloor(compact),
|
|
1286
|
+
maxZoom: fitZoomCeiling(compact)
|
|
1287
|
+
},
|
|
1019
1288
|
proOptions: { hideAttribution: true },
|
|
1020
1289
|
nodesDraggable: !isPreview,
|
|
1021
1290
|
nodesConnectable: editable,
|
|
@@ -1049,9 +1318,9 @@ function WorkflowGraph({
|
|
|
1049
1318
|
maxZoom: 1.5,
|
|
1050
1319
|
children: [
|
|
1051
1320
|
/* @__PURE__ */ jsx3(Background, { gap: 18 }),
|
|
1052
|
-
!isPreview && /* @__PURE__ */
|
|
1321
|
+
!isPreview && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1053
1322
|
/* @__PURE__ */ jsx3(Controls, { showInteractive: false, position: "bottom-right" }),
|
|
1054
|
-
/* @__PURE__ */ jsx3(Panel, { position: "top-left", children: /* @__PURE__ */
|
|
1323
|
+
/* @__PURE__ */ jsx3(Panel, { position: "top-left", children: /* @__PURE__ */ jsxs3(
|
|
1055
1324
|
"button",
|
|
1056
1325
|
{
|
|
1057
1326
|
type: "button",
|
|
@@ -1068,17 +1337,22 @@ function WorkflowGraph({
|
|
|
1068
1337
|
] })
|
|
1069
1338
|
]
|
|
1070
1339
|
}
|
|
1071
|
-
) }) }) }) }) });
|
|
1340
|
+
) }) }) }) }) }) });
|
|
1072
1341
|
}
|
|
1073
1342
|
export {
|
|
1074
1343
|
ConnectableContext,
|
|
1075
1344
|
DensityContext,
|
|
1076
1345
|
DirectionContext,
|
|
1346
|
+
FIT_VIEW,
|
|
1077
1347
|
LAYOUT_TRANSITION_MS,
|
|
1348
|
+
RunModeContext,
|
|
1078
1349
|
SelectedNodeContext,
|
|
1079
1350
|
WorkflowGraph,
|
|
1080
1351
|
WorkflowNode,
|
|
1081
1352
|
buildStyledEdges,
|
|
1082
1353
|
fitZoomCeiling,
|
|
1083
|
-
|
|
1354
|
+
fitZoomFloor,
|
|
1355
|
+
framingViewport,
|
|
1356
|
+
isEditableEdge,
|
|
1357
|
+
layoutBounds
|
|
1084
1358
|
};
|
|
@@ -237,9 +237,29 @@ var EDGE_LABEL_LANE = 180;
|
|
|
237
237
|
var CARD_CHROME = 20;
|
|
238
238
|
var HEADER_ROW = 34;
|
|
239
239
|
var DESCRIPTION_ROW = 40;
|
|
240
|
-
var
|
|
241
|
-
var
|
|
242
|
-
var
|
|
240
|
+
var AGENT_BODY_ROWS = 5;
|
|
241
|
+
var ACTION_OUTPUT_ROWS = 3;
|
|
242
|
+
var STRUCTURAL_OUTPUT_ROWS = 1;
|
|
243
|
+
var WELL_PROSE_LINE = 15.125;
|
|
244
|
+
var WELL_JSON_LINE = 14.4375;
|
|
245
|
+
var WELL_JSON_GAP = 2;
|
|
246
|
+
var WELL_CHROME = 39.5;
|
|
247
|
+
function wellRow(rows) {
|
|
248
|
+
const body = Math.max(
|
|
249
|
+
rows * WELL_PROSE_LINE,
|
|
250
|
+
rows * WELL_JSON_LINE + (rows - 1) * WELL_JSON_GAP
|
|
251
|
+
);
|
|
252
|
+
return Math.ceil(WELL_CHROME + body) + 3;
|
|
253
|
+
}
|
|
254
|
+
var SLIM_HEADER_ROW = 22;
|
|
255
|
+
var BODY_LINE = 15.125;
|
|
256
|
+
var AGENT_BODY_ROW = Math.ceil(8 + AGENT_BODY_ROWS * BODY_LINE);
|
|
257
|
+
var CHIP_SLOT_ROW = 22;
|
|
258
|
+
var DECISION_HEADER_ROW = 45;
|
|
259
|
+
var STRUCTURAL_KINDS = /* @__PURE__ */ new Set(["parallel", "foreach"]);
|
|
260
|
+
function isStructuralKind(kind) {
|
|
261
|
+
return kind !== void 0 && STRUCTURAL_KINDS.has(kind);
|
|
262
|
+
}
|
|
243
263
|
var OUTPUT_PREVIEW_CHARS = 240;
|
|
244
264
|
var FOOTER_ROW = 28;
|
|
245
265
|
var COMPACT_TILE = 76;
|
|
@@ -258,10 +278,19 @@ var COMPACT_NODE_SIZE_TB = {
|
|
|
258
278
|
height: COMPACT_TILE
|
|
259
279
|
};
|
|
260
280
|
function nodeHeight(data, withRunState) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
281
|
+
if (!withRunState || data.tone === "trigger") {
|
|
282
|
+
return CARD_CHROME + HEADER_ROW + (data.description ? DESCRIPTION_ROW : 0);
|
|
283
|
+
}
|
|
284
|
+
if (data.kind === "decision") {
|
|
285
|
+
return CARD_CHROME + DECISION_HEADER_ROW + DESCRIPTION_ROW + FOOTER_ROW;
|
|
286
|
+
}
|
|
287
|
+
if (isStructuralKind(data.kind)) {
|
|
288
|
+
return CARD_CHROME + HEADER_ROW + CHIP_SLOT_ROW + FOOTER_ROW;
|
|
289
|
+
}
|
|
290
|
+
if (data.kind === "agent.run") {
|
|
291
|
+
return CARD_CHROME + SLIM_HEADER_ROW + AGENT_BODY_ROW + FOOTER_ROW;
|
|
292
|
+
}
|
|
293
|
+
return CARD_CHROME + HEADER_ROW + wellRow(ACTION_OUTPUT_ROWS) + FOOTER_ROW;
|
|
265
294
|
}
|
|
266
295
|
function geometry(compact, runMode, direction) {
|
|
267
296
|
if (compact) {
|
|
@@ -552,6 +581,75 @@ function describeTrigger(on) {
|
|
|
552
581
|
asRecord(kind ? rec[kind] : void 0)
|
|
553
582
|
);
|
|
554
583
|
}
|
|
584
|
+
var WRAP_TARGET_ASPECT = 742 / 480;
|
|
585
|
+
var WRAP_ASPECT_TRIGGER = 4.2;
|
|
586
|
+
var WRAP_ROW_GAP = 28;
|
|
587
|
+
function foldColumns(count, cellW, cellH, colSep, rowSep) {
|
|
588
|
+
let best = count;
|
|
589
|
+
let bestError = Number.POSITIVE_INFINITY;
|
|
590
|
+
for (let cols = 1; cols <= count; cols++) {
|
|
591
|
+
const rows = Math.ceil(count / cols);
|
|
592
|
+
const width = cols * cellW + (cols - 1) * colSep;
|
|
593
|
+
const height = rows * cellH + (rows - 1) * rowSep;
|
|
594
|
+
const error = Math.abs(Math.log(width / height / WRAP_TARGET_ASPECT));
|
|
595
|
+
if (error < bestError) {
|
|
596
|
+
bestError = error;
|
|
597
|
+
best = cols;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return best;
|
|
601
|
+
}
|
|
602
|
+
function layoutSerpentine(nodes, edges, colSep, laneSep) {
|
|
603
|
+
const rowSep = Math.max(laneSep, WRAP_ROW_GAP);
|
|
604
|
+
const byRank = /* @__PURE__ */ new Map();
|
|
605
|
+
for (const n of nodes) {
|
|
606
|
+
const arr = byRank.get(n.rank);
|
|
607
|
+
if (arr) arr.push(n);
|
|
608
|
+
else byRank.set(n.rank, [n]);
|
|
609
|
+
}
|
|
610
|
+
const ranks = [...byRank.keys()].sort((a, b) => a - b);
|
|
611
|
+
if (ranks.some((r) => byRank.get(r).length !== 1)) return null;
|
|
612
|
+
const rankOf = new Map(nodes.map((n) => [n.id, n.rank]));
|
|
613
|
+
const joinsNextLayer = (e) => {
|
|
614
|
+
const from = rankOf.get(e.source);
|
|
615
|
+
const to = rankOf.get(e.target);
|
|
616
|
+
return from !== void 0 && to !== void 0 && to === from + 1;
|
|
617
|
+
};
|
|
618
|
+
if (!edges.every(joinsNextLayer)) return null;
|
|
619
|
+
const ordered = ranks.map((r) => byRank.get(r)[0]);
|
|
620
|
+
const count = ordered.length;
|
|
621
|
+
const cellW = Math.max(...ordered.map((n) => n.width));
|
|
622
|
+
const cellH = Math.max(...ordered.map((n) => n.height));
|
|
623
|
+
const lineAspect = (count * cellW + (count - 1) * colSep) / cellH;
|
|
624
|
+
if (lineAspect <= WRAP_TARGET_ASPECT * WRAP_ASPECT_TRIGGER) return null;
|
|
625
|
+
const cols = foldColumns(count, cellW, cellH, colSep, rowSep);
|
|
626
|
+
if (cols >= count) return null;
|
|
627
|
+
return ordered.map((node, i) => {
|
|
628
|
+
const row = Math.floor(i / cols);
|
|
629
|
+
const indexInRow = i % cols;
|
|
630
|
+
const leftToRight = row % 2 === 0;
|
|
631
|
+
const col = leftToRight ? indexInRow : cols - 1 - indexInRow;
|
|
632
|
+
return {
|
|
633
|
+
id: node.id,
|
|
634
|
+
position: {
|
|
635
|
+
x: col * (cellW + colSep) + (cellW - node.width) / 2,
|
|
636
|
+
y: row * (cellH + rowSep) + (cellH - node.height) / 2
|
|
637
|
+
},
|
|
638
|
+
width: node.width,
|
|
639
|
+
height: node.height,
|
|
640
|
+
data: node.data,
|
|
641
|
+
// Every node leaves the way its row travels and is entered from behind —
|
|
642
|
+
// including the one that turns the corner. Mirroring puts the last cell
|
|
643
|
+
// of a row directly above the first cell of the next, so a turn is
|
|
644
|
+
// source-right into target-right (or left into left): the renderer draws
|
|
645
|
+
// it as a bracket in the margin beside the column. Routing it through the
|
|
646
|
+
// BOTTOM instead would have to start below the name that sits under the
|
|
647
|
+
// tile, leaving the arrow floating clear of the node it comes from.
|
|
648
|
+
targetSide: leftToRight ? "left" : "right",
|
|
649
|
+
sourceSide: leftToRight ? "right" : "left"
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
}
|
|
555
653
|
function layoutLayers(nodes, direction, rankSep, crossSep) {
|
|
556
654
|
const isLR = direction === "LR";
|
|
557
655
|
const mainSize = (n) => isLR ? n.width : n.height;
|
|
@@ -778,8 +876,9 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
778
876
|
(e) => e.whenLabel !== void 0 || e.backEdge === true
|
|
779
877
|
);
|
|
780
878
|
const rankSep = labelled ? Math.max(geo.rankSep, EDGE_LABEL_LANE) : geo.rankSep;
|
|
879
|
+
const folded = options?.wrap && direction === "LR" && !labelled ? layoutSerpentine(logical, edges, rankSep, geo.crossSep) : null;
|
|
781
880
|
return {
|
|
782
|
-
nodes: layoutLayers(logical, direction, rankSep, geo.crossSep),
|
|
881
|
+
nodes: folded ?? layoutLayers(logical, direction, rankSep, geo.crossSep),
|
|
783
882
|
edges,
|
|
784
883
|
error: null
|
|
785
884
|
};
|
|
@@ -797,7 +896,10 @@ export {
|
|
|
797
896
|
triggerNodeIndex,
|
|
798
897
|
actionNodeId,
|
|
799
898
|
branchNodeId,
|
|
800
|
-
|
|
899
|
+
AGENT_BODY_ROWS,
|
|
900
|
+
ACTION_OUTPUT_ROWS,
|
|
901
|
+
STRUCTURAL_OUTPUT_ROWS,
|
|
902
|
+
isStructuralKind,
|
|
801
903
|
OUTPUT_PREVIEW_CHARS,
|
|
802
904
|
COMPACT_TILE,
|
|
803
905
|
COMPACT_NODE_SIZE,
|
package/dist/globals.css
CHANGED
|
@@ -643,6 +643,24 @@
|
|
|
643
643
|
-webkit-box-orient: vertical;
|
|
644
644
|
-webkit-line-clamp: 3;
|
|
645
645
|
}
|
|
646
|
+
.line-clamp-4 {
|
|
647
|
+
overflow: hidden;
|
|
648
|
+
display: -webkit-box;
|
|
649
|
+
-webkit-box-orient: vertical;
|
|
650
|
+
-webkit-line-clamp: 4;
|
|
651
|
+
}
|
|
652
|
+
.line-clamp-5 {
|
|
653
|
+
overflow: hidden;
|
|
654
|
+
display: -webkit-box;
|
|
655
|
+
-webkit-box-orient: vertical;
|
|
656
|
+
-webkit-line-clamp: 5;
|
|
657
|
+
}
|
|
658
|
+
.line-clamp-6 {
|
|
659
|
+
overflow: hidden;
|
|
660
|
+
display: -webkit-box;
|
|
661
|
+
-webkit-box-orient: vertical;
|
|
662
|
+
-webkit-line-clamp: 6;
|
|
663
|
+
}
|
|
646
664
|
.block {
|
|
647
665
|
display: block;
|
|
648
666
|
}
|
|
@@ -805,6 +823,9 @@
|
|
|
805
823
|
.h-\[28rem\] {
|
|
806
824
|
height: 28rem;
|
|
807
825
|
}
|
|
826
|
+
.h-\[30rem\] {
|
|
827
|
+
height: 30rem;
|
|
828
|
+
}
|
|
808
829
|
.h-\[32rem\] {
|
|
809
830
|
height: 32rem;
|
|
810
831
|
}
|
package/dist/styles.css
CHANGED
|
@@ -643,6 +643,24 @@
|
|
|
643
643
|
-webkit-box-orient: vertical;
|
|
644
644
|
-webkit-line-clamp: 3;
|
|
645
645
|
}
|
|
646
|
+
.line-clamp-4 {
|
|
647
|
+
overflow: hidden;
|
|
648
|
+
display: -webkit-box;
|
|
649
|
+
-webkit-box-orient: vertical;
|
|
650
|
+
-webkit-line-clamp: 4;
|
|
651
|
+
}
|
|
652
|
+
.line-clamp-5 {
|
|
653
|
+
overflow: hidden;
|
|
654
|
+
display: -webkit-box;
|
|
655
|
+
-webkit-box-orient: vertical;
|
|
656
|
+
-webkit-line-clamp: 5;
|
|
657
|
+
}
|
|
658
|
+
.line-clamp-6 {
|
|
659
|
+
overflow: hidden;
|
|
660
|
+
display: -webkit-box;
|
|
661
|
+
-webkit-box-orient: vertical;
|
|
662
|
+
-webkit-line-clamp: 6;
|
|
663
|
+
}
|
|
646
664
|
.block {
|
|
647
665
|
display: block;
|
|
648
666
|
}
|
|
@@ -805,6 +823,9 @@
|
|
|
805
823
|
.h-\[28rem\] {
|
|
806
824
|
height: 28rem;
|
|
807
825
|
}
|
|
826
|
+
.h-\[30rem\] {
|
|
827
|
+
height: 30rem;
|
|
828
|
+
}
|
|
808
829
|
.h-\[32rem\] {
|
|
809
830
|
height: 32rem;
|
|
810
831
|
}
|
package/dist/workflows.d.ts
CHANGED
|
@@ -106,6 +106,9 @@ interface WfNodeData extends Record<string, unknown> {
|
|
|
106
106
|
/** Live run state, merged in by the host at render time (never from YAML). */
|
|
107
107
|
state?: WfNodeState;
|
|
108
108
|
}
|
|
109
|
+
/** Which edge of a node box an edge attaches to. Named in plain compass terms
|
|
110
|
+
* rather than React Flow's `Position` so the layouter stays free of it. */
|
|
111
|
+
type WfSide = "left" | "right" | "top" | "bottom";
|
|
109
112
|
interface WfNode {
|
|
110
113
|
id: string;
|
|
111
114
|
position: {
|
|
@@ -118,6 +121,15 @@ interface WfNode {
|
|
|
118
121
|
width: number;
|
|
119
122
|
height: number;
|
|
120
123
|
data: WfNodeData;
|
|
124
|
+
/**
|
|
125
|
+
* Where this node's edges attach, when the LAYOUT decides rather than the
|
|
126
|
+
* flow direction. A straight layer flow has one answer for every node (edges
|
|
127
|
+
* enter the leading edge and leave the trailing one, per `direction`), so it
|
|
128
|
+
* sets neither and the renderer derives both. A folded layout does not — a row
|
|
129
|
+
* that runs right-to-left mirrors them. Absent ⇒ derive from `direction`.
|
|
130
|
+
*/
|
|
131
|
+
sourceSide?: WfSide;
|
|
132
|
+
targetSide?: WfSide;
|
|
121
133
|
}
|
|
122
134
|
/**
|
|
123
135
|
* Node ids are PUBLIC contract, not an internal detail. A host keys its live
|
|
@@ -217,6 +229,13 @@ interface BuildWorkflowGraphOptions {
|
|
|
217
229
|
/** Collapse every node to the fixed icon-tile size, and pitch the layers for
|
|
218
230
|
* it. Defaults to `false` (the full, expanded card). */
|
|
219
231
|
compact?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Fold a long single-file pipeline into rows instead of one unbounded line,
|
|
234
|
+
* so it occupies both axes of the panel rather than running off one edge.
|
|
235
|
+
* Only a straight chain folds — a graph with any fan-out keeps its layered
|
|
236
|
+
* flow, whose branches need the cross axis. "LR" only. Defaults to `false`.
|
|
237
|
+
*/
|
|
238
|
+
wrap?: boolean;
|
|
220
239
|
/**
|
|
221
240
|
* The graph's DECLARED topology. Omit — the default — and edges are inferred
|
|
222
241
|
* from `do`-list order: a linear spine, which is exactly right for a workflow
|
|
@@ -250,14 +269,25 @@ interface WorkflowGraphProps {
|
|
|
250
269
|
/** Flow direction: "LR" (default) reads left-to-right — best for the wide/short
|
|
251
270
|
* run-detail panel; "TB" is a vertical column. */
|
|
252
271
|
direction?: WfDirection;
|
|
253
|
-
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
272
|
+
/**
|
|
273
|
+
* Start collapsed (compact icon tiles) rather than expanded.
|
|
274
|
+
*
|
|
275
|
+
* Defaults to whether the graph has a RUN: a definition is read
|
|
276
|
+
* structure-first, so it opens as tiles, while a run is read result-first and
|
|
277
|
+
* opens as cards — the answers are the reason the reader opened it. Passing
|
|
278
|
+
* the prop pins the density either way; the full variant still exposes the
|
|
279
|
+
* toggle, and the preview variant is always compact.
|
|
280
|
+
*/
|
|
258
281
|
defaultCompact?: boolean;
|
|
259
282
|
/** Sizing for the wrapper; the caller controls height. */
|
|
260
283
|
className?: string;
|
|
284
|
+
/**
|
|
285
|
+
* Fold a long single-file pipeline into rows instead of one unbounded line,
|
|
286
|
+
* so it uses both axes of the panel rather than running off one edge. Only a
|
|
287
|
+
* straight chain folds; anything with fan-out keeps its layered flow. "LR"
|
|
288
|
+
* only. Defaults to `false`.
|
|
289
|
+
*/
|
|
290
|
+
wrap?: boolean;
|
|
261
291
|
/**
|
|
262
292
|
* Live per-node run state, keyed by graph node id (`trigger`, `a0`, `a0-b1`).
|
|
263
293
|
* Absent ⇒ the static definition view (the proposal-card preview passes
|
package/dist/workflows.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
buildWorkflowGraph,
|
|
6
6
|
triggerNodeId,
|
|
7
7
|
triggerNodeIndex
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-O7MHUR4U.js";
|
|
9
9
|
import {
|
|
10
10
|
ModelBrandStack,
|
|
11
11
|
modelBrandFor
|
|
@@ -36,7 +36,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
|
|
|
36
36
|
function lazyWorkflowGraph() {
|
|
37
37
|
return lazy(
|
|
38
38
|
() => retryImport(
|
|
39
|
-
() => import("./WorkflowGraph-
|
|
39
|
+
() => import("./WorkflowGraph-MPNVC5FE.js").then((m) => ({ default: m.WorkflowGraph }))
|
|
40
40
|
)
|
|
41
41
|
);
|
|
42
42
|
}
|
package/package.json
CHANGED