@tangle-network/sandbox-ui 2.0.3 → 2.0.5
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/{AgentGraph-BqLPrigw.d.ts → AgentGraph-DzdpPFEI.d.ts} +9 -4
- package/dist/{AgentGraph-Dd1EBlhu.js → AgentGraph-YuBa5O8m.js} +2 -2
- package/dist/{AgentGraphCanvas-CIGQjYtw.js → AgentGraphCanvas-DYAMCkhv.js} +349 -202
- package/dist/{WorkflowGraph-Bj6VMnOz.js → WorkflowGraph-DkdcJ8wb.js} +3 -3
- package/dist/{canvas-theme-DGd0fm1H.js → canvas-theme-aqWjGbfu.js} +2 -5
- package/dist/globals.css +149 -72
- package/dist/{index-CjGeAD3E.d.ts → index-DFNf9mJ2.d.ts} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{latest-events-D1i5Ny3N.js → latest-events-CWvZr8Ut.js} +36 -56
- package/dist/{node-ui-DWv_oLvy.js → node-ui-Dst2R2M5.js} +1 -1
- package/dist/{run-B0HDqO5h.js → run-wUKH3Qh6.js} +2 -2
- package/dist/run.d.ts +1 -1
- package/dist/run.js +2 -2
- package/dist/styles.css +149 -72
- package/dist/tailwind.css +44 -32
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +2 -2
- package/package.json +3 -3
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { t as MOTION_CONTROL } from "./motion-Bn7ESHrj.js";
|
|
2
|
-
import { d as quietActivity, f as syncAmbient, g as useRunNow, h as useMotionAllowed, i as fmtCost, m as useAttention, o as fmtEventTime, p as tokenMilliseconds, s as fmtTokenTotal, u as ambientStyle } from "./AgentGraph-
|
|
3
|
-
import { t as LatestEvents } from "./latest-events-
|
|
4
|
-
import { P as shortModel, m as edgeColor, o as RESTING_BORDER, r as NodeMark, s as RESTING_SHADOW, t as KIND_ACCENT } from "./node-ui-
|
|
5
|
-
import { i as loadElk, r as useColorMode, t as SELECTION_OUTLINE } from "./canvas-theme-
|
|
2
|
+
import { d as quietActivity, f as syncAmbient, g as useRunNow, h as useMotionAllowed, i as fmtCost, m as useAttention, o as fmtEventTime, p as tokenMilliseconds, s as fmtTokenTotal, u as ambientStyle } from "./AgentGraph-YuBa5O8m.js";
|
|
3
|
+
import { t as LatestEvents } from "./latest-events-CWvZr8Ut.js";
|
|
4
|
+
import { P as shortModel, m as edgeColor, o as RESTING_BORDER, r as NodeMark, s as RESTING_SHADOW, t as KIND_ACCENT } from "./node-ui-Dst2R2M5.js";
|
|
5
|
+
import { i as loadElk, r as useColorMode, t as SELECTION_OUTLINE } from "./canvas-theme-aqWjGbfu.js";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import {
|
|
7
|
+
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
9
|
+
import { ChevronDown, Wrench } from "lucide-react";
|
|
9
10
|
import { RunStatusIcon, statusMeta } from "@tangle-network/ui/run";
|
|
10
11
|
import { Background, BaseEdge, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, Position, ReactFlow, useInternalNode } from "@xyflow/react";
|
|
11
12
|
import "@xyflow/react/dist/style.css";
|
|
@@ -17,12 +18,11 @@ const AGENT_CARD = {
|
|
|
17
18
|
};
|
|
18
19
|
/** A live card: room for the header, a two-line thinking line and the last three events. Fixed for the same reason. */
|
|
19
20
|
const LIVE_AGENT_CARD = {
|
|
20
|
-
width:
|
|
21
|
+
width: 296,
|
|
21
22
|
height: 164
|
|
22
23
|
};
|
|
23
24
|
const GRID_GAP = 10;
|
|
24
25
|
const FRAME_PAD = 12;
|
|
25
|
-
const MAX_GRID_COLUMNS = 6;
|
|
26
26
|
const LAYER_GAP = 56;
|
|
27
27
|
const SIBLING_GAP = 24;
|
|
28
28
|
function buildAgentTopology(nodes, edges) {
|
|
@@ -71,27 +71,32 @@ function buildAgentTopology(nodes, edges) {
|
|
|
71
71
|
groupOf
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
function gridSize(members, columns, card) {
|
|
74
|
+
function gridSize(members, columns, card, collapsed = false) {
|
|
75
|
+
if (collapsed) return {
|
|
76
|
+
width: card.width + 24,
|
|
77
|
+
height: 80
|
|
78
|
+
};
|
|
75
79
|
const cols = Math.max(1, Math.min(columns, members));
|
|
76
80
|
const rows = Math.ceil(members / cols);
|
|
77
81
|
return {
|
|
78
82
|
width: cols * card.width + (cols - 1) * GRID_GAP + 24,
|
|
79
|
-
height:
|
|
83
|
+
height: 80 + rows * card.height + (rows - 1) * GRID_GAP + FRAME_PAD
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
/** The boxes the layouter places: each ungrouped agent, and each grid as one block. */
|
|
83
|
-
function layoutUnits(topology, columns, card) {
|
|
87
|
+
function layoutUnits(topology, columns, card, collapsed) {
|
|
84
88
|
const units = [];
|
|
85
|
-
|
|
89
|
+
const ownerGroup = new Map(topology.groups.map((group) => [group.parentId, group.id]));
|
|
90
|
+
for (const id of topology.agentIds) if (!topology.groupOf.has(id) && !ownerGroup.has(id)) units.push({
|
|
86
91
|
id,
|
|
87
92
|
width: card.width,
|
|
88
93
|
height: card.height
|
|
89
94
|
});
|
|
90
95
|
for (const g of topology.groups) units.push({
|
|
91
96
|
id: g.id,
|
|
92
|
-
...gridSize(g.memberIds.length, columns, card)
|
|
97
|
+
...gridSize(g.memberIds.length, columns, card, collapsed.has(g.id))
|
|
93
98
|
});
|
|
94
|
-
const unitOf = (id) => topology.groupOf.get(id) ?? id;
|
|
99
|
+
const unitOf = (id) => topology.groupOf.get(id) ?? ownerGroup.get(id) ?? id;
|
|
95
100
|
const seen = /* @__PURE__ */ new Set();
|
|
96
101
|
const links = [];
|
|
97
102
|
for (const s of topology.spawns) {
|
|
@@ -190,8 +195,8 @@ function placeByDepth(units, links, direction) {
|
|
|
190
195
|
return at;
|
|
191
196
|
}
|
|
192
197
|
/** Lay the run out with every grid `columns` wide. */
|
|
193
|
-
async function layoutAgents(topology, direction, columns, card = AGENT_CARD) {
|
|
194
|
-
const { units, links } = layoutUnits(topology, columns, card);
|
|
198
|
+
async function layoutAgents(topology, direction, columns, card = AGENT_CARD, collapsed = /* @__PURE__ */ new Set()) {
|
|
199
|
+
const { units, links } = layoutUnits(topology, columns, card, collapsed);
|
|
195
200
|
const placed = await placeWithElk(units, links, direction) ?? placeByDepth(units, links, direction);
|
|
196
201
|
const positions = /* @__PURE__ */ new Map();
|
|
197
202
|
const frames = [];
|
|
@@ -212,17 +217,24 @@ async function layoutAgents(topology, direction, columns, card = AGENT_CARD) {
|
|
|
212
217
|
y: 0
|
|
213
218
|
};
|
|
214
219
|
positions.delete(g.id);
|
|
215
|
-
const
|
|
220
|
+
const closed = collapsed.has(g.id);
|
|
221
|
+
const size = gridSize(g.memberIds.length, columns, card, closed);
|
|
216
222
|
frames.push({
|
|
217
223
|
group: g,
|
|
218
224
|
...origin,
|
|
219
|
-
...size
|
|
225
|
+
...size,
|
|
226
|
+
collapsed: closed
|
|
227
|
+
});
|
|
228
|
+
positions.set(g.parentId, {
|
|
229
|
+
x: origin.x + FRAME_PAD,
|
|
230
|
+
y: origin.y + 8
|
|
220
231
|
});
|
|
232
|
+
if (closed) continue;
|
|
221
233
|
const cols = Math.max(1, Math.min(columns, g.memberIds.length));
|
|
222
234
|
g.memberIds.forEach((id, i) => {
|
|
223
235
|
positions.set(id, {
|
|
224
236
|
x: origin.x + FRAME_PAD + i % cols * (card.width + GRID_GAP),
|
|
225
|
-
y: origin.y +
|
|
237
|
+
y: origin.y + 80 + Math.floor(i / cols) * (card.height + GRID_GAP)
|
|
226
238
|
});
|
|
227
239
|
});
|
|
228
240
|
}
|
|
@@ -234,34 +246,6 @@ async function layoutAgents(topology, direction, columns, card = AGENT_CARD) {
|
|
|
234
246
|
height
|
|
235
247
|
};
|
|
236
248
|
}
|
|
237
|
-
/** The grid widths worth trying for this run: one per column count a grid can use. */
|
|
238
|
-
function candidateColumns(topology) {
|
|
239
|
-
const largest = Math.max(0, ...topology.groups.map((g) => g.memberIds.length));
|
|
240
|
-
const top = Math.min(MAX_GRID_COLUMNS, largest);
|
|
241
|
-
return top <= 1 ? [1] : Array.from({ length: top }, (_, i) => i + 1);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* The layout that shows the run largest in this canvas: the highest zoom at
|
|
245
|
-
* which it fits whole, capped at the cards' designed size. Layouts that all
|
|
246
|
-
* reach that cap tie, and the tie goes to the one whose shape is closest to the
|
|
247
|
-
* canvas's, so a wide canvas gets a wide grid and a phone a tall one.
|
|
248
|
-
*/
|
|
249
|
-
function pickAgentLayout(layouts, canvas, padding) {
|
|
250
|
-
let best = null;
|
|
251
|
-
let bestZoom = -1;
|
|
252
|
-
let bestShape = Number.POSITIVE_INFINITY;
|
|
253
|
-
const aspect = canvas.width / Math.max(1, canvas.height);
|
|
254
|
-
for (const l of layouts) {
|
|
255
|
-
const zoom = Math.min(1, (canvas.width - 2 * padding) / Math.max(1, l.width), (canvas.height - 2 * padding) / Math.max(1, l.height));
|
|
256
|
-
const shape = Math.abs(Math.log(l.width / Math.max(1, l.height) / aspect));
|
|
257
|
-
if (zoom > bestZoom + .02 || Math.abs(zoom - bestZoom) <= .02 && shape < bestShape) {
|
|
258
|
-
best = l;
|
|
259
|
-
bestZoom = zoom;
|
|
260
|
-
bestShape = shape;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return best;
|
|
264
|
-
}
|
|
265
249
|
// src/workflows/agent-graph-parts.tsx
|
|
266
250
|
/**
|
|
267
251
|
* What `AgentGraph` draws: an agent's card, the frame around a grid of leaf
|
|
@@ -364,7 +348,7 @@ function AgentCardView({ agent, selected, direction, now: fixed, onOpen, parentL
|
|
|
364
348
|
})
|
|
365
349
|
}),
|
|
366
350
|
/* @__PURE__ */ jsxs("span", {
|
|
367
|
-
className: "min-w-0 flex-1 truncate text-
|
|
351
|
+
className: "min-w-0 flex-1 truncate text-sm font-semibold leading-[18px]",
|
|
368
352
|
children: [parentLabel && /* @__PURE__ */ jsxs("span", {
|
|
369
353
|
className: "ag-parent-label",
|
|
370
354
|
children: [parentLabel, " / "]
|
|
@@ -410,62 +394,28 @@ function AgentCard({ data }) {
|
|
|
410
394
|
handles: true
|
|
411
395
|
});
|
|
412
396
|
}
|
|
413
|
-
/** The statuses a grid caption counts: the ones a reader looks for. */
|
|
414
|
-
const CAPTION_STATUSES = [
|
|
415
|
-
"needs-you",
|
|
416
|
-
"failed",
|
|
417
|
-
"quiet",
|
|
418
|
-
"waiting",
|
|
419
|
-
"running"
|
|
420
|
-
];
|
|
421
397
|
function AgentFrameBox({ data }) {
|
|
422
|
-
const { frame,
|
|
423
|
-
const counts = CAPTION_STATUSES.map((status) => ({
|
|
424
|
-
status,
|
|
425
|
-
n: status === "quiet" ? data.quietCount ?? 0 : statuses.filter((s) => s === status).length
|
|
426
|
-
})).filter((c) => c.n > 0);
|
|
427
|
-
const words = [`${frame.group.memberIds.length} agents`, ...counts.map((c) => `${c.n} ${c.status === "quiet" ? "quiet" : agentStatus(c.status).label.toLowerCase()}`)].join(", ");
|
|
398
|
+
const { frame, direction } = data;
|
|
428
399
|
return /* @__PURE__ */ jsxs("div", {
|
|
429
400
|
className: "relative h-full w-full rounded-2xl border",
|
|
430
401
|
style: {
|
|
431
402
|
background: "color-mix(in srgb, hsl(var(--muted-foreground)) 7%, transparent)",
|
|
432
403
|
borderColor: "hsl(var(--foreground) / 0.08)"
|
|
433
404
|
},
|
|
434
|
-
children: [/* @__PURE__ */ jsx(Handles, {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
/* @__PURE__ */ jsx(Users, {
|
|
449
|
-
size: 12,
|
|
450
|
-
strokeWidth: 2
|
|
451
|
-
}),
|
|
452
|
-
frame.group.memberIds.length,
|
|
453
|
-
" agents"
|
|
454
|
-
]
|
|
455
|
-
}),
|
|
456
|
-
counts.map((c) => /* @__PURE__ */ jsxs("span", {
|
|
457
|
-
"aria-hidden": true,
|
|
458
|
-
className: "flex items-center gap-1 tabular-nums",
|
|
459
|
-
children: [
|
|
460
|
-
c.status !== "quiet" && /* @__PURE__ */ jsx(StatusGlyph, {
|
|
461
|
-
status: c.status,
|
|
462
|
-
size: 11
|
|
463
|
-
}),
|
|
464
|
-
c.n,
|
|
465
|
-
c.status === "quiet" ? " quiet" : ""
|
|
466
|
-
]
|
|
467
|
-
}, c.status))
|
|
468
|
-
]
|
|
405
|
+
children: [/* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("button", {
|
|
406
|
+
type: "button",
|
|
407
|
+
className: "nodrag nopan absolute right-3 top-2 flex h-16 w-8 flex-col items-center justify-center gap-1 rounded text-sm text-muted-foreground hover:bg-muted focus-visible:outline",
|
|
408
|
+
"aria-label": `${frame.collapsed ? "Expand" : "Collapse"} ${data.label} team`,
|
|
409
|
+
"aria-expanded": !frame.collapsed,
|
|
410
|
+
title: `${data.label} · ${frame.group.memberIds.length} agents`,
|
|
411
|
+
onClick: (event) => {
|
|
412
|
+
event.stopPropagation();
|
|
413
|
+
data.onToggle();
|
|
414
|
+
},
|
|
415
|
+
children: [/* @__PURE__ */ jsx(ChevronDown, {
|
|
416
|
+
size: 16,
|
|
417
|
+
className: frame.collapsed ? "-rotate-90" : void 0
|
|
418
|
+
}), /* @__PURE__ */ jsx("span", { children: frame.group.memberIds.length })]
|
|
469
419
|
})]
|
|
470
420
|
});
|
|
471
421
|
}
|
|
@@ -574,48 +524,13 @@ function LiveAgentCardView({ agent, selected, direction = "TB", now: fixed, hand
|
|
|
574
524
|
const quiet = quietActivity(agent, now);
|
|
575
525
|
const s = agentStatus(agent.status);
|
|
576
526
|
const meta = [agent.model ? shortModel(agent.model) : void 0, agent.effort].filter(Boolean).join(" · ");
|
|
577
|
-
const
|
|
527
|
+
const events = (agent.events ?? []).slice(-3);
|
|
578
528
|
const cost = Number.isFinite(agent.costUsd) ? fmtCost(agent.costUsd) : void 0;
|
|
579
529
|
const metrics = [
|
|
580
530
|
cost,
|
|
581
531
|
!failed && agent.startedAt != null ? `${fmtEventTime(agent.startedAt, now, false)} elapsed` : void 0,
|
|
582
532
|
Number.isFinite(agent.commits) ? `${agent.commits} ${agent.commits === 1 ? "commit" : "commits"}` : void 0
|
|
583
533
|
].filter(Boolean).join(" · ") || "—";
|
|
584
|
-
const tail = useRef(null);
|
|
585
|
-
const motion = useMotionAllowed();
|
|
586
|
-
const previous = useRef(events.at(-1)?.id);
|
|
587
|
-
const pending = useRef(void 0);
|
|
588
|
-
const latest = useRef(agent.events);
|
|
589
|
-
latest.current = agent.events;
|
|
590
|
-
useEffect(() => {
|
|
591
|
-
if (pending.current !== void 0) return;
|
|
592
|
-
pending.current = setTimeout(() => {
|
|
593
|
-
setEvents((latest.current ?? []).slice(-4));
|
|
594
|
-
pending.current = void 0;
|
|
595
|
-
}, tokenMilliseconds("--duration-ambient", ref.current) * .15);
|
|
596
|
-
}, [agent.events]);
|
|
597
|
-
useEffect(() => () => clearTimeout(pending.current), []);
|
|
598
|
-
const tailAnimation = useRef(null);
|
|
599
|
-
useEffect(() => () => tailAnimation.current?.cancel(), []);
|
|
600
|
-
useLayoutEffect(() => {
|
|
601
|
-
const last = events.at(-1)?.id;
|
|
602
|
-
const old = previous.current;
|
|
603
|
-
previous.current = last;
|
|
604
|
-
const element = tail.current;
|
|
605
|
-
if (!motion) {
|
|
606
|
-
tailAnimation.current?.cancel();
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
if (!element || !old || old === last) return;
|
|
610
|
-
const queued = events.findIndex((event) => event.id === old);
|
|
611
|
-
const offset = new DOMMatrix(getComputedStyle(element).transform).m42 + 16;
|
|
612
|
-
tailAnimation.current?.cancel();
|
|
613
|
-
const frames = queued < 0 ? [{ opacity: 0 }, { opacity: 1 }] : [{ transform: `translateY(${offset}px)` }, { transform: "translateY(-16px)" }];
|
|
614
|
-
tailAnimation.current = element.animate(frames, {
|
|
615
|
-
duration: tokenMilliseconds("--duration-stream", element),
|
|
616
|
-
easing: getComputedStyle(element).getPropertyValue("--ease-standard").trim()
|
|
617
|
-
});
|
|
618
|
-
}, [events, motion]);
|
|
619
534
|
return /* @__PURE__ */ jsxs("div", {
|
|
620
535
|
className: "ag-live-slot",
|
|
621
536
|
children: [handles && /* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("div", {
|
|
@@ -722,7 +637,6 @@ function LiveAgentCardView({ agent, selected, direction = "TB", now: fixed, hand
|
|
|
722
637
|
}), events.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
723
638
|
className: "ag-live-events-clip",
|
|
724
639
|
children: /* @__PURE__ */ jsx("ol", {
|
|
725
|
-
ref: tail,
|
|
726
640
|
className: "ag-live-events",
|
|
727
641
|
"aria-label": "Latest events",
|
|
728
642
|
children: events.map((event) => /* @__PURE__ */ jsxs("li", {
|
|
@@ -765,7 +679,7 @@ const NODE_TYPES = {
|
|
|
765
679
|
const EDGE_TYPES = { agentMessage: AgentMessageEdgeView };
|
|
766
680
|
/** The fit's margin, in screen pixels. */
|
|
767
681
|
const FIT_PADDING = 16;
|
|
768
|
-
const
|
|
682
|
+
const PHONE_PAGE = 24;
|
|
769
683
|
/** The canvas's size, once it has one. */
|
|
770
684
|
function useCanvasSize() {
|
|
771
685
|
const [size, setSize] = useState(null);
|
|
@@ -794,7 +708,7 @@ function revealSelection(inst, wrapper, id) {
|
|
|
794
708
|
const width = node?.measured.width ?? node?.width, height = node?.measured.height ?? node?.height;
|
|
795
709
|
if (!node || !box || !width || !height) return;
|
|
796
710
|
const current = inst.getViewport(), position = node.internals.positionAbsolute;
|
|
797
|
-
const zoom = Math.max(.02, Math.min(
|
|
711
|
+
const zoom = Math.max(.02, Math.min(1, (box.width - 32) / width, (box.height - 32) / height));
|
|
798
712
|
const x = Math.max(FIT_PADDING - position.x * zoom, Math.min(box.width - FIT_PADDING - (position.x + width) * zoom, current.x));
|
|
799
713
|
const y = Math.max(FIT_PADDING - position.y * zoom, Math.min(box.height - FIT_PADDING - (position.y + height) * zoom, current.y));
|
|
800
714
|
if (x !== current.x || y !== current.y || zoom !== current.zoom) inst.setViewport({
|
|
@@ -807,7 +721,23 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
807
721
|
const card = live ? LIVE_AGENT_CARD : AGENT_CARD;
|
|
808
722
|
const clock = useRunNow(now);
|
|
809
723
|
const animateMotion = useMotionAllowed();
|
|
724
|
+
const detailsId = useId();
|
|
725
|
+
const hoverTimer = useRef(void 0);
|
|
726
|
+
const hoverTarget = useRef(null);
|
|
727
|
+
const returningFocus = useRef(false);
|
|
728
|
+
const keepDetails = () => clearTimeout(hoverTimer.current);
|
|
729
|
+
const hideDetails = () => {
|
|
730
|
+
keepDetails();
|
|
731
|
+
setHovered(null);
|
|
732
|
+
};
|
|
733
|
+
const leaveDetails = () => {
|
|
734
|
+
keepDetails();
|
|
735
|
+
hoverTimer.current = setTimeout(() => setHovered(null), tokenMilliseconds("--duration-fast", wrapperRef.current));
|
|
736
|
+
};
|
|
737
|
+
useEffect(() => () => clearTimeout(hoverTimer.current), []);
|
|
810
738
|
const [hovered, setHovered] = useState(null);
|
|
739
|
+
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
740
|
+
const [phonePage, setPhonePage] = useState(0);
|
|
811
741
|
const wrapperRef = useRef(null);
|
|
812
742
|
const [canvas, measureCanvas] = useCanvasSize();
|
|
813
743
|
const colorMode = useColorMode(wrapperRef, nodes.length > 0);
|
|
@@ -816,37 +746,44 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
816
746
|
const topologyRef = useRef(nextTopology);
|
|
817
747
|
if (topologyRef.current.key !== nextTopology.key) topologyRef.current = nextTopology;
|
|
818
748
|
const topology = topologyRef.current;
|
|
819
|
-
const
|
|
820
|
-
const
|
|
749
|
+
const columnKey = `${direction}:${live}:${topology.key}`;
|
|
750
|
+
const columnRef = useRef(null);
|
|
751
|
+
if (canvas && !narrow && columnRef.current?.key !== columnKey) columnRef.current = {
|
|
752
|
+
key: columnKey,
|
|
753
|
+
columns: Math.max(1, Math.min(6, Math.floor((canvas.width - 32) / (card.width + 10))))
|
|
754
|
+
};
|
|
755
|
+
const columns = columnRef.current?.columns ?? 1;
|
|
756
|
+
const layoutKey = JSON.stringify([
|
|
757
|
+
direction,
|
|
758
|
+
live,
|
|
759
|
+
topology.key,
|
|
760
|
+
columns,
|
|
761
|
+
[...collapsed]
|
|
762
|
+
]);
|
|
763
|
+
const [placed, setPlaced] = useState(null);
|
|
821
764
|
useEffect(() => {
|
|
822
765
|
if (narrow) return;
|
|
823
|
-
let
|
|
824
|
-
|
|
825
|
-
if (
|
|
766
|
+
let active = true;
|
|
767
|
+
layoutAgents(topology, direction, columns, card, collapsed).then((layout) => {
|
|
768
|
+
if (active) setPlaced({
|
|
826
769
|
key: layoutKey,
|
|
827
|
-
|
|
770
|
+
layout
|
|
828
771
|
});
|
|
829
772
|
});
|
|
830
773
|
return () => {
|
|
831
|
-
|
|
774
|
+
active = false;
|
|
832
775
|
};
|
|
833
|
-
}, [
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}, [
|
|
845
|
-
layouts,
|
|
846
|
-
layoutKey,
|
|
847
|
-
canvas,
|
|
848
|
-
selectedId
|
|
849
|
-
]);
|
|
776
|
+
}, [layoutKey, narrow]);
|
|
777
|
+
const layout = placed?.key === layoutKey ? placed.layout : null;
|
|
778
|
+
useEffect(() => {
|
|
779
|
+
const group = selectedId && topology.groupOf.get(selectedId);
|
|
780
|
+
if (group) setCollapsed((current) => {
|
|
781
|
+
if (!current.has(group)) return current;
|
|
782
|
+
const next = new Set(current);
|
|
783
|
+
next.delete(group);
|
|
784
|
+
return next;
|
|
785
|
+
});
|
|
786
|
+
}, [selectedId, topology.key]);
|
|
850
787
|
const byId = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);
|
|
851
788
|
const rfRef = useRef(null);
|
|
852
789
|
const flowNodes = useMemo(() => {
|
|
@@ -868,8 +805,15 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
868
805
|
data: {
|
|
869
806
|
frame,
|
|
870
807
|
direction,
|
|
871
|
-
|
|
872
|
-
|
|
808
|
+
label: byId.get(frame.group.parentId)?.label ?? "Agent",
|
|
809
|
+
onToggle: () => {
|
|
810
|
+
if (!frame.collapsed && selectedId && frame.group.memberIds.includes(selectedId)) onSelect?.(frame.group.parentId);
|
|
811
|
+
setCollapsed((current) => {
|
|
812
|
+
const next = new Set(current);
|
|
813
|
+
next.has(frame.group.id) ? next.delete(frame.group.id) : next.add(frame.group.id);
|
|
814
|
+
return next;
|
|
815
|
+
});
|
|
816
|
+
}
|
|
873
817
|
}
|
|
874
818
|
}));
|
|
875
819
|
const cards = [];
|
|
@@ -877,19 +821,24 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
877
821
|
const agent = byId.get(id);
|
|
878
822
|
const position = layout.positions.get(id);
|
|
879
823
|
if (!agent || !position) continue;
|
|
880
|
-
const selected = selectedId === id;
|
|
824
|
+
const selected = selectedId === id, owned = layout.frames.find((frame) => frame.group.parentId === id);
|
|
825
|
+
const width = owned ? owned.width - 64 : card.width, height = owned ? 64 : card.height;
|
|
826
|
+
const measured = rfRef.current?.getInternalNode(id)?.measured;
|
|
881
827
|
cards.push({
|
|
882
828
|
id,
|
|
883
829
|
type: live ? "liveAgent" : "agent",
|
|
884
830
|
position,
|
|
885
|
-
width
|
|
886
|
-
height
|
|
887
|
-
measured:
|
|
888
|
-
className: AGENT_NODE_CLASS
|
|
831
|
+
width,
|
|
832
|
+
height,
|
|
833
|
+
measured: measured?.width === width && measured.height === height ? measured : void 0,
|
|
834
|
+
className: `${AGENT_NODE_CLASS}${owned ? " ag-frame-owner" : ""}`,
|
|
889
835
|
draggable: false,
|
|
890
836
|
ariaRole: "button",
|
|
891
837
|
ariaLabel: agentSummary(agent),
|
|
892
|
-
domAttributes: {
|
|
838
|
+
domAttributes: {
|
|
839
|
+
"aria-pressed": selected,
|
|
840
|
+
"aria-describedby": hovered?.id === id ? detailsId : void 0
|
|
841
|
+
},
|
|
893
842
|
data: {
|
|
894
843
|
agent,
|
|
895
844
|
selected,
|
|
@@ -911,20 +860,22 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
911
860
|
card,
|
|
912
861
|
clock,
|
|
913
862
|
onSelect,
|
|
914
|
-
renderEventIcon
|
|
863
|
+
renderEventIcon,
|
|
864
|
+
hovered
|
|
915
865
|
]);
|
|
916
866
|
const flowEdges = useMemo(() => {
|
|
917
867
|
if (!layout) return [];
|
|
918
868
|
const out = [];
|
|
919
869
|
const seen = /* @__PURE__ */ new Set();
|
|
920
870
|
for (const s of topology.spawns) {
|
|
921
|
-
const target = topology.groupOf.get(s.to) ?? s.to;
|
|
922
|
-
|
|
871
|
+
const source = topology.groups.find((group) => group.parentId === s.from)?.id ?? s.from, target = topology.groupOf.get(s.to) ?? s.to;
|
|
872
|
+
if (source === target) continue;
|
|
873
|
+
const id = `spawn:${source}:${target}`;
|
|
923
874
|
if (seen.has(id)) continue;
|
|
924
875
|
seen.add(id);
|
|
925
876
|
out.push({
|
|
926
877
|
id,
|
|
927
|
-
source
|
|
878
|
+
source,
|
|
928
879
|
target,
|
|
929
880
|
type: "smoothstep",
|
|
930
881
|
pathOptions: { borderRadius: 14 },
|
|
@@ -936,11 +887,11 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
936
887
|
}
|
|
937
888
|
});
|
|
938
889
|
}
|
|
939
|
-
const ids = new Set(
|
|
890
|
+
const ids = new Set(layout.positions.keys());
|
|
940
891
|
const messages = /* @__PURE__ */ new Map();
|
|
941
892
|
for (const e of edges) {
|
|
942
893
|
if (e.kind === "spawn" || e.from === e.to || !ids.has(e.from) || !ids.has(e.to)) continue;
|
|
943
|
-
if (![selectedId, hovered].some((id) => id === e.from || id === e.to)) continue;
|
|
894
|
+
if (![selectedId, hovered?.id].some((id) => id === e.from || id === e.to)) continue;
|
|
944
895
|
const id = `${e.kind}:${e.from}:${e.to}`;
|
|
945
896
|
const prior = messages.get(id);
|
|
946
897
|
const count = (prior?.data?.count ?? 0) + Math.max(1, e.count ?? 1);
|
|
@@ -978,7 +929,6 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
978
929
|
]);
|
|
979
930
|
const [ready, setReady] = useState(false);
|
|
980
931
|
const [shown, setShown] = useState(false);
|
|
981
|
-
const readerMoved = useRef(false);
|
|
982
932
|
const selection = useRef(selectedId);
|
|
983
933
|
selection.current = selectedId;
|
|
984
934
|
const framed = useRef(null);
|
|
@@ -996,19 +946,22 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
996
946
|
const inst = rfRef.current;
|
|
997
947
|
if (!ready || !inst || !layoutIdentity || !canvas) return;
|
|
998
948
|
const relayout = framed.current !== layoutIdentity;
|
|
999
|
-
const preserveView = !relayout
|
|
949
|
+
const preserveView = !relayout;
|
|
1000
950
|
if (preserveView && !selection.current) return;
|
|
1001
951
|
const animate = framed.current !== null && relayout && animateMotion;
|
|
1002
952
|
framed.current = layoutIdentity;
|
|
1003
|
-
readerMoved.current = preserveView;
|
|
1004
953
|
const frame = requestAnimationFrame(() => {
|
|
1005
954
|
if (preserveView) revealSelection(inst, wrapperRef.current, selection.current);
|
|
1006
|
-
else
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
955
|
+
else {
|
|
956
|
+
const id = selection.current ?? topology.agentIds[0], position = layout?.positions.get(id), width = inst.getInternalNode(id)?.width ?? card.width;
|
|
957
|
+
const x = layout.width + 32 <= canvas.width ? (canvas.width - layout.width) / 2 : position ? Math.max(FIT_PADDING - position.x, canvas.width / 2 - position.x - width / 2) : FIT_PADDING;
|
|
958
|
+
const y = layout.height + 32 <= canvas.height ? FIT_PADDING : FIT_PADDING - (position?.y ?? 0);
|
|
959
|
+
inst.setViewport({
|
|
960
|
+
x,
|
|
961
|
+
y,
|
|
962
|
+
zoom: 1
|
|
963
|
+
}, { duration: animate ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0 });
|
|
964
|
+
}
|
|
1012
965
|
setShown(true);
|
|
1013
966
|
});
|
|
1014
967
|
return () => cancelAnimationFrame(frame);
|
|
@@ -1017,7 +970,8 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1017
970
|
layoutIdentity,
|
|
1018
971
|
canvas,
|
|
1019
972
|
minZoom,
|
|
1020
|
-
animateMotion
|
|
973
|
+
animateMotion,
|
|
974
|
+
selectedId
|
|
1021
975
|
]);
|
|
1022
976
|
const select = useCallback((id) => {
|
|
1023
977
|
if (byId.has(id)) {
|
|
@@ -1054,12 +1008,12 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1054
1008
|
if (!target) return;
|
|
1055
1009
|
event.preventDefault();
|
|
1056
1010
|
event.stopPropagation();
|
|
1057
|
-
const position = layout?.positions.get(target);
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1011
|
+
const position = layout?.positions.get(target), size = rfRef.current?.getInternalNode(target)?.measured;
|
|
1012
|
+
if (position) rfRef.current?.setCenter(position.x + (size?.width ?? card.width) / 2, position.y + (size?.height ?? card.height) / 2, { zoom: 1 });
|
|
1013
|
+
const focus = () => wrapperRef.current?.querySelector(`.react-flow__node[data-id="${CSS.escape(target)}"]`);
|
|
1014
|
+
const visible = focus();
|
|
1015
|
+
if (visible) visible.focus();
|
|
1016
|
+
else requestAnimationFrame(() => requestAnimationFrame(() => focus()?.focus()));
|
|
1063
1017
|
}, [
|
|
1064
1018
|
byId,
|
|
1065
1019
|
select,
|
|
@@ -1080,6 +1034,23 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1080
1034
|
}
|
|
1081
1035
|
return depth;
|
|
1082
1036
|
};
|
|
1037
|
+
const readingOrder = useRef([]), known = new Set(readingOrder.current);
|
|
1038
|
+
readingOrder.current = [...readingOrder.current.filter((id) => byId.has(id)), ...nodes.filter((agent) => !known.has(agent.id)).sort((a, b) => activityRank(a) - activityRank(b)).map((agent) => agent.id)];
|
|
1039
|
+
const phoneNodes = readingOrder.current.map((id) => byId.get(id));
|
|
1040
|
+
useEffect(() => {
|
|
1041
|
+
const at = phoneNodes.findIndex((node) => node.id === selectedId);
|
|
1042
|
+
if (at >= 0) setPhonePage(Math.floor(at / PHONE_PAGE));
|
|
1043
|
+
}, [selectedId]);
|
|
1044
|
+
useEffect(() => {
|
|
1045
|
+
if (!narrow || !selectedId) return;
|
|
1046
|
+
const frame = requestAnimationFrame(() => wrapperRef.current?.querySelector(`[data-agent-id="${CSS.escape(selectedId)}"]`)?.scrollIntoView({ block: "nearest" }));
|
|
1047
|
+
return () => cancelAnimationFrame(frame);
|
|
1048
|
+
}, [
|
|
1049
|
+
selectedId,
|
|
1050
|
+
phonePage,
|
|
1051
|
+
narrow
|
|
1052
|
+
]);
|
|
1053
|
+
const first = Math.min(phonePage * PHONE_PAGE, Math.max(0, Math.ceil(nodes.length / PHONE_PAGE) - 1) * PHONE_PAGE);
|
|
1083
1054
|
const counts = [
|
|
1084
1055
|
"needs-you",
|
|
1085
1056
|
"failed",
|
|
@@ -1106,8 +1077,8 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1106
1077
|
const index = group.matches.findIndex((agent) => agent.id === selectedId);
|
|
1107
1078
|
const next = group.matches[(index + 1) % group.matches.length];
|
|
1108
1079
|
select(next.id);
|
|
1109
|
-
const p = layout?.positions.get(next.id);
|
|
1110
|
-
if (p) rfRef.current?.setCenter(p.x + card.width / 2, p.y + card.height / 2, {
|
|
1080
|
+
const p = layout?.positions.get(next.id), size = rfRef.current?.getInternalNode(next.id)?.measured;
|
|
1081
|
+
if (p) rfRef.current?.setCenter(p.x + (size?.width ?? card.width) / 2, p.y + (size?.height ?? card.height) / 2, {
|
|
1111
1082
|
zoom: 1,
|
|
1112
1083
|
duration: animateMotion ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0
|
|
1113
1084
|
});
|
|
@@ -1139,7 +1110,35 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1139
1110
|
},
|
|
1140
1111
|
className: `agent-graph ag-live relative flex flex-col ${className ?? ""}`,
|
|
1141
1112
|
tabIndex: -1,
|
|
1142
|
-
onKeyDownCapture:
|
|
1113
|
+
onKeyDownCapture: (event) => {
|
|
1114
|
+
if (event.target instanceof HTMLElement && event.target.closest("[data-agent-metrics]")) return;
|
|
1115
|
+
if (event.key === "Tab" && !event.shiftKey && hovered) {
|
|
1116
|
+
event.preventDefault();
|
|
1117
|
+
document.getElementById(detailsId)?.focus();
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (event.key === "Escape") hideDetails();
|
|
1121
|
+
handleKeyDown(event);
|
|
1122
|
+
},
|
|
1123
|
+
onFocusCapture: (event) => {
|
|
1124
|
+
if (!(event.target instanceof HTMLElement) || returningFocus.current) return;
|
|
1125
|
+
keepDetails();
|
|
1126
|
+
if (event.target.closest("[data-agent-metrics]")) return;
|
|
1127
|
+
if (!event.target.matches(":focus-visible")) {
|
|
1128
|
+
hideDetails();
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const node = event.target.closest(".react-flow__node, [data-agent-id]");
|
|
1132
|
+
const id = node?.dataset.id ?? node?.dataset.agentId;
|
|
1133
|
+
hoverTarget.current = node ?? null;
|
|
1134
|
+
setHovered(id && byId.has(id) ? {
|
|
1135
|
+
id,
|
|
1136
|
+
rect: node.getBoundingClientRect()
|
|
1137
|
+
} : null);
|
|
1138
|
+
},
|
|
1139
|
+
onBlurCapture: (event) => {
|
|
1140
|
+
if (!event.currentTarget.contains(event.relatedTarget) && event.relatedTarget?.id !== detailsId) hideDetails();
|
|
1141
|
+
},
|
|
1143
1142
|
"data-layout": narrow ? "list" : layout && shown ? "ready" : "pending",
|
|
1144
1143
|
"data-columns": layout?.columns,
|
|
1145
1144
|
children: [
|
|
@@ -1151,10 +1150,10 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1151
1150
|
selectedId,
|
|
1152
1151
|
onSelect: onSelect ? select : void 0
|
|
1153
1152
|
}),
|
|
1154
|
-
narrow ? /* @__PURE__ */
|
|
1153
|
+
narrow ? /* @__PURE__ */ jsxs("ul", {
|
|
1155
1154
|
className: "ag-graph-viewport ag-phone-list overflow-auto",
|
|
1156
1155
|
"aria-label": "Agents by attention",
|
|
1157
|
-
children: [
|
|
1156
|
+
children: [phoneNodes.slice(first, first + PHONE_PAGE).map((agent) => /* @__PURE__ */ jsx("li", {
|
|
1158
1157
|
style: { ["--depth"]: depthOf(agent.id) },
|
|
1159
1158
|
children: /* @__PURE__ */ jsx("div", {
|
|
1160
1159
|
role: "button",
|
|
@@ -1186,7 +1185,39 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1186
1185
|
onOpen: onSelect ? () => select(agent.id) : void 0
|
|
1187
1186
|
})
|
|
1188
1187
|
})
|
|
1189
|
-
}, agent.id))
|
|
1188
|
+
}, agent.id)), nodes.length > PHONE_PAGE && /* @__PURE__ */ jsxs("li", {
|
|
1189
|
+
className: "flex items-center justify-between gap-2 border-t border-border pt-3 text-sm",
|
|
1190
|
+
"aria-label": "Agent pages",
|
|
1191
|
+
children: [
|
|
1192
|
+
/* @__PURE__ */ jsx("button", {
|
|
1193
|
+
type: "button",
|
|
1194
|
+
className: "rounded px-2 py-1 hover:bg-muted disabled:opacity-40 focus-visible:outline",
|
|
1195
|
+
disabled: !first,
|
|
1196
|
+
onClick: () => {
|
|
1197
|
+
setPhonePage((page) => page - 1);
|
|
1198
|
+
wrapperRef.current?.querySelector(".ag-phone-list")?.scrollTo({ top: 0 });
|
|
1199
|
+
},
|
|
1200
|
+
children: "Previous"
|
|
1201
|
+
}),
|
|
1202
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1203
|
+
first + 1,
|
|
1204
|
+
"–",
|
|
1205
|
+
Math.min(nodes.length, first + PHONE_PAGE),
|
|
1206
|
+
" of ",
|
|
1207
|
+
nodes.length
|
|
1208
|
+
] }),
|
|
1209
|
+
/* @__PURE__ */ jsx("button", {
|
|
1210
|
+
type: "button",
|
|
1211
|
+
className: "rounded px-2 py-1 hover:bg-muted disabled:opacity-40 focus-visible:outline",
|
|
1212
|
+
disabled: first + PHONE_PAGE >= nodes.length,
|
|
1213
|
+
onClick: () => {
|
|
1214
|
+
setPhonePage((page) => page + 1);
|
|
1215
|
+
wrapperRef.current?.querySelector(".ag-phone-list")?.scrollTo({ top: 0 });
|
|
1216
|
+
},
|
|
1217
|
+
children: "Next"
|
|
1218
|
+
})
|
|
1219
|
+
]
|
|
1220
|
+
})]
|
|
1190
1221
|
}) : /* @__PURE__ */ jsx("div", {
|
|
1191
1222
|
className: "ag-graph-viewport min-h-0 flex-1",
|
|
1192
1223
|
style: { opacity: shown ? 1 : 0 },
|
|
@@ -1200,13 +1231,18 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1200
1231
|
rfRef.current = inst;
|
|
1201
1232
|
setReady(true);
|
|
1202
1233
|
},
|
|
1203
|
-
onMoveStart: (event) => {
|
|
1204
|
-
if (event) readerMoved.current = true;
|
|
1205
|
-
},
|
|
1206
1234
|
onNodeClick: handleNodeClick,
|
|
1207
|
-
onNodeMouseEnter: (
|
|
1208
|
-
|
|
1235
|
+
onNodeMouseEnter: (event, node) => {
|
|
1236
|
+
keepDetails();
|
|
1237
|
+
hoverTarget.current = event.target.closest(".react-flow__node");
|
|
1238
|
+
if (hoverTarget.current) setHovered({
|
|
1239
|
+
id: node.id,
|
|
1240
|
+
rect: hoverTarget.current.getBoundingClientRect()
|
|
1241
|
+
});
|
|
1242
|
+
},
|
|
1243
|
+
onNodeMouseLeave: leaveDetails,
|
|
1209
1244
|
proOptions: { hideAttribution: true },
|
|
1245
|
+
onlyRenderVisibleElements: true,
|
|
1210
1246
|
nodesDraggable: false,
|
|
1211
1247
|
nodesConnectable: false,
|
|
1212
1248
|
elementsSelectable: false,
|
|
@@ -1228,7 +1264,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1228
1264
|
size: 1,
|
|
1229
1265
|
color: "hsl(var(--foreground) / 0.06)"
|
|
1230
1266
|
}),
|
|
1231
|
-
|
|
1267
|
+
layout && canvas && (layout.width > canvas.width || layout.height > canvas.height) && /* @__PURE__ */ jsx(MiniMap, {
|
|
1232
1268
|
pannable: true,
|
|
1233
1269
|
zoomable: true,
|
|
1234
1270
|
position: "bottom-left",
|
|
@@ -1249,8 +1285,119 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
|
|
|
1249
1285
|
})
|
|
1250
1286
|
]
|
|
1251
1287
|
})
|
|
1288
|
+
}),
|
|
1289
|
+
hovered && byId.has(hovered.id) && /* @__PURE__ */ jsx(AgentMetrics, {
|
|
1290
|
+
anchor: wrapperRef.current,
|
|
1291
|
+
mode: colorMode,
|
|
1292
|
+
id: detailsId,
|
|
1293
|
+
agent: byId.get(hovered.id),
|
|
1294
|
+
rect: hovered.rect,
|
|
1295
|
+
onEnter: keepDetails,
|
|
1296
|
+
onLeave: leaveDetails,
|
|
1297
|
+
onClose: () => {
|
|
1298
|
+
hideDetails();
|
|
1299
|
+
returningFocus.current = true;
|
|
1300
|
+
hoverTarget.current?.focus({ preventScroll: true });
|
|
1301
|
+
returningFocus.current = false;
|
|
1302
|
+
}
|
|
1252
1303
|
})
|
|
1253
1304
|
]
|
|
1254
1305
|
});
|
|
1255
1306
|
}
|
|
1307
|
+
/** Metrics stay at reading size outside the zoomed canvas and its clipping container. */
|
|
1308
|
+
function AgentMetrics({ anchor, mode, id, agent, rect, onEnter, onLeave, onClose }) {
|
|
1309
|
+
const [theme, setTheme] = useState({});
|
|
1310
|
+
useLayoutEffect(() => {
|
|
1311
|
+
if (!anchor) return;
|
|
1312
|
+
const read = () => {
|
|
1313
|
+
const css = getComputedStyle(anchor);
|
|
1314
|
+
setTheme(Object.fromEntries(Array.from(css).filter((name) => name.startsWith("--") && !name.startsWith("--tw-")).map((name) => [name, css.getPropertyValue(name)])));
|
|
1315
|
+
};
|
|
1316
|
+
read();
|
|
1317
|
+
const observer = new MutationObserver(read);
|
|
1318
|
+
for (let el = anchor; el; el = el.parentElement) observer.observe(el, {
|
|
1319
|
+
attributes: true,
|
|
1320
|
+
attributeFilter: [
|
|
1321
|
+
"class",
|
|
1322
|
+
"style",
|
|
1323
|
+
"data-theme",
|
|
1324
|
+
"data-sandbox-theme"
|
|
1325
|
+
]
|
|
1326
|
+
});
|
|
1327
|
+
return () => observer.disconnect();
|
|
1328
|
+
}, [anchor]);
|
|
1329
|
+
const facts = new Map([
|
|
1330
|
+
{
|
|
1331
|
+
label: "Tokens",
|
|
1332
|
+
value: fmtTokenTotal(agent.tokens) ?? "Not recorded"
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
label: "Cost",
|
|
1336
|
+
value: fmtCost(agent.costUsd ?? void 0) ?? "Not recorded"
|
|
1337
|
+
},
|
|
1338
|
+
...agent.facts ?? []
|
|
1339
|
+
].map((fact) => [fact.label.toLowerCase(), fact]));
|
|
1340
|
+
const width = Math.min(420, window.innerWidth - 24), height = Math.min(560, window.innerHeight - 24);
|
|
1341
|
+
const left = Math.max(12, Math.min(rect.right + 12 + width <= window.innerWidth ? rect.right + 12 : rect.left - width - 12, window.innerWidth - width - 12));
|
|
1342
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
1343
|
+
id,
|
|
1344
|
+
"data-agent-metrics": true,
|
|
1345
|
+
"data-theme": anchor?.closest("[data-theme]")?.getAttribute("data-theme") ?? mode,
|
|
1346
|
+
role: "tooltip",
|
|
1347
|
+
tabIndex: 0,
|
|
1348
|
+
onMouseEnter: onEnter,
|
|
1349
|
+
onMouseLeave: onLeave,
|
|
1350
|
+
onWheel: (event) => event.stopPropagation(),
|
|
1351
|
+
onBlur: onLeave,
|
|
1352
|
+
onKeyDown: (event) => {
|
|
1353
|
+
if (event.key === "Escape") {
|
|
1354
|
+
event.preventDefault();
|
|
1355
|
+
onClose();
|
|
1356
|
+
}
|
|
1357
|
+
},
|
|
1358
|
+
className: `${mode} rounded-xl border border-border bg-card p-4 text-sm text-foreground shadow-xl`,
|
|
1359
|
+
style: {
|
|
1360
|
+
...theme,
|
|
1361
|
+
colorScheme: mode,
|
|
1362
|
+
position: "fixed",
|
|
1363
|
+
zIndex: 1e3,
|
|
1364
|
+
width,
|
|
1365
|
+
maxHeight: height,
|
|
1366
|
+
overflow: "auto",
|
|
1367
|
+
left,
|
|
1368
|
+
top: Math.max(12, Math.min(rect.top, window.innerHeight - height - 12))
|
|
1369
|
+
},
|
|
1370
|
+
children: [
|
|
1371
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1372
|
+
className: "mb-2 flex items-center justify-between gap-3",
|
|
1373
|
+
children: [/* @__PURE__ */ jsx("strong", { children: agent.label }), /* @__PURE__ */ jsx("span", { children: agentStatus(agent.status).label })]
|
|
1374
|
+
}),
|
|
1375
|
+
/* @__PURE__ */ jsx("p", {
|
|
1376
|
+
className: "mb-3 text-muted-foreground",
|
|
1377
|
+
children: [
|
|
1378
|
+
agent.model,
|
|
1379
|
+
agent.harness,
|
|
1380
|
+
agent.role
|
|
1381
|
+
].filter(Boolean).join(" · ")
|
|
1382
|
+
}),
|
|
1383
|
+
/* @__PURE__ */ jsx("dl", {
|
|
1384
|
+
className: "grid grid-cols-[minmax(0,1fr)_auto] gap-x-5 gap-y-1.5",
|
|
1385
|
+
children: [...facts.values()].map((fact) => /* @__PURE__ */ jsxs("div", {
|
|
1386
|
+
className: "contents",
|
|
1387
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
1388
|
+
className: "min-w-0 break-words text-muted-foreground",
|
|
1389
|
+
children: fact.label
|
|
1390
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
1391
|
+
className: "m-0 min-w-0 max-w-64 whitespace-normal break-words text-right tabular-nums",
|
|
1392
|
+
children: fact.value
|
|
1393
|
+
})]
|
|
1394
|
+
}, fact.label))
|
|
1395
|
+
}),
|
|
1396
|
+
agent.status === "failed" && /* @__PURE__ */ jsx("p", {
|
|
1397
|
+
className: "mt-3 whitespace-pre-wrap break-words text-[var(--surface-danger-text)]",
|
|
1398
|
+
children: agent.result ?? agent.turn ?? "Failure cause not recorded"
|
|
1399
|
+
})
|
|
1400
|
+
]
|
|
1401
|
+
}), document.body);
|
|
1402
|
+
}
|
|
1256
1403
|
export { AgentGraphCanvas };
|