@tangle-network/charts 0.19.0 → 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-D6RDkVrJ.d.ts → flow-trace-Sr8GtLm0.d.ts} +29 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +49 -39
- package/dist/react/flow-waterfall.css +67 -24
- package/dist/react/index.d.ts +13 -3
- package/dist/react/index.js +392 -202
- 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,23 +2703,29 @@ 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
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2670
|
-
cells.
|
|
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))}`]);
|
|
2671
2723
|
if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
|
|
2672
|
-
if (
|
|
2724
|
+
if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
|
|
2673
2725
|
cells.splice(6);
|
|
2674
|
-
const
|
|
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;
|
|
2675
2729
|
return /* @__PURE__ */ jsxs("div", {
|
|
2676
2730
|
id,
|
|
2677
2731
|
role: pinned ? "dialog" : "tooltip",
|
|
@@ -2732,6 +2786,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2732
2786
|
className: "fw-card-cells",
|
|
2733
2787
|
children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
|
|
2734
2788
|
}),
|
|
2789
|
+
step && /* @__PURE__ */ jsx("p", {
|
|
2790
|
+
className: "fw-card-step",
|
|
2791
|
+
children: step
|
|
2792
|
+
}),
|
|
2735
2793
|
sentence && /* @__PURE__ */ jsx("p", {
|
|
2736
2794
|
className: "fw-card-result",
|
|
2737
2795
|
children: sentence
|
|
@@ -2807,9 +2865,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2807
2865
|
children: [
|
|
2808
2866
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
|
|
2809
2867
|
/* @__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) })] }),
|
|
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)),
|
|
2813
2872
|
row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
|
|
2814
2873
|
]
|
|
2815
2874
|
}),
|
|
@@ -2868,7 +2927,7 @@ function endLabel(row, clockMs) {
|
|
|
2868
2927
|
}
|
|
2869
2928
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
2870
2929
|
const FlowRow = memo(function FlowRow(props) {
|
|
2871
|
-
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;
|
|
2872
2931
|
const span = row.span;
|
|
2873
2932
|
const running = spanRunning(span);
|
|
2874
2933
|
const agentView = view === "agents";
|
|
@@ -2884,18 +2943,26 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2884
2943
|
"data-span-id": row.id,
|
|
2885
2944
|
"data-span-kind": kind,
|
|
2886
2945
|
"data-slowest": slowest || void 0,
|
|
2946
|
+
"data-running": running || void 0,
|
|
2887
2947
|
"aria-level": agentView ? void 0 : row.depth + 1,
|
|
2888
2948
|
"aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
|
|
2889
2949
|
"aria-selected": selected,
|
|
2890
2950
|
tabIndex: focusable ? 0 : -1,
|
|
2891
2951
|
"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: {
|
|
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
|
+
},
|
|
2895
2958
|
onFocus: (event) => {
|
|
2896
2959
|
if (event.target === event.currentTarget) props.onActivate(row);
|
|
2897
2960
|
},
|
|
2898
|
-
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
|
+
},
|
|
2899
2966
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2900
2967
|
role: "gridcell",
|
|
2901
2968
|
className: "fw-label",
|
|
@@ -2921,10 +2988,6 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2921
2988
|
className: "fw-toggle-space",
|
|
2922
2989
|
"aria-hidden": true
|
|
2923
2990
|
})),
|
|
2924
|
-
agentView && /* @__PURE__ */ jsx("span", {
|
|
2925
|
-
className: "fw-agent-dot",
|
|
2926
|
-
"aria-hidden": true
|
|
2927
|
-
}),
|
|
2928
2991
|
!agentView && /* @__PURE__ */ jsx("span", {
|
|
2929
2992
|
className: "fw-icon-tile",
|
|
2930
2993
|
"aria-hidden": true,
|
|
@@ -2936,14 +2999,27 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2936
2999
|
children: [
|
|
2937
3000
|
/* @__PURE__ */ jsxs("span", {
|
|
2938
3001
|
className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
|
|
2939
|
-
children: [
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
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
|
+
]
|
|
2947
3023
|
}),
|
|
2948
3024
|
agentView && span.detail && /* @__PURE__ */ jsx("span", {
|
|
2949
3025
|
className: "fw-subtitle",
|
|
@@ -2970,11 +3046,19 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2970
3046
|
role: "gridcell",
|
|
2971
3047
|
className: "fw-lane",
|
|
2972
3048
|
"data-waterfall-track": true,
|
|
2973
|
-
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ?
|
|
3049
|
+
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}`,
|
|
2974
3050
|
children: invalid ? /* @__PURE__ */ jsx("span", {
|
|
2975
3051
|
className: "fw-invalid",
|
|
2976
3052
|
children: "—"
|
|
2977
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}`)),
|
|
2978
3062
|
/* @__PURE__ */ jsxs("span", {
|
|
2979
3063
|
className: `fw-bar fw-k-${isAgent ? "pipeline" : kind}${row.approx ? " fw-approx" : ""}${marks.length ? " fw-has-marks" : ""}${row.hasAgents ? " fw-has-agents" : ""}`,
|
|
2980
3064
|
"data-tone": toneKey(row.tone),
|
|
@@ -2998,21 +3082,28 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2998
3082
|
events: props,
|
|
2999
3083
|
agentView
|
|
3000
3084
|
}, `${mark.id}-${i}`)),
|
|
3001
|
-
|
|
3085
|
+
/* @__PURE__ */ jsxs("span", {
|
|
3002
3086
|
className: "fw-end",
|
|
3003
|
-
style: at(span.startMs, running ?
|
|
3004
|
-
children: [
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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
|
+
]
|
|
3011
3102
|
})
|
|
3012
3103
|
] })
|
|
3013
3104
|
})]
|
|
3014
3105
|
});
|
|
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)));
|
|
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)));
|
|
3016
3107
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3017
3108
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3018
3109
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3029,6 +3120,48 @@ function LinkMark({ link, rowHeight, hot }) {
|
|
|
3029
3120
|
});
|
|
3030
3121
|
}
|
|
3031
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
|
|
3032
3165
|
//#region src/react/flow-waterfall-time.ts
|
|
3033
3166
|
/** Time-axis geometry for the trace inspector: clock-aligned steps, labels and the zoom window. */
|
|
3034
3167
|
const STEPS = [
|
|
@@ -3108,7 +3241,7 @@ function clampWindow(view, limitMs) {
|
|
|
3108
3241
|
//#region src/react/flow-waterfall-body.tsx
|
|
3109
3242
|
const ROW_HEIGHT = {
|
|
3110
3243
|
spans: 36,
|
|
3111
|
-
agents:
|
|
3244
|
+
agents: 40
|
|
3112
3245
|
};
|
|
3113
3246
|
const OVERSCAN = 8;
|
|
3114
3247
|
/** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
|
|
@@ -3181,6 +3314,7 @@ function FlowBody(props) {
|
|
|
3181
3314
|
view
|
|
3182
3315
|
]);
|
|
3183
3316
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3317
|
+
const motion = useRowMotion(rows);
|
|
3184
3318
|
const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
|
|
3185
3319
|
const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
|
|
3186
3320
|
const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
|
|
@@ -3310,7 +3444,7 @@ function FlowBody(props) {
|
|
|
3310
3444
|
handle.addEventListener("pointerup", up);
|
|
3311
3445
|
};
|
|
3312
3446
|
const axis = /* @__PURE__ */ jsxs("div", {
|
|
3313
|
-
className:
|
|
3447
|
+
className: "fw-axis fw-axis-top",
|
|
3314
3448
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3315
3449
|
className: "fw-axis-label",
|
|
3316
3450
|
children: [view === "spans" && /* @__PURE__ */ jsx(Fragment, { children: props.onExpandAll && /* @__PURE__ */ jsxs("span", {
|
|
@@ -3349,14 +3483,27 @@ function FlowBody(props) {
|
|
|
3349
3483
|
ref: props.onTrack,
|
|
3350
3484
|
onPointerDown: scrub,
|
|
3351
3485
|
title: "Drag to scrub",
|
|
3352
|
-
children: [
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
"
|
|
3359
|
-
|
|
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
|
+
]
|
|
3360
3507
|
})]
|
|
3361
3508
|
});
|
|
3362
3509
|
const row = (item, i, isSticky = false) => /* @__PURE__ */ jsx(FlowRow, {
|
|
@@ -3366,12 +3513,15 @@ function FlowBody(props) {
|
|
|
3366
3513
|
collapsed: collapsed.has(item.id),
|
|
3367
3514
|
searching,
|
|
3368
3515
|
slowest: item.id === slowestId,
|
|
3516
|
+
costliest: item.id === props.costliestId,
|
|
3369
3517
|
active: item.id === activeId,
|
|
3370
3518
|
focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
|
|
3371
3519
|
selected: item.id === selectedId,
|
|
3372
3520
|
sticky: isSticky,
|
|
3521
|
+
motion: isSticky ? void 0 : motion.get(item.id),
|
|
3373
3522
|
clockMs,
|
|
3374
3523
|
renderIcon: props.renderIcon,
|
|
3524
|
+
fillByAgent: props.fillByAgent,
|
|
3375
3525
|
onToggle: props.onToggle,
|
|
3376
3526
|
onInspect: props.onInspect,
|
|
3377
3527
|
onLeave: props.onLeave,
|
|
@@ -3381,83 +3531,91 @@ function FlowBody(props) {
|
|
|
3381
3531
|
return /* @__PURE__ */ jsxs("div", {
|
|
3382
3532
|
className: `fw-body fw-view-${view}${zoomed ? " fw-zoomed" : ""}`,
|
|
3383
3533
|
ref: surface,
|
|
3384
|
-
children: [
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
"
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
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",
|
|
3405
3592
|
style: {
|
|
3406
|
-
|
|
3407
|
-
|
|
3593
|
+
"--s": pause.startMs,
|
|
3594
|
+
"--e": pause.endMs
|
|
3408
3595
|
},
|
|
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
|
-
]
|
|
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
|
+
})]
|
|
3461
3619
|
});
|
|
3462
3620
|
}
|
|
3463
3621
|
/** Ancestors of the first visible row stay pinned while their children scroll beneath them. This layer follows
|
|
@@ -3721,13 +3879,51 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
|
|
|
3721
3879
|
const LANE_PAD = 12;
|
|
3722
3880
|
const END_LABEL_GUTTER = {
|
|
3723
3881
|
spans: 120,
|
|
3724
|
-
agents:
|
|
3882
|
+
agents: 176
|
|
3725
3883
|
};
|
|
3726
3884
|
const NAME_WIDTH = {
|
|
3727
3885
|
spans: 320,
|
|
3728
3886
|
agents: 240
|
|
3729
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
|
+
}
|
|
3730
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
|
+
];
|
|
3731
3927
|
function OutcomeLegend({ agents }) {
|
|
3732
3928
|
const items = /* @__PURE__ */ new Map();
|
|
3733
3929
|
for (const row of agents) {
|
|
@@ -3745,7 +3941,7 @@ function OutcomeLegend({ agents }) {
|
|
|
3745
3941
|
return /* @__PURE__ */ jsxs("ul", {
|
|
3746
3942
|
className: "fw-legend",
|
|
3747
3943
|
"aria-label": "Outcomes",
|
|
3748
|
-
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: [
|
|
3749
3945
|
/* @__PURE__ */ jsx("span", {
|
|
3750
3946
|
className: "fw-swatch",
|
|
3751
3947
|
"data-tone": "0",
|
|
@@ -3766,10 +3962,9 @@ function OutcomeLegend({ agents }) {
|
|
|
3766
3962
|
] })]
|
|
3767
3963
|
});
|
|
3768
3964
|
}
|
|
3769
|
-
const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
|
|
3770
3965
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
3771
3966
|
* 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 }) {
|
|
3967
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect }) {
|
|
3773
3968
|
const root = useRef(null);
|
|
3774
3969
|
const search = useRef(null);
|
|
3775
3970
|
const slider = useRef(null);
|
|
@@ -3850,6 +4045,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3850
4045
|
collapsed
|
|
3851
4046
|
]);
|
|
3852
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
|
+
]);
|
|
3853
4053
|
const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
|
|
3854
4054
|
const resized = useRef(false);
|
|
3855
4055
|
useLayoutEffect(() => {
|
|
@@ -3880,7 +4080,17 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3880
4080
|
if (event.key === "+" || event.key === "=") zoomBy(.5);
|
|
3881
4081
|
else if (event.key === "-" || event.key === "_") zoomBy(2);
|
|
3882
4082
|
else if (event.key === "0") setZoom(null);
|
|
3883
|
-
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
|
+
}
|
|
3884
4094
|
};
|
|
3885
4095
|
document.addEventListener("keydown", keydown);
|
|
3886
4096
|
return () => document.removeEventListener("keydown", keydown);
|
|
@@ -3907,7 +4117,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3907
4117
|
const width = current.endMs - current.startMs;
|
|
3908
4118
|
const end = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
|
|
3909
4119
|
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);
|
|
4120
|
+
const pad = Math.max((end - span.startMs) * .15, 500, (width / 8 - (end - span.startMs)) / 2);
|
|
3911
4121
|
const target = clampWindow({
|
|
3912
4122
|
startMs: span.startMs - pad,
|
|
3913
4123
|
endMs: end + pad
|
|
@@ -3998,6 +4208,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
3998
4208
|
}
|
|
3999
4209
|
};
|
|
4000
4210
|
const pin = useCallback((target, element) => latest.current.pin(target, element), []);
|
|
4211
|
+
const selection = selectedId ?? (card?.pinned ? card.target : void 0);
|
|
4001
4212
|
const closeCard = () => {
|
|
4002
4213
|
const target = card?.target;
|
|
4003
4214
|
setCard(null);
|
|
@@ -4007,6 +4218,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4007
4218
|
setPlaying(false);
|
|
4008
4219
|
setPosition(Math.min(ms, clock.limitMs()));
|
|
4009
4220
|
}, [clock]);
|
|
4221
|
+
const timeListener = useRef(onTimeChange);
|
|
4222
|
+
timeListener.current = onTimeChange;
|
|
4223
|
+
useEffect(() => {
|
|
4224
|
+
timeListener.current?.(position);
|
|
4225
|
+
}, [position]);
|
|
4010
4226
|
const togglePlay = () => {
|
|
4011
4227
|
if (playing) {
|
|
4012
4228
|
setPosition(clock.positionMs());
|
|
@@ -4017,31 +4233,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4017
4233
|
setPlaying(true);
|
|
4018
4234
|
};
|
|
4019
4235
|
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
|
-
];
|
|
4236
|
+
const { errors, summary } = summaryCells(trace, scrubbing ? totalsAt(trace, clockMs) : void 0, scrubbing ? clockMs : limit);
|
|
4045
4237
|
const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
|
|
4046
4238
|
const speedup = limit > 0 ? busy / limit : 0;
|
|
4047
4239
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
@@ -4059,11 +4251,37 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4059
4251
|
count: 1,
|
|
4060
4252
|
retry: false
|
|
4061
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
|
+
});
|
|
4062
4279
|
return /* @__PURE__ */ jsxs("section", {
|
|
4063
4280
|
ref: root,
|
|
4064
4281
|
"data-flow-waterfall": true,
|
|
4065
4282
|
"aria-label": label,
|
|
4066
4283
|
className: `fw-root fw-root-${view}`,
|
|
4284
|
+
"data-selection": selectedId ? "" : void 0,
|
|
4067
4285
|
style: {
|
|
4068
4286
|
"--fw-name-w": `${nameWidth}px`,
|
|
4069
4287
|
"--fw-pad": `${LANE_PAD}px`,
|
|
@@ -4093,11 +4311,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4093
4311
|
})
|
|
4094
4312
|
]
|
|
4095
4313
|
}),
|
|
4096
|
-
view === "agents" && /* @__PURE__ */
|
|
4314
|
+
view === "agents" && /* @__PURE__ */ jsxs("p", {
|
|
4097
4315
|
className: "fw-headline",
|
|
4098
4316
|
"aria-live": "polite",
|
|
4099
|
-
children:
|
|
4100
|
-
/* @__PURE__ */ jsx("span", {
|
|
4317
|
+
children: [
|
|
4318
|
+
done && /* @__PURE__ */ jsx("span", {
|
|
4101
4319
|
className: "fw-done-mark",
|
|
4102
4320
|
"aria-label": "done",
|
|
4103
4321
|
children: "✓"
|
|
@@ -4105,17 +4323,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4105
4323
|
agents.length,
|
|
4106
4324
|
" ",
|
|
4107
4325
|
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
|
-
] })
|
|
4326
|
+
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
4327
|
+
]
|
|
4119
4328
|
}),
|
|
4120
4329
|
/* @__PURE__ */ jsx(OutcomeLegend, { agents })
|
|
4121
4330
|
]
|
|
@@ -4129,6 +4338,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4129
4338
|
type: "button",
|
|
4130
4339
|
role: "radio",
|
|
4131
4340
|
"aria-checked": view === option,
|
|
4341
|
+
disabled: view === option,
|
|
4132
4342
|
className: "fw-view-button",
|
|
4133
4343
|
onClick: () => {
|
|
4134
4344
|
setView(option);
|
|
@@ -4170,7 +4380,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4170
4380
|
})]
|
|
4171
4381
|
})]
|
|
4172
4382
|
}),
|
|
4173
|
-
|
|
4383
|
+
/* @__PURE__ */ jsx("dl", {
|
|
4174
4384
|
className: "fw-summary",
|
|
4175
4385
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
4176
4386
|
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
@@ -4181,6 +4391,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4181
4391
|
})]
|
|
4182
4392
|
}, name))
|
|
4183
4393
|
}),
|
|
4394
|
+
view === "agents" && playback,
|
|
4184
4395
|
/* @__PURE__ */ jsx(FlowBody, {
|
|
4185
4396
|
rows: visible,
|
|
4186
4397
|
byId,
|
|
@@ -4189,15 +4400,18 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4189
4400
|
maxHeight: rowsHeight,
|
|
4190
4401
|
collapsed,
|
|
4191
4402
|
searching: Boolean(query.trim()),
|
|
4192
|
-
|
|
4403
|
+
pauses: trace.pauses,
|
|
4404
|
+
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4405
|
+
costliestId: callouts.costliestId,
|
|
4193
4406
|
activeId,
|
|
4194
|
-
selectedId:
|
|
4407
|
+
selectedId: selection,
|
|
4195
4408
|
focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
|
|
4196
4409
|
clockMs,
|
|
4197
4410
|
window: win,
|
|
4198
4411
|
trackPx,
|
|
4199
4412
|
showCursor,
|
|
4200
4413
|
markers,
|
|
4414
|
+
fillByAgent: agentFill === "agent",
|
|
4201
4415
|
zoomed: zoom !== null,
|
|
4202
4416
|
renderIcon,
|
|
4203
4417
|
emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
|
|
@@ -4236,31 +4450,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4236
4450
|
compact: view === "agents"
|
|
4237
4451
|
})
|
|
4238
4452
|
}),
|
|
4239
|
-
|
|
4240
|
-
view,
|
|
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
|
-
})
|
|
4453
|
+
view === "spans" && playback
|
|
4264
4454
|
]
|
|
4265
4455
|
});
|
|
4266
4456
|
}
|