@tangle-network/charts 0.20.1 → 0.20.3
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/{flow-trace-DqCMrnTs.d.ts → flow-trace-DfSefpBM.d.ts} +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/flow-waterfall.css +23 -10
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.js +346 -43
- package/dist/react/live.css +1 -1
- package/package.json +1 -1
|
@@ -382,8 +382,9 @@ interface FlowTrace {
|
|
|
382
382
|
costUsd?: number;
|
|
383
383
|
/** `list`: priced from a model catalog's list prices, not a bill; the summary says so. */
|
|
384
384
|
costBasis?: 'billed' | 'list';
|
|
385
|
-
/**
|
|
386
|
-
|
|
385
|
+
/** `costUsd` is a bound, not a count: `lower` prints "≥" (a floor, such as the larger of usage and meter per agent),
|
|
386
|
+
* `upper` prints "≤" (list price on inputs that include cached tokens priced as fresh). */
|
|
387
|
+
costBound?: 'lower' | 'upper';
|
|
387
388
|
/** How many model calls `costUsd` covers; when `priced < total` the total is a lower bound and shown as one. */
|
|
388
389
|
costCoverage?: {
|
|
389
390
|
priced: number;
|
|
@@ -391,7 +392,8 @@ interface FlowTrace {
|
|
|
391
392
|
};
|
|
392
393
|
/** The real charge when the producer knows it (for example $0 on a subscription seat), shown beside a list price. */
|
|
393
394
|
billedUsd?: number;
|
|
394
|
-
|
|
395
|
+
/** Tool calls the producer counted; absent means unknown and is shown as a dash, never as zero. */
|
|
396
|
+
toolCalls?: number;
|
|
395
397
|
/** Authoritative LLM request count when supplied; absent means unknown. */
|
|
396
398
|
modelCalls?: number;
|
|
397
399
|
errors?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-
|
|
1
|
+
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-DfSefpBM.js";
|
|
2
2
|
//#region src/block.d.ts
|
|
3
3
|
interface RenderFigureOptions {
|
|
4
4
|
/** Chart uses a closed data disclosure; plot keeps only the SVG and accessible data. */
|
package/dist/index.js
CHANGED
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
/* Light fills carry enough of the hue to tell agents apart on white (24% and 42% both read as "pastel, washed out"); the outline is
|
|
66
66
|
the full hue, which passes 3:1 against the surface, so every mark keeps its edge. */
|
|
67
67
|
.fw-root [data-tone] {
|
|
68
|
-
--fill: light-dark(
|
|
68
|
+
--fill: light-dark(var(--tone), color-mix(in oklab, var(--tone) 94%, #000));
|
|
69
69
|
--line: light-dark(var(--tone), var(--tone));
|
|
70
|
-
--dim: light-dark(color-mix(in oklab, var(--tone)
|
|
70
|
+
--dim: light-dark(color-mix(in oklab, var(--tone) 62%, #fff), color-mix(in oklab, var(--tone) 40%, #17171d));
|
|
71
71
|
--dim-line: light-dark(color-mix(in oklab, var(--tone) 70%, #fff), color-mix(in oklab, var(--tone) 55%, #17171d));
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -138,6 +138,8 @@
|
|
|
138
138
|
/* The event track: one dot per marker on the axis; click or N/P moves the cursor there. */
|
|
139
139
|
.fw-root .fw-event { --fw-marker: var(--fw-muted); position: absolute; bottom: 2px; width: 8px; height: 8px; margin-left: -4px; padding: 0; border: 0; border-radius: 50%; background: var(--fw-marker); box-shadow: 0 0 0 2px var(--fw-surface); cursor: pointer; z-index: 4; left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
|
|
140
140
|
.fw-root .fw-event[data-tone="ok"] { --fw-marker: var(--fw-ok); }
|
|
141
|
+
.fw-root .fw-event[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--fw-surface), 0 0 0 4px var(--fw-marker); }
|
|
142
|
+
.fw-root .fw-event-label { position: absolute; bottom: 14px; z-index: 5; transform: translateX(-50%); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); padding: 0 8px; border-radius: 4px; background: var(--fw-surface); box-shadow: inset 0 0 0 1px var(--fw-border); color: var(--fw-text); font-size: 12px; line-height: 20px; white-space: nowrap; pointer-events: none; }
|
|
141
143
|
.fw-root .fw-event[data-tone="failed"] { --fw-marker: var(--fw-failed); }
|
|
142
144
|
.fw-root .fw-event:hover, .fw-root .fw-event:focus-visible { transform: scale(1.4); outline: 0; }
|
|
143
145
|
.fw-root .fw-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: color-mix(in oklab, var(--fw-text) 70%, transparent); left: calc(var(--fw-pad) + (var(--fw-t) - var(--fw-v0)) * var(--fw-k) * 1px); }
|
|
@@ -231,7 +233,7 @@
|
|
|
231
233
|
.fw-root [data-outcome="neutral"], .fw-root [data-outcome="neutral"] [data-tone] { --tone: var(--fw-muted); }
|
|
232
234
|
.fw-root .fw-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 8px 0 0; padding: 0; list-style: none; color: var(--fw-muted); font-size: 12px; line-height: 16px; }
|
|
233
235
|
.fw-root .fw-legend li { display: inline-flex; align-items: center; gap: 8px; }
|
|
234
|
-
.fw-root .fw-kinds { margin: 0; padding:
|
|
236
|
+
.fw-root .fw-kinds { margin: 0; padding: 4px 24px 8px; justify-content: flex-start; color: var(--fw-text); }
|
|
235
237
|
.fw-root .fw-kinds .fw-swatch-model { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 40%, var(--fw-surface)); }
|
|
236
238
|
.fw-root .fw-kinds .fw-swatch-tool { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 16%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 30%, transparent); }
|
|
237
239
|
.fw-root .fw-kinds .fw-swatch-wait { width: 12px; height: 8px; border-radius: 2px; background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 4px); box-shadow: inset 0 0 0 1px var(--fw-hatch); }
|
|
@@ -248,34 +250,45 @@
|
|
|
248
250
|
.fw-root .fw-seg-model { background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
|
|
249
251
|
.fw-root .fw-seg-tool { background: var(--dim); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
250
252
|
.fw-root .fw-seg-failed { background: color-mix(in oklab, var(--fw-failed) 18%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 70%, transparent); color: var(--fw-failed); }
|
|
251
|
-
|
|
253
|
+
/* In the agent view a failed turn draws like any turn; the agent's own failure shows once, by its name and its end label. */
|
|
254
|
+
.fw-root .fw-view-agents .fw-seg-failed { background: var(--dim); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
255
|
+
.fw-root .fw-view-agents .fw-alert { color: var(--fw-muted); }
|
|
252
256
|
.fw-root .fw-glyph { display: inline-flex; color: currentColor; }
|
|
253
257
|
.fw-root .fw-glyph svg { width: 11px; height: 11px; stroke-width: 2; }
|
|
254
258
|
.fw-root .fw-seg-model .fw-glyph, .fw-root .fw-k-model .fw-glyph { color: light-dark(var(--tone), #fff); }
|
|
255
|
-
.fw-root .fw-end { display: inline-flex; align-items: center; gap: 8px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h); left: calc(var(--x0) + max(var(--w), 3px) + 8px); white-space: nowrap; pointer-events: none; }
|
|
259
|
+
.fw-root .fw-end { z-index: 2; display: inline-flex; align-items: center; gap: 8px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h); left: calc(var(--x0) + max(var(--w), 3px) + 8px); white-space: nowrap; pointer-events: none; }
|
|
256
260
|
.fw-root .fw-duration { font-size: 12px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
257
261
|
.fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
|
|
258
262
|
.fw-root .fw-badge { height: 18px; padding: 0 8px; border-radius: 999px; font-size: 12px; line-height: 16px; font-weight: 600; color: var(--fw-amber); background: color-mix(in oklab, var(--fw-amber) 13%, transparent); border: 1px solid color-mix(in oklab, var(--fw-amber) 28%, transparent); }
|
|
259
263
|
|
|
260
264
|
/* Handoff elbows: 1.5 px stroke, 6 px radius, a 5 px dot where the handoff leaves its sender. */
|
|
261
|
-
|
|
265
|
+
/* Under the rows (which follow it), so end labels and badges always sit above the connectors. */
|
|
266
|
+
.fw-root .fw-links { position: absolute; top: 0; bottom: 0; left: var(--fw-label-col); right: 0; overflow: hidden; pointer-events: none; z-index: 0; }
|
|
262
267
|
.fw-root .fw-link {
|
|
263
268
|
--x: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px);
|
|
264
269
|
position: absolute; width: 8px; border: 0 solid var(--line); border-bottom-width: 1.5px; translate: 0 var(--fw-bar-shift);
|
|
265
270
|
opacity: clamp(0, (var(--fw-t) - var(--s)) * 1000, 1);
|
|
266
271
|
}
|
|
267
272
|
.fw-root .fw-view-spans .fw-link { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .7); }
|
|
268
|
-
/* Agent-view connectors
|
|
269
|
-
|
|
273
|
+
/* Agent-view connectors rest faint, one curved elbow per spawn in the agent's hue (they show who started whom), and come to
|
|
274
|
+
full strength for the agent under the pointer, pinned or focused. */
|
|
275
|
+
.fw-root .fw-view-agents .fw-link:not(.fw-link-hot) { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .6); }
|
|
270
276
|
.fw-root .fw-link { transition: opacity 160ms var(--fw-ease); }
|
|
271
|
-
.fw-root .fw-link-down { left: calc(var(--x) - 8px); border-left-width: 1.5px; border-bottom-left-radius:
|
|
272
|
-
.fw-root .fw-link-up { left: var(--x); border-right-width: 1.5px; border-bottom-right-radius:
|
|
277
|
+
.fw-root .fw-link-down { left: calc(var(--x) - 8px); border-left-width: 1.5px; border-bottom-left-radius: 12px; }
|
|
278
|
+
.fw-root .fw-link-up { left: var(--x); border-right-width: 1.5px; border-bottom-right-radius: 12px; }
|
|
273
279
|
.fw-root .fw-link::before { content: ""; position: absolute; top: -3.5px; width: 5px; height: 5px; border-radius: 50%; background: var(--line); box-shadow: 0 0 0 1.5px var(--fw-surface); }
|
|
274
280
|
.fw-root .fw-link-down::before { left: -3.25px; }
|
|
275
281
|
.fw-root .fw-link-up::before { right: -3.25px; }
|
|
276
282
|
|
|
277
283
|
/* Span card */
|
|
278
284
|
.fw-root .fw-card-anchor { display: contents; }
|
|
285
|
+
/* Agent view: the card docks under the lanes instead of floating over the plot (judges: "covers the data it describes"). */
|
|
286
|
+
/* Two classes on the root so these win over the base .fw-root .fw-card rule that follows. */
|
|
287
|
+
.fw-root.fw-root-agents .fw-card-anchor { display: block; padding: 0 24px 12px; }
|
|
288
|
+
.fw-root.fw-root-agents .fw-card, .fw-root.fw-root-agents .fw-card-above .fw-card { position: relative; left: auto !important; top: auto !important; transform: none; animation-name: fw-arrive; width: auto; max-width: 560px; }
|
|
289
|
+
.fw-root .fw-seg-alt { filter: brightness(.78); }
|
|
290
|
+
/* Agent view: the scrubber track shows how far the run has played, so at rest it reads full, not empty. */
|
|
291
|
+
.fw-root-agents .fw-slider { background: linear-gradient(90deg, color-mix(in oklab, var(--fw-accent) 45%, transparent) var(--fw-progress), var(--fw-well) var(--fw-progress)) 0 50% / 100% 4px no-repeat; }
|
|
279
292
|
.fw-root .fw-card { position: absolute; z-index: 20; width: 320px; padding: 12px; border-radius: 12px; background: var(--fw-raised); border: 1px solid var(--fw-border);
|
|
280
293
|
box-shadow: 0 12px 32px -8px rgb(0 0 0 / .28), 0 2px 6px rgb(0 0 0 / .08); animation: fw-arrive 140ms var(--fw-ease); }
|
|
281
294
|
/* A hover card never covers the next mark: only a pinned card (a dialog) takes the pointer. */
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-
|
|
1
|
+
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-DfSefpBM.js";
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
//#region src/react/sparkline.d.ts
|
|
4
4
|
type SparklineDirection = "rising" | "falling" | "flat";
|
|
@@ -384,9 +384,11 @@ interface FlowWaterfallProps {
|
|
|
384
384
|
selectedId?: string;
|
|
385
385
|
/** Escape clears the host's selection, after closing any open card. */
|
|
386
386
|
onDeselect?: () => void;
|
|
387
|
+
/** Show the stat strip (default); a page with its own stat row passes false. */
|
|
388
|
+
summary?: boolean;
|
|
387
389
|
}
|
|
388
390
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
389
391
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
390
|
-
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect, description }: FlowWaterfallProps): import("react").JSX.Element;
|
|
392
|
+
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect, description, summary: showSummary }: FlowWaterfallProps): import("react").JSX.Element;
|
|
391
393
|
//#endregion
|
|
392
394
|
export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, LineSeries, MarkKind, MarkVerdict, MarkerShape, MeterSegment, PointFields, PointGlyph, Refusal, SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries, TickOutcome, TimelineLane, TimelineOptions, TimelineSpan, TimelineTick, TimelineViewProps, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow, XCategory, YScale, YTarget, YTick };
|
package/dist/react/index.js
CHANGED
|
@@ -2441,7 +2441,17 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
2441
2441
|
if (childCost === void 0) {
|
|
2442
2442
|
if (child.kind === "model" || child.hasChildren) costKnown = false;
|
|
2443
2443
|
} else cost += childCost;
|
|
2444
|
-
if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span))
|
|
2444
|
+
if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span)) {
|
|
2445
|
+
row.own.push(segmentOf(child));
|
|
2446
|
+
const counts = row.counts ??= {
|
|
2447
|
+
model: 0,
|
|
2448
|
+
tool: 0,
|
|
2449
|
+
failed: 0
|
|
2450
|
+
};
|
|
2451
|
+
if (!child.ok) counts.failed++;
|
|
2452
|
+
else if (child.kind === "model") counts.model++;
|
|
2453
|
+
else counts.tool++;
|
|
2454
|
+
}
|
|
2445
2455
|
}
|
|
2446
2456
|
if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
|
|
2447
2457
|
if (row.hasChildren) {
|
|
@@ -2560,7 +2570,7 @@ function totalsAt(trace, clockMs) {
|
|
|
2560
2570
|
costUsd: trace.costUsd === void 0 ? void 0 : costs ? cost : trace.costUsd,
|
|
2561
2571
|
tokens: counted ? tokens : trace.promptTokens + trace.completionTokens,
|
|
2562
2572
|
modelCalls: trace.modelCalls === void 0 ? void 0 : models,
|
|
2563
|
-
toolCalls: tools,
|
|
2573
|
+
toolCalls: trace.toolCalls === void 0 ? void 0 : tools,
|
|
2564
2574
|
errors
|
|
2565
2575
|
};
|
|
2566
2576
|
}
|
|
@@ -2579,18 +2589,208 @@ function costSummary(costUsd, trace) {
|
|
|
2579
2589
|
};
|
|
2580
2590
|
const coverage = trace.costCoverage;
|
|
2581
2591
|
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
2582
|
-
const upper = !partial && trace.
|
|
2592
|
+
const upper = !partial && trace.costBound === "upper";
|
|
2583
2593
|
return {
|
|
2584
|
-
value: `${partial ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
|
|
2594
|
+
value: `${partial || trace.costBound === "lower" ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
|
|
2585
2595
|
title: [
|
|
2586
2596
|
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
2587
2597
|
list ? "at the model catalog list price; not a bill" : "",
|
|
2588
2598
|
upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
|
|
2599
|
+
trace.costBound === "lower" ? "a floor: the producer reports at least this" : "",
|
|
2589
2600
|
trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
|
|
2590
2601
|
].filter(Boolean).join("; "),
|
|
2591
2602
|
label
|
|
2592
2603
|
};
|
|
2593
2604
|
}
|
|
2605
|
+
/** The role an agent plays: its name without a trailing counter ("memscale-engineer-3", "Subagent 2"). */
|
|
2606
|
+
const roleOf = (name) => name.replace(/[-\s]\d+$/, "");
|
|
2607
|
+
/**
|
|
2608
|
+
* Agents sharing a role fold into one family row until opened: a 17-agent run reads as its few roles, and the bar of a
|
|
2609
|
+
* family carries every member's work in each member's hue. Members follow their opened family, marked with `family`.
|
|
2610
|
+
*/
|
|
2611
|
+
function agentFamilies(agents, open) {
|
|
2612
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2613
|
+
for (const row of agents) {
|
|
2614
|
+
const key = row.parentId === void 0 ? `root:${row.id}` : roleOf(row.name);
|
|
2615
|
+
groups.set(key, [...groups.get(key) ?? [], row]);
|
|
2616
|
+
}
|
|
2617
|
+
const out = [];
|
|
2618
|
+
let next = 0;
|
|
2619
|
+
const hue = (row) => row.tone <= 0 ? row.tone : ++next;
|
|
2620
|
+
for (const [role, members] of groups) {
|
|
2621
|
+
if (members.length < 2) {
|
|
2622
|
+
out.push(...members.map((row) => ({
|
|
2623
|
+
...row,
|
|
2624
|
+
tone: hue(row)
|
|
2625
|
+
})));
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
const tone = hue(members[0]);
|
|
2629
|
+
const id = `family:${role}`;
|
|
2630
|
+
const startMs = Math.min(...members.map((row) => row.span.startMs));
|
|
2631
|
+
const endMs = Math.max(...members.map((row) => row.span.endMs));
|
|
2632
|
+
const running = members.some((row) => spanRunning(row.span));
|
|
2633
|
+
const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
2634
|
+
const failed = members.filter((row) => !row.ok).length;
|
|
2635
|
+
out.push({
|
|
2636
|
+
id,
|
|
2637
|
+
parentId: members[0].parentId,
|
|
2638
|
+
name: `${role} ×${members.length}`,
|
|
2639
|
+
kind: "pipeline",
|
|
2640
|
+
durationLabel: formatSpanDuration(endMs - startMs),
|
|
2641
|
+
approx: members.some((row) => row.approx),
|
|
2642
|
+
ok: failed === 0,
|
|
2643
|
+
depth: 0,
|
|
2644
|
+
hasChildren: true,
|
|
2645
|
+
tone,
|
|
2646
|
+
descendants: members.length,
|
|
2647
|
+
segments: [],
|
|
2648
|
+
retry: false,
|
|
2649
|
+
hasAgents: false,
|
|
2650
|
+
own: members.flatMap((row, i) => row.own.map((mark) => ({
|
|
2651
|
+
...mark,
|
|
2652
|
+
tone,
|
|
2653
|
+
alt: i % 2 === 1
|
|
2654
|
+
}))).sort((a, b) => a.startMs - b.startMs),
|
|
2655
|
+
...known.length ? { rollupCostUsd: known.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2656
|
+
span: {
|
|
2657
|
+
id,
|
|
2658
|
+
kind: "pipeline",
|
|
2659
|
+
name: role,
|
|
2660
|
+
startMs,
|
|
2661
|
+
endMs,
|
|
2662
|
+
status: running ? "running" : failed ? "failed" : "done",
|
|
2663
|
+
detail: members[0].span.detail,
|
|
2664
|
+
meta: {
|
|
2665
|
+
failedAgents: failed,
|
|
2666
|
+
costPartial: known.length < members.length,
|
|
2667
|
+
members: members.map((row) => row.id)
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
if (open.has(id)) out.push(...members.map((row, i) => ({
|
|
2672
|
+
...row,
|
|
2673
|
+
family: id,
|
|
2674
|
+
inset: 1,
|
|
2675
|
+
tone,
|
|
2676
|
+
own: row.own.map((mark) => ({
|
|
2677
|
+
...mark,
|
|
2678
|
+
tone,
|
|
2679
|
+
alt: i % 2 === 1
|
|
2680
|
+
}))
|
|
2681
|
+
})));
|
|
2682
|
+
}
|
|
2683
|
+
return out;
|
|
2684
|
+
}
|
|
2685
|
+
/** The second line of an agent row: how it ended and how much it did ("failed · 3 turns · 412 tool calls", "6 agents · 4 failed"). */
|
|
2686
|
+
function agentSummary(row) {
|
|
2687
|
+
if (row.id.startsWith("family:")) {
|
|
2688
|
+
const failed = Number(row.span.meta?.failedAgents ?? 0);
|
|
2689
|
+
return `${row.descendants} agents${failed ? ` · ${failed} failed` : ""}`;
|
|
2690
|
+
}
|
|
2691
|
+
if (row.id.startsWith("run:")) {
|
|
2692
|
+
const failed = row.own.filter((mark) => mark.kind === "failed").length;
|
|
2693
|
+
return failed ? `${failed} failed` : "all done";
|
|
2694
|
+
}
|
|
2695
|
+
const counts = row.counts ?? {
|
|
2696
|
+
model: 0,
|
|
2697
|
+
tool: 0,
|
|
2698
|
+
failed: 0
|
|
2699
|
+
};
|
|
2700
|
+
const turns = counts.model + counts.failed;
|
|
2701
|
+
const tools = counts.tool;
|
|
2702
|
+
const state = row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : row.ok ? "done" : "failed");
|
|
2703
|
+
const unit = row.kind === "pipeline" ? ["turn", "turns"] : ["model call", "model calls"];
|
|
2704
|
+
return [
|
|
2705
|
+
state,
|
|
2706
|
+
turns ? `${turns} ${turns === 1 ? unit[0] : unit[1]}` : "",
|
|
2707
|
+
tools ? `${tools} tool ${tools === 1 ? "call" : "calls"}` : ""
|
|
2708
|
+
].filter(Boolean).join(" · ");
|
|
2709
|
+
}
|
|
2710
|
+
/**
|
|
2711
|
+
* The agent view's rows: families folded until opened, and an opened agent's own steps (its turns and tool calls, not its
|
|
2712
|
+
* child agents) nested beneath it with their icons, durations and costs, as a reader drills from the run into one agent.
|
|
2713
|
+
*/
|
|
2714
|
+
function agentViewRows(agents, rows, openFamilies, openSteps) {
|
|
2715
|
+
const base = agentFamilies(agents, openFamilies);
|
|
2716
|
+
if (openSteps.size === 0) return base;
|
|
2717
|
+
const steps = /* @__PURE__ */ new Map();
|
|
2718
|
+
for (const row of rows) {
|
|
2719
|
+
if (row.parentId === void 0 || !openSteps.has(row.parentId) || row.kind === "pipeline") continue;
|
|
2720
|
+
steps.set(row.parentId, [...steps.get(row.parentId) ?? [], row]);
|
|
2721
|
+
}
|
|
2722
|
+
return base.flatMap((row) => openSteps.has(row.id) ? [row, ...stepRuns(steps.get(row.id) ?? []).map((step) => ({
|
|
2723
|
+
...step,
|
|
2724
|
+
stepOf: row.id,
|
|
2725
|
+
inset: (row.inset ?? 0) + 1,
|
|
2726
|
+
tone: row.tone,
|
|
2727
|
+
own: step.own.map((mark) => ({
|
|
2728
|
+
...mark,
|
|
2729
|
+
tone: row.tone
|
|
2730
|
+
}))
|
|
2731
|
+
}))] : [row]);
|
|
2732
|
+
}
|
|
2733
|
+
/** Three or more consecutive steps of one kind and name read as one row ("15 model calls · 4h"), each a mark in its bar. */
|
|
2734
|
+
function stepRuns(steps) {
|
|
2735
|
+
const out = [];
|
|
2736
|
+
for (let i = 0; i < steps.length;) {
|
|
2737
|
+
let j = i;
|
|
2738
|
+
while (j < steps.length && steps[j].kind === steps[i].kind && steps[j].name === steps[i].name) j++;
|
|
2739
|
+
const run = steps.slice(i, j);
|
|
2740
|
+
if (run.length < 3) {
|
|
2741
|
+
out.push(...run);
|
|
2742
|
+
i = j;
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
const first = run[0];
|
|
2746
|
+
const startMs = Math.min(...run.map((step) => step.span.startMs));
|
|
2747
|
+
const endMs = Math.max(...run.map((step) => step.span.endMs));
|
|
2748
|
+
const failed = run.filter((step) => !step.ok).length;
|
|
2749
|
+
const costs = run.map((step) => step.span.costUsd ?? step.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
2750
|
+
const noun = first.kind === "model" ? "model calls" : first.kind === "tool" ? `${first.name} calls` : `${first.name} steps`;
|
|
2751
|
+
out.push({
|
|
2752
|
+
...first,
|
|
2753
|
+
id: `run:${first.id}`,
|
|
2754
|
+
name: `${run.length} ${noun}`,
|
|
2755
|
+
durationLabel: formatSpanDuration(endMs - startMs),
|
|
2756
|
+
ok: failed === 0,
|
|
2757
|
+
hasChildren: false,
|
|
2758
|
+
descendants: 0,
|
|
2759
|
+
segments: [],
|
|
2760
|
+
own: run.map((step) => ({
|
|
2761
|
+
startMs: step.span.startMs,
|
|
2762
|
+
endMs: step.span.endMs,
|
|
2763
|
+
kind: step.ok ? step.kind === "model" ? "model" : "tool" : "failed",
|
|
2764
|
+
tone: first.tone,
|
|
2765
|
+
id: step.id,
|
|
2766
|
+
count: 1,
|
|
2767
|
+
retry: step.retry
|
|
2768
|
+
})),
|
|
2769
|
+
...costs.length ? { rollupCostUsd: costs.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2770
|
+
span: {
|
|
2771
|
+
...first.span,
|
|
2772
|
+
id: `run:${first.id}`,
|
|
2773
|
+
name: `${run.length} ${noun}`,
|
|
2774
|
+
startMs,
|
|
2775
|
+
endMs,
|
|
2776
|
+
status: failed ? "failed" : first.span.status,
|
|
2777
|
+
costUsd: void 0
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
i = j;
|
|
2781
|
+
}
|
|
2782
|
+
return out;
|
|
2783
|
+
}
|
|
2784
|
+
/** A row as the view draws it: its drawn copy, or for a member of a folded family, itself in the family's hue. */
|
|
2785
|
+
function drawnRow(row, visible) {
|
|
2786
|
+
const drawn = visible.find((item) => item.id === row.id);
|
|
2787
|
+
if (drawn) return drawn;
|
|
2788
|
+
const family = visible.find((item) => (item.span.meta?.members)?.includes(row.id));
|
|
2789
|
+
return family ? {
|
|
2790
|
+
...row,
|
|
2791
|
+
tone: family.tone
|
|
2792
|
+
} : row;
|
|
2793
|
+
}
|
|
2594
2794
|
//#endregion
|
|
2595
2795
|
//#region src/react/flow-waterfall-icons.tsx
|
|
2596
2796
|
const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
@@ -2608,7 +2808,52 @@ const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
|
2608
2808
|
children
|
|
2609
2809
|
});
|
|
2610
2810
|
/** Kind glyphs drawn when the host supplies no `renderIcon`. Brand marks belong to the host. */
|
|
2611
|
-
|
|
2811
|
+
/** Generic tool identity by name: a terminal, a file, a pencil, a lens, a globe or a branch, never a vendor mark. */
|
|
2812
|
+
function toolGlyph(name) {
|
|
2813
|
+
const n = (name ?? "").toLowerCase();
|
|
2814
|
+
if (/bash|shell|exec|terminal|command|run/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
|
|
2815
|
+
x: "2",
|
|
2816
|
+
y: "3",
|
|
2817
|
+
width: "12",
|
|
2818
|
+
height: "10",
|
|
2819
|
+
rx: "2"
|
|
2820
|
+
}), /* @__PURE__ */ jsx("path", { d: "m5 7 2 1.5L5 10M8.5 10.5h2.5" })] }));
|
|
2821
|
+
if (/edit|write|patch|apply/.test(n)) return svg(/* @__PURE__ */ jsx("path", { d: "M3 13l1-3.5L10.5 3a1.4 1.4 0 0 1 2 2L6 11.5 3 13ZM9.5 4l2 2" }));
|
|
2822
|
+
if (/grep|search|find|query/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
|
|
2823
|
+
cx: "7",
|
|
2824
|
+
cy: "7",
|
|
2825
|
+
r: "4.25"
|
|
2826
|
+
}), /* @__PURE__ */ jsx("path", { d: "m10.25 10.25 3.25 3.25" })] }));
|
|
2827
|
+
if (/read|glob|list|ls|cat|file|view/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M4 2.5h5l3 3v8H4z" }), /* @__PURE__ */ jsx("path", { d: "M9 2.5v3h3M6 9h4M6 11h3" })] }));
|
|
2828
|
+
if (/web|fetch|http|browser|url/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
|
|
2829
|
+
cx: "8",
|
|
2830
|
+
cy: "8",
|
|
2831
|
+
r: "5.5"
|
|
2832
|
+
}), /* @__PURE__ */ jsx("path", { d: "M2.5 8h11M8 2.5c2 2 2 9 0 11M8 2.5c-2 2-2 9 0 11" })] }));
|
|
2833
|
+
if (/git|commit|branch|merge/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2834
|
+
/* @__PURE__ */ jsx("circle", {
|
|
2835
|
+
cx: "5",
|
|
2836
|
+
cy: "4",
|
|
2837
|
+
r: "1.5"
|
|
2838
|
+
}),
|
|
2839
|
+
/* @__PURE__ */ jsx("circle", {
|
|
2840
|
+
cx: "5",
|
|
2841
|
+
cy: "12",
|
|
2842
|
+
r: "1.5"
|
|
2843
|
+
}),
|
|
2844
|
+
/* @__PURE__ */ jsx("circle", {
|
|
2845
|
+
cx: "11",
|
|
2846
|
+
cy: "6",
|
|
2847
|
+
r: "1.5"
|
|
2848
|
+
}),
|
|
2849
|
+
/* @__PURE__ */ jsx("path", { d: "M5 5.5v5M11 7.5c0 2-3 2-6 3" })
|
|
2850
|
+
] }));
|
|
2851
|
+
}
|
|
2852
|
+
function KindGlyph({ kind, name }) {
|
|
2853
|
+
if (kind === "tool") {
|
|
2854
|
+
const glyph = toolGlyph(name);
|
|
2855
|
+
if (glyph) return glyph;
|
|
2856
|
+
}
|
|
2612
2857
|
switch (kind) {
|
|
2613
2858
|
case "pipeline": return svg(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2614
2859
|
/* @__PURE__ */ jsx("circle", {
|
|
@@ -2732,7 +2977,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2732
2977
|
if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
|
|
2733
2978
|
cells.splice(6);
|
|
2734
2979
|
const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
|
|
2735
|
-
const step = owner ? `${kind}${span.name && span.name !== kind && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : ""} · ${duration}${row.ok ? "" : " · failed"}` : void 0;
|
|
2980
|
+
const step = owner && span.endMs > span.startMs ? `${kind}${span.name && span.name !== kind && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : ""} · ${duration}${row.ok ? "" : " · failed"}` : void 0;
|
|
2736
2981
|
const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
|
|
2737
2982
|
return /* @__PURE__ */ jsxs("div", {
|
|
2738
2983
|
id,
|
|
@@ -2842,7 +3087,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2842
3087
|
/* @__PURE__ */ jsx("span", {
|
|
2843
3088
|
className: "fw-icon-tile",
|
|
2844
3089
|
"aria-hidden": true,
|
|
2845
|
-
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3090
|
+
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3091
|
+
kind: span.kind,
|
|
3092
|
+
name: span.name
|
|
3093
|
+
})
|
|
2846
3094
|
}),
|
|
2847
3095
|
/* @__PURE__ */ jsxs("div", {
|
|
2848
3096
|
className: "fw-card-title",
|
|
@@ -2905,10 +3153,10 @@ const at = (startMs, endMs) => ({
|
|
|
2905
3153
|
"--s": startMs,
|
|
2906
3154
|
"--e": Number.isFinite(endMs) ? endMs : OPEN_END
|
|
2907
3155
|
});
|
|
2908
|
-
function Segment({ mark, events, agentView }) {
|
|
2909
|
-
const glyph = mark.kind === "failed" ? agentView ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
3156
|
+
function Segment({ mark, events, agentView, ending }) {
|
|
3157
|
+
const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
2910
3158
|
return /* @__PURE__ */ jsx("span", {
|
|
2911
|
-
className: `fw-seg fw-seg-${mark.kind}`,
|
|
3159
|
+
className: `fw-seg fw-seg-${mark.kind}${mark.alt ? " fw-seg-alt" : ""}`,
|
|
2912
3160
|
"data-tone": toneKey(mark.tone),
|
|
2913
3161
|
style: at(mark.startMs, mark.endMs),
|
|
2914
3162
|
onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget) : void 0,
|
|
@@ -2931,7 +3179,7 @@ function endLabel(row, clockMs) {
|
|
|
2931
3179
|
if (!spanRunning(span) && clockMs < span.endMs) return `${formatSpanDuration(Math.max(0, clockMs - span.startMs))}${row.approx ? "~" : ""}`;
|
|
2932
3180
|
const duration = spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel;
|
|
2933
3181
|
const cost = span.costUsd ?? (row.rollupCostUsd ? row.rollupCostUsd : void 0);
|
|
2934
|
-
return cost === void 0 ? duration : `${duration} · ${formatCost(cost)}`;
|
|
3182
|
+
return cost === void 0 ? duration : `${duration} · ${span.meta?.costPartial ? "≥" : ""}${formatCost(cost)}`;
|
|
2935
3183
|
}
|
|
2936
3184
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
2937
3185
|
const FlowRow = memo(function FlowRow(props) {
|
|
@@ -2943,9 +3191,13 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2943
3191
|
const invalid = row.durationLabel === "—";
|
|
2944
3192
|
const isAgent = row.kind === "pipeline";
|
|
2945
3193
|
const kind = !row.ok ? "failed" : row.kind;
|
|
2946
|
-
const marks = isAgent || row.hasChildren ? folded && !agentView ? row.segments : row.own : [];
|
|
3194
|
+
const marks = isAgent || row.hasChildren || row.id.startsWith("run:") ? folded && !agentView ? row.segments : row.own : [];
|
|
2947
3195
|
const label = endLabel(row, clockMs);
|
|
2948
3196
|
const inspect = (event) => props.onInspect(row.id, event.currentTarget);
|
|
3197
|
+
const family = agentView && row.id.startsWith("family:");
|
|
3198
|
+
const step = agentView && row.stepOf !== void 0;
|
|
3199
|
+
const opens = family || agentView && isAgent && row.own.length > 0;
|
|
3200
|
+
const ending = agentView && !row.ok ? marks.map((mark) => mark.kind).lastIndexOf("failed") : -1;
|
|
2949
3201
|
return /* @__PURE__ */ jsxs("div", {
|
|
2950
3202
|
role: "row",
|
|
2951
3203
|
"data-span-id": row.id,
|
|
@@ -2958,7 +3210,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2958
3210
|
tabIndex: focusable ? 0 : -1,
|
|
2959
3211
|
"data-tone": toneKey(row.tone),
|
|
2960
3212
|
"data-outcome": fillByAgent ? void 0 : span.outcome?.tone,
|
|
2961
|
-
className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
|
|
3213
|
+
className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${step ? " fw-step" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
|
|
2962
3214
|
style: {
|
|
2963
3215
|
"--row-y": `${top}px`,
|
|
2964
3216
|
"--fw-enter-delay": `${motion?.enter ?? 0}ms`
|
|
@@ -2968,14 +3220,30 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2968
3220
|
},
|
|
2969
3221
|
onClick: (event) => {
|
|
2970
3222
|
props.onActivate(row);
|
|
3223
|
+
if (family) {
|
|
3224
|
+
props.onToggle(row);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
2971
3227
|
const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
|
|
2972
3228
|
if (bar) props.onPin(row.id, bar);
|
|
2973
3229
|
},
|
|
2974
3230
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2975
3231
|
role: "gridcell",
|
|
2976
3232
|
className: "fw-label",
|
|
2977
|
-
style: { "--depth": agentView ? 0 : row.depth },
|
|
3233
|
+
style: { "--depth": agentView ? row.inset ?? 0 : row.depth },
|
|
2978
3234
|
children: [
|
|
3235
|
+
opens && /* @__PURE__ */ jsx("button", {
|
|
3236
|
+
type: "button",
|
|
3237
|
+
tabIndex: -1,
|
|
3238
|
+
className: `fw-toggle${props.expanded ? " fw-open" : ""}`,
|
|
3239
|
+
"aria-label": `${props.expanded ? "Collapse" : "Expand"} ${row.name}`,
|
|
3240
|
+
"aria-expanded": Boolean(props.expanded),
|
|
3241
|
+
onClick: (event) => {
|
|
3242
|
+
event.stopPropagation();
|
|
3243
|
+
props.onToggle(row);
|
|
3244
|
+
},
|
|
3245
|
+
children: /* @__PURE__ */ jsx(ChevronGlyph, {})
|
|
3246
|
+
}),
|
|
2979
3247
|
!agentView && /* @__PURE__ */ jsx("span", {
|
|
2980
3248
|
className: "fw-guides",
|
|
2981
3249
|
"aria-hidden": true
|
|
@@ -2996,10 +3264,13 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2996
3264
|
className: "fw-toggle-space",
|
|
2997
3265
|
"aria-hidden": true
|
|
2998
3266
|
})),
|
|
2999
|
-
!agentView && /* @__PURE__ */ jsx("span", {
|
|
3267
|
+
(!agentView || step) && /* @__PURE__ */ jsx("span", {
|
|
3000
3268
|
className: "fw-icon-tile",
|
|
3001
3269
|
"aria-hidden": true,
|
|
3002
|
-
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3270
|
+
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3271
|
+
kind: span.kind,
|
|
3272
|
+
name: span.name
|
|
3273
|
+
})
|
|
3003
3274
|
}),
|
|
3004
3275
|
/* @__PURE__ */ jsxs("span", {
|
|
3005
3276
|
className: "fw-names",
|
|
@@ -3014,7 +3285,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3014
3285
|
"data-outcome": span.outcome.tone,
|
|
3015
3286
|
title: span.outcome.label,
|
|
3016
3287
|
"aria-hidden": true
|
|
3017
|
-
}) : agentView && /* @__PURE__ */ jsx("span", {
|
|
3288
|
+
}) : agentView && !step && /* @__PURE__ */ jsx("span", {
|
|
3018
3289
|
className: "fw-agent-dot",
|
|
3019
3290
|
"aria-hidden": true
|
|
3020
3291
|
}),
|
|
@@ -3029,9 +3300,9 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3029
3300
|
})
|
|
3030
3301
|
]
|
|
3031
3302
|
}),
|
|
3032
|
-
agentView &&
|
|
3303
|
+
agentView && /* @__PURE__ */ jsx("span", {
|
|
3033
3304
|
className: "fw-subtitle",
|
|
3034
|
-
children: span.detail
|
|
3305
|
+
children: span.detail && span.detail !== props.sharedDetail ? `${span.detail} · ${agentSummary(row)}` : agentSummary(row)
|
|
3035
3306
|
}),
|
|
3036
3307
|
agentView && renderIcon && /* @__PURE__ */ jsx("span", {
|
|
3037
3308
|
className: "fw-detail-icon",
|
|
@@ -3075,7 +3346,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3075
3346
|
onPointerLeave: props.onLeave,
|
|
3076
3347
|
onClick: (event) => {
|
|
3077
3348
|
event.stopPropagation();
|
|
3078
|
-
props.
|
|
3349
|
+
if (family) props.onToggle(row);
|
|
3350
|
+
else props.onPin(row.id, event.currentTarget);
|
|
3079
3351
|
},
|
|
3080
3352
|
children: [!isAgent && !row.ok && /* @__PURE__ */ jsx("span", {
|
|
3081
3353
|
className: "fw-glyph",
|
|
@@ -3088,7 +3360,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3088
3360
|
marks.map((mark, i) => /* @__PURE__ */ jsx(Segment, {
|
|
3089
3361
|
mark,
|
|
3090
3362
|
events: props,
|
|
3091
|
-
agentView
|
|
3363
|
+
agentView,
|
|
3364
|
+
ending: i === ending
|
|
3092
3365
|
}, `${mark.id}-${i}`)),
|
|
3093
3366
|
/* @__PURE__ */ jsxs("span", {
|
|
3094
3367
|
className: "fw-end",
|
|
@@ -3111,7 +3384,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3111
3384
|
] })
|
|
3112
3385
|
})]
|
|
3113
3386
|
});
|
|
3114
|
-
}, (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.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.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
3387
|
+
}, (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.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)));
|
|
3115
3388
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3116
3389
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3117
3390
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3315,7 +3588,7 @@ function FlowBody(props) {
|
|
|
3315
3588
|
const [viewport, setViewport] = useState(maxHeight);
|
|
3316
3589
|
const latest = useRef(props);
|
|
3317
3590
|
latest.current = props;
|
|
3318
|
-
const height = Math.min(maxHeight, Math.max(rows.length, 1) * rowHeight);
|
|
3591
|
+
const height = props.fixedHeight ? maxHeight : Math.min(maxHeight, Math.max(rows.length, 1) * rowHeight);
|
|
3319
3592
|
const links = useMemo(() => linksFor(rows, props.byId, view), [
|
|
3320
3593
|
rows,
|
|
3321
3594
|
props.byId,
|
|
@@ -3323,6 +3596,7 @@ function FlowBody(props) {
|
|
|
3323
3596
|
]);
|
|
3324
3597
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3325
3598
|
const motion = useRowMotion(rows);
|
|
3599
|
+
const [jumped, setJumped] = useState();
|
|
3326
3600
|
const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
|
|
3327
3601
|
const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
|
|
3328
3602
|
const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
|
|
@@ -3504,9 +3778,19 @@ function FlowBody(props) {
|
|
|
3504
3778
|
style: { "--s": marker.at },
|
|
3505
3779
|
title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
|
|
3506
3780
|
"aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
|
|
3781
|
+
"aria-pressed": jumped === marker,
|
|
3507
3782
|
onPointerDown: (event) => event.stopPropagation(),
|
|
3508
|
-
onClick: () =>
|
|
3783
|
+
onClick: () => {
|
|
3784
|
+
setJumped(marker);
|
|
3785
|
+
props.onScrub(marker.at);
|
|
3786
|
+
}
|
|
3509
3787
|
}, `e-${marker.at}-${marker.label}`)),
|
|
3788
|
+
jumped && /* @__PURE__ */ jsx("span", {
|
|
3789
|
+
className: "fw-event-label",
|
|
3790
|
+
"data-tone": jumped.tone ?? "neutral",
|
|
3791
|
+
style: { "--s": jumped.at },
|
|
3792
|
+
children: jumped.label
|
|
3793
|
+
}),
|
|
3510
3794
|
showCursor && /* @__PURE__ */ jsx("span", {
|
|
3511
3795
|
className: "fw-cursor-handle",
|
|
3512
3796
|
"aria-hidden": true
|
|
@@ -3530,6 +3814,8 @@ function FlowBody(props) {
|
|
|
3530
3814
|
clockMs,
|
|
3531
3815
|
renderIcon: props.renderIcon,
|
|
3532
3816
|
fillByAgent: props.fillByAgent,
|
|
3817
|
+
sharedDetail: props.sharedDetail,
|
|
3818
|
+
expanded: props.expandedIds?.has(item.id),
|
|
3533
3819
|
onToggle: props.onToggle,
|
|
3534
3820
|
onInspect: props.onInspect,
|
|
3535
3821
|
onLeave: props.onLeave,
|
|
@@ -3617,7 +3903,7 @@ function FlowBody(props) {
|
|
|
3617
3903
|
})
|
|
3618
3904
|
}, `${marker.at}-${marker.label}`))]
|
|
3619
3905
|
}) : null,
|
|
3620
|
-
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3906
|
+
showCursor && props.cursorLine !== false && /* @__PURE__ */ jsx("div", {
|
|
3621
3907
|
className: "fw-cursor-layer",
|
|
3622
3908
|
"aria-hidden": true,
|
|
3623
3909
|
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
@@ -3905,11 +4191,11 @@ function summaryCells(trace, at, durationMs) {
|
|
|
3905
4191
|
errors,
|
|
3906
4192
|
summary: [
|
|
3907
4193
|
["Duration", formatSpanDuration(durationMs)],
|
|
3908
|
-
[
|
|
4194
|
+
...cost.value === "—" ? [] : [[
|
|
3909
4195
|
cost.label,
|
|
3910
4196
|
cost.value,
|
|
3911
4197
|
cost.title
|
|
3912
|
-
],
|
|
4198
|
+
]],
|
|
3913
4199
|
[
|
|
3914
4200
|
"Tokens",
|
|
3915
4201
|
formatCount(tokens),
|
|
@@ -3920,7 +4206,11 @@ function summaryCells(trace, at, durationMs) {
|
|
|
3920
4206
|
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
3921
4207
|
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
3922
4208
|
],
|
|
3923
|
-
[
|
|
4209
|
+
[
|
|
4210
|
+
"Tool calls",
|
|
4211
|
+
(at ? at.toolCalls : trace.toolCalls)?.toLocaleString() ?? "—",
|
|
4212
|
+
(at ? at.toolCalls : trace.toolCalls) === void 0 ? "The producer did not count tool calls" : void 0
|
|
4213
|
+
],
|
|
3924
4214
|
["Errors", errors.toLocaleString()]
|
|
3925
4215
|
]
|
|
3926
4216
|
};
|
|
@@ -3939,8 +4229,7 @@ function KindLegend({ rows }) {
|
|
|
3939
4229
|
["model", "Model call"],
|
|
3940
4230
|
["tool", "Tool call"],
|
|
3941
4231
|
["waiting", "Waiting"],
|
|
3942
|
-
["handoff", "Handoff"]
|
|
3943
|
-
["failed", "Failed turn"]
|
|
4232
|
+
["handoff", "Handoff"]
|
|
3944
4233
|
].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
|
|
3945
4234
|
if (shown.length === 0) return null;
|
|
3946
4235
|
return /* @__PURE__ */ jsx("ul", {
|
|
@@ -3992,7 +4281,7 @@ function OutcomeLegend({ agents }) {
|
|
|
3992
4281
|
}
|
|
3993
4282
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
3994
4283
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
3995
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description }) {
|
|
4284
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description, summary: showSummary = true }) {
|
|
3996
4285
|
const root = useRef(null);
|
|
3997
4286
|
const search = useRef(null);
|
|
3998
4287
|
const slider = useRef(null);
|
|
@@ -4065,17 +4354,22 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4065
4354
|
const pipelines = rows.filter((row) => row.kind === "pipeline");
|
|
4066
4355
|
return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
|
|
4067
4356
|
}, [rows]);
|
|
4068
|
-
const
|
|
4357
|
+
const sharedDetail = agents.length > 1 && agents.every((row) => row.span.detail && row.span.detail === agents[0]?.span.detail) ? agents[0]?.span.detail : void 0;
|
|
4358
|
+
const [openFamilies, setOpenFamilies] = useState(/* @__PURE__ */ new Set());
|
|
4359
|
+
const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
|
|
4360
|
+
const visible = useMemo(() => view === "agents" ? agentViewRows(agents, rows, openFamilies, openSteps) : visibleFlowWaterfallRows(rows, query, collapsed), [
|
|
4069
4361
|
view,
|
|
4070
4362
|
agents,
|
|
4071
4363
|
rows,
|
|
4364
|
+
openFamilies,
|
|
4365
|
+
openSteps,
|
|
4072
4366
|
query,
|
|
4073
4367
|
collapsed
|
|
4074
4368
|
]);
|
|
4075
4369
|
const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
|
|
4076
|
-
const callouts = useMemo(() => view === "agents" ? agentCallouts(
|
|
4370
|
+
const callouts = useMemo(() => view === "agents" ? agentCallouts(visible, clockMs) : {}, [
|
|
4077
4371
|
view,
|
|
4078
|
-
|
|
4372
|
+
visible,
|
|
4079
4373
|
clockMs
|
|
4080
4374
|
]);
|
|
4081
4375
|
const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
|
|
@@ -4177,12 +4471,14 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4177
4471
|
inspect: (_target, _element) => {}
|
|
4178
4472
|
});
|
|
4179
4473
|
const toggleNow = (row) => {
|
|
4474
|
+
const flip = (open) => new Set(open.has(row.id) ? [...open].filter((id) => id !== row.id) : [...open, row.id]);
|
|
4475
|
+
if (row.id.startsWith("family:")) {
|
|
4476
|
+
setOpenFamilies(flip);
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4180
4479
|
onSelect?.(row.span);
|
|
4181
4480
|
if (view === "agents") {
|
|
4182
|
-
|
|
4183
|
-
for (let cursor = row; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) keep.add(cursor.id);
|
|
4184
|
-
setCollapsed(new Set(rows.filter((item) => item.hasChildren && !keep.has(item.id) && item.kind === "pipeline").map((item) => item.id)));
|
|
4185
|
-
setView("spans");
|
|
4481
|
+
setOpenSteps(flip);
|
|
4186
4482
|
setActiveId(row.id);
|
|
4187
4483
|
return;
|
|
4188
4484
|
}
|
|
@@ -4265,11 +4561,13 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4265
4561
|
const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
|
|
4266
4562
|
const speedup = limit > 0 ? busy / limit : 0;
|
|
4267
4563
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
4268
|
-
const
|
|
4564
|
+
const shown = (row) => row && drawnRow(row, visible);
|
|
4565
|
+
const cardRow = shown(card ? byId.get(card.target) : void 0);
|
|
4269
4566
|
const cardAgent = cardRow && (() => {
|
|
4270
|
-
for (let cursor = cardRow; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return cursor;
|
|
4567
|
+
for (let cursor = cardRow; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return shown(cursor);
|
|
4271
4568
|
})();
|
|
4272
|
-
const
|
|
4569
|
+
const moving = isLive || position !== null || playing;
|
|
4570
|
+
const showCursor = moving || view === "agents";
|
|
4273
4571
|
const overviewMarks = useMemo(() => rows.filter((row) => !row.parentId).flatMap((row) => row.hasChildren ? row.segments : row.kind === "pipeline" ? [] : [{
|
|
4274
4572
|
startMs: row.span.startMs,
|
|
4275
4573
|
endMs: row.span.endMs,
|
|
@@ -4356,6 +4654,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4356
4654
|
agents.length,
|
|
4357
4655
|
" ",
|
|
4358
4656
|
agents.length === 1 ? "agent" : "agents",
|
|
4657
|
+
sharedDetail ? ` · ${sharedDetail}` : "",
|
|
4359
4658
|
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
4360
4659
|
]
|
|
4361
4660
|
}),
|
|
@@ -4413,7 +4712,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4413
4712
|
})]
|
|
4414
4713
|
})]
|
|
4415
4714
|
}),
|
|
4416
|
-
/* @__PURE__ */ jsx("dl", {
|
|
4715
|
+
showSummary && /* @__PURE__ */ jsx("dl", {
|
|
4417
4716
|
className: "fw-summary",
|
|
4418
4717
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
4419
4718
|
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
@@ -4425,6 +4724,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4425
4724
|
}, name))
|
|
4426
4725
|
}),
|
|
4427
4726
|
view === "agents" && playback,
|
|
4727
|
+
view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
|
|
4428
4728
|
/* @__PURE__ */ jsx(FlowBody, {
|
|
4429
4729
|
rows: visible,
|
|
4430
4730
|
byId,
|
|
@@ -4434,6 +4734,10 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4434
4734
|
collapsed,
|
|
4435
4735
|
searching: Boolean(query.trim()),
|
|
4436
4736
|
pauses: trace.pauses,
|
|
4737
|
+
cursorLine: moving,
|
|
4738
|
+
sharedDetail,
|
|
4739
|
+
expandedIds: view === "agents" ? /* @__PURE__ */ new Set([...openFamilies, ...openSteps]) : void 0,
|
|
4740
|
+
fixedHeight: isLive,
|
|
4437
4741
|
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4438
4742
|
costliestId: callouts.costliestId,
|
|
4439
4743
|
activeId,
|
|
@@ -4483,7 +4787,6 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4483
4787
|
compact: view === "agents"
|
|
4484
4788
|
})
|
|
4485
4789
|
}),
|
|
4486
|
-
view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
|
|
4487
4790
|
view === "spans" && playback
|
|
4488
4791
|
]
|
|
4489
4792
|
});
|
package/dist/react/live.css
CHANGED
|
@@ -59,7 +59,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
|
|
|
59
59
|
.ov-gantt-open { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-width: 1.5; stroke-dasharray: 3 2; }
|
|
60
60
|
.ov-events, .ov-key, .ov-meter-key { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 12px; line-height: 16px; color: var(--chart-live-muted, var(--chart-ink-muted)); }
|
|
61
61
|
.ov-key li, .ov-events li, .ov-meter-key li { display: inline-flex; align-items: center; gap: 8px; }
|
|
62
|
-
.ov-meter-key b { font-weight:
|
|
62
|
+
.ov-meter-key b { font-weight: 600; color: var(--chart-live-fg, var(--chart-ink)); font-family: var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
|
|
63
63
|
.ov-meter-key i, .ov-event-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 2px; }
|
|
64
64
|
[data-segmented] > .ov-meter-track { display: flex; }
|
|
65
65
|
[data-segmented] > .ov-meter-track > span { flex: none; border-radius: 0; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
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",
|