@tangle-network/sandbox-ui 0.48.0 → 0.49.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.
- package/dist/WorkflowGraph-4JREVTSK.js +295 -0
- package/dist/{chunk-SUC6ZJZW.js → chunk-46VFHJPQ.js} +33 -0
- package/dist/globals.css +21 -0
- package/dist/styles.css +21 -0
- package/dist/workflows.d.ts +53 -1
- package/dist/workflows.js +2 -2
- package/package.json +1 -1
- package/dist/WorkflowGraph-EOV2P5AM.js +0 -184
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProviderIcon
|
|
3
|
+
} from "./chunk-GTJ5PSK5.js";
|
|
4
|
+
import {
|
|
5
|
+
buildWorkflowGraph
|
|
6
|
+
} from "./chunk-46VFHJPQ.js";
|
|
7
|
+
import {
|
|
8
|
+
providerLabel
|
|
9
|
+
} from "./chunk-7JLB7UZ3.js";
|
|
10
|
+
import "./chunk-U67V476Y.js";
|
|
11
|
+
|
|
12
|
+
// src/workflows/WorkflowGraph.tsx
|
|
13
|
+
import {
|
|
14
|
+
Background,
|
|
15
|
+
Controls,
|
|
16
|
+
Handle,
|
|
17
|
+
Position,
|
|
18
|
+
ReactFlow
|
|
19
|
+
} from "@xyflow/react";
|
|
20
|
+
import "@xyflow/react/dist/style.css";
|
|
21
|
+
import {
|
|
22
|
+
useCallback,
|
|
23
|
+
useEffect,
|
|
24
|
+
useMemo,
|
|
25
|
+
useState
|
|
26
|
+
} from "react";
|
|
27
|
+
|
|
28
|
+
// src/workflows/flow-graph.ts
|
|
29
|
+
function buildFlowGraph(yaml, nodeState) {
|
|
30
|
+
const graph = buildWorkflowGraph(yaml);
|
|
31
|
+
return {
|
|
32
|
+
error: graph.error,
|
|
33
|
+
nodes: graph.nodes.map(
|
|
34
|
+
(n) => ({
|
|
35
|
+
id: n.id,
|
|
36
|
+
type: "wfNode",
|
|
37
|
+
position: n.position,
|
|
38
|
+
data: nodeState?.[n.id] ? { ...n.data, state: nodeState[n.id] } : n.data
|
|
39
|
+
})
|
|
40
|
+
),
|
|
41
|
+
edges: graph.edges.map((e) => ({
|
|
42
|
+
id: e.id,
|
|
43
|
+
source: e.source,
|
|
44
|
+
target: e.target,
|
|
45
|
+
sourceHandle: e.sourceHandle,
|
|
46
|
+
type: "smoothstep"
|
|
47
|
+
}))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/workflows/format.ts
|
|
52
|
+
function fmtDuration(ms) {
|
|
53
|
+
if (ms === void 0 || !Number.isFinite(ms) || ms < 0) return void 0;
|
|
54
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
55
|
+
const s = ms / 1e3;
|
|
56
|
+
if (s < 60) return `${s.toFixed(1)}s`;
|
|
57
|
+
const totalSeconds = Math.round(s);
|
|
58
|
+
return `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
|
|
59
|
+
}
|
|
60
|
+
function fmtCost(usd) {
|
|
61
|
+
if (usd === void 0 || !Number.isFinite(usd) || usd < 0) return void 0;
|
|
62
|
+
if (usd === 0) return "$0";
|
|
63
|
+
return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
64
|
+
}
|
|
65
|
+
function clampPreview(text, max = 200) {
|
|
66
|
+
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
67
|
+
}
|
|
68
|
+
function fmtTokens(input, output) {
|
|
69
|
+
const valid = (n) => n !== void 0 && Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
70
|
+
const i = valid(input);
|
|
71
|
+
const o = valid(output);
|
|
72
|
+
if (i === void 0 && o === void 0) return void 0;
|
|
73
|
+
return `${i ?? 0}/${o ?? 0} tok`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/workflows/WorkflowGraph.tsx
|
|
77
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
78
|
+
function useColorMode() {
|
|
79
|
+
const [mode, setMode] = useState(
|
|
80
|
+
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
|
|
81
|
+
);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const el = document.documentElement;
|
|
84
|
+
const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
|
|
85
|
+
update();
|
|
86
|
+
const observer = new MutationObserver(update);
|
|
87
|
+
observer.observe(el, { attributes: true, attributeFilter: ["class"] });
|
|
88
|
+
return () => observer.disconnect();
|
|
89
|
+
}, []);
|
|
90
|
+
return mode;
|
|
91
|
+
}
|
|
92
|
+
var TONE_CARD = {
|
|
93
|
+
trigger: "border-primary/60 bg-primary/5",
|
|
94
|
+
structural: "border-accent-yellow/50 bg-accent-yellow/5",
|
|
95
|
+
action: "border-border bg-card"
|
|
96
|
+
};
|
|
97
|
+
var TONE_DOT = {
|
|
98
|
+
trigger: "bg-primary",
|
|
99
|
+
structural: "bg-accent-yellow",
|
|
100
|
+
action: "bg-text-muted"
|
|
101
|
+
};
|
|
102
|
+
var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
103
|
+
var STATUS_LABEL = {
|
|
104
|
+
queued: "Queued",
|
|
105
|
+
running: "Running",
|
|
106
|
+
succeeded: "Done",
|
|
107
|
+
failed: "Failed"
|
|
108
|
+
};
|
|
109
|
+
var STATUS_BADGE = {
|
|
110
|
+
queued: "bg-background-tertiary text-text-muted",
|
|
111
|
+
running: "bg-primary/15 text-primary",
|
|
112
|
+
succeeded: "bg-green-600/10 text-green-600",
|
|
113
|
+
failed: "bg-red-500/10 text-red-400"
|
|
114
|
+
};
|
|
115
|
+
function statusBorder(status) {
|
|
116
|
+
switch (status) {
|
|
117
|
+
case "running":
|
|
118
|
+
return "border-primary ring-2 ring-primary/40 animate-pulse";
|
|
119
|
+
case "succeeded":
|
|
120
|
+
return "border-green-500";
|
|
121
|
+
case "failed":
|
|
122
|
+
return "border-red-500";
|
|
123
|
+
default:
|
|
124
|
+
return "opacity-70";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function MetaChip({ children }) {
|
|
128
|
+
return /* @__PURE__ */ jsx("span", { className: "rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children });
|
|
129
|
+
}
|
|
130
|
+
function WorkflowNode({ data }) {
|
|
131
|
+
const d = data;
|
|
132
|
+
const state = d.state;
|
|
133
|
+
const model = state?.model ?? d.model;
|
|
134
|
+
const duration = fmtDuration(state?.durationMs);
|
|
135
|
+
const cost = fmtCost(state?.costUsd);
|
|
136
|
+
const tokens = fmtTokens(state?.inputTokens, state?.outputTokens);
|
|
137
|
+
const errorText = state?.error ? clampPreview(state.error) : void 0;
|
|
138
|
+
const outputText = state?.outputPreview ? clampPreview(state.outputPreview) : void 0;
|
|
139
|
+
return /* @__PURE__ */ jsxs(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
className: `relative w-[240px] rounded-lg border px-3 py-2 shadow-sm transition-colors ${TONE_CARD[d.tone]} ${state ? statusBorder(state.status) : ""}`,
|
|
143
|
+
children: [
|
|
144
|
+
!d.isRoot && /* @__PURE__ */ jsx(
|
|
145
|
+
Handle,
|
|
146
|
+
{
|
|
147
|
+
type: "target",
|
|
148
|
+
position: Position.Top,
|
|
149
|
+
className: HANDLE_CLASS
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
"span",
|
|
155
|
+
{
|
|
156
|
+
"aria-hidden": "true",
|
|
157
|
+
className: `h-2 w-2 shrink-0 rounded-full ${TONE_DOT[d.tone]}`
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[12px] text-text", children: d.title }),
|
|
161
|
+
state ? /* @__PURE__ */ jsx(
|
|
162
|
+
"span",
|
|
163
|
+
{
|
|
164
|
+
className: `ml-auto rounded px-1.5 py-0.5 text-[10px] ${STATUS_BADGE[state.status]}`,
|
|
165
|
+
children: STATUS_LABEL[state.status]
|
|
166
|
+
}
|
|
167
|
+
) : d.badge && /* @__PURE__ */ jsx("span", { className: "ml-auto rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children: d.badge })
|
|
168
|
+
] }),
|
|
169
|
+
d.subtitle && /* @__PURE__ */ jsx(
|
|
170
|
+
"p",
|
|
171
|
+
{
|
|
172
|
+
className: "mt-1 truncate text-[11px] text-text-muted",
|
|
173
|
+
title: d.subtitle,
|
|
174
|
+
children: d.subtitle
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
(model || cost || duration || tokens || state) && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
|
|
178
|
+
d.kind && d.tone === "action" && /* @__PURE__ */ jsx(MetaChip, { children: d.kind }),
|
|
179
|
+
model && /* @__PURE__ */ jsx(MetaChip, { children: model }),
|
|
180
|
+
cost && /* @__PURE__ */ jsx(MetaChip, { children: cost }),
|
|
181
|
+
duration && /* @__PURE__ */ jsx(MetaChip, { children: duration }),
|
|
182
|
+
tokens && /* @__PURE__ */ jsx(MetaChip, { children: tokens })
|
|
183
|
+
] }),
|
|
184
|
+
state?.status === "failed" && errorText && /* @__PURE__ */ jsx(
|
|
185
|
+
"p",
|
|
186
|
+
{
|
|
187
|
+
className: "mt-1.5 line-clamp-2 text-[10px] text-red-400",
|
|
188
|
+
title: errorText,
|
|
189
|
+
children: errorText
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
outputText && state?.status !== "failed" && /* @__PURE__ */ jsx(
|
|
193
|
+
"p",
|
|
194
|
+
{
|
|
195
|
+
className: "mt-1.5 line-clamp-2 text-[10px] text-text-muted",
|
|
196
|
+
title: outputText,
|
|
197
|
+
children: outputText
|
|
198
|
+
}
|
|
199
|
+
),
|
|
200
|
+
d.provider && /* @__PURE__ */ jsxs("span", { className: "mt-1.5 inline-flex items-center gap-1 rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children: [
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
ProviderIcon,
|
|
203
|
+
{
|
|
204
|
+
id: d.provider,
|
|
205
|
+
displayName: providerLabel(d.provider),
|
|
206
|
+
size: 12,
|
|
207
|
+
className: "rounded-[2px]"
|
|
208
|
+
}
|
|
209
|
+
),
|
|
210
|
+
providerLabel(d.provider)
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
Handle,
|
|
214
|
+
{
|
|
215
|
+
type: "source",
|
|
216
|
+
id: "out",
|
|
217
|
+
position: Position.Bottom,
|
|
218
|
+
className: HANDLE_CLASS
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
d.hasBranches && /* @__PURE__ */ jsx(
|
|
222
|
+
Handle,
|
|
223
|
+
{
|
|
224
|
+
type: "source",
|
|
225
|
+
id: "branch",
|
|
226
|
+
position: Position.Right,
|
|
227
|
+
className: HANDLE_CLASS
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
var NODE_TYPES = { wfNode: WorkflowNode };
|
|
235
|
+
function WorkflowGraph({
|
|
236
|
+
yaml,
|
|
237
|
+
variant = "full",
|
|
238
|
+
className,
|
|
239
|
+
nodeState,
|
|
240
|
+
onNodeClick
|
|
241
|
+
}) {
|
|
242
|
+
const colorMode = useColorMode();
|
|
243
|
+
const { nodes, edges, error } = useMemo(
|
|
244
|
+
() => buildFlowGraph(yaml, nodeState),
|
|
245
|
+
[yaml, nodeState]
|
|
246
|
+
);
|
|
247
|
+
const handleNodeClick = useCallback(
|
|
248
|
+
(_event, node) => {
|
|
249
|
+
onNodeClick?.(node.id, node.data);
|
|
250
|
+
},
|
|
251
|
+
[onNodeClick]
|
|
252
|
+
);
|
|
253
|
+
if (error || nodes.length === 0) {
|
|
254
|
+
return /* @__PURE__ */ jsx(
|
|
255
|
+
"div",
|
|
256
|
+
{
|
|
257
|
+
className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-text-muted text-xs ${className ?? ""}`,
|
|
258
|
+
children: error ?? "Nothing to show"
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const isPreview = variant === "preview";
|
|
263
|
+
return /* @__PURE__ */ jsx("div", { className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs(
|
|
264
|
+
ReactFlow,
|
|
265
|
+
{
|
|
266
|
+
nodes,
|
|
267
|
+
edges,
|
|
268
|
+
nodeTypes: NODE_TYPES,
|
|
269
|
+
colorMode,
|
|
270
|
+
onNodeClick: onNodeClick ? handleNodeClick : void 0,
|
|
271
|
+
fitView: true,
|
|
272
|
+
fitViewOptions: { padding: 0.2 },
|
|
273
|
+
proOptions: { hideAttribution: true },
|
|
274
|
+
nodesDraggable: !isPreview,
|
|
275
|
+
nodesConnectable: false,
|
|
276
|
+
elementsSelectable: !isPreview,
|
|
277
|
+
edgesFocusable: false,
|
|
278
|
+
zoomOnScroll: false,
|
|
279
|
+
zoomOnPinch: true,
|
|
280
|
+
zoomOnDoubleClick: !isPreview,
|
|
281
|
+
panOnDrag: true,
|
|
282
|
+
preventScrolling: false,
|
|
283
|
+
minZoom: 0.2,
|
|
284
|
+
maxZoom: 1.5,
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ jsx(Background, { gap: 16 }),
|
|
287
|
+
/* @__PURE__ */ jsx(Controls, { showInteractive: false, position: "bottom-right" })
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
) });
|
|
291
|
+
}
|
|
292
|
+
export {
|
|
293
|
+
WorkflowGraph,
|
|
294
|
+
WorkflowNode
|
|
295
|
+
};
|
|
@@ -21,6 +21,16 @@ function urlHost(url) {
|
|
|
21
21
|
return url;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
function pickDetail(cfg, keys) {
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const v = cfg[key];
|
|
28
|
+
if (v === void 0 || v === null) continue;
|
|
29
|
+
const text = typeof v === "string" ? v : typeof v === "number" || typeof v === "boolean" ? String(v) : Array.isArray(v) ? `${v.length} item${v.length === 1 ? "" : "s"}` : "\u2026";
|
|
30
|
+
out[key] = text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
|
|
31
|
+
}
|
|
32
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
33
|
+
}
|
|
24
34
|
function describeAction(action) {
|
|
25
35
|
const rec = asRecord(action);
|
|
26
36
|
const [kind] = Object.keys(rec);
|
|
@@ -29,7 +39,9 @@ function describeAction(action) {
|
|
|
29
39
|
case "sandbox.spawn":
|
|
30
40
|
return {
|
|
31
41
|
title: "Spawn sandbox",
|
|
42
|
+
kind,
|
|
32
43
|
subtitle: str(cfg.template),
|
|
44
|
+
detail: pickDetail(cfg, ["template", "size", "region"]),
|
|
33
45
|
hasBranches: false,
|
|
34
46
|
isRoot: false,
|
|
35
47
|
tone: "action"
|
|
@@ -38,8 +50,11 @@ function describeAction(action) {
|
|
|
38
50
|
const path = str(cfg.path);
|
|
39
51
|
return {
|
|
40
52
|
title: "Integration",
|
|
53
|
+
kind,
|
|
41
54
|
subtitle: path,
|
|
55
|
+
path,
|
|
42
56
|
provider: path?.split(".")[0],
|
|
57
|
+
detail: pickDetail(cfg, ["path"]),
|
|
43
58
|
hasBranches: false,
|
|
44
59
|
isRoot: false,
|
|
45
60
|
tone: "action"
|
|
@@ -49,7 +64,9 @@ function describeAction(action) {
|
|
|
49
64
|
const url = str(cfg.url);
|
|
50
65
|
return {
|
|
51
66
|
title: "Notify",
|
|
67
|
+
kind,
|
|
52
68
|
subtitle: url ? urlHost(url) : void 0,
|
|
69
|
+
detail: pickDetail(cfg, ["url"]),
|
|
53
70
|
hasBranches: false,
|
|
54
71
|
isRoot: false,
|
|
55
72
|
tone: "action"
|
|
@@ -58,7 +75,16 @@ function describeAction(action) {
|
|
|
58
75
|
case "agent.run":
|
|
59
76
|
return {
|
|
60
77
|
title: "Run agent",
|
|
78
|
+
kind,
|
|
61
79
|
subtitle: str(cfg.model) ?? str(cfg.profile) ?? str(cfg.prompt),
|
|
80
|
+
model: str(cfg.model),
|
|
81
|
+
detail: pickDetail(cfg, [
|
|
82
|
+
"profile",
|
|
83
|
+
"model",
|
|
84
|
+
"maxRounds",
|
|
85
|
+
"size",
|
|
86
|
+
"prompt"
|
|
87
|
+
]),
|
|
62
88
|
hasBranches: false,
|
|
63
89
|
isRoot: false,
|
|
64
90
|
tone: "action"
|
|
@@ -67,6 +93,7 @@ function describeAction(action) {
|
|
|
67
93
|
const branches = Array.isArray(cfg.branches) ? cfg.branches : [];
|
|
68
94
|
return {
|
|
69
95
|
title: "Parallel",
|
|
96
|
+
kind,
|
|
70
97
|
subtitle: `${branches.length} branch${branches.length === 1 ? "" : "es"}`,
|
|
71
98
|
badge: branches.length > 0 ? `\xD7${branches.length}` : void 0,
|
|
72
99
|
hasBranches: branches.length > 0,
|
|
@@ -77,7 +104,9 @@ function describeAction(action) {
|
|
|
77
104
|
case "foreach":
|
|
78
105
|
return {
|
|
79
106
|
title: "For each",
|
|
107
|
+
kind,
|
|
80
108
|
subtitle: typeof cfg.items === "string" ? cfg.items : "list",
|
|
109
|
+
detail: pickDetail(cfg, ["items"]),
|
|
81
110
|
// Only fans out when a `do` template is actually present.
|
|
82
111
|
hasBranches: Boolean(cfg.do),
|
|
83
112
|
isRoot: false,
|
|
@@ -86,6 +115,7 @@ function describeAction(action) {
|
|
|
86
115
|
default:
|
|
87
116
|
return {
|
|
88
117
|
title: kind ? kind : "Action",
|
|
118
|
+
kind: kind ?? "action",
|
|
89
119
|
hasBranches: false,
|
|
90
120
|
isRoot: false,
|
|
91
121
|
tone: "action"
|
|
@@ -106,6 +136,7 @@ function describeTrigger(on) {
|
|
|
106
136
|
if (repo) subtitle += ` on ${repo}`;
|
|
107
137
|
return {
|
|
108
138
|
title: "Trigger",
|
|
139
|
+
kind: "provider_event",
|
|
109
140
|
subtitle,
|
|
110
141
|
provider: connection,
|
|
111
142
|
hasBranches: false,
|
|
@@ -119,6 +150,7 @@ function describeTrigger(on) {
|
|
|
119
150
|
const tz = str(sch.timezone);
|
|
120
151
|
return {
|
|
121
152
|
title: "Schedule",
|
|
153
|
+
kind: "schedule",
|
|
122
154
|
subtitle: cron ? tz ? `${cron} (${tz})` : cron : void 0,
|
|
123
155
|
hasBranches: false,
|
|
124
156
|
isRoot: true,
|
|
@@ -127,6 +159,7 @@ function describeTrigger(on) {
|
|
|
127
159
|
}
|
|
128
160
|
return {
|
|
129
161
|
title: "Trigger",
|
|
162
|
+
kind: "trigger",
|
|
130
163
|
hasBranches: false,
|
|
131
164
|
isRoot: true,
|
|
132
165
|
tone: "trigger"
|
package/dist/globals.css
CHANGED
|
@@ -1915,6 +1915,9 @@
|
|
|
1915
1915
|
.border-foreground {
|
|
1916
1916
|
border-color: var(--color-foreground);
|
|
1917
1917
|
}
|
|
1918
|
+
.border-green-500 {
|
|
1919
|
+
border-color: var(--color-green-500);
|
|
1920
|
+
}
|
|
1918
1921
|
.border-green-500\/30 {
|
|
1919
1922
|
border-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 30%, transparent);
|
|
1920
1923
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -1960,6 +1963,9 @@
|
|
|
1960
1963
|
border-color: color-mix(in oklab, var(--color-primary) 60%, transparent);
|
|
1961
1964
|
}
|
|
1962
1965
|
}
|
|
1966
|
+
.border-red-500 {
|
|
1967
|
+
border-color: var(--color-red-500);
|
|
1968
|
+
}
|
|
1963
1969
|
.border-red-500\/20 {
|
|
1964
1970
|
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
|
|
1965
1971
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -2275,6 +2281,12 @@
|
|
|
2275
2281
|
.bg-green-500 {
|
|
2276
2282
|
background-color: var(--color-green-500);
|
|
2277
2283
|
}
|
|
2284
|
+
.bg-green-600\/10 {
|
|
2285
|
+
background-color: color-mix(in srgb, oklch(62.7% 0.194 149.214) 10%, transparent);
|
|
2286
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2287
|
+
background-color: color-mix(in oklab, var(--color-green-600) 10%, transparent);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2278
2290
|
.bg-green-600\/20 {
|
|
2279
2291
|
background-color: color-mix(in srgb, oklch(62.7% 0.194 149.214) 20%, transparent);
|
|
2280
2292
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -3202,6 +3214,9 @@
|
|
|
3202
3214
|
.text-green-400 {
|
|
3203
3215
|
color: var(--color-green-400);
|
|
3204
3216
|
}
|
|
3217
|
+
.text-green-600 {
|
|
3218
|
+
color: var(--color-green-600);
|
|
3219
|
+
}
|
|
3205
3220
|
.text-muted-foreground {
|
|
3206
3221
|
color: var(--color-muted-foreground);
|
|
3207
3222
|
}
|
|
@@ -3455,6 +3470,12 @@
|
|
|
3455
3470
|
--tw-ring-color: color-mix(in oklab, var(--color-primary) 25%, transparent);
|
|
3456
3471
|
}
|
|
3457
3472
|
}
|
|
3473
|
+
.ring-primary\/40 {
|
|
3474
|
+
--tw-ring-color: color-mix(in srgb, hsl(var(--primary)) 40%, transparent);
|
|
3475
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
3476
|
+
--tw-ring-color: color-mix(in oklab, var(--color-primary) 40%, transparent);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3458
3479
|
.ring-offset-background {
|
|
3459
3480
|
--tw-ring-offset-color: var(--color-background);
|
|
3460
3481
|
}
|
package/dist/styles.css
CHANGED
|
@@ -1915,6 +1915,9 @@
|
|
|
1915
1915
|
.border-foreground {
|
|
1916
1916
|
border-color: var(--color-foreground);
|
|
1917
1917
|
}
|
|
1918
|
+
.border-green-500 {
|
|
1919
|
+
border-color: var(--color-green-500);
|
|
1920
|
+
}
|
|
1918
1921
|
.border-green-500\/30 {
|
|
1919
1922
|
border-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 30%, transparent);
|
|
1920
1923
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -1960,6 +1963,9 @@
|
|
|
1960
1963
|
border-color: color-mix(in oklab, var(--color-primary) 60%, transparent);
|
|
1961
1964
|
}
|
|
1962
1965
|
}
|
|
1966
|
+
.border-red-500 {
|
|
1967
|
+
border-color: var(--color-red-500);
|
|
1968
|
+
}
|
|
1963
1969
|
.border-red-500\/20 {
|
|
1964
1970
|
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
|
|
1965
1971
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -2275,6 +2281,12 @@
|
|
|
2275
2281
|
.bg-green-500 {
|
|
2276
2282
|
background-color: var(--color-green-500);
|
|
2277
2283
|
}
|
|
2284
|
+
.bg-green-600\/10 {
|
|
2285
|
+
background-color: color-mix(in srgb, oklch(62.7% 0.194 149.214) 10%, transparent);
|
|
2286
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2287
|
+
background-color: color-mix(in oklab, var(--color-green-600) 10%, transparent);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2278
2290
|
.bg-green-600\/20 {
|
|
2279
2291
|
background-color: color-mix(in srgb, oklch(62.7% 0.194 149.214) 20%, transparent);
|
|
2280
2292
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -3202,6 +3214,9 @@
|
|
|
3202
3214
|
.text-green-400 {
|
|
3203
3215
|
color: var(--color-green-400);
|
|
3204
3216
|
}
|
|
3217
|
+
.text-green-600 {
|
|
3218
|
+
color: var(--color-green-600);
|
|
3219
|
+
}
|
|
3205
3220
|
.text-muted-foreground {
|
|
3206
3221
|
color: var(--color-muted-foreground);
|
|
3207
3222
|
}
|
|
@@ -3455,6 +3470,12 @@
|
|
|
3455
3470
|
--tw-ring-color: color-mix(in oklab, var(--color-primary) 25%, transparent);
|
|
3456
3471
|
}
|
|
3457
3472
|
}
|
|
3473
|
+
.ring-primary\/40 {
|
|
3474
|
+
--tw-ring-color: color-mix(in srgb, hsl(var(--primary)) 40%, transparent);
|
|
3475
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
3476
|
+
--tw-ring-color: color-mix(in oklab, var(--color-primary) 40%, transparent);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3458
3479
|
.ring-offset-background {
|
|
3459
3480
|
--tw-ring-offset-color: var(--color-background);
|
|
3460
3481
|
}
|
package/dist/workflows.d.ts
CHANGED
|
@@ -12,11 +12,46 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
12
12
|
* so the spine stays a single readable column.
|
|
13
13
|
*/
|
|
14
14
|
type WfNodeTone = "trigger" | "structural" | "action";
|
|
15
|
+
type WfNodeStatus = "queued" | "running" | "succeeded" | "failed";
|
|
16
|
+
/**
|
|
17
|
+
* Live RUN state for one node, supplied by the host (keyed by node id) and
|
|
18
|
+
* merged onto {@link WfNodeData.state} at render time. Absent ⇒ the graph shows
|
|
19
|
+
* the static definition only (the assistant-proposal preview path). Driven from
|
|
20
|
+
* a workflow run's per-action results so the graph animates as the run executes.
|
|
21
|
+
*/
|
|
22
|
+
interface WfNodeState {
|
|
23
|
+
status: WfNodeStatus;
|
|
24
|
+
/** Amount booked for this action, USD. */
|
|
25
|
+
costUsd?: number;
|
|
26
|
+
/** Wall-clock the action took, ms. */
|
|
27
|
+
durationMs?: number;
|
|
28
|
+
/** Model the action actually used (may differ from the requested model). */
|
|
29
|
+
model?: string;
|
|
30
|
+
/** Failure message, when the node failed. */
|
|
31
|
+
error?: string;
|
|
32
|
+
/** Short preview of the action's output (or a failure's partial text). */
|
|
33
|
+
outputPreview?: string;
|
|
34
|
+
/** Input/output token usage, when known. */
|
|
35
|
+
inputTokens?: number;
|
|
36
|
+
outputTokens?: number;
|
|
37
|
+
}
|
|
15
38
|
interface WfNodeData extends Record<string, unknown> {
|
|
16
39
|
/** Headline for the node, e.g. "Run agent". */
|
|
17
40
|
title: string;
|
|
41
|
+
/** The action/trigger kind verbatim, e.g. "agent.run", "schedule". Set on every
|
|
42
|
+
* node `buildWorkflowGraph` produces so a card can label what it is regardless
|
|
43
|
+
* of which subtitle it shows; optional on the type so external consumers
|
|
44
|
+
* constructing `WfNodeData` directly aren't forced to supply it (the render
|
|
45
|
+
* guards its usage). */
|
|
46
|
+
kind?: string;
|
|
18
47
|
/** Secondary detail, e.g. an integration path or a cron expression. */
|
|
19
48
|
subtitle?: string;
|
|
49
|
+
/** Requested model (agent.run), shown as a chip even before a run. */
|
|
50
|
+
model?: string;
|
|
51
|
+
/** `integration.invoke` provider.method path. */
|
|
52
|
+
path?: string;
|
|
53
|
+
/** Notable config fields for the expand drawer (kept small + stringified). */
|
|
54
|
+
detail?: Record<string, string>;
|
|
20
55
|
/** Connector slug (e.g. `github`) for the provider chip, when one applies. */
|
|
21
56
|
provider?: string;
|
|
22
57
|
/** Small corner tag, e.g. "×3" for a parallel fan-out. */
|
|
@@ -26,6 +61,8 @@ interface WfNodeData extends Record<string, unknown> {
|
|
|
26
61
|
/** Whether this node is the spine root (no incoming/target handle). */
|
|
27
62
|
isRoot: boolean;
|
|
28
63
|
tone: WfNodeTone;
|
|
64
|
+
/** Live run state, merged in by the host at render time (never from YAML). */
|
|
65
|
+
state?: WfNodeState;
|
|
29
66
|
}
|
|
30
67
|
interface WfNode {
|
|
31
68
|
id: string;
|
|
@@ -60,8 +97,23 @@ interface WorkflowGraphProps {
|
|
|
60
97
|
variant?: "full" | "preview";
|
|
61
98
|
/** Sizing for the wrapper; the caller controls height. */
|
|
62
99
|
className?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Live per-node run state, keyed by graph node id (`trigger`, `a0`, `a0-b1`).
|
|
102
|
+
* Absent ⇒ the static definition view (the proposal-card preview passes
|
|
103
|
+
* nothing). When present, each node shows its status/cost/duration/output and
|
|
104
|
+
* the running node pulses.
|
|
105
|
+
*
|
|
106
|
+
* Immutability contract: the node memo keys on this object's reference, so the
|
|
107
|
+
* host MUST pass a NEW top-level `nodeState` object whenever any node's state
|
|
108
|
+
* changes (mutating a nested entry in place will not re-render). Building a
|
|
109
|
+
* fresh record each update — e.g. from a poll/SSE tick — satisfies this.
|
|
110
|
+
*/
|
|
111
|
+
nodeState?: Record<string, WfNodeState>;
|
|
112
|
+
/** Click handler for a node (e.g. open a detail drawer). Absent ⇒ nodes are
|
|
113
|
+
* non-interactive on click. */
|
|
114
|
+
onNodeClick?: (nodeId: string, data: WfNodeData) => void;
|
|
63
115
|
}
|
|
64
116
|
|
|
65
117
|
declare function WorkflowGraph(props: WorkflowGraphProps): react_jsx_runtime.JSX.Element;
|
|
66
118
|
|
|
67
|
-
export { type WfEdge, type WfGraph, type WfNode, type WfNodeData, type WfNodeTone, WorkflowGraph as WorkflowGraphLazy, type WorkflowGraphProps, buildWorkflowGraph };
|
|
119
|
+
export { type WfEdge, type WfGraph, type WfNode, type WfNodeData, type WfNodeState, type WfNodeStatus, type WfNodeTone, WorkflowGraph as WorkflowGraphLazy, type WorkflowGraphProps, buildWorkflowGraph };
|
package/dist/workflows.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildWorkflowGraph
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-46VFHJPQ.js";
|
|
4
4
|
import "./chunk-7JLB7UZ3.js";
|
|
5
5
|
import "./chunk-U67V476Y.js";
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
|
|
|
27
27
|
function lazyWorkflowGraph() {
|
|
28
28
|
return lazy(
|
|
29
29
|
() => retryImport(
|
|
30
|
-
() => import("./WorkflowGraph-
|
|
30
|
+
() => import("./WorkflowGraph-4JREVTSK.js").then((m) => ({ default: m.WorkflowGraph }))
|
|
31
31
|
)
|
|
32
32
|
);
|
|
33
33
|
}
|
package/package.json
CHANGED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ProviderIcon
|
|
3
|
-
} from "./chunk-GTJ5PSK5.js";
|
|
4
|
-
import {
|
|
5
|
-
buildWorkflowGraph
|
|
6
|
-
} from "./chunk-SUC6ZJZW.js";
|
|
7
|
-
import {
|
|
8
|
-
providerLabel
|
|
9
|
-
} from "./chunk-7JLB7UZ3.js";
|
|
10
|
-
import "./chunk-U67V476Y.js";
|
|
11
|
-
|
|
12
|
-
// src/workflows/WorkflowGraph.tsx
|
|
13
|
-
import {
|
|
14
|
-
Background,
|
|
15
|
-
Controls,
|
|
16
|
-
Handle,
|
|
17
|
-
Position,
|
|
18
|
-
ReactFlow
|
|
19
|
-
} from "@xyflow/react";
|
|
20
|
-
import "@xyflow/react/dist/style.css";
|
|
21
|
-
import { useEffect, useMemo, useState } from "react";
|
|
22
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
23
|
-
function useColorMode() {
|
|
24
|
-
const [mode, setMode] = useState(
|
|
25
|
-
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
|
|
26
|
-
);
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
const el = document.documentElement;
|
|
29
|
-
const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
|
|
30
|
-
update();
|
|
31
|
-
const observer = new MutationObserver(update);
|
|
32
|
-
observer.observe(el, { attributes: true, attributeFilter: ["class"] });
|
|
33
|
-
return () => observer.disconnect();
|
|
34
|
-
}, []);
|
|
35
|
-
return mode;
|
|
36
|
-
}
|
|
37
|
-
var TONE_CARD = {
|
|
38
|
-
trigger: "border-primary/60 bg-primary/5",
|
|
39
|
-
structural: "border-accent-yellow/50 bg-accent-yellow/5",
|
|
40
|
-
action: "border-border bg-card"
|
|
41
|
-
};
|
|
42
|
-
var TONE_DOT = {
|
|
43
|
-
trigger: "bg-primary",
|
|
44
|
-
structural: "bg-accent-yellow",
|
|
45
|
-
action: "bg-text-muted"
|
|
46
|
-
};
|
|
47
|
-
var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
48
|
-
function WorkflowNode({ data }) {
|
|
49
|
-
const d = data;
|
|
50
|
-
return /* @__PURE__ */ jsxs(
|
|
51
|
-
"div",
|
|
52
|
-
{
|
|
53
|
-
className: `relative w-[240px] rounded-lg border px-3 py-2 shadow-sm ${TONE_CARD[d.tone]}`,
|
|
54
|
-
children: [
|
|
55
|
-
!d.isRoot && /* @__PURE__ */ jsx(
|
|
56
|
-
Handle,
|
|
57
|
-
{
|
|
58
|
-
type: "target",
|
|
59
|
-
position: Position.Top,
|
|
60
|
-
className: HANDLE_CLASS
|
|
61
|
-
}
|
|
62
|
-
),
|
|
63
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
64
|
-
/* @__PURE__ */ jsx(
|
|
65
|
-
"span",
|
|
66
|
-
{
|
|
67
|
-
"aria-hidden": "true",
|
|
68
|
-
className: `h-2 w-2 shrink-0 rounded-full ${TONE_DOT[d.tone]}`
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-[12px] text-text", children: d.title }),
|
|
72
|
-
d.badge && /* @__PURE__ */ jsx("span", { className: "ml-auto rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children: d.badge })
|
|
73
|
-
] }),
|
|
74
|
-
d.subtitle && /* @__PURE__ */ jsx(
|
|
75
|
-
"p",
|
|
76
|
-
{
|
|
77
|
-
className: "mt-1 truncate text-[11px] text-text-muted",
|
|
78
|
-
title: d.subtitle,
|
|
79
|
-
children: d.subtitle
|
|
80
|
-
}
|
|
81
|
-
),
|
|
82
|
-
d.provider && /* @__PURE__ */ jsxs("span", { className: "mt-1.5 inline-flex items-center gap-1 rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children: [
|
|
83
|
-
/* @__PURE__ */ jsx(
|
|
84
|
-
ProviderIcon,
|
|
85
|
-
{
|
|
86
|
-
id: d.provider,
|
|
87
|
-
displayName: providerLabel(d.provider),
|
|
88
|
-
size: 12,
|
|
89
|
-
className: "rounded-[2px]"
|
|
90
|
-
}
|
|
91
|
-
),
|
|
92
|
-
providerLabel(d.provider)
|
|
93
|
-
] }),
|
|
94
|
-
/* @__PURE__ */ jsx(
|
|
95
|
-
Handle,
|
|
96
|
-
{
|
|
97
|
-
type: "source",
|
|
98
|
-
id: "out",
|
|
99
|
-
position: Position.Bottom,
|
|
100
|
-
className: HANDLE_CLASS
|
|
101
|
-
}
|
|
102
|
-
),
|
|
103
|
-
d.hasBranches && /* @__PURE__ */ jsx(
|
|
104
|
-
Handle,
|
|
105
|
-
{
|
|
106
|
-
type: "source",
|
|
107
|
-
id: "branch",
|
|
108
|
-
position: Position.Right,
|
|
109
|
-
className: HANDLE_CLASS
|
|
110
|
-
}
|
|
111
|
-
)
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
var NODE_TYPES = { wfNode: WorkflowNode };
|
|
117
|
-
function WorkflowGraph({
|
|
118
|
-
yaml,
|
|
119
|
-
variant = "full",
|
|
120
|
-
className
|
|
121
|
-
}) {
|
|
122
|
-
const colorMode = useColorMode();
|
|
123
|
-
const { nodes, edges, error } = useMemo(() => {
|
|
124
|
-
const graph = buildWorkflowGraph(yaml);
|
|
125
|
-
return {
|
|
126
|
-
error: graph.error,
|
|
127
|
-
nodes: graph.nodes.map(
|
|
128
|
-
(n) => ({
|
|
129
|
-
id: n.id,
|
|
130
|
-
type: "wfNode",
|
|
131
|
-
position: n.position,
|
|
132
|
-
data: n.data
|
|
133
|
-
})
|
|
134
|
-
),
|
|
135
|
-
edges: graph.edges.map((e) => ({
|
|
136
|
-
id: e.id,
|
|
137
|
-
source: e.source,
|
|
138
|
-
target: e.target,
|
|
139
|
-
sourceHandle: e.sourceHandle,
|
|
140
|
-
type: "smoothstep"
|
|
141
|
-
}))
|
|
142
|
-
};
|
|
143
|
-
}, [yaml]);
|
|
144
|
-
if (error || nodes.length === 0) {
|
|
145
|
-
return /* @__PURE__ */ jsx(
|
|
146
|
-
"div",
|
|
147
|
-
{
|
|
148
|
-
className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-text-muted text-xs ${className ?? ""}`,
|
|
149
|
-
children: error ?? "Nothing to show"
|
|
150
|
-
}
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
const isPreview = variant === "preview";
|
|
154
|
-
return /* @__PURE__ */ jsx("div", { className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs(
|
|
155
|
-
ReactFlow,
|
|
156
|
-
{
|
|
157
|
-
nodes,
|
|
158
|
-
edges,
|
|
159
|
-
nodeTypes: NODE_TYPES,
|
|
160
|
-
colorMode,
|
|
161
|
-
fitView: true,
|
|
162
|
-
fitViewOptions: { padding: 0.2 },
|
|
163
|
-
proOptions: { hideAttribution: true },
|
|
164
|
-
nodesDraggable: !isPreview,
|
|
165
|
-
nodesConnectable: false,
|
|
166
|
-
elementsSelectable: !isPreview,
|
|
167
|
-
edgesFocusable: false,
|
|
168
|
-
zoomOnScroll: false,
|
|
169
|
-
zoomOnPinch: true,
|
|
170
|
-
zoomOnDoubleClick: !isPreview,
|
|
171
|
-
panOnDrag: true,
|
|
172
|
-
preventScrolling: false,
|
|
173
|
-
minZoom: 0.2,
|
|
174
|
-
maxZoom: 1.5,
|
|
175
|
-
children: [
|
|
176
|
-
/* @__PURE__ */ jsx(Background, { gap: 16 }),
|
|
177
|
-
/* @__PURE__ */ jsx(Controls, { showInteractive: false, position: "bottom-right" })
|
|
178
|
-
]
|
|
179
|
-
}
|
|
180
|
-
) });
|
|
181
|
-
}
|
|
182
|
-
export {
|
|
183
|
-
WorkflowGraph
|
|
184
|
-
};
|