@tangle-network/charts 0.21.13 → 0.21.15
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/react/flow-waterfall.css +12 -1
- package/dist/react/index.js +61 -28
- package/package.json +1 -1
|
@@ -196,8 +196,10 @@
|
|
|
196
196
|
.fw-root .fw-view-agents .fw-names { flex-direction: column; align-items: flex-start; gap: 0; }
|
|
197
197
|
.fw-root .fw-view-agents .fw-span-name { font-weight: 600; line-height: 18px; }
|
|
198
198
|
/* One 16 px line that wraps its last part out of sight: when the line is full the tool count drops whole, never "7 to…". */
|
|
199
|
-
.fw-root .fw-view-agents .fw-subtitle { display: flex;
|
|
199
|
+
.fw-root .fw-view-agents .fw-subtitle { display: flex; align-items: center; column-gap: 4px; height: 16px; overflow: hidden; max-width: 100%; min-width: 0; color: var(--fw-muted); font-size: 13px; line-height: 16px; }
|
|
200
200
|
.fw-root .fw-view-agents .fw-subtitle-extra { flex: none; white-space: nowrap; }
|
|
201
|
+
/* Only the tail wraps, so the text is always first on its line and shrinks with an ellipsis; the tool count wraps out of sight. */
|
|
202
|
+
.fw-root .fw-view-agents .fw-subtitle-tail { display: flex; flex-wrap: wrap; column-gap: 4px; height: 16px; overflow: hidden; min-width: 0; flex: 0 1 auto; }
|
|
201
203
|
.fw-root .fw-view-agents .fw-subtitle-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
202
204
|
.fw-root .fw-view-agents .fw-detail-icon { flex: none; width: 16px; height: 16px; align-items: center; justify-content: center; overflow: hidden; }
|
|
203
205
|
.fw-root .fw-view-agents .fw-detail { font-size: 12px; line-height: 16px; }
|
|
@@ -224,6 +226,9 @@
|
|
|
224
226
|
.fw-root .fw-row[data-running] .fw-bar { transition: width 120ms linear; }
|
|
225
227
|
/* The chain that gates the trace's end: an accent edge under its bars, so cutting latency there is visibly what moves the end. */
|
|
226
228
|
.fw-root .fw-row[data-critical] .fw-bar { box-shadow: inset 0 -2px 0 var(--fw-accent); }
|
|
229
|
+
/* While searching, ancestors shown only for context recede and the matching names stand out. */
|
|
230
|
+
.fw-root .fw-row[data-search="context"] .fw-label { opacity: 0.55; }
|
|
231
|
+
.fw-root .fw-row[data-search="match"] .fw-name-text { font-weight: 600; }
|
|
227
232
|
/* A restart: an amber notch through the bar at that moment; the bar keeps its own state. */
|
|
228
233
|
.fw-root .fw-mark { position: absolute; z-index: 2; left: calc(var(--x0) - 1px); width: 2px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2 - 4px); height: calc(var(--fw-bar-h) + 8px);
|
|
229
234
|
border-radius: 2px; background: var(--fw-amber); box-shadow: 0 0 0 1px var(--fw-surface); opacity: var(--on); pointer-events: auto; }
|
|
@@ -284,6 +289,12 @@
|
|
|
284
289
|
agent's own hue (the rules above), so each agent stays one color. */
|
|
285
290
|
.fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents .fw-seg-model { background: var(--fw-kind-model); box-shadow: none; }
|
|
286
291
|
.fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents .fw-seg-tool { background: var(--fw-kind-tool); box-shadow: none; }
|
|
292
|
+
/* Per-agent hues at full saturation (blind round 26: muted per-agent bars "blur together", "wash out on white"): kind is a darker or
|
|
293
|
+
lighter step of the agent's own saturated hue, every solid segment at least 3:1 against the card; waiting stays the only pale fill. */
|
|
294
|
+
.fw-root.fw-fill-agent .fw-view-agents .fw-seg-model { background: light-dark(color-mix(in oklab, var(--tone) 78%, #000), var(--tone)); box-shadow: none; }
|
|
295
|
+
.fw-root.fw-fill-agent .fw-view-agents .fw-seg-tool { background: light-dark(var(--tone), color-mix(in oklab, var(--tone) 72%, #fff)); box-shadow: none; }
|
|
296
|
+
/* A failed turn is still the agent's work: it keeps the agent's saturated hue, and the red cross on the ending turn says it failed. */
|
|
297
|
+
.fw-root.fw-fill-agent .fw-view-agents .fw-seg-failed { background: light-dark(var(--tone), color-mix(in oklab, var(--tone) 72%, #fff)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 55%, transparent); }
|
|
287
298
|
.fw-root .fw-view-agents .fw-seg-tool { container-type: inline-size; justify-content: flex-start; }
|
|
288
299
|
.fw-root .fw-seg-name { padding: 0 6px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 11px; line-height: 16px; font-weight: 600; color: light-dark(#fff, #06222b); pointer-events: none; }
|
|
289
300
|
@container (max-width: 56px) { .fw-root .fw-seg-name { display: none; } }
|
package/dist/react/index.js
CHANGED
|
@@ -2422,6 +2422,7 @@ function DotMap({ positions, radii, colors, palette, extent, rects, labels, dimm
|
|
|
2422
2422
|
}
|
|
2423
2423
|
//#endregion
|
|
2424
2424
|
//#region src/react/flow-waterfall-model.ts
|
|
2425
|
+
const toolOf = (name) => name?.split(" · ")[0];
|
|
2425
2426
|
/** The CSS hue slot: 0 is the root agent's neutral, then five agent hues cycle. */
|
|
2426
2427
|
const toneKey = (tone) => tone <= 0 ? 0 : (tone - 1) % 5 + 1;
|
|
2427
2428
|
const spanFailed = (span) => span.status === "failed";
|
|
@@ -2474,6 +2475,7 @@ function mergeSegments(marks, gapMs, cap = 240) {
|
|
|
2474
2475
|
covered.set(last, share);
|
|
2475
2476
|
last.endMs = Math.max(last.endMs, mark.endMs);
|
|
2476
2477
|
last.count += mark.count;
|
|
2478
|
+
if (last.tool !== mark.tool) last.tool = void 0;
|
|
2477
2479
|
last.kind = share.model >= share.tool ? "model" : "tool";
|
|
2478
2480
|
} else out.push({ ...mark });
|
|
2479
2481
|
}
|
|
@@ -2587,18 +2589,23 @@ function segmentOf(row) {
|
|
|
2587
2589
|
id: row.id,
|
|
2588
2590
|
count: 1,
|
|
2589
2591
|
retry: row.retry,
|
|
2590
|
-
name: row.name
|
|
2592
|
+
name: row.name,
|
|
2593
|
+
tool: toolOf(row.name)
|
|
2591
2594
|
};
|
|
2592
2595
|
}
|
|
2596
|
+
/** The rows a search term matches by themselves, before ancestor context is added. */
|
|
2597
|
+
function searchMatchIds(rows, query, matchSpan) {
|
|
2598
|
+
const term = query.trim();
|
|
2599
|
+
const lower = term.toLowerCase();
|
|
2600
|
+
return new Set(rows.filter((row) => matchSpan ? matchSpan(row.span, term) : `${row.name} ${row.span.detail ?? ""} ${row.kind} ${row.ok ? "" : "failed"} ${row.span.status ?? ""}`.toLowerCase().includes(lower)).map((row) => row.id));
|
|
2601
|
+
}
|
|
2593
2602
|
/** Search keeps ancestor context and deliberately reveals matches inside collapsed branches. */
|
|
2594
2603
|
function visibleFlowWaterfallRows(rows, query, collapsed, matchSpan) {
|
|
2595
2604
|
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
2596
|
-
|
|
2597
|
-
if (term) {
|
|
2605
|
+
if (query.trim()) {
|
|
2598
2606
|
const matching = /* @__PURE__ */ new Set();
|
|
2599
|
-
for (const
|
|
2600
|
-
|
|
2601
|
-
let cursor = row;
|
|
2607
|
+
for (const id of searchMatchIds(rows, query, matchSpan)) {
|
|
2608
|
+
let cursor = byId.get(id);
|
|
2602
2609
|
while (cursor && !matching.has(cursor.id)) {
|
|
2603
2610
|
matching.add(cursor.id);
|
|
2604
2611
|
cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0;
|
|
@@ -2632,7 +2639,12 @@ function slowestRow(rows, clockMs) {
|
|
|
2632
2639
|
* chain ends last wins. Cutting latency anywhere else leaves the trace's end unchanged. */
|
|
2633
2640
|
function criticalPathIds(rows) {
|
|
2634
2641
|
const children = /* @__PURE__ */ new Map();
|
|
2635
|
-
for (const row of rows)
|
|
2642
|
+
for (const row of rows) {
|
|
2643
|
+
if (!row.parentId) continue;
|
|
2644
|
+
const siblings = children.get(row.parentId);
|
|
2645
|
+
if (siblings) siblings.push(row);
|
|
2646
|
+
else children.set(row.parentId, [row]);
|
|
2647
|
+
}
|
|
2636
2648
|
let best;
|
|
2637
2649
|
for (const root of rows) {
|
|
2638
2650
|
if (root.parentId) continue;
|
|
@@ -2777,7 +2789,7 @@ function agentFamilies(agents, open) {
|
|
|
2777
2789
|
const endMs = Math.max(...members.map((row) => row.span.endMs));
|
|
2778
2790
|
const running = members.some((row) => spanRunning(row.span));
|
|
2779
2791
|
const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
2780
|
-
const failed = members.filter((row) => !row.ok).length;
|
|
2792
|
+
const failed = members.filter((row) => row.span.outcome ? row.span.outcome.tone === "failed" : !row.ok).length;
|
|
2781
2793
|
const outcome = members.every((row) => row.span.outcome && row.span.outcome.tone === members[0].span.outcome?.tone) ? members[0].span.outcome : void 0;
|
|
2782
2794
|
out.push({
|
|
2783
2795
|
costFloor: known.length < members.length || members.some((row) => row.costFloor),
|
|
@@ -2848,7 +2860,7 @@ function agentSummary(row) {
|
|
|
2848
2860
|
};
|
|
2849
2861
|
const turns = counts.model + counts.failed;
|
|
2850
2862
|
const tools = counts.tool;
|
|
2851
|
-
const state = row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : row.ok ? "
|
|
2863
|
+
const state = row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done");
|
|
2852
2864
|
const unit = row.kind === "pipeline" ? ["turn", "turns"] : ["model call", "model calls"];
|
|
2853
2865
|
return [
|
|
2854
2866
|
state,
|
|
@@ -2919,7 +2931,8 @@ function stepRuns(steps) {
|
|
|
2919
2931
|
id: step.id,
|
|
2920
2932
|
count: 1,
|
|
2921
2933
|
retry: step.retry,
|
|
2922
|
-
name: step.name
|
|
2934
|
+
name: step.name,
|
|
2935
|
+
tool: toolOf(step.name)
|
|
2923
2936
|
})),
|
|
2924
2937
|
...costs.length ? { rollupCostUsd: costs.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2925
2938
|
span: {
|
|
@@ -3313,7 +3326,7 @@ const at = (startMs, endMs) => ({
|
|
|
3313
3326
|
const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ? 74 : 0) + (costliest ? 84 : 0);
|
|
3314
3327
|
function Segment({ mark, events, agentView, ending }) {
|
|
3315
3328
|
const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
3316
|
-
const named = agentView && mark.kind === "tool" && mark.count === 1 && mark.
|
|
3329
|
+
const named = agentView && mark.kind === "tool" && (mark.count === 1 ? mark.name : mark.tool && `${mark.tool} ×${mark.count}`);
|
|
3317
3330
|
return /* @__PURE__ */ jsx("span", {
|
|
3318
3331
|
className: `fw-seg fw-seg-${mark.kind}${mark.alt ? " fw-seg-alt" : ""}`,
|
|
3319
3332
|
"data-tone": toneKey(mark.tone),
|
|
@@ -3329,7 +3342,7 @@ function Segment({ mark, events, agentView, ending }) {
|
|
|
3329
3342
|
children: glyph
|
|
3330
3343
|
}) : named && /* @__PURE__ */ jsx("span", {
|
|
3331
3344
|
className: "fw-seg-name",
|
|
3332
|
-
children:
|
|
3345
|
+
children: named
|
|
3333
3346
|
})
|
|
3334
3347
|
});
|
|
3335
3348
|
}
|
|
@@ -3346,7 +3359,7 @@ function endLabel(row, clockMs) {
|
|
|
3346
3359
|
}
|
|
3347
3360
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
3348
3361
|
const FlowRow = memo(function FlowRow(props) {
|
|
3349
|
-
const { row, view, top, collapsed, searching, slowest, costliest, critical, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
3362
|
+
const { row, view, top, collapsed, searching, slowest, costliest, critical, matched, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
3350
3363
|
const span = row.span;
|
|
3351
3364
|
const running = spanRunning(span);
|
|
3352
3365
|
const agentView = view === "agents";
|
|
@@ -3368,6 +3381,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3368
3381
|
"data-span-kind": kind,
|
|
3369
3382
|
"data-slowest": slowest || void 0,
|
|
3370
3383
|
"data-critical": critical || void 0,
|
|
3384
|
+
"data-search": searching ? matched ? "match" : "context" : void 0,
|
|
3371
3385
|
"data-running": running || void 0,
|
|
3372
3386
|
"aria-level": agentView ? void 0 : row.depth + 1,
|
|
3373
3387
|
"aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
|
|
@@ -3469,21 +3483,20 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3469
3483
|
]
|
|
3470
3484
|
}), agentView && /* @__PURE__ */ jsxs("span", {
|
|
3471
3485
|
className: "fw-subtitle",
|
|
3472
|
-
children: [
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
/* @__PURE__ */ jsx("span", {
|
|
3486
|
+
children: [renderIcon && /* @__PURE__ */ jsx("span", {
|
|
3487
|
+
className: "fw-detail-icon",
|
|
3488
|
+
"aria-hidden": true,
|
|
3489
|
+
children: renderIcon(span)
|
|
3490
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
3491
|
+
className: "fw-subtitle-tail",
|
|
3492
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3479
3493
|
className: "fw-subtitle-text",
|
|
3480
3494
|
children: span.detail && span.detail !== props.sharedDetail ? `${span.detail} · ${summary[0]}` : summary[0]
|
|
3481
|
-
}),
|
|
3482
|
-
summary[1] && /* @__PURE__ */ jsxs("span", {
|
|
3495
|
+
}), summary[1] && /* @__PURE__ */ jsxs("span", {
|
|
3483
3496
|
className: "fw-subtitle-extra",
|
|
3484
3497
|
children: ["· ", summary[1]]
|
|
3485
|
-
})
|
|
3486
|
-
]
|
|
3498
|
+
})]
|
|
3499
|
+
})]
|
|
3487
3500
|
})]
|
|
3488
3501
|
}),
|
|
3489
3502
|
folded && /* @__PURE__ */ jsxs("span", {
|
|
@@ -3562,7 +3575,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3562
3575
|
] })
|
|
3563
3576
|
})]
|
|
3564
3577
|
});
|
|
3565
|
-
}, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
3578
|
+
}, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.matched === b.matched && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
3566
3579
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3567
3580
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3568
3581
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3774,6 +3787,12 @@ function FlowBody(props) {
|
|
|
3774
3787
|
props.byId,
|
|
3775
3788
|
view
|
|
3776
3789
|
]);
|
|
3790
|
+
const matched = useMemo(() => searching ? searchMatchIds(rows, props.query, props.matchSpan) : void 0, [
|
|
3791
|
+
searching,
|
|
3792
|
+
rows,
|
|
3793
|
+
props.query,
|
|
3794
|
+
props.matchSpan
|
|
3795
|
+
]);
|
|
3777
3796
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3778
3797
|
const motion = useRowMotion(rows);
|
|
3779
3798
|
const [jumped, setJumped] = useState();
|
|
@@ -3847,13 +3866,24 @@ function FlowBody(props) {
|
|
|
3847
3866
|
}, []);
|
|
3848
3867
|
const first = Math.max(0, chunk * CHUNK - OVERSCAN);
|
|
3849
3868
|
const last = Math.min(rows.length, chunk * CHUNK + Math.ceil(viewport / rowHeight) + CHUNK + OVERSCAN);
|
|
3869
|
+
const scrollToRow = (i, element) => {
|
|
3870
|
+
const offset = (view === "spans" ? Math.min(STICKY_LEVELS, rows[i].depth) : 0) * rowHeight;
|
|
3871
|
+
if (i * rowHeight - offset < element.scrollTop) element.scrollTop = Math.max(0, i * rowHeight - offset);
|
|
3872
|
+
else if ((i + 1) * rowHeight > element.scrollTop + element.clientHeight) element.scrollTop = (i + 1) * rowHeight - element.clientHeight;
|
|
3873
|
+
};
|
|
3874
|
+
const shownSelection = useRef(void 0);
|
|
3875
|
+
useEffect(() => {
|
|
3876
|
+
if (!selectedId || selectedId === shownSelection.current) return;
|
|
3877
|
+
const i = index.get(selectedId);
|
|
3878
|
+
if (i === void 0 || !scroller.current) return;
|
|
3879
|
+
shownSelection.current = selectedId;
|
|
3880
|
+
scrollToRow(i, scroller.current);
|
|
3881
|
+
});
|
|
3850
3882
|
const focusRow = (id) => {
|
|
3851
3883
|
const i = index.get(id);
|
|
3852
3884
|
const element = scroller.current;
|
|
3853
3885
|
if (i === void 0 || !element) return;
|
|
3854
|
-
|
|
3855
|
-
if (i * rowHeight - offset < element.scrollTop) element.scrollTop = Math.max(0, i * rowHeight - offset);
|
|
3856
|
-
else if ((i + 1) * rowHeight > element.scrollTop + element.clientHeight) element.scrollTop = (i + 1) * rowHeight - element.clientHeight;
|
|
3886
|
+
scrollToRow(i, element);
|
|
3857
3887
|
props.onActiveChange(id);
|
|
3858
3888
|
requestAnimationFrame(() => element.querySelector(`.fw-canvas [data-span-id="${CSS.escape(id)}"]`)?.focus({ preventScroll: true }));
|
|
3859
3889
|
};
|
|
@@ -4021,6 +4051,7 @@ function FlowBody(props) {
|
|
|
4021
4051
|
slowest: item.id === slowestId,
|
|
4022
4052
|
costliest: item.id === props.costliestId,
|
|
4023
4053
|
critical: props.criticalIds?.has(item.id),
|
|
4054
|
+
matched: matched?.has(item.id),
|
|
4024
4055
|
active: item.id === activeId,
|
|
4025
4056
|
focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
|
|
4026
4057
|
selected: item.id === selectedId,
|
|
@@ -4975,6 +5006,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4975
5006
|
maxHeight: rowsHeight,
|
|
4976
5007
|
collapsed,
|
|
4977
5008
|
searching: Boolean(query.trim()),
|
|
5009
|
+
query,
|
|
5010
|
+
matchSpan,
|
|
4978
5011
|
pauses: trace.pauses,
|
|
4979
5012
|
cursorLine: position !== null || playing,
|
|
4980
5013
|
sharedDetail,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.15",
|
|
4
4
|
"description": "Static SVG and HTML figures for benchmarks and operational timing. Draws from producer data and refuses what it cannot draw honestly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|