@tangle-network/charts 0.18.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/figure.css +8 -8
- package/dist/{flow-trace-BYKE-PMb.d.ts → flow-trace-Sr8GtLm0.d.ts} +34 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +49 -39
- package/dist/react/flow-waterfall.css +76 -25
- package/dist/react/index.d.ts +13 -3
- package/dist/react/index.js +423 -209
- 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
|
|
@@ -2500,6 +2500,29 @@ function slowestRow(rows, clockMs) {
|
|
|
2500
2500
|
}
|
|
2501
2501
|
return best;
|
|
2502
2502
|
}
|
|
2503
|
+
/** The agents a reader should look at first: the longest one by the clock, and the costliest when two or more are priced. */
|
|
2504
|
+
function agentCallouts(agents, clockMs) {
|
|
2505
|
+
let slowest, slowestMs = -1, costliest, top = -1, priced = 0;
|
|
2506
|
+
for (const row of agents) {
|
|
2507
|
+
if (row.durationLabel === "—" || row.span.startMs > clockMs) continue;
|
|
2508
|
+
const ms = (spanRunning(row.span) ? Math.max(row.span.endMs, clockMs) : Math.min(row.span.endMs, clockMs)) - row.span.startMs;
|
|
2509
|
+
if (ms > slowestMs) {
|
|
2510
|
+
slowest = row;
|
|
2511
|
+
slowestMs = ms;
|
|
2512
|
+
}
|
|
2513
|
+
const cost = row.span.costUsd ?? row.rollupCostUsd;
|
|
2514
|
+
if (cost === void 0) continue;
|
|
2515
|
+
priced += 1;
|
|
2516
|
+
if (cost > top) {
|
|
2517
|
+
costliest = row;
|
|
2518
|
+
top = cost;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
return {
|
|
2522
|
+
slowestId: agents.length > 1 ? slowest?.id : void 0,
|
|
2523
|
+
costliestId: priced > 1 && top > 0 ? costliest?.id : void 0
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2503
2526
|
/** Busy time per agent row: the union of its own work. Their sum over wall time is the parallel speedup. */
|
|
2504
2527
|
function busyMs(row, clockMs) {
|
|
2505
2528
|
let total = 0, end = -Infinity;
|
|
@@ -2537,6 +2560,31 @@ function totalsAt(trace, clockMs) {
|
|
|
2537
2560
|
errors
|
|
2538
2561
|
};
|
|
2539
2562
|
}
|
|
2563
|
+
/** The cost a reader can rely on: a lower bound when some calls are unpriced, a dash only when nothing is priced. */
|
|
2564
|
+
function costSummary(costUsd, trace) {
|
|
2565
|
+
const list = trace.costBasis === "list";
|
|
2566
|
+
const label = list ? "Cost · list price" : "Cost";
|
|
2567
|
+
if (costUsd === void 0) return trace.billedUsd !== void 0 ? {
|
|
2568
|
+
value: `${formatCost(trace.billedUsd)} billed`,
|
|
2569
|
+
title: "No list price for these calls; billed is the seat charge",
|
|
2570
|
+
label
|
|
2571
|
+
} : {
|
|
2572
|
+
value: "—",
|
|
2573
|
+
title: "No cost source: the harness recorded no cost and no price catalog was given",
|
|
2574
|
+
label
|
|
2575
|
+
};
|
|
2576
|
+
const coverage = trace.costCoverage;
|
|
2577
|
+
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
2578
|
+
return {
|
|
2579
|
+
value: `${partial ? "≥" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
|
|
2580
|
+
title: [
|
|
2581
|
+
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
2582
|
+
list ? "at the model catalog list price; not a bill" : "",
|
|
2583
|
+
trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
|
|
2584
|
+
].filter(Boolean).join("; "),
|
|
2585
|
+
label
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2540
2588
|
//#endregion
|
|
2541
2589
|
//#region src/react/flow-waterfall-icons.tsx
|
|
2542
2590
|
const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
@@ -2646,7 +2694,7 @@ const KIND_LABEL = {
|
|
|
2646
2694
|
const unknown = /* @__PURE__ */ jsx("span", {
|
|
2647
2695
|
className: "fw-unknown",
|
|
2648
2696
|
title: "Not recorded by the harness",
|
|
2649
|
-
children: "
|
|
2697
|
+
children: "—"
|
|
2650
2698
|
});
|
|
2651
2699
|
const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
|
|
2652
2700
|
title: n.toLocaleString(),
|
|
@@ -2655,24 +2703,36 @@ const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
|
|
|
2655
2703
|
/** The span card: what ran, for how long, at what recorded cost, on what input. */
|
|
2656
2704
|
function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
|
|
2657
2705
|
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";
|
|
2706
|
+
const status = spanRunning(span) ? "Running" : row.ok ? "Done" : "Failed";
|
|
2661
2707
|
const tokens = span.kind === "model" || span.kind === "pipeline" || span.promptTokens !== void 0;
|
|
2662
2708
|
const owner = agent && agent.id !== row.id ? agent : void 0;
|
|
2663
|
-
const
|
|
2709
|
+
const lasted = (s) => {
|
|
2710
|
+
const stop = spanRunning(s) ? Math.max(s.endMs, clockMs) : s.endMs;
|
|
2711
|
+
return Number.isFinite(stop - s.startMs) && stop >= s.startMs ? formatSpanDuration(stop - s.startMs) : "—";
|
|
2712
|
+
};
|
|
2713
|
+
const duration = lasted(span);
|
|
2664
2714
|
if (compact) {
|
|
2665
2715
|
const who = owner ?? row;
|
|
2666
|
-
const
|
|
2667
|
-
const
|
|
2716
|
+
const subject = who.span;
|
|
2717
|
+
const outcome = subject.outcome;
|
|
2718
|
+
const tokenCount = subject.promptTokens !== void 0 || subject.completionTokens !== void 0 ? (subject.promptTokens ?? 0) + (subject.completionTokens ?? 0) : void 0;
|
|
2719
|
+
const cost = subject.costUsd ?? who.rollupCostUsd;
|
|
2720
|
+
const cells = [[subject.kind === "model" ? "Thinking" : KIND_LABEL[subject.kind], lasted(subject)]];
|
|
2721
|
+
for (const fact of (subject.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
|
|
2722
|
+
cells.push(["Started", `+${formatSpanDuration(Math.max(0, subject.startMs))}`]);
|
|
2668
2723
|
if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
|
|
2669
|
-
if (
|
|
2724
|
+
if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
|
|
2725
|
+
cells.splice(6);
|
|
2726
|
+
const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
|
|
2727
|
+
const step = owner ? `${kind}${span.name && span.name !== kind && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : ""} · ${duration}${row.ok ? "" : " · failed"}` : void 0;
|
|
2728
|
+
const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
|
|
2670
2729
|
return /* @__PURE__ */ jsxs("div", {
|
|
2671
2730
|
id,
|
|
2672
2731
|
role: pinned ? "dialog" : "tooltip",
|
|
2673
2732
|
"aria-label": pinned ? `${span.name} details` : void 0,
|
|
2674
2733
|
className: "fw-card fw-card-compact",
|
|
2675
2734
|
"data-tone": toneKey(who.tone),
|
|
2735
|
+
"data-outcome": outcome?.tone,
|
|
2676
2736
|
style: {
|
|
2677
2737
|
left,
|
|
2678
2738
|
top
|
|
@@ -2695,7 +2755,12 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2695
2755
|
className: "fw-card-who",
|
|
2696
2756
|
children: who.name
|
|
2697
2757
|
}),
|
|
2698
|
-
/* @__PURE__ */ jsx("span", {
|
|
2758
|
+
outcome ? /* @__PURE__ */ jsx("span", {
|
|
2759
|
+
className: "fw-pill fw-pill-outcome",
|
|
2760
|
+
"data-tone": "0",
|
|
2761
|
+
"data-outcome": outcome.tone,
|
|
2762
|
+
children: outcome.label
|
|
2763
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
2699
2764
|
className: `fw-pill fw-pill-${status.toLowerCase()}`,
|
|
2700
2765
|
children: status
|
|
2701
2766
|
}),
|
|
@@ -2713,13 +2778,21 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2713
2778
|
})
|
|
2714
2779
|
]
|
|
2715
2780
|
}),
|
|
2781
|
+
who.span.detail && /* @__PURE__ */ jsx("p", {
|
|
2782
|
+
className: "fw-card-detail",
|
|
2783
|
+
children: who.span.detail
|
|
2784
|
+
}),
|
|
2716
2785
|
/* @__PURE__ */ jsx("dl", {
|
|
2717
2786
|
className: "fw-card-cells",
|
|
2718
2787
|
children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
|
|
2719
2788
|
}),
|
|
2720
|
-
/* @__PURE__ */ jsx("p", {
|
|
2789
|
+
step && /* @__PURE__ */ jsx("p", {
|
|
2790
|
+
className: "fw-card-step",
|
|
2791
|
+
children: step
|
|
2792
|
+
}),
|
|
2793
|
+
sentence && /* @__PURE__ */ jsx("p", {
|
|
2721
2794
|
className: "fw-card-result",
|
|
2722
|
-
children:
|
|
2795
|
+
children: sentence
|
|
2723
2796
|
})
|
|
2724
2797
|
]
|
|
2725
2798
|
});
|
|
@@ -2792,9 +2865,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2792
2865
|
children: [
|
|
2793
2866
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
|
|
2794
2867
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
|
|
2795
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children:
|
|
2796
|
-
tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
|
|
2797
|
-
tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
|
|
2868
|
+
span.costUsd !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: formatCost(span.costUsd) })] }),
|
|
2869
|
+
tokens && span.promptTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
|
|
2870
|
+
tokens && span.completionTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
|
|
2871
|
+
(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)),
|
|
2798
2872
|
row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
|
|
2799
2873
|
]
|
|
2800
2874
|
}),
|
|
@@ -2853,7 +2927,7 @@ function endLabel(row, clockMs) {
|
|
|
2853
2927
|
}
|
|
2854
2928
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
2855
2929
|
const FlowRow = memo(function FlowRow(props) {
|
|
2856
|
-
const { row, view, top, collapsed, searching, slowest, active, focusable, selected, sticky, clockMs, renderIcon } = props;
|
|
2930
|
+
const { row, view, top, collapsed, searching, slowest, costliest, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
2857
2931
|
const span = row.span;
|
|
2858
2932
|
const running = spanRunning(span);
|
|
2859
2933
|
const agentView = view === "agents";
|
|
@@ -2869,18 +2943,26 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2869
2943
|
"data-span-id": row.id,
|
|
2870
2944
|
"data-span-kind": kind,
|
|
2871
2945
|
"data-slowest": slowest || void 0,
|
|
2946
|
+
"data-running": running || void 0,
|
|
2872
2947
|
"aria-level": agentView ? void 0 : row.depth + 1,
|
|
2873
2948
|
"aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
|
|
2874
2949
|
"aria-selected": selected,
|
|
2875
2950
|
tabIndex: focusable ? 0 : -1,
|
|
2876
2951
|
"data-tone": toneKey(row.tone),
|
|
2877
|
-
"data-outcome": span.outcome?.tone,
|
|
2878
|
-
className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}`,
|
|
2879
|
-
style: {
|
|
2952
|
+
"data-outcome": fillByAgent ? void 0 : span.outcome?.tone,
|
|
2953
|
+
className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
|
|
2954
|
+
style: {
|
|
2955
|
+
"--row-y": `${top}px`,
|
|
2956
|
+
"--fw-enter-delay": `${motion?.enter ?? 0}ms`
|
|
2957
|
+
},
|
|
2880
2958
|
onFocus: (event) => {
|
|
2881
2959
|
if (event.target === event.currentTarget) props.onActivate(row);
|
|
2882
2960
|
},
|
|
2883
|
-
onClick: () =>
|
|
2961
|
+
onClick: (event) => {
|
|
2962
|
+
props.onActivate(row);
|
|
2963
|
+
const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
|
|
2964
|
+
if (bar) props.onPin(row.id, bar);
|
|
2965
|
+
},
|
|
2884
2966
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2885
2967
|
role: "gridcell",
|
|
2886
2968
|
className: "fw-label",
|
|
@@ -2906,10 +2988,6 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2906
2988
|
className: "fw-toggle-space",
|
|
2907
2989
|
"aria-hidden": true
|
|
2908
2990
|
})),
|
|
2909
|
-
agentView && /* @__PURE__ */ jsx("span", {
|
|
2910
|
-
className: "fw-agent-dot",
|
|
2911
|
-
"aria-hidden": true
|
|
2912
|
-
}),
|
|
2913
2991
|
!agentView && /* @__PURE__ */ jsx("span", {
|
|
2914
2992
|
className: "fw-icon-tile",
|
|
2915
2993
|
"aria-hidden": true,
|
|
@@ -2918,21 +2996,41 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2918
2996
|
/* @__PURE__ */ jsxs("span", {
|
|
2919
2997
|
className: "fw-names",
|
|
2920
2998
|
title: `${row.name}${span.detail ? ` · ${span.detail}` : ""} · ${KIND_LABEL[span.kind]}${running ? " · running" : ""}${row.ok ? "" : " · failed"}`,
|
|
2921
|
-
children: [
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2999
|
+
children: [
|
|
3000
|
+
/* @__PURE__ */ jsxs("span", {
|
|
3001
|
+
className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
|
|
3002
|
+
children: [
|
|
3003
|
+
fillByAgent && isAgent && span.outcome ? /* @__PURE__ */ jsx("span", {
|
|
3004
|
+
className: "fw-agent-dot",
|
|
3005
|
+
"data-tone": "0",
|
|
3006
|
+
"data-outcome": span.outcome.tone,
|
|
3007
|
+
title: span.outcome.label,
|
|
3008
|
+
"aria-hidden": true
|
|
3009
|
+
}) : agentView && /* @__PURE__ */ jsx("span", {
|
|
3010
|
+
className: "fw-agent-dot",
|
|
3011
|
+
"aria-hidden": true
|
|
3012
|
+
}),
|
|
3013
|
+
running && /* @__PURE__ */ jsx("span", {
|
|
3014
|
+
className: "fw-spinner",
|
|
3015
|
+
role: "status",
|
|
3016
|
+
"aria-label": "running"
|
|
3017
|
+
}),
|
|
3018
|
+
/* @__PURE__ */ jsx("span", {
|
|
3019
|
+
className: "fw-name-text",
|
|
3020
|
+
children: row.name
|
|
3021
|
+
})
|
|
3022
|
+
]
|
|
3023
|
+
}),
|
|
3024
|
+
agentView && span.detail && /* @__PURE__ */ jsx("span", {
|
|
3025
|
+
className: "fw-subtitle",
|
|
3026
|
+
children: span.detail
|
|
3027
|
+
}),
|
|
3028
|
+
agentView && renderIcon && /* @__PURE__ */ jsx("span", {
|
|
3029
|
+
className: "fw-detail-icon",
|
|
3030
|
+
"aria-hidden": true,
|
|
3031
|
+
children: renderIcon(span)
|
|
3032
|
+
})
|
|
3033
|
+
]
|
|
2936
3034
|
}),
|
|
2937
3035
|
folded && /* @__PURE__ */ jsxs("span", {
|
|
2938
3036
|
className: "fw-count",
|
|
@@ -2948,11 +3046,19 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2948
3046
|
role: "gridcell",
|
|
2949
3047
|
className: "fw-lane",
|
|
2950
3048
|
"data-waterfall-track": true,
|
|
2951
|
-
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ?
|
|
3049
|
+
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}`,
|
|
2952
3050
|
children: invalid ? /* @__PURE__ */ jsx("span", {
|
|
2953
3051
|
className: "fw-invalid",
|
|
2954
3052
|
children: "—"
|
|
2955
3053
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3054
|
+
span.marks?.map((mark) => /* @__PURE__ */ jsx("span", {
|
|
3055
|
+
className: "fw-mark",
|
|
3056
|
+
"data-mark": mark.kind,
|
|
3057
|
+
style: at(mark.at, mark.at),
|
|
3058
|
+
title: mark.label,
|
|
3059
|
+
"aria-label": mark.label,
|
|
3060
|
+
role: "img"
|
|
3061
|
+
}, `m-${mark.at}-${mark.label}`)),
|
|
2956
3062
|
/* @__PURE__ */ jsxs("span", {
|
|
2957
3063
|
className: `fw-bar fw-k-${isAgent ? "pipeline" : kind}${row.approx ? " fw-approx" : ""}${marks.length ? " fw-has-marks" : ""}${row.hasAgents ? " fw-has-agents" : ""}`,
|
|
2958
3064
|
"data-tone": toneKey(row.tone),
|
|
@@ -2976,23 +3082,30 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2976
3082
|
events: props,
|
|
2977
3083
|
agentView
|
|
2978
3084
|
}, `${mark.id}-${i}`)),
|
|
2979
|
-
|
|
3085
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2980
3086
|
className: "fw-end",
|
|
2981
|
-
style: at(span.startMs, running ?
|
|
2982
|
-
children: [
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3087
|
+
style: at(span.startMs, running ? OPEN_END : span.endMs),
|
|
3088
|
+
children: [
|
|
3089
|
+
/* @__PURE__ */ jsx("span", {
|
|
3090
|
+
className: "fw-duration",
|
|
3091
|
+
children: label
|
|
3092
|
+
}),
|
|
3093
|
+
slowest && /* @__PURE__ */ jsx("span", {
|
|
3094
|
+
className: "fw-badge",
|
|
3095
|
+
children: "Slowest"
|
|
3096
|
+
}),
|
|
3097
|
+
costliest && /* @__PURE__ */ jsx("span", {
|
|
3098
|
+
className: "fw-badge",
|
|
3099
|
+
children: "Costliest"
|
|
3100
|
+
})
|
|
3101
|
+
]
|
|
2989
3102
|
})
|
|
2990
3103
|
] })
|
|
2991
3104
|
})]
|
|
2992
3105
|
});
|
|
2993
|
-
}, (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)));
|
|
3106
|
+
}, (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)));
|
|
2994
3107
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
2995
|
-
function LinkMark({ link, rowHeight }) {
|
|
3108
|
+
function LinkMark({ link, rowHeight, hot }) {
|
|
2996
3109
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
2997
3110
|
const style = {
|
|
2998
3111
|
...at(link.atMs, link.atMs),
|
|
@@ -3000,13 +3113,55 @@ function LinkMark({ link, rowHeight }) {
|
|
|
3000
3113
|
height: (lower - upper) * rowHeight - 5
|
|
3001
3114
|
};
|
|
3002
3115
|
return /* @__PURE__ */ jsx("span", {
|
|
3003
|
-
className: `fw-link fw-link-${link.dir}`,
|
|
3116
|
+
className: `fw-link fw-link-${link.dir}${hot ? " fw-link-hot" : ""}`,
|
|
3004
3117
|
"data-tone": toneKey(link.tone),
|
|
3005
3118
|
style,
|
|
3006
3119
|
"aria-hidden": true
|
|
3007
3120
|
});
|
|
3008
3121
|
}
|
|
3009
3122
|
//#endregion
|
|
3123
|
+
//#region src/react/flow-waterfall-motion.ts
|
|
3124
|
+
const HOLD_MS = 700;
|
|
3125
|
+
const STAGGER_MS = 20;
|
|
3126
|
+
const STAGGER_CAP_MS = 200;
|
|
3127
|
+
/**
|
|
3128
|
+
* Grow, unfold and settle. Rows that arrived since the last change (a live span, an unfolded branch, the first paint) grow
|
|
3129
|
+
* from their start in a short stagger; a row that stops running settles. Motion is keyed to the row set, not to what is
|
|
3130
|
+
* mounted, so scrolling a virtualized tree never replays it; an entry expires after its animation so a remount stays still.
|
|
3131
|
+
*/
|
|
3132
|
+
function useRowMotion(rows) {
|
|
3133
|
+
const known = useRef(null);
|
|
3134
|
+
const running = useRef(/* @__PURE__ */ new Set());
|
|
3135
|
+
const active = useRef(/* @__PURE__ */ new Map());
|
|
3136
|
+
const [expiry, setExpiry] = useState(0);
|
|
3137
|
+
const motion = useMemo(() => {
|
|
3138
|
+
const now = Date.now();
|
|
3139
|
+
for (const [id, entry] of active.current) if (entry.until <= now) active.current.delete(id);
|
|
3140
|
+
const previous = known.current;
|
|
3141
|
+
let order = 0;
|
|
3142
|
+
for (const row of rows) {
|
|
3143
|
+
const isRunning = spanRunning(row.span);
|
|
3144
|
+
if (!previous || !previous.has(row.id)) active.current.set(row.id, {
|
|
3145
|
+
enter: Math.min(order++ * STAGGER_MS, STAGGER_CAP_MS),
|
|
3146
|
+
until: now + HOLD_MS + STAGGER_CAP_MS
|
|
3147
|
+
});
|
|
3148
|
+
else if (running.current.has(row.id) && !isRunning) active.current.set(row.id, {
|
|
3149
|
+
settle: true,
|
|
3150
|
+
until: now + HOLD_MS
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
known.current = new Set(rows.map((row) => row.id));
|
|
3154
|
+
running.current = new Set(rows.filter((row) => spanRunning(row.span)).map((row) => row.id));
|
|
3155
|
+
return new Map(active.current);
|
|
3156
|
+
}, [rows, expiry]);
|
|
3157
|
+
useEffect(() => {
|
|
3158
|
+
if (motion.size === 0) return;
|
|
3159
|
+
const timer = setTimeout(() => setExpiry((value) => value + 1), 920);
|
|
3160
|
+
return () => clearTimeout(timer);
|
|
3161
|
+
}, [motion]);
|
|
3162
|
+
return motion;
|
|
3163
|
+
}
|
|
3164
|
+
//#endregion
|
|
3010
3165
|
//#region src/react/flow-waterfall-time.ts
|
|
3011
3166
|
/** Time-axis geometry for the trace inspector: clock-aligned steps, labels and the zoom window. */
|
|
3012
3167
|
const STEPS = [
|
|
@@ -3086,7 +3241,7 @@ function clampWindow(view, limitMs) {
|
|
|
3086
3241
|
//#region src/react/flow-waterfall-body.tsx
|
|
3087
3242
|
const ROW_HEIGHT = {
|
|
3088
3243
|
spans: 36,
|
|
3089
|
-
agents:
|
|
3244
|
+
agents: 40
|
|
3090
3245
|
};
|
|
3091
3246
|
const OVERSCAN = 8;
|
|
3092
3247
|
/** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
|
|
@@ -3159,6 +3314,7 @@ function FlowBody(props) {
|
|
|
3159
3314
|
view
|
|
3160
3315
|
]);
|
|
3161
3316
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3317
|
+
const motion = useRowMotion(rows);
|
|
3162
3318
|
const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
|
|
3163
3319
|
const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
|
|
3164
3320
|
const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
|
|
@@ -3288,7 +3444,7 @@ function FlowBody(props) {
|
|
|
3288
3444
|
handle.addEventListener("pointerup", up);
|
|
3289
3445
|
};
|
|
3290
3446
|
const axis = /* @__PURE__ */ jsxs("div", {
|
|
3291
|
-
className:
|
|
3447
|
+
className: "fw-axis fw-axis-top",
|
|
3292
3448
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3293
3449
|
className: "fw-axis-label",
|
|
3294
3450
|
children: [view === "spans" && /* @__PURE__ */ jsx(Fragment, { children: props.onExpandAll && /* @__PURE__ */ jsxs("span", {
|
|
@@ -3327,14 +3483,27 @@ function FlowBody(props) {
|
|
|
3327
3483
|
ref: props.onTrack,
|
|
3328
3484
|
onPointerDown: scrub,
|
|
3329
3485
|
title: "Drag to scrub",
|
|
3330
|
-
children: [
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
"
|
|
3337
|
-
|
|
3486
|
+
children: [
|
|
3487
|
+
ticks.map((tick) => /* @__PURE__ */ jsx("span", {
|
|
3488
|
+
className: "fw-tick",
|
|
3489
|
+
style: { "--s": tick },
|
|
3490
|
+
children: formatClock(tick)
|
|
3491
|
+
}, tick)),
|
|
3492
|
+
props.markers?.map((marker) => /* @__PURE__ */ jsx("button", {
|
|
3493
|
+
type: "button",
|
|
3494
|
+
className: "fw-event",
|
|
3495
|
+
"data-tone": marker.tone ?? "neutral",
|
|
3496
|
+
style: { "--s": marker.at },
|
|
3497
|
+
title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
|
|
3498
|
+
"aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
|
|
3499
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
3500
|
+
onClick: () => props.onScrub(marker.at)
|
|
3501
|
+
}, `e-${marker.at}-${marker.label}`)),
|
|
3502
|
+
showCursor && /* @__PURE__ */ jsx("span", {
|
|
3503
|
+
className: "fw-cursor-handle",
|
|
3504
|
+
"aria-hidden": true
|
|
3505
|
+
})
|
|
3506
|
+
]
|
|
3338
3507
|
})]
|
|
3339
3508
|
});
|
|
3340
3509
|
const row = (item, i, isSticky = false) => /* @__PURE__ */ jsx(FlowRow, {
|
|
@@ -3344,12 +3513,15 @@ function FlowBody(props) {
|
|
|
3344
3513
|
collapsed: collapsed.has(item.id),
|
|
3345
3514
|
searching,
|
|
3346
3515
|
slowest: item.id === slowestId,
|
|
3516
|
+
costliest: item.id === props.costliestId,
|
|
3347
3517
|
active: item.id === activeId,
|
|
3348
3518
|
focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
|
|
3349
3519
|
selected: item.id === selectedId,
|
|
3350
3520
|
sticky: isSticky,
|
|
3521
|
+
motion: isSticky ? void 0 : motion.get(item.id),
|
|
3351
3522
|
clockMs,
|
|
3352
3523
|
renderIcon: props.renderIcon,
|
|
3524
|
+
fillByAgent: props.fillByAgent,
|
|
3353
3525
|
onToggle: props.onToggle,
|
|
3354
3526
|
onInspect: props.onInspect,
|
|
3355
3527
|
onLeave: props.onLeave,
|
|
@@ -3359,82 +3531,91 @@ function FlowBody(props) {
|
|
|
3359
3531
|
return /* @__PURE__ */ jsxs("div", {
|
|
3360
3532
|
className: `fw-body fw-view-${view}${zoomed ? " fw-zoomed" : ""}`,
|
|
3361
3533
|
ref: surface,
|
|
3362
|
-
children: [
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
"
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3534
|
+
children: [axis, /* @__PURE__ */ jsxs("div", {
|
|
3535
|
+
className: "fw-viewport",
|
|
3536
|
+
children: [
|
|
3537
|
+
/* @__PURE__ */ jsx("div", {
|
|
3538
|
+
className: "fw-gridlayer",
|
|
3539
|
+
"aria-hidden": true,
|
|
3540
|
+
children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
|
|
3541
|
+
className: "fw-gridline",
|
|
3542
|
+
style: { "--s": tick }
|
|
3543
|
+
}, tick))
|
|
3544
|
+
}),
|
|
3545
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3546
|
+
ref: scroller,
|
|
3547
|
+
role: "treegrid",
|
|
3548
|
+
"aria-label": "Trace timeline",
|
|
3549
|
+
"aria-rowcount": rows.length,
|
|
3550
|
+
"aria-multiselectable": false,
|
|
3551
|
+
tabIndex: -1,
|
|
3552
|
+
className: "fw-scroller",
|
|
3553
|
+
"data-primary-content": "",
|
|
3554
|
+
style: {
|
|
3555
|
+
height,
|
|
3556
|
+
maxHeight
|
|
3557
|
+
},
|
|
3558
|
+
onKeyDown: keydown,
|
|
3559
|
+
onScroll: (event) => {
|
|
3560
|
+
const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
|
|
3561
|
+
if (next !== chunk) setChunk(next);
|
|
3562
|
+
},
|
|
3563
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3564
|
+
className: "fw-canvas",
|
|
3565
|
+
style: { height: rows.length * rowHeight },
|
|
3566
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3567
|
+
className: "fw-links",
|
|
3568
|
+
"aria-hidden": true,
|
|
3569
|
+
children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
|
|
3570
|
+
link,
|
|
3571
|
+
rowHeight,
|
|
3572
|
+
hot: props.focusId !== void 0 && (rows[link.from]?.id === props.focusId || rows[link.to]?.id === props.focusId)
|
|
3573
|
+
}, link.id))
|
|
3574
|
+
}), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
|
|
3575
|
+
}), rows.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
3576
|
+
role: "status",
|
|
3577
|
+
className: "fw-empty",
|
|
3578
|
+
children: props.emptyText
|
|
3579
|
+
})]
|
|
3580
|
+
}),
|
|
3581
|
+
view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
|
|
3582
|
+
scroller,
|
|
3583
|
+
rows,
|
|
3584
|
+
index,
|
|
3585
|
+
rowHeight,
|
|
3586
|
+
render: row
|
|
3587
|
+
}),
|
|
3588
|
+
props.markers?.length || props.pauses?.length ? /* @__PURE__ */ jsxs("div", {
|
|
3589
|
+
className: "fw-marker-layer",
|
|
3590
|
+
children: [props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
|
|
3591
|
+
className: "fw-pause",
|
|
3383
3592
|
style: {
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
},
|
|
3387
|
-
onKeyDown: keydown,
|
|
3388
|
-
onScroll: (event) => {
|
|
3389
|
-
const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
|
|
3390
|
-
if (next !== chunk) setChunk(next);
|
|
3593
|
+
"--s": pause.startMs,
|
|
3594
|
+
"--e": pause.endMs
|
|
3391
3595
|
},
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
children:
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
}),
|
|
3416
|
-
props.markers?.length ? /* @__PURE__ */ jsx("div", {
|
|
3417
|
-
className: "fw-marker-layer",
|
|
3418
|
-
children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
3419
|
-
className: "fw-marker",
|
|
3420
|
-
"data-tone": marker.tone ?? "neutral",
|
|
3421
|
-
style: { "--s": marker.at },
|
|
3422
|
-
title: marker.label,
|
|
3423
|
-
children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
|
|
3424
|
-
className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
|
|
3425
|
-
children: marker.label
|
|
3426
|
-
})
|
|
3427
|
-
}, `${marker.at}-${marker.label}`))
|
|
3428
|
-
}) : null,
|
|
3429
|
-
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3430
|
-
className: "fw-cursor-layer",
|
|
3431
|
-
"aria-hidden": true,
|
|
3432
|
-
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
3433
|
-
})
|
|
3434
|
-
]
|
|
3435
|
-
}),
|
|
3436
|
-
view === "agents" && axis
|
|
3437
|
-
]
|
|
3596
|
+
title: pause.label,
|
|
3597
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3598
|
+
className: "fw-pause-label",
|
|
3599
|
+
children: pause.label
|
|
3600
|
+
})
|
|
3601
|
+
}, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
3602
|
+
className: "fw-marker",
|
|
3603
|
+
"data-tone": marker.tone ?? "neutral",
|
|
3604
|
+
style: { "--s": marker.at },
|
|
3605
|
+
title: marker.label,
|
|
3606
|
+
children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
|
|
3607
|
+
className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
|
|
3608
|
+
children: marker.label
|
|
3609
|
+
})
|
|
3610
|
+
}, `${marker.at}-${marker.label}`))]
|
|
3611
|
+
}) : null,
|
|
3612
|
+
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3613
|
+
className: "fw-cursor-layer",
|
|
3614
|
+
"aria-hidden": true,
|
|
3615
|
+
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
3616
|
+
})
|
|
3617
|
+
]
|
|
3618
|
+
})]
|
|
3438
3619
|
});
|
|
3439
3620
|
}
|
|
3440
3621
|
/** Ancestors of the first visible row stay pinned while their children scroll beneath them. This layer follows
|
|
@@ -3698,13 +3879,51 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
|
|
|
3698
3879
|
const LANE_PAD = 12;
|
|
3699
3880
|
const END_LABEL_GUTTER = {
|
|
3700
3881
|
spans: 120,
|
|
3701
|
-
agents:
|
|
3882
|
+
agents: 176
|
|
3702
3883
|
};
|
|
3703
3884
|
const NAME_WIDTH = {
|
|
3704
3885
|
spans: 320,
|
|
3705
3886
|
agents: 240
|
|
3706
3887
|
};
|
|
3888
|
+
/** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
|
|
3889
|
+
function summaryCells(trace, at, durationMs) {
|
|
3890
|
+
const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
|
|
3891
|
+
const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
|
|
3892
|
+
const modelCalls = at ? at.modelCalls : trace.modelCalls;
|
|
3893
|
+
const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
|
|
3894
|
+
return {
|
|
3895
|
+
cost,
|
|
3896
|
+
tokens,
|
|
3897
|
+
errors,
|
|
3898
|
+
summary: [
|
|
3899
|
+
["Duration", formatSpanDuration(durationMs)],
|
|
3900
|
+
[
|
|
3901
|
+
cost.label,
|
|
3902
|
+
cost.value,
|
|
3903
|
+
cost.title
|
|
3904
|
+
],
|
|
3905
|
+
[
|
|
3906
|
+
"Tokens",
|
|
3907
|
+
formatCount(tokens),
|
|
3908
|
+
`${tokens.toLocaleString()} tokens`
|
|
3909
|
+
],
|
|
3910
|
+
[
|
|
3911
|
+
"LLM calls",
|
|
3912
|
+
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
3913
|
+
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
3914
|
+
],
|
|
3915
|
+
["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
|
|
3916
|
+
["Errors", errors.toLocaleString()]
|
|
3917
|
+
]
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3707
3920
|
/** One legend item per outcome label the agents carry, with its count; nothing when no agent has an outcome. */
|
|
3921
|
+
const TONE_ORDER = [
|
|
3922
|
+
"ok",
|
|
3923
|
+
"warn",
|
|
3924
|
+
"failed",
|
|
3925
|
+
"neutral"
|
|
3926
|
+
];
|
|
3708
3927
|
function OutcomeLegend({ agents }) {
|
|
3709
3928
|
const items = /* @__PURE__ */ new Map();
|
|
3710
3929
|
for (const row of agents) {
|
|
@@ -3722,7 +3941,7 @@ function OutcomeLegend({ agents }) {
|
|
|
3722
3941
|
return /* @__PURE__ */ jsxs("ul", {
|
|
3723
3942
|
className: "fw-legend",
|
|
3724
3943
|
"aria-label": "Outcomes",
|
|
3725
|
-
children: [[...items].map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
|
|
3944
|
+
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: [
|
|
3726
3945
|
/* @__PURE__ */ jsx("span", {
|
|
3727
3946
|
className: "fw-swatch",
|
|
3728
3947
|
"data-tone": "0",
|
|
@@ -3743,10 +3962,9 @@ function OutcomeLegend({ agents }) {
|
|
|
3743
3962
|
] })]
|
|
3744
3963
|
});
|
|
3745
3964
|
}
|
|
3746
|
-
const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
|
|
3747
3965
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
3748
3966
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
3749
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers }) {
|
|
3967
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect }) {
|
|
3750
3968
|
const root = useRef(null);
|
|
3751
3969
|
const search = useRef(null);
|
|
3752
3970
|
const slider = useRef(null);
|
|
@@ -3827,6 +4045,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3827
4045
|
collapsed
|
|
3828
4046
|
]);
|
|
3829
4047
|
const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
|
|
4048
|
+
const callouts = useMemo(() => view === "agents" ? agentCallouts(agents, clockMs) : {}, [
|
|
4049
|
+
view,
|
|
4050
|
+
agents,
|
|
4051
|
+
clockMs
|
|
4052
|
+
]);
|
|
3830
4053
|
const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
|
|
3831
4054
|
const resized = useRef(false);
|
|
3832
4055
|
useLayoutEffect(() => {
|
|
@@ -3857,7 +4080,17 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3857
4080
|
if (event.key === "+" || event.key === "=") zoomBy(.5);
|
|
3858
4081
|
else if (event.key === "-" || event.key === "_") zoomBy(2);
|
|
3859
4082
|
else if (event.key === "0") setZoom(null);
|
|
3860
|
-
else if (event.key === "Escape" && card)
|
|
4083
|
+
else if (event.key === "Escape" && (card || selectedId)) {
|
|
4084
|
+
if (card) closeCard();
|
|
4085
|
+
onDeselect?.();
|
|
4086
|
+
} else if ((event.key === "n" || event.key === "p") && markers?.length) {
|
|
4087
|
+
const ats = markers.map((marker) => marker.at).sort((a, b) => a - b);
|
|
4088
|
+
const next = event.key === "n" ? ats.find((at) => at > clockMs + 1) : [...ats].reverse().find((at) => at < clockMs - 1);
|
|
4089
|
+
if (next !== void 0) {
|
|
4090
|
+
event.preventDefault();
|
|
4091
|
+
scrubTo(next);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
3861
4094
|
};
|
|
3862
4095
|
document.addEventListener("keydown", keydown);
|
|
3863
4096
|
return () => document.removeEventListener("keydown", keydown);
|
|
@@ -3884,7 +4117,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3884
4117
|
const width = current.endMs - current.startMs;
|
|
3885
4118
|
const end = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
|
|
3886
4119
|
if (view !== "spans" || !Number.isFinite(end) || (end - span.startMs) / Math.max(1, width) > .25) return;
|
|
3887
|
-
const pad = Math.max((end - span.startMs) * .15, 500);
|
|
4120
|
+
const pad = Math.max((end - span.startMs) * .15, 500, (width / 8 - (end - span.startMs)) / 2);
|
|
3888
4121
|
const target = clampWindow({
|
|
3889
4122
|
startMs: span.startMs - pad,
|
|
3890
4123
|
endMs: end + pad
|
|
@@ -3975,6 +4208,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3975
4208
|
}
|
|
3976
4209
|
};
|
|
3977
4210
|
const pin = useCallback((target, element) => latest.current.pin(target, element), []);
|
|
4211
|
+
const selection = selectedId ?? (card?.pinned ? card.target : void 0);
|
|
3978
4212
|
const closeCard = () => {
|
|
3979
4213
|
const target = card?.target;
|
|
3980
4214
|
setCard(null);
|
|
@@ -3984,6 +4218,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3984
4218
|
setPlaying(false);
|
|
3985
4219
|
setPosition(Math.min(ms, clock.limitMs()));
|
|
3986
4220
|
}, [clock]);
|
|
4221
|
+
const timeListener = useRef(onTimeChange);
|
|
4222
|
+
timeListener.current = onTimeChange;
|
|
4223
|
+
useEffect(() => {
|
|
4224
|
+
timeListener.current?.(position);
|
|
4225
|
+
}, [position]);
|
|
3987
4226
|
const togglePlay = () => {
|
|
3988
4227
|
if (playing) {
|
|
3989
4228
|
setPosition(clock.positionMs());
|
|
@@ -3994,31 +4233,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3994
4233
|
setPlaying(true);
|
|
3995
4234
|
};
|
|
3996
4235
|
const scrubbing = position !== null || playing;
|
|
3997
|
-
const
|
|
3998
|
-
const costUsd = at ? at.costUsd : trace.costUsd;
|
|
3999
|
-
const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
|
|
4000
|
-
const modelCalls = at ? at.modelCalls : trace.modelCalls;
|
|
4001
|
-
const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
|
|
4002
|
-
const summary = [
|
|
4003
|
-
["Duration", formatSpanDuration(scrubbing ? clockMs : limit)],
|
|
4004
|
-
[
|
|
4005
|
-
trace.costBasis === "list" ? "Cost · list price" : "Cost",
|
|
4006
|
-
costUsd === void 0 ? "Unknown" : formatCost(costUsd),
|
|
4007
|
-
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
|
|
4008
|
-
],
|
|
4009
|
-
[
|
|
4010
|
-
"Tokens",
|
|
4011
|
-
formatCount(tokens),
|
|
4012
|
-
`${tokens.toLocaleString()} tokens`
|
|
4013
|
-
],
|
|
4014
|
-
[
|
|
4015
|
-
"LLM calls",
|
|
4016
|
-
modelCalls === void 0 ? "Unknown" : modelCalls.toLocaleString(),
|
|
4017
|
-
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
4018
|
-
],
|
|
4019
|
-
["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
|
|
4020
|
-
["Errors", errors.toLocaleString()]
|
|
4021
|
-
];
|
|
4236
|
+
const { errors, summary } = summaryCells(trace, scrubbing ? totalsAt(trace, clockMs) : void 0, scrubbing ? clockMs : limit);
|
|
4022
4237
|
const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
|
|
4023
4238
|
const speedup = limit > 0 ? busy / limit : 0;
|
|
4024
4239
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
@@ -4036,11 +4251,37 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4036
4251
|
count: 1,
|
|
4037
4252
|
retry: false
|
|
4038
4253
|
}]), [rows]);
|
|
4254
|
+
const playback = /* @__PURE__ */ jsx(PlaybackBar, {
|
|
4255
|
+
view,
|
|
4256
|
+
playing,
|
|
4257
|
+
limit,
|
|
4258
|
+
clockMs,
|
|
4259
|
+
speed,
|
|
4260
|
+
zoom,
|
|
4261
|
+
marks: overviewMarks,
|
|
4262
|
+
slider,
|
|
4263
|
+
timeOutput,
|
|
4264
|
+
followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
|
|
4265
|
+
onTogglePlay: togglePlay,
|
|
4266
|
+
onRestart: () => {
|
|
4267
|
+
setPosition(0);
|
|
4268
|
+
setPlaying(false);
|
|
4269
|
+
},
|
|
4270
|
+
onSpeed: setSpeed,
|
|
4271
|
+
onScrub: scrubTo,
|
|
4272
|
+
onFollow: () => {
|
|
4273
|
+
setPosition(null);
|
|
4274
|
+
setPlaying(false);
|
|
4275
|
+
},
|
|
4276
|
+
onZoom: zoomBy,
|
|
4277
|
+
onFit: () => setZoom(null)
|
|
4278
|
+
});
|
|
4039
4279
|
return /* @__PURE__ */ jsxs("section", {
|
|
4040
4280
|
ref: root,
|
|
4041
4281
|
"data-flow-waterfall": true,
|
|
4042
4282
|
"aria-label": label,
|
|
4043
4283
|
className: `fw-root fw-root-${view}`,
|
|
4284
|
+
"data-selection": selectedId ? "" : void 0,
|
|
4044
4285
|
style: {
|
|
4045
4286
|
"--fw-name-w": `${nameWidth}px`,
|
|
4046
4287
|
"--fw-pad": `${LANE_PAD}px`,
|
|
@@ -4070,11 +4311,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4070
4311
|
})
|
|
4071
4312
|
]
|
|
4072
4313
|
}),
|
|
4073
|
-
view === "agents" && /* @__PURE__ */
|
|
4314
|
+
view === "agents" && /* @__PURE__ */ jsxs("p", {
|
|
4074
4315
|
className: "fw-headline",
|
|
4075
4316
|
"aria-live": "polite",
|
|
4076
|
-
children:
|
|
4077
|
-
/* @__PURE__ */ jsx("span", {
|
|
4317
|
+
children: [
|
|
4318
|
+
done && /* @__PURE__ */ jsx("span", {
|
|
4078
4319
|
className: "fw-done-mark",
|
|
4079
4320
|
"aria-label": "done",
|
|
4080
4321
|
children: "✓"
|
|
@@ -4082,17 +4323,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4082
4323
|
agents.length,
|
|
4083
4324
|
" ",
|
|
4084
4325
|
agents.length === 1 ? "agent" : "agents",
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
|
|
4088
|
-
trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
|
|
4089
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4090
|
-
headlineSeconds(clockMs),
|
|
4091
|
-
" · ",
|
|
4092
|
-
tokens.toLocaleString(),
|
|
4093
|
-
" tokens",
|
|
4094
|
-
trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
|
|
4095
|
-
] })
|
|
4326
|
+
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
4327
|
+
]
|
|
4096
4328
|
}),
|
|
4097
4329
|
/* @__PURE__ */ jsx(OutcomeLegend, { agents })
|
|
4098
4330
|
]
|
|
@@ -4106,6 +4338,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4106
4338
|
type: "button",
|
|
4107
4339
|
role: "radio",
|
|
4108
4340
|
"aria-checked": view === option,
|
|
4341
|
+
disabled: view === option,
|
|
4109
4342
|
className: "fw-view-button",
|
|
4110
4343
|
onClick: () => {
|
|
4111
4344
|
setView(option);
|
|
@@ -4147,7 +4380,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4147
4380
|
})]
|
|
4148
4381
|
})]
|
|
4149
4382
|
}),
|
|
4150
|
-
|
|
4383
|
+
/* @__PURE__ */ jsx("dl", {
|
|
4151
4384
|
className: "fw-summary",
|
|
4152
4385
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
4153
4386
|
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
@@ -4158,6 +4391,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4158
4391
|
})]
|
|
4159
4392
|
}, name))
|
|
4160
4393
|
}),
|
|
4394
|
+
view === "agents" && playback,
|
|
4161
4395
|
/* @__PURE__ */ jsx(FlowBody, {
|
|
4162
4396
|
rows: visible,
|
|
4163
4397
|
byId,
|
|
@@ -4166,14 +4400,18 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4166
4400
|
maxHeight: rowsHeight,
|
|
4167
4401
|
collapsed,
|
|
4168
4402
|
searching: Boolean(query.trim()),
|
|
4169
|
-
|
|
4403
|
+
pauses: trace.pauses,
|
|
4404
|
+
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4405
|
+
costliestId: callouts.costliestId,
|
|
4170
4406
|
activeId,
|
|
4171
|
-
selectedId:
|
|
4407
|
+
selectedId: selection,
|
|
4408
|
+
focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
|
|
4172
4409
|
clockMs,
|
|
4173
4410
|
window: win,
|
|
4174
4411
|
trackPx,
|
|
4175
4412
|
showCursor,
|
|
4176
4413
|
markers,
|
|
4414
|
+
fillByAgent: agentFill === "agent",
|
|
4177
4415
|
zoomed: zoom !== null,
|
|
4178
4416
|
renderIcon,
|
|
4179
4417
|
emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
|
|
@@ -4212,31 +4450,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4212
4450
|
compact: view === "agents"
|
|
4213
4451
|
})
|
|
4214
4452
|
}),
|
|
4215
|
-
|
|
4216
|
-
view,
|
|
4217
|
-
playing,
|
|
4218
|
-
limit,
|
|
4219
|
-
clockMs,
|
|
4220
|
-
speed,
|
|
4221
|
-
zoom,
|
|
4222
|
-
marks: overviewMarks,
|
|
4223
|
-
slider,
|
|
4224
|
-
timeOutput,
|
|
4225
|
-
followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
|
|
4226
|
-
onTogglePlay: togglePlay,
|
|
4227
|
-
onRestart: () => {
|
|
4228
|
-
setPosition(0);
|
|
4229
|
-
setPlaying(false);
|
|
4230
|
-
},
|
|
4231
|
-
onSpeed: setSpeed,
|
|
4232
|
-
onScrub: scrubTo,
|
|
4233
|
-
onFollow: () => {
|
|
4234
|
-
setPosition(null);
|
|
4235
|
-
setPlaying(false);
|
|
4236
|
-
},
|
|
4237
|
-
onZoom: zoomBy,
|
|
4238
|
-
onFit: () => setZoom(null)
|
|
4239
|
-
})
|
|
4453
|
+
view === "spans" && playback
|
|
4240
4454
|
]
|
|
4241
4455
|
});
|
|
4242
4456
|
}
|