@tangle-network/charts 0.19.0 → 0.20.1
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/README.md +1 -1
- package/dist/figure.css +8 -8
- package/dist/{flow-trace-D6RDkVrJ.d.ts → flow-trace-DqCMrnTs.d.ts} +31 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +59 -39
- package/dist/react/flow-waterfall.css +74 -24
- package/dist/react/index.d.ts +16 -3
- package/dist/react/index.js +431 -207
- package/dist/react/live.css +24 -23
- package/dist/{timeline-CVXBpZzt.js → timeline-B7c-cede.js} +13 -13
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-
|
|
1
|
+
import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-B7c-cede.js";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Fragment as Fragment$1, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
//#region src/react/sparkline.tsx
|
|
@@ -1001,10 +1001,10 @@ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limi
|
|
|
1001
1001
|
return /* @__PURE__ */ jsxs("ol", {
|
|
1002
1002
|
className: "ov-hbars",
|
|
1003
1003
|
children: [rows.slice(0, limit).map((row) => {
|
|
1004
|
+
const title = row.title ?? `${row.label}: ${format(row.value)}`;
|
|
1004
1005
|
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1005
1006
|
/* @__PURE__ */ jsx("span", {
|
|
1006
1007
|
className: "ov-hbar-label",
|
|
1007
|
-
title: row.label,
|
|
1008
1008
|
children: row.label
|
|
1009
1009
|
}),
|
|
1010
1010
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -1025,8 +1025,12 @@ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limi
|
|
|
1025
1025
|
] });
|
|
1026
1026
|
return /* @__PURE__ */ jsx("li", { children: row.href ? /* @__PURE__ */ jsx("a", {
|
|
1027
1027
|
href: row.href,
|
|
1028
|
+
title,
|
|
1028
1029
|
children: content
|
|
1029
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
1030
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1031
|
+
title,
|
|
1032
|
+
children: content
|
|
1033
|
+
}) }, row.label);
|
|
1030
1034
|
}), rest.length > 0 && /* @__PURE__ */ jsxs("li", {
|
|
1031
1035
|
className: "ov-hbars-more",
|
|
1032
1036
|
children: [
|
|
@@ -2500,6 +2504,29 @@ function slowestRow(rows, clockMs) {
|
|
|
2500
2504
|
}
|
|
2501
2505
|
return best;
|
|
2502
2506
|
}
|
|
2507
|
+
/** The agents a reader should look at first: the longest one by the clock, and the costliest when two or more are priced. */
|
|
2508
|
+
function agentCallouts(agents, clockMs) {
|
|
2509
|
+
let slowest, slowestMs = -1, costliest, top = -1, priced = 0;
|
|
2510
|
+
for (const row of agents) {
|
|
2511
|
+
if (row.durationLabel === "—" || row.span.startMs > clockMs) continue;
|
|
2512
|
+
const ms = (spanRunning(row.span) ? Math.max(row.span.endMs, clockMs) : Math.min(row.span.endMs, clockMs)) - row.span.startMs;
|
|
2513
|
+
if (ms > slowestMs) {
|
|
2514
|
+
slowest = row;
|
|
2515
|
+
slowestMs = ms;
|
|
2516
|
+
}
|
|
2517
|
+
const cost = row.span.costUsd ?? row.rollupCostUsd;
|
|
2518
|
+
if (cost === void 0) continue;
|
|
2519
|
+
priced += 1;
|
|
2520
|
+
if (cost > top) {
|
|
2521
|
+
costliest = row;
|
|
2522
|
+
top = cost;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
return {
|
|
2526
|
+
slowestId: agents.length > 1 ? slowest?.id : void 0,
|
|
2527
|
+
costliestId: priced > 1 && top > 0 ? costliest?.id : void 0
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2503
2530
|
/** Busy time per agent row: the union of its own work. Their sum over wall time is the parallel speedup. */
|
|
2504
2531
|
function busyMs(row, clockMs) {
|
|
2505
2532
|
let total = 0, end = -Infinity;
|
|
@@ -2537,6 +2564,33 @@ function totalsAt(trace, clockMs) {
|
|
|
2537
2564
|
errors
|
|
2538
2565
|
};
|
|
2539
2566
|
}
|
|
2567
|
+
/** The cost a reader can rely on: a lower bound when some calls are unpriced, a dash only when nothing is priced. */
|
|
2568
|
+
function costSummary(costUsd, trace) {
|
|
2569
|
+
const list = trace.costBasis === "list";
|
|
2570
|
+
const label = list ? "Cost · list price" : "Cost";
|
|
2571
|
+
if (costUsd === void 0) return trace.billedUsd !== void 0 ? {
|
|
2572
|
+
value: `${formatCost(trace.billedUsd)} billed`,
|
|
2573
|
+
title: "No list price for these calls; billed is the seat charge",
|
|
2574
|
+
label
|
|
2575
|
+
} : {
|
|
2576
|
+
value: "—",
|
|
2577
|
+
title: "No cost source: the harness recorded no cost and no price catalog was given",
|
|
2578
|
+
label
|
|
2579
|
+
};
|
|
2580
|
+
const coverage = trace.costCoverage;
|
|
2581
|
+
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
2582
|
+
const upper = !partial && trace.costUpperBound === true;
|
|
2583
|
+
return {
|
|
2584
|
+
value: `${partial ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
|
|
2585
|
+
title: [
|
|
2586
|
+
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
2587
|
+
list ? "at the model catalog list price; not a bill" : "",
|
|
2588
|
+
upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
|
|
2589
|
+
trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
|
|
2590
|
+
].filter(Boolean).join("; "),
|
|
2591
|
+
label
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2540
2594
|
//#endregion
|
|
2541
2595
|
//#region src/react/flow-waterfall-icons.tsx
|
|
2542
2596
|
const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
@@ -2636,6 +2690,8 @@ const ZoomOutGlyph = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @_
|
|
|
2636
2690
|
const FitGlyph = () => svg(/* @__PURE__ */ jsx("path", { d: "M2.5 6V3.5a1 1 0 0 1 1-1H6M10 2.5h2.5a1 1 0 0 1 1 1V6M13.5 10v2.5a1 1 0 0 1-1 1H10M6 13.5H3.5a1 1 0 0 1-1-1V10" }));
|
|
2637
2691
|
//#endregion
|
|
2638
2692
|
//#region src/react/flow-waterfall-card.tsx
|
|
2693
|
+
/** Names wrap between words, never at their hyphens ("memscale-engineer-" / "2" read as two names). */
|
|
2694
|
+
const keepWords = (name) => name.replace(/-/g, "‑");
|
|
2639
2695
|
const KIND_LABEL = {
|
|
2640
2696
|
pipeline: "Agent",
|
|
2641
2697
|
model: "LLM call",
|
|
@@ -2646,7 +2702,7 @@ const KIND_LABEL = {
|
|
|
2646
2702
|
const unknown = /* @__PURE__ */ jsx("span", {
|
|
2647
2703
|
className: "fw-unknown",
|
|
2648
2704
|
title: "Not recorded by the harness",
|
|
2649
|
-
children: "
|
|
2705
|
+
children: "—"
|
|
2650
2706
|
});
|
|
2651
2707
|
const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
|
|
2652
2708
|
title: n.toLocaleString(),
|
|
@@ -2655,23 +2711,29 @@ const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
|
|
|
2655
2711
|
/** The span card: what ran, for how long, at what recorded cost, on what input. */
|
|
2656
2712
|
function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
|
|
2657
2713
|
const span = row.span;
|
|
2658
|
-
const
|
|
2659
|
-
const end = running ? Math.max(span.endMs, clockMs) : span.endMs;
|
|
2660
|
-
const status = running ? "Running" : row.ok ? "Done" : "Failed";
|
|
2714
|
+
const status = spanRunning(span) ? "Running" : row.ok ? "Done" : "Failed";
|
|
2661
2715
|
const tokens = span.kind === "model" || span.kind === "pipeline" || span.promptTokens !== void 0;
|
|
2662
2716
|
const owner = agent && agent.id !== row.id ? agent : void 0;
|
|
2663
|
-
const
|
|
2717
|
+
const lasted = (s) => {
|
|
2718
|
+
const stop = spanRunning(s) ? Math.max(s.endMs, clockMs) : s.endMs;
|
|
2719
|
+
return Number.isFinite(stop - s.startMs) && stop >= s.startMs ? formatSpanDuration(stop - s.startMs) : "—";
|
|
2720
|
+
};
|
|
2721
|
+
const duration = lasted(span);
|
|
2664
2722
|
if (compact) {
|
|
2665
2723
|
const who = owner ?? row;
|
|
2666
|
-
const
|
|
2667
|
-
const
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2670
|
-
cells.
|
|
2724
|
+
const subject = who.span;
|
|
2725
|
+
const outcome = subject.outcome;
|
|
2726
|
+
const tokenCount = subject.promptTokens !== void 0 || subject.completionTokens !== void 0 ? (subject.promptTokens ?? 0) + (subject.completionTokens ?? 0) : void 0;
|
|
2727
|
+
const cost = subject.costUsd ?? who.rollupCostUsd;
|
|
2728
|
+
const cells = [[subject.kind === "model" ? "Thinking" : KIND_LABEL[subject.kind], lasted(subject)]];
|
|
2729
|
+
for (const fact of (subject.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
|
|
2730
|
+
cells.push(["Started", `+${formatSpanDuration(Math.max(0, subject.startMs))}`]);
|
|
2671
2731
|
if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
|
|
2672
|
-
if (
|
|
2732
|
+
if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
|
|
2673
2733
|
cells.splice(6);
|
|
2674
|
-
const
|
|
2734
|
+
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;
|
|
2736
|
+
const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
|
|
2675
2737
|
return /* @__PURE__ */ jsxs("div", {
|
|
2676
2738
|
id,
|
|
2677
2739
|
role: pinned ? "dialog" : "tooltip",
|
|
@@ -2699,7 +2761,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2699
2761
|
}),
|
|
2700
2762
|
/* @__PURE__ */ jsx("span", {
|
|
2701
2763
|
className: "fw-card-who",
|
|
2702
|
-
children: who.name
|
|
2764
|
+
children: keepWords(who.name)
|
|
2703
2765
|
}),
|
|
2704
2766
|
outcome ? /* @__PURE__ */ jsx("span", {
|
|
2705
2767
|
className: "fw-pill fw-pill-outcome",
|
|
@@ -2732,6 +2794,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2732
2794
|
className: "fw-card-cells",
|
|
2733
2795
|
children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
|
|
2734
2796
|
}),
|
|
2797
|
+
step && /* @__PURE__ */ jsx("p", {
|
|
2798
|
+
className: "fw-card-step",
|
|
2799
|
+
children: step
|
|
2800
|
+
}),
|
|
2735
2801
|
sentence && /* @__PURE__ */ jsx("p", {
|
|
2736
2802
|
className: "fw-card-result",
|
|
2737
2803
|
children: sentence
|
|
@@ -2782,7 +2848,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2782
2848
|
className: "fw-card-title",
|
|
2783
2849
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2784
2850
|
className: "fw-card-name",
|
|
2785
|
-
children: span.name
|
|
2851
|
+
children: keepWords(span.name)
|
|
2786
2852
|
}), span.detail && /* @__PURE__ */ jsx("div", {
|
|
2787
2853
|
className: "fw-muted",
|
|
2788
2854
|
children: span.detail
|
|
@@ -2807,9 +2873,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2807
2873
|
children: [
|
|
2808
2874
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
|
|
2809
2875
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
|
|
2810
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children:
|
|
2811
|
-
tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
|
|
2812
|
-
tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
|
|
2876
|
+
span.costUsd !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: formatCost(span.costUsd) })] }),
|
|
2877
|
+
tokens && span.promptTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
|
|
2878
|
+
tokens && span.completionTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
|
|
2879
|
+
(span.facts ?? []).slice(0, 3).map((fact) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: fact.label }), /* @__PURE__ */ jsx("dd", { children: fact.value })] }, fact.label)),
|
|
2813
2880
|
row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
|
|
2814
2881
|
]
|
|
2815
2882
|
}),
|
|
@@ -2839,7 +2906,7 @@ const at = (startMs, endMs) => ({
|
|
|
2839
2906
|
"--e": Number.isFinite(endMs) ? endMs : OPEN_END
|
|
2840
2907
|
});
|
|
2841
2908
|
function Segment({ mark, events, agentView }) {
|
|
2842
|
-
const glyph = mark.kind === "failed" ? /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
2909
|
+
const glyph = mark.kind === "failed" ? agentView ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
2843
2910
|
return /* @__PURE__ */ jsx("span", {
|
|
2844
2911
|
className: `fw-seg fw-seg-${mark.kind}`,
|
|
2845
2912
|
"data-tone": toneKey(mark.tone),
|
|
@@ -2868,7 +2935,7 @@ function endLabel(row, clockMs) {
|
|
|
2868
2935
|
}
|
|
2869
2936
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
2870
2937
|
const FlowRow = memo(function FlowRow(props) {
|
|
2871
|
-
const { row, view, top, collapsed, searching, slowest, active, focusable, selected, sticky, clockMs, renderIcon } = props;
|
|
2938
|
+
const { row, view, top, collapsed, searching, slowest, costliest, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
2872
2939
|
const span = row.span;
|
|
2873
2940
|
const running = spanRunning(span);
|
|
2874
2941
|
const agentView = view === "agents";
|
|
@@ -2884,18 +2951,26 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2884
2951
|
"data-span-id": row.id,
|
|
2885
2952
|
"data-span-kind": kind,
|
|
2886
2953
|
"data-slowest": slowest || void 0,
|
|
2954
|
+
"data-running": running || void 0,
|
|
2887
2955
|
"aria-level": agentView ? void 0 : row.depth + 1,
|
|
2888
2956
|
"aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
|
|
2889
2957
|
"aria-selected": selected,
|
|
2890
2958
|
tabIndex: focusable ? 0 : -1,
|
|
2891
2959
|
"data-tone": toneKey(row.tone),
|
|
2892
|
-
"data-outcome": span.outcome?.tone,
|
|
2893
|
-
className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}`,
|
|
2894
|
-
style: {
|
|
2960
|
+
"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" : ""}`,
|
|
2962
|
+
style: {
|
|
2963
|
+
"--row-y": `${top}px`,
|
|
2964
|
+
"--fw-enter-delay": `${motion?.enter ?? 0}ms`
|
|
2965
|
+
},
|
|
2895
2966
|
onFocus: (event) => {
|
|
2896
2967
|
if (event.target === event.currentTarget) props.onActivate(row);
|
|
2897
2968
|
},
|
|
2898
|
-
onClick: () =>
|
|
2969
|
+
onClick: (event) => {
|
|
2970
|
+
props.onActivate(row);
|
|
2971
|
+
const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
|
|
2972
|
+
if (bar) props.onPin(row.id, bar);
|
|
2973
|
+
},
|
|
2899
2974
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2900
2975
|
role: "gridcell",
|
|
2901
2976
|
className: "fw-label",
|
|
@@ -2921,10 +2996,6 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2921
2996
|
className: "fw-toggle-space",
|
|
2922
2997
|
"aria-hidden": true
|
|
2923
2998
|
})),
|
|
2924
|
-
agentView && /* @__PURE__ */ jsx("span", {
|
|
2925
|
-
className: "fw-agent-dot",
|
|
2926
|
-
"aria-hidden": true
|
|
2927
|
-
}),
|
|
2928
2999
|
!agentView && /* @__PURE__ */ jsx("span", {
|
|
2929
3000
|
className: "fw-icon-tile",
|
|
2930
3001
|
"aria-hidden": true,
|
|
@@ -2936,14 +3007,27 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2936
3007
|
children: [
|
|
2937
3008
|
/* @__PURE__ */ jsxs("span", {
|
|
2938
3009
|
className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
|
|
2939
|
-
children: [
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3010
|
+
children: [
|
|
3011
|
+
fillByAgent && isAgent && span.outcome ? /* @__PURE__ */ jsx("span", {
|
|
3012
|
+
className: "fw-agent-dot",
|
|
3013
|
+
"data-tone": "0",
|
|
3014
|
+
"data-outcome": span.outcome.tone,
|
|
3015
|
+
title: span.outcome.label,
|
|
3016
|
+
"aria-hidden": true
|
|
3017
|
+
}) : agentView && /* @__PURE__ */ jsx("span", {
|
|
3018
|
+
className: "fw-agent-dot",
|
|
3019
|
+
"aria-hidden": true
|
|
3020
|
+
}),
|
|
3021
|
+
running && /* @__PURE__ */ jsx("span", {
|
|
3022
|
+
className: "fw-spinner",
|
|
3023
|
+
role: "status",
|
|
3024
|
+
"aria-label": "running"
|
|
3025
|
+
}),
|
|
3026
|
+
/* @__PURE__ */ jsx("span", {
|
|
3027
|
+
className: "fw-name-text",
|
|
3028
|
+
children: row.name
|
|
3029
|
+
})
|
|
3030
|
+
]
|
|
2947
3031
|
}),
|
|
2948
3032
|
agentView && span.detail && /* @__PURE__ */ jsx("span", {
|
|
2949
3033
|
className: "fw-subtitle",
|
|
@@ -2970,11 +3054,19 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2970
3054
|
role: "gridcell",
|
|
2971
3055
|
className: "fw-lane",
|
|
2972
3056
|
"data-waterfall-track": true,
|
|
2973
|
-
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ?
|
|
3057
|
+
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}`,
|
|
2974
3058
|
children: invalid ? /* @__PURE__ */ jsx("span", {
|
|
2975
3059
|
className: "fw-invalid",
|
|
2976
3060
|
children: "—"
|
|
2977
3061
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3062
|
+
span.marks?.map((mark) => /* @__PURE__ */ jsx("span", {
|
|
3063
|
+
className: "fw-mark",
|
|
3064
|
+
"data-mark": mark.kind,
|
|
3065
|
+
style: at(mark.at, mark.at),
|
|
3066
|
+
title: mark.label,
|
|
3067
|
+
"aria-label": mark.label,
|
|
3068
|
+
role: "img"
|
|
3069
|
+
}, `m-${mark.at}-${mark.label}`)),
|
|
2978
3070
|
/* @__PURE__ */ jsxs("span", {
|
|
2979
3071
|
className: `fw-bar fw-k-${isAgent ? "pipeline" : kind}${row.approx ? " fw-approx" : ""}${marks.length ? " fw-has-marks" : ""}${row.hasAgents ? " fw-has-agents" : ""}`,
|
|
2980
3072
|
"data-tone": toneKey(row.tone),
|
|
@@ -2998,21 +3090,28 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2998
3090
|
events: props,
|
|
2999
3091
|
agentView
|
|
3000
3092
|
}, `${mark.id}-${i}`)),
|
|
3001
|
-
|
|
3093
|
+
/* @__PURE__ */ jsxs("span", {
|
|
3002
3094
|
className: "fw-end",
|
|
3003
|
-
style: at(span.startMs, running ?
|
|
3004
|
-
children: [
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3095
|
+
style: at(span.startMs, running ? OPEN_END : span.endMs),
|
|
3096
|
+
children: [
|
|
3097
|
+
/* @__PURE__ */ jsx("span", {
|
|
3098
|
+
className: "fw-duration",
|
|
3099
|
+
children: label
|
|
3100
|
+
}),
|
|
3101
|
+
slowest && /* @__PURE__ */ jsx("span", {
|
|
3102
|
+
className: "fw-badge",
|
|
3103
|
+
children: "Slowest"
|
|
3104
|
+
}),
|
|
3105
|
+
costliest && /* @__PURE__ */ jsx("span", {
|
|
3106
|
+
className: "fw-badge",
|
|
3107
|
+
children: "Costliest"
|
|
3108
|
+
})
|
|
3109
|
+
]
|
|
3011
3110
|
})
|
|
3012
3111
|
] })
|
|
3013
3112
|
})]
|
|
3014
3113
|
});
|
|
3015
|
-
}, (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.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.renderIcon === b.renderIcon && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
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)));
|
|
3016
3115
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3017
3116
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3018
3117
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3029,6 +3128,48 @@ function LinkMark({ link, rowHeight, hot }) {
|
|
|
3029
3128
|
});
|
|
3030
3129
|
}
|
|
3031
3130
|
//#endregion
|
|
3131
|
+
//#region src/react/flow-waterfall-motion.ts
|
|
3132
|
+
const HOLD_MS = 700;
|
|
3133
|
+
const STAGGER_MS = 20;
|
|
3134
|
+
const STAGGER_CAP_MS = 200;
|
|
3135
|
+
/**
|
|
3136
|
+
* Grow, unfold and settle. Rows that arrived since the last change (a live span, an unfolded branch, the first paint) grow
|
|
3137
|
+
* from their start in a short stagger; a row that stops running settles. Motion is keyed to the row set, not to what is
|
|
3138
|
+
* mounted, so scrolling a virtualized tree never replays it; an entry expires after its animation so a remount stays still.
|
|
3139
|
+
*/
|
|
3140
|
+
function useRowMotion(rows) {
|
|
3141
|
+
const known = useRef(null);
|
|
3142
|
+
const running = useRef(/* @__PURE__ */ new Set());
|
|
3143
|
+
const active = useRef(/* @__PURE__ */ new Map());
|
|
3144
|
+
const [expiry, setExpiry] = useState(0);
|
|
3145
|
+
const motion = useMemo(() => {
|
|
3146
|
+
const now = Date.now();
|
|
3147
|
+
for (const [id, entry] of active.current) if (entry.until <= now) active.current.delete(id);
|
|
3148
|
+
const previous = known.current;
|
|
3149
|
+
let order = 0;
|
|
3150
|
+
for (const row of rows) {
|
|
3151
|
+
const isRunning = spanRunning(row.span);
|
|
3152
|
+
if (!previous || !previous.has(row.id)) active.current.set(row.id, {
|
|
3153
|
+
enter: Math.min(order++ * STAGGER_MS, STAGGER_CAP_MS),
|
|
3154
|
+
until: now + HOLD_MS + STAGGER_CAP_MS
|
|
3155
|
+
});
|
|
3156
|
+
else if (running.current.has(row.id) && !isRunning) active.current.set(row.id, {
|
|
3157
|
+
settle: true,
|
|
3158
|
+
until: now + HOLD_MS
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
known.current = new Set(rows.map((row) => row.id));
|
|
3162
|
+
running.current = new Set(rows.filter((row) => spanRunning(row.span)).map((row) => row.id));
|
|
3163
|
+
return new Map(active.current);
|
|
3164
|
+
}, [rows, expiry]);
|
|
3165
|
+
useEffect(() => {
|
|
3166
|
+
if (motion.size === 0) return;
|
|
3167
|
+
const timer = setTimeout(() => setExpiry((value) => value + 1), 920);
|
|
3168
|
+
return () => clearTimeout(timer);
|
|
3169
|
+
}, [motion]);
|
|
3170
|
+
return motion;
|
|
3171
|
+
}
|
|
3172
|
+
//#endregion
|
|
3032
3173
|
//#region src/react/flow-waterfall-time.ts
|
|
3033
3174
|
/** Time-axis geometry for the trace inspector: clock-aligned steps, labels and the zoom window. */
|
|
3034
3175
|
const STEPS = [
|
|
@@ -3108,7 +3249,7 @@ function clampWindow(view, limitMs) {
|
|
|
3108
3249
|
//#region src/react/flow-waterfall-body.tsx
|
|
3109
3250
|
const ROW_HEIGHT = {
|
|
3110
3251
|
spans: 36,
|
|
3111
|
-
agents:
|
|
3252
|
+
agents: 40
|
|
3112
3253
|
};
|
|
3113
3254
|
const OVERSCAN = 8;
|
|
3114
3255
|
/** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
|
|
@@ -3181,6 +3322,7 @@ function FlowBody(props) {
|
|
|
3181
3322
|
view
|
|
3182
3323
|
]);
|
|
3183
3324
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3325
|
+
const motion = useRowMotion(rows);
|
|
3184
3326
|
const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
|
|
3185
3327
|
const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
|
|
3186
3328
|
const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
|
|
@@ -3310,7 +3452,7 @@ function FlowBody(props) {
|
|
|
3310
3452
|
handle.addEventListener("pointerup", up);
|
|
3311
3453
|
};
|
|
3312
3454
|
const axis = /* @__PURE__ */ jsxs("div", {
|
|
3313
|
-
className:
|
|
3455
|
+
className: "fw-axis fw-axis-top",
|
|
3314
3456
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3315
3457
|
className: "fw-axis-label",
|
|
3316
3458
|
children: [view === "spans" && /* @__PURE__ */ jsx(Fragment, { children: props.onExpandAll && /* @__PURE__ */ jsxs("span", {
|
|
@@ -3349,14 +3491,27 @@ function FlowBody(props) {
|
|
|
3349
3491
|
ref: props.onTrack,
|
|
3350
3492
|
onPointerDown: scrub,
|
|
3351
3493
|
title: "Drag to scrub",
|
|
3352
|
-
children: [
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
"
|
|
3359
|
-
|
|
3494
|
+
children: [
|
|
3495
|
+
ticks.map((tick) => /* @__PURE__ */ jsx("span", {
|
|
3496
|
+
className: "fw-tick",
|
|
3497
|
+
style: { "--s": tick },
|
|
3498
|
+
children: formatClock(tick)
|
|
3499
|
+
}, tick)),
|
|
3500
|
+
props.markers?.map((marker) => /* @__PURE__ */ jsx("button", {
|
|
3501
|
+
type: "button",
|
|
3502
|
+
className: "fw-event",
|
|
3503
|
+
"data-tone": marker.tone ?? "neutral",
|
|
3504
|
+
style: { "--s": marker.at },
|
|
3505
|
+
title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
|
|
3506
|
+
"aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
|
|
3507
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
3508
|
+
onClick: () => props.onScrub(marker.at)
|
|
3509
|
+
}, `e-${marker.at}-${marker.label}`)),
|
|
3510
|
+
showCursor && /* @__PURE__ */ jsx("span", {
|
|
3511
|
+
className: "fw-cursor-handle",
|
|
3512
|
+
"aria-hidden": true
|
|
3513
|
+
})
|
|
3514
|
+
]
|
|
3360
3515
|
})]
|
|
3361
3516
|
});
|
|
3362
3517
|
const row = (item, i, isSticky = false) => /* @__PURE__ */ jsx(FlowRow, {
|
|
@@ -3366,12 +3521,15 @@ function FlowBody(props) {
|
|
|
3366
3521
|
collapsed: collapsed.has(item.id),
|
|
3367
3522
|
searching,
|
|
3368
3523
|
slowest: item.id === slowestId,
|
|
3524
|
+
costliest: item.id === props.costliestId,
|
|
3369
3525
|
active: item.id === activeId,
|
|
3370
3526
|
focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
|
|
3371
3527
|
selected: item.id === selectedId,
|
|
3372
3528
|
sticky: isSticky,
|
|
3529
|
+
motion: isSticky ? void 0 : motion.get(item.id),
|
|
3373
3530
|
clockMs,
|
|
3374
3531
|
renderIcon: props.renderIcon,
|
|
3532
|
+
fillByAgent: props.fillByAgent,
|
|
3375
3533
|
onToggle: props.onToggle,
|
|
3376
3534
|
onInspect: props.onInspect,
|
|
3377
3535
|
onLeave: props.onLeave,
|
|
@@ -3381,83 +3539,91 @@ function FlowBody(props) {
|
|
|
3381
3539
|
return /* @__PURE__ */ jsxs("div", {
|
|
3382
3540
|
className: `fw-body fw-view-${view}${zoomed ? " fw-zoomed" : ""}`,
|
|
3383
3541
|
ref: surface,
|
|
3384
|
-
children: [
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
"
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3542
|
+
children: [axis, /* @__PURE__ */ jsxs("div", {
|
|
3543
|
+
className: "fw-viewport",
|
|
3544
|
+
children: [
|
|
3545
|
+
/* @__PURE__ */ jsx("div", {
|
|
3546
|
+
className: "fw-gridlayer",
|
|
3547
|
+
"aria-hidden": true,
|
|
3548
|
+
children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
|
|
3549
|
+
className: "fw-gridline",
|
|
3550
|
+
style: { "--s": tick }
|
|
3551
|
+
}, tick))
|
|
3552
|
+
}),
|
|
3553
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3554
|
+
ref: scroller,
|
|
3555
|
+
role: "treegrid",
|
|
3556
|
+
"aria-label": "Trace timeline",
|
|
3557
|
+
"aria-rowcount": rows.length,
|
|
3558
|
+
"aria-multiselectable": false,
|
|
3559
|
+
tabIndex: -1,
|
|
3560
|
+
className: "fw-scroller",
|
|
3561
|
+
"data-primary-content": "",
|
|
3562
|
+
style: {
|
|
3563
|
+
height,
|
|
3564
|
+
maxHeight
|
|
3565
|
+
},
|
|
3566
|
+
onKeyDown: keydown,
|
|
3567
|
+
onScroll: (event) => {
|
|
3568
|
+
const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
|
|
3569
|
+
if (next !== chunk) setChunk(next);
|
|
3570
|
+
},
|
|
3571
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3572
|
+
className: "fw-canvas",
|
|
3573
|
+
style: { height: rows.length * rowHeight },
|
|
3574
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3575
|
+
className: "fw-links",
|
|
3576
|
+
"aria-hidden": true,
|
|
3577
|
+
children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
|
|
3578
|
+
link,
|
|
3579
|
+
rowHeight,
|
|
3580
|
+
hot: props.focusId !== void 0 && (rows[link.from]?.id === props.focusId || rows[link.to]?.id === props.focusId)
|
|
3581
|
+
}, link.id))
|
|
3582
|
+
}), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
|
|
3583
|
+
}), rows.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
3584
|
+
role: "status",
|
|
3585
|
+
className: "fw-empty",
|
|
3586
|
+
children: props.emptyText
|
|
3587
|
+
})]
|
|
3588
|
+
}),
|
|
3589
|
+
view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
|
|
3590
|
+
scroller,
|
|
3591
|
+
rows,
|
|
3592
|
+
index,
|
|
3593
|
+
rowHeight,
|
|
3594
|
+
render: row
|
|
3595
|
+
}),
|
|
3596
|
+
props.markers?.length || props.pauses?.length ? /* @__PURE__ */ jsxs("div", {
|
|
3597
|
+
className: "fw-marker-layer",
|
|
3598
|
+
children: [props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
|
|
3599
|
+
className: "fw-pause",
|
|
3405
3600
|
style: {
|
|
3406
|
-
|
|
3407
|
-
|
|
3601
|
+
"--s": pause.startMs,
|
|
3602
|
+
"--e": pause.endMs
|
|
3408
3603
|
},
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
|
|
3433
|
-
scroller,
|
|
3434
|
-
rows,
|
|
3435
|
-
index,
|
|
3436
|
-
rowHeight,
|
|
3437
|
-
render: row
|
|
3438
|
-
}),
|
|
3439
|
-
props.markers?.length ? /* @__PURE__ */ jsx("div", {
|
|
3440
|
-
className: "fw-marker-layer",
|
|
3441
|
-
children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
3442
|
-
className: "fw-marker",
|
|
3443
|
-
"data-tone": marker.tone ?? "neutral",
|
|
3444
|
-
style: { "--s": marker.at },
|
|
3445
|
-
title: marker.label,
|
|
3446
|
-
children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
|
|
3447
|
-
className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
|
|
3448
|
-
children: marker.label
|
|
3449
|
-
})
|
|
3450
|
-
}, `${marker.at}-${marker.label}`))
|
|
3451
|
-
}) : null,
|
|
3452
|
-
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3453
|
-
className: "fw-cursor-layer",
|
|
3454
|
-
"aria-hidden": true,
|
|
3455
|
-
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
3456
|
-
})
|
|
3457
|
-
]
|
|
3458
|
-
}),
|
|
3459
|
-
view === "agents" && axis
|
|
3460
|
-
]
|
|
3604
|
+
title: pause.label,
|
|
3605
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3606
|
+
className: "fw-pause-label",
|
|
3607
|
+
children: pause.label
|
|
3608
|
+
})
|
|
3609
|
+
}, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
3610
|
+
className: "fw-marker",
|
|
3611
|
+
"data-tone": marker.tone ?? "neutral",
|
|
3612
|
+
style: { "--s": marker.at },
|
|
3613
|
+
title: marker.label,
|
|
3614
|
+
children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
|
|
3615
|
+
className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
|
|
3616
|
+
children: marker.label
|
|
3617
|
+
})
|
|
3618
|
+
}, `${marker.at}-${marker.label}`))]
|
|
3619
|
+
}) : null,
|
|
3620
|
+
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3621
|
+
className: "fw-cursor-layer",
|
|
3622
|
+
"aria-hidden": true,
|
|
3623
|
+
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
3624
|
+
})
|
|
3625
|
+
]
|
|
3626
|
+
})]
|
|
3461
3627
|
});
|
|
3462
3628
|
}
|
|
3463
3629
|
/** Ancestors of the first visible row stay pinned while their children scroll beneath them. This layer follows
|
|
@@ -3721,13 +3887,71 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
|
|
|
3721
3887
|
const LANE_PAD = 12;
|
|
3722
3888
|
const END_LABEL_GUTTER = {
|
|
3723
3889
|
spans: 120,
|
|
3724
|
-
agents:
|
|
3890
|
+
agents: 176
|
|
3725
3891
|
};
|
|
3726
3892
|
const NAME_WIDTH = {
|
|
3727
3893
|
spans: 320,
|
|
3728
3894
|
agents: 240
|
|
3729
3895
|
};
|
|
3896
|
+
/** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
|
|
3897
|
+
function summaryCells(trace, at, durationMs) {
|
|
3898
|
+
const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
|
|
3899
|
+
const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
|
|
3900
|
+
const modelCalls = at ? at.modelCalls : trace.modelCalls;
|
|
3901
|
+
const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
|
|
3902
|
+
return {
|
|
3903
|
+
cost,
|
|
3904
|
+
tokens,
|
|
3905
|
+
errors,
|
|
3906
|
+
summary: [
|
|
3907
|
+
["Duration", formatSpanDuration(durationMs)],
|
|
3908
|
+
[
|
|
3909
|
+
cost.label,
|
|
3910
|
+
cost.value,
|
|
3911
|
+
cost.title
|
|
3912
|
+
],
|
|
3913
|
+
[
|
|
3914
|
+
"Tokens",
|
|
3915
|
+
formatCount(tokens),
|
|
3916
|
+
`${tokens.toLocaleString()} tokens`
|
|
3917
|
+
],
|
|
3918
|
+
[
|
|
3919
|
+
"LLM calls",
|
|
3920
|
+
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
3921
|
+
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
3922
|
+
],
|
|
3923
|
+
["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
|
|
3924
|
+
["Errors", errors.toLocaleString()]
|
|
3925
|
+
]
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3730
3928
|
/** One legend item per outcome label the agents carry, with its count; nothing when no agent has an outcome. */
|
|
3929
|
+
const TONE_ORDER = [
|
|
3930
|
+
"ok",
|
|
3931
|
+
"warn",
|
|
3932
|
+
"failed",
|
|
3933
|
+
"neutral"
|
|
3934
|
+
];
|
|
3935
|
+
/** What each mark means, as the agent view draws it; a key only for kinds the run has. */
|
|
3936
|
+
function KindLegend({ rows }) {
|
|
3937
|
+
const has = (kind) => rows.some((row) => row.own.some((mark) => mark.kind === kind) || row.span.kind === kind);
|
|
3938
|
+
const shown = [
|
|
3939
|
+
["model", "Model call"],
|
|
3940
|
+
["tool", "Tool call"],
|
|
3941
|
+
["waiting", "Waiting"],
|
|
3942
|
+
["handoff", "Handoff"],
|
|
3943
|
+
["failed", "Failed turn"]
|
|
3944
|
+
].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
|
|
3945
|
+
if (shown.length === 0) return null;
|
|
3946
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
3947
|
+
className: "fw-legend fw-kinds",
|
|
3948
|
+
"aria-label": "Key",
|
|
3949
|
+
children: shown.map(([kind, name]) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
|
|
3950
|
+
className: `fw-swatch-${kind === "waiting" ? "wait" : kind}`,
|
|
3951
|
+
"aria-hidden": true
|
|
3952
|
+
}), name] }, kind))
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3731
3955
|
function OutcomeLegend({ agents }) {
|
|
3732
3956
|
const items = /* @__PURE__ */ new Map();
|
|
3733
3957
|
for (const row of agents) {
|
|
@@ -3745,7 +3969,7 @@ function OutcomeLegend({ agents }) {
|
|
|
3745
3969
|
return /* @__PURE__ */ jsxs("ul", {
|
|
3746
3970
|
className: "fw-legend",
|
|
3747
3971
|
"aria-label": "Outcomes",
|
|
3748
|
-
children: [[...items].map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
|
|
3972
|
+
children: [[...items].sort(([a, x], [b, y]) => TONE_ORDER.indexOf(x.tone) - TONE_ORDER.indexOf(y.tone) || a.localeCompare(b)).map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
|
|
3749
3973
|
/* @__PURE__ */ jsx("span", {
|
|
3750
3974
|
className: "fw-swatch",
|
|
3751
3975
|
"data-tone": "0",
|
|
@@ -3766,10 +3990,9 @@ function OutcomeLegend({ agents }) {
|
|
|
3766
3990
|
] })]
|
|
3767
3991
|
});
|
|
3768
3992
|
}
|
|
3769
|
-
const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
|
|
3770
3993
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
3771
3994
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
3772
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers }) {
|
|
3995
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description }) {
|
|
3773
3996
|
const root = useRef(null);
|
|
3774
3997
|
const search = useRef(null);
|
|
3775
3998
|
const slider = useRef(null);
|
|
@@ -3850,6 +4073,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3850
4073
|
collapsed
|
|
3851
4074
|
]);
|
|
3852
4075
|
const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
|
|
4076
|
+
const callouts = useMemo(() => view === "agents" ? agentCallouts(agents, clockMs) : {}, [
|
|
4077
|
+
view,
|
|
4078
|
+
agents,
|
|
4079
|
+
clockMs
|
|
4080
|
+
]);
|
|
3853
4081
|
const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
|
|
3854
4082
|
const resized = useRef(false);
|
|
3855
4083
|
useLayoutEffect(() => {
|
|
@@ -3880,7 +4108,17 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3880
4108
|
if (event.key === "+" || event.key === "=") zoomBy(.5);
|
|
3881
4109
|
else if (event.key === "-" || event.key === "_") zoomBy(2);
|
|
3882
4110
|
else if (event.key === "0") setZoom(null);
|
|
3883
|
-
else if (event.key === "Escape" && card)
|
|
4111
|
+
else if (event.key === "Escape" && (card || selectedId)) {
|
|
4112
|
+
if (card) closeCard();
|
|
4113
|
+
onDeselect?.();
|
|
4114
|
+
} else if ((event.key === "n" || event.key === "p") && markers?.length) {
|
|
4115
|
+
const ats = markers.map((marker) => marker.at).sort((a, b) => a - b);
|
|
4116
|
+
const next = event.key === "n" ? ats.find((at) => at > clockMs + 1) : [...ats].reverse().find((at) => at < clockMs - 1);
|
|
4117
|
+
if (next !== void 0) {
|
|
4118
|
+
event.preventDefault();
|
|
4119
|
+
scrubTo(next);
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
3884
4122
|
};
|
|
3885
4123
|
document.addEventListener("keydown", keydown);
|
|
3886
4124
|
return () => document.removeEventListener("keydown", keydown);
|
|
@@ -3907,7 +4145,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3907
4145
|
const width = current.endMs - current.startMs;
|
|
3908
4146
|
const end = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
|
|
3909
4147
|
if (view !== "spans" || !Number.isFinite(end) || (end - span.startMs) / Math.max(1, width) > .25) return;
|
|
3910
|
-
const pad = Math.max((end - span.startMs) * .15, 500);
|
|
4148
|
+
const pad = Math.max((end - span.startMs) * .15, 500, (width / 8 - (end - span.startMs)) / 2);
|
|
3911
4149
|
const target = clampWindow({
|
|
3912
4150
|
startMs: span.startMs - pad,
|
|
3913
4151
|
endMs: end + pad
|
|
@@ -3998,6 +4236,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3998
4236
|
}
|
|
3999
4237
|
};
|
|
4000
4238
|
const pin = useCallback((target, element) => latest.current.pin(target, element), []);
|
|
4239
|
+
const selection = selectedId ?? (card?.pinned ? card.target : void 0);
|
|
4001
4240
|
const closeCard = () => {
|
|
4002
4241
|
const target = card?.target;
|
|
4003
4242
|
setCard(null);
|
|
@@ -4007,6 +4246,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4007
4246
|
setPlaying(false);
|
|
4008
4247
|
setPosition(Math.min(ms, clock.limitMs()));
|
|
4009
4248
|
}, [clock]);
|
|
4249
|
+
const timeListener = useRef(onTimeChange);
|
|
4250
|
+
timeListener.current = onTimeChange;
|
|
4251
|
+
useEffect(() => {
|
|
4252
|
+
timeListener.current?.(position);
|
|
4253
|
+
}, [position]);
|
|
4010
4254
|
const togglePlay = () => {
|
|
4011
4255
|
if (playing) {
|
|
4012
4256
|
setPosition(clock.positionMs());
|
|
@@ -4017,31 +4261,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4017
4261
|
setPlaying(true);
|
|
4018
4262
|
};
|
|
4019
4263
|
const scrubbing = position !== null || playing;
|
|
4020
|
-
const
|
|
4021
|
-
const costUsd = at ? at.costUsd : trace.costUsd;
|
|
4022
|
-
const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
|
|
4023
|
-
const modelCalls = at ? at.modelCalls : trace.modelCalls;
|
|
4024
|
-
const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
|
|
4025
|
-
const summary = [
|
|
4026
|
-
["Duration", formatSpanDuration(scrubbing ? clockMs : limit)],
|
|
4027
|
-
[
|
|
4028
|
-
trace.costBasis === "list" ? "Cost · list price" : "Cost",
|
|
4029
|
-
costUsd === void 0 ? "Unknown" : formatCost(costUsd),
|
|
4030
|
-
costUsd === void 0 ? "The harness recorded no cost for this trace" : trace.costBasis === "list" ? "Input and output tokens at the model catalog list price; not a bill" : void 0
|
|
4031
|
-
],
|
|
4032
|
-
[
|
|
4033
|
-
"Tokens",
|
|
4034
|
-
formatCount(tokens),
|
|
4035
|
-
`${tokens.toLocaleString()} tokens`
|
|
4036
|
-
],
|
|
4037
|
-
[
|
|
4038
|
-
"LLM calls",
|
|
4039
|
-
modelCalls === void 0 ? "Unknown" : modelCalls.toLocaleString(),
|
|
4040
|
-
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
4041
|
-
],
|
|
4042
|
-
["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
|
|
4043
|
-
["Errors", errors.toLocaleString()]
|
|
4044
|
-
];
|
|
4264
|
+
const { errors, summary } = summaryCells(trace, scrubbing ? totalsAt(trace, clockMs) : void 0, scrubbing ? clockMs : limit);
|
|
4045
4265
|
const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
|
|
4046
4266
|
const speedup = limit > 0 ? busy / limit : 0;
|
|
4047
4267
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
@@ -4059,11 +4279,37 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4059
4279
|
count: 1,
|
|
4060
4280
|
retry: false
|
|
4061
4281
|
}]), [rows]);
|
|
4282
|
+
const playback = /* @__PURE__ */ jsx(PlaybackBar, {
|
|
4283
|
+
view,
|
|
4284
|
+
playing,
|
|
4285
|
+
limit,
|
|
4286
|
+
clockMs,
|
|
4287
|
+
speed,
|
|
4288
|
+
zoom,
|
|
4289
|
+
marks: overviewMarks,
|
|
4290
|
+
slider,
|
|
4291
|
+
timeOutput,
|
|
4292
|
+
followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
|
|
4293
|
+
onTogglePlay: togglePlay,
|
|
4294
|
+
onRestart: () => {
|
|
4295
|
+
setPosition(0);
|
|
4296
|
+
setPlaying(false);
|
|
4297
|
+
},
|
|
4298
|
+
onSpeed: setSpeed,
|
|
4299
|
+
onScrub: scrubTo,
|
|
4300
|
+
onFollow: () => {
|
|
4301
|
+
setPosition(null);
|
|
4302
|
+
setPlaying(false);
|
|
4303
|
+
},
|
|
4304
|
+
onZoom: zoomBy,
|
|
4305
|
+
onFit: () => setZoom(null)
|
|
4306
|
+
});
|
|
4062
4307
|
return /* @__PURE__ */ jsxs("section", {
|
|
4063
4308
|
ref: root,
|
|
4064
4309
|
"data-flow-waterfall": true,
|
|
4065
4310
|
"aria-label": label,
|
|
4066
4311
|
className: `fw-root fw-root-${view}`,
|
|
4312
|
+
"data-selection": selectedId ? "" : void 0,
|
|
4067
4313
|
style: {
|
|
4068
4314
|
"--fw-name-w": `${nameWidth}px`,
|
|
4069
4315
|
"--fw-pad": `${LANE_PAD}px`,
|
|
@@ -4087,17 +4333,22 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4087
4333
|
className: "fw-title",
|
|
4088
4334
|
children: label
|
|
4089
4335
|
}),
|
|
4336
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
4337
|
+
className: "fw-description",
|
|
4338
|
+
title: description,
|
|
4339
|
+
children: description
|
|
4340
|
+
}),
|
|
4090
4341
|
isLive && /* @__PURE__ */ jsx("span", {
|
|
4091
4342
|
className: "fw-state fw-state-live",
|
|
4092
4343
|
children: "Live"
|
|
4093
4344
|
})
|
|
4094
4345
|
]
|
|
4095
4346
|
}),
|
|
4096
|
-
view === "agents" && /* @__PURE__ */
|
|
4347
|
+
view === "agents" && /* @__PURE__ */ jsxs("p", {
|
|
4097
4348
|
className: "fw-headline",
|
|
4098
4349
|
"aria-live": "polite",
|
|
4099
|
-
children:
|
|
4100
|
-
/* @__PURE__ */ jsx("span", {
|
|
4350
|
+
children: [
|
|
4351
|
+
done && /* @__PURE__ */ jsx("span", {
|
|
4101
4352
|
className: "fw-done-mark",
|
|
4102
4353
|
"aria-label": "done",
|
|
4103
4354
|
children: "✓"
|
|
@@ -4105,17 +4356,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4105
4356
|
agents.length,
|
|
4106
4357
|
" ",
|
|
4107
4358
|
agents.length === 1 ? "agent" : "agents",
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
|
|
4111
|
-
trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
|
|
4112
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4113
|
-
headlineSeconds(clockMs),
|
|
4114
|
-
" · ",
|
|
4115
|
-
tokens.toLocaleString(),
|
|
4116
|
-
" tokens",
|
|
4117
|
-
trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
|
|
4118
|
-
] })
|
|
4359
|
+
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
4360
|
+
]
|
|
4119
4361
|
}),
|
|
4120
4362
|
/* @__PURE__ */ jsx(OutcomeLegend, { agents })
|
|
4121
4363
|
]
|
|
@@ -4129,6 +4371,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4129
4371
|
type: "button",
|
|
4130
4372
|
role: "radio",
|
|
4131
4373
|
"aria-checked": view === option,
|
|
4374
|
+
disabled: view === option,
|
|
4132
4375
|
className: "fw-view-button",
|
|
4133
4376
|
onClick: () => {
|
|
4134
4377
|
setView(option);
|
|
@@ -4170,7 +4413,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4170
4413
|
})]
|
|
4171
4414
|
})]
|
|
4172
4415
|
}),
|
|
4173
|
-
|
|
4416
|
+
/* @__PURE__ */ jsx("dl", {
|
|
4174
4417
|
className: "fw-summary",
|
|
4175
4418
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
4176
4419
|
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
@@ -4181,6 +4424,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4181
4424
|
})]
|
|
4182
4425
|
}, name))
|
|
4183
4426
|
}),
|
|
4427
|
+
view === "agents" && playback,
|
|
4184
4428
|
/* @__PURE__ */ jsx(FlowBody, {
|
|
4185
4429
|
rows: visible,
|
|
4186
4430
|
byId,
|
|
@@ -4189,15 +4433,18 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4189
4433
|
maxHeight: rowsHeight,
|
|
4190
4434
|
collapsed,
|
|
4191
4435
|
searching: Boolean(query.trim()),
|
|
4192
|
-
|
|
4436
|
+
pauses: trace.pauses,
|
|
4437
|
+
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4438
|
+
costliestId: callouts.costliestId,
|
|
4193
4439
|
activeId,
|
|
4194
|
-
selectedId:
|
|
4440
|
+
selectedId: selection,
|
|
4195
4441
|
focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
|
|
4196
4442
|
clockMs,
|
|
4197
4443
|
window: win,
|
|
4198
4444
|
trackPx,
|
|
4199
4445
|
showCursor,
|
|
4200
4446
|
markers,
|
|
4447
|
+
fillByAgent: agentFill === "agent",
|
|
4201
4448
|
zoomed: zoom !== null,
|
|
4202
4449
|
renderIcon,
|
|
4203
4450
|
emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
|
|
@@ -4236,31 +4483,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4236
4483
|
compact: view === "agents"
|
|
4237
4484
|
})
|
|
4238
4485
|
}),
|
|
4239
|
-
/* @__PURE__ */ jsx(
|
|
4240
|
-
|
|
4241
|
-
playing,
|
|
4242
|
-
limit,
|
|
4243
|
-
clockMs,
|
|
4244
|
-
speed,
|
|
4245
|
-
zoom,
|
|
4246
|
-
marks: overviewMarks,
|
|
4247
|
-
slider,
|
|
4248
|
-
timeOutput,
|
|
4249
|
-
followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
|
|
4250
|
-
onTogglePlay: togglePlay,
|
|
4251
|
-
onRestart: () => {
|
|
4252
|
-
setPosition(0);
|
|
4253
|
-
setPlaying(false);
|
|
4254
|
-
},
|
|
4255
|
-
onSpeed: setSpeed,
|
|
4256
|
-
onScrub: scrubTo,
|
|
4257
|
-
onFollow: () => {
|
|
4258
|
-
setPosition(null);
|
|
4259
|
-
setPlaying(false);
|
|
4260
|
-
},
|
|
4261
|
-
onZoom: zoomBy,
|
|
4262
|
-
onFit: () => setZoom(null)
|
|
4263
|
-
})
|
|
4486
|
+
view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
|
|
4487
|
+
view === "spans" && playback
|
|
4264
4488
|
]
|
|
4265
4489
|
});
|
|
4266
4490
|
}
|