@tangle-network/charts 0.21.14 → 0.21.16
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 +2 -2
- package/dist/{flow-trace-DBJY1YAA.d.ts → flow-trace-CSy-mQ31.d.ts} +4 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/react/flow-waterfall.css +126 -103
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +293 -233
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -2422,6 +2422,7 @@ function DotMap({ positions, radii, colors, palette, extent, rects, labels, dimm
|
|
|
2422
2422
|
}
|
|
2423
2423
|
//#endregion
|
|
2424
2424
|
//#region src/react/flow-waterfall-model.ts
|
|
2425
|
+
const toolOf = (name) => name?.split(" · ")[0];
|
|
2425
2426
|
/** The CSS hue slot: 0 is the root agent's neutral, then five agent hues cycle. */
|
|
2426
2427
|
const toneKey = (tone) => tone <= 0 ? 0 : (tone - 1) % 5 + 1;
|
|
2427
2428
|
const spanFailed = (span) => span.status === "failed";
|
|
@@ -2474,6 +2475,7 @@ function mergeSegments(marks, gapMs, cap = 240) {
|
|
|
2474
2475
|
covered.set(last, share);
|
|
2475
2476
|
last.endMs = Math.max(last.endMs, mark.endMs);
|
|
2476
2477
|
last.count += mark.count;
|
|
2478
|
+
if (last.tool !== mark.tool) last.tool = void 0;
|
|
2477
2479
|
last.kind = share.model >= share.tool ? "model" : "tool";
|
|
2478
2480
|
} else out.push({ ...mark });
|
|
2479
2481
|
}
|
|
@@ -2544,6 +2546,7 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
2544
2546
|
row.hasChildren = kids.length > 0;
|
|
2545
2547
|
ordered_.push(row);
|
|
2546
2548
|
const marks = [];
|
|
2549
|
+
const own = row.span.promptTokens !== void 0 || row.span.completionTokens !== void 0 || row.span.cachedTokens !== void 0;
|
|
2547
2550
|
let cost = row.span.costUsd ?? 0;
|
|
2548
2551
|
let costKnown = row.span.kind !== "model" || row.span.costUsd !== void 0;
|
|
2549
2552
|
for (const child of kids) {
|
|
@@ -2554,18 +2557,15 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
2554
2557
|
if (childCost === void 0) {
|
|
2555
2558
|
if (child.kind === "model" || child.hasChildren) costKnown = false;
|
|
2556
2559
|
} else cost += childCost;
|
|
2557
|
-
if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span))
|
|
2558
|
-
row.own.push(segmentOf(child));
|
|
2559
|
-
const counts = row.counts ??= {
|
|
2560
|
-
model: 0,
|
|
2561
|
-
tool: 0,
|
|
2562
|
-
failed: 0
|
|
2563
|
-
};
|
|
2564
|
-
if (!child.ok) counts.failed++;
|
|
2565
|
-
else if (child.kind === "model") counts.model++;
|
|
2566
|
-
else counts.tool++;
|
|
2567
|
-
}
|
|
2560
|
+
if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span)) row.own.push(segmentOf(child));
|
|
2568
2561
|
}
|
|
2562
|
+
Object.assign(row, stepStats(kids));
|
|
2563
|
+
const tokens = own ? {
|
|
2564
|
+
prompt: row.span.promptTokens,
|
|
2565
|
+
completion: row.span.completionTokens,
|
|
2566
|
+
cached: row.span.cachedTokens
|
|
2567
|
+
} : sumTokens(kids);
|
|
2568
|
+
if (tokens) row.tokens = tokens;
|
|
2569
2569
|
if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
|
|
2570
2570
|
if (row.hasChildren) {
|
|
2571
2571
|
row.segments = mergeSegments(marks.map((mark) => ({ ...mark })), gap);
|
|
@@ -2578,6 +2578,74 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
2578
2578
|
for (const row of rows) if (!row.parentId) visit(row, 0, -1);
|
|
2579
2579
|
return ordered_;
|
|
2580
2580
|
}
|
|
2581
|
+
/** What a row's direct steps add up to; nothing when it has none. */
|
|
2582
|
+
function stepStats(steps) {
|
|
2583
|
+
let counts;
|
|
2584
|
+
const modelMs = [];
|
|
2585
|
+
for (const step of steps) {
|
|
2586
|
+
if (step.kind === "pipeline" || step.kind === "waiting" || !ordered(step.span)) continue;
|
|
2587
|
+
counts ??= {
|
|
2588
|
+
model: 0,
|
|
2589
|
+
tool: 0,
|
|
2590
|
+
failed: 0,
|
|
2591
|
+
retried: 0,
|
|
2592
|
+
tools: {}
|
|
2593
|
+
};
|
|
2594
|
+
if (!step.ok) counts.failed++;
|
|
2595
|
+
if (step.retry) counts.retried++;
|
|
2596
|
+
if (step.kind === "model") {
|
|
2597
|
+
counts.model++;
|
|
2598
|
+
if (!spanRunning(step.span)) modelMs.push(step.span.endMs - step.span.startMs);
|
|
2599
|
+
} else {
|
|
2600
|
+
counts.tool++;
|
|
2601
|
+
const tool = toolOf(step.name) || step.kind;
|
|
2602
|
+
counts.tools[tool] = (counts.tools[tool] ?? 0) + 1;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
return counts ? {
|
|
2606
|
+
counts,
|
|
2607
|
+
modelMs: modelMs.sort((a, b) => a - b)
|
|
2608
|
+
} : {};
|
|
2609
|
+
}
|
|
2610
|
+
/** Several rows' steps as one: a family's members or a folded run. */
|
|
2611
|
+
function mergeStats(rows) {
|
|
2612
|
+
let counts;
|
|
2613
|
+
for (const row of rows) {
|
|
2614
|
+
if (!row.counts) continue;
|
|
2615
|
+
counts ??= {
|
|
2616
|
+
model: 0,
|
|
2617
|
+
tool: 0,
|
|
2618
|
+
failed: 0,
|
|
2619
|
+
retried: 0,
|
|
2620
|
+
tools: {}
|
|
2621
|
+
};
|
|
2622
|
+
counts.model += row.counts.model;
|
|
2623
|
+
counts.tool += row.counts.tool;
|
|
2624
|
+
counts.failed += row.counts.failed;
|
|
2625
|
+
counts.retried += row.counts.retried;
|
|
2626
|
+
for (const [tool, n] of Object.entries(row.counts.tools)) counts.tools[tool] = (counts.tools[tool] ?? 0) + n;
|
|
2627
|
+
}
|
|
2628
|
+
const tokens = sumTokens(rows);
|
|
2629
|
+
return {
|
|
2630
|
+
...counts ? {
|
|
2631
|
+
counts,
|
|
2632
|
+
modelMs: rows.flatMap((row) => row.modelMs ?? []).sort((a, b) => a - b)
|
|
2633
|
+
} : {},
|
|
2634
|
+
...tokens ? { tokens } : {}
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
function sumTokens(rows) {
|
|
2638
|
+
let out;
|
|
2639
|
+
for (const row of rows) for (const key of [
|
|
2640
|
+
"prompt",
|
|
2641
|
+
"completion",
|
|
2642
|
+
"cached"
|
|
2643
|
+
]) {
|
|
2644
|
+
const n = row.tokens?.[key];
|
|
2645
|
+
if (n !== void 0) (out ??= {})[key] = (out[key] ?? 0) + n;
|
|
2646
|
+
}
|
|
2647
|
+
return out;
|
|
2648
|
+
}
|
|
2581
2649
|
function segmentOf(row) {
|
|
2582
2650
|
return {
|
|
2583
2651
|
startMs: row.span.startMs,
|
|
@@ -2587,7 +2655,8 @@ function segmentOf(row) {
|
|
|
2587
2655
|
id: row.id,
|
|
2588
2656
|
count: 1,
|
|
2589
2657
|
retry: row.retry,
|
|
2590
|
-
name: row.name
|
|
2658
|
+
name: row.name,
|
|
2659
|
+
tool: toolOf(row.name)
|
|
2591
2660
|
};
|
|
2592
2661
|
}
|
|
2593
2662
|
/** The rows a search term matches by themselves, before ancestor context is added. */
|
|
@@ -2786,7 +2855,7 @@ function agentFamilies(agents, open) {
|
|
|
2786
2855
|
const endMs = Math.max(...members.map((row) => row.span.endMs));
|
|
2787
2856
|
const running = members.some((row) => spanRunning(row.span));
|
|
2788
2857
|
const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
2789
|
-
const failed = members.filter((row) => !row.ok).length;
|
|
2858
|
+
const failed = members.filter((row) => row.span.outcome ? row.span.outcome.tone === "failed" : !row.ok).length;
|
|
2790
2859
|
const outcome = members.every((row) => row.span.outcome && row.span.outcome.tone === members[0].span.outcome?.tone) ? members[0].span.outcome : void 0;
|
|
2791
2860
|
out.push({
|
|
2792
2861
|
costFloor: known.length < members.length || members.some((row) => row.costFloor),
|
|
@@ -2810,6 +2879,7 @@ function agentFamilies(agents, open) {
|
|
|
2810
2879
|
alt: i % 2 === 1
|
|
2811
2880
|
}))).sort((a, b) => a.startMs - b.startMs),
|
|
2812
2881
|
...known.length ? { rollupCostUsd: known.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2882
|
+
...mergeStats(members),
|
|
2813
2883
|
span: {
|
|
2814
2884
|
id,
|
|
2815
2885
|
kind: "pipeline",
|
|
@@ -2840,35 +2910,24 @@ function agentFamilies(agents, open) {
|
|
|
2840
2910
|
}
|
|
2841
2911
|
return out;
|
|
2842
2912
|
}
|
|
2843
|
-
/** The second line of an agent row: how it ended and how much it did (
|
|
2913
|
+
/** The second line of an agent row in parts: how it ended and how much it did (failed, 3 LLM calls, 412 tool calls; 6 agents, 4 failed).
|
|
2914
|
+
* The row shows the parts in order and drops the last ones whole when the line is full, never half a word. */
|
|
2844
2915
|
function agentSummary(row) {
|
|
2845
2916
|
if (row.id.startsWith("family:")) {
|
|
2846
2917
|
const failed = Number(row.span.meta?.failedAgents ?? 0);
|
|
2847
|
-
return `${row.descendants} agents
|
|
2918
|
+
return [`${row.descendants} agents`, ...failed ? [`${failed} failed`] : []];
|
|
2848
2919
|
}
|
|
2849
2920
|
if (row.id.startsWith("run:")) {
|
|
2850
2921
|
const failed = row.own.filter((mark) => mark.kind === "failed").length;
|
|
2851
|
-
return failed ? `${failed} failed` : "all done";
|
|
2922
|
+
return [failed ? `${failed} failed` : "all done"];
|
|
2852
2923
|
}
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2855
|
-
tool: 0,
|
|
2856
|
-
failed: 0
|
|
2857
|
-
};
|
|
2858
|
-
const turns = counts.model + counts.failed;
|
|
2859
|
-
const tools = counts.tool;
|
|
2860
|
-
const state = row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : row.ok ? "done" : "failed");
|
|
2861
|
-
const unit = row.kind === "pipeline" ? ["turn", "turns"] : ["model call", "model calls"];
|
|
2924
|
+
const calls = row.counts?.model ?? 0;
|
|
2925
|
+
const tools = row.counts?.tool ?? 0;
|
|
2862
2926
|
return [
|
|
2863
|
-
|
|
2864
|
-
|
|
2927
|
+
row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done"),
|
|
2928
|
+
calls ? `${calls} LLM ${calls === 1 ? "call" : "calls"}` : "",
|
|
2865
2929
|
tools ? `${tools} tool ${tools === 1 ? "call" : "calls"}` : ""
|
|
2866
|
-
].filter(Boolean)
|
|
2867
|
-
}
|
|
2868
|
-
/** The summary split where it may be cut: the tool count goes first when the line does not fit, never half a word. */
|
|
2869
|
-
function agentSummaryParts(row) {
|
|
2870
|
-
const tools = / · (\d+ tool calls?)$/.exec(agentSummary(row));
|
|
2871
|
-
return tools ? [agentSummary(row).slice(0, tools.index), tools[1]] : [agentSummary(row)];
|
|
2930
|
+
].filter(Boolean);
|
|
2872
2931
|
}
|
|
2873
2932
|
/**
|
|
2874
2933
|
* The agent view's rows: families folded until opened, and an opened agent's own steps (its turns and tool calls, not its
|
|
@@ -2920,6 +2979,8 @@ function stepRuns(steps) {
|
|
|
2920
2979
|
hasChildren: false,
|
|
2921
2980
|
descendants: 0,
|
|
2922
2981
|
segments: [],
|
|
2982
|
+
...stepStats(run),
|
|
2983
|
+
tokens: sumTokens(run),
|
|
2923
2984
|
own: run.map((step) => ({
|
|
2924
2985
|
startMs: step.span.startMs,
|
|
2925
2986
|
endMs: step.span.endMs,
|
|
@@ -2928,7 +2989,8 @@ function stepRuns(steps) {
|
|
|
2928
2989
|
id: step.id,
|
|
2929
2990
|
count: 1,
|
|
2930
2991
|
retry: step.retry,
|
|
2931
|
-
name: step.name
|
|
2992
|
+
name: step.name,
|
|
2993
|
+
tool: toolOf(step.name)
|
|
2932
2994
|
})),
|
|
2933
2995
|
...costs.length ? { rollupCostUsd: costs.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2934
2996
|
span: {
|
|
@@ -3110,58 +3172,110 @@ const KIND_LABEL = {
|
|
|
3110
3172
|
waiting: "Waiting",
|
|
3111
3173
|
handoff: "Handoff"
|
|
3112
3174
|
};
|
|
3113
|
-
const
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3175
|
+
const count = (n, unit) => /* @__PURE__ */ jsxs("span", {
|
|
3176
|
+
title: `${n.toLocaleString()} ${unit}`,
|
|
3177
|
+
children: [
|
|
3178
|
+
formatCount(n),
|
|
3179
|
+
" ",
|
|
3180
|
+
unit
|
|
3181
|
+
]
|
|
3117
3182
|
});
|
|
3118
|
-
const
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
/** The
|
|
3183
|
+
const plural = (n, one, many = `${one}s`) => `${n.toLocaleString()} ${n === 1 ? one : many}`;
|
|
3184
|
+
/** Nearest-rank percentile of ascending values. */
|
|
3185
|
+
const percentile = (sorted, p) => sorted[Math.min(sorted.length - 1, Math.ceil(p * sorted.length) - 1)];
|
|
3186
|
+
const TOOLS_SHOWN = 4;
|
|
3187
|
+
/** The card's table: what the row recorded, every value with its unit. A value nobody recorded is left out, never printed as zero. */
|
|
3188
|
+
function metrics(row, clockMs, full) {
|
|
3189
|
+
const span = row.span;
|
|
3190
|
+
const out = [];
|
|
3191
|
+
if (span.detail) out.push([span.kind === "tool" ? "Provider" : "Model", span.detail]);
|
|
3192
|
+
const stop = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
|
|
3193
|
+
out.push(["Duration", Number.isFinite(stop - span.startMs) && stop >= span.startMs ? formatSpanDuration(stop - span.startMs) : "—"]);
|
|
3194
|
+
if (full) out.push(["Started", `+${formatSpanDuration(Math.max(0, span.startMs))}`]);
|
|
3195
|
+
const tokens = row.tokens;
|
|
3196
|
+
if (tokens) {
|
|
3197
|
+
const parts = [
|
|
3198
|
+
["prompt", "in"],
|
|
3199
|
+
["completion", "out"],
|
|
3200
|
+
["cached", "cached"]
|
|
3201
|
+
].flatMap(([key, unit]) => tokens[key] === void 0 ? [] : [count(tokens[key], unit)]);
|
|
3202
|
+
out.push(["Tokens", /* @__PURE__ */ jsx("span", {
|
|
3203
|
+
className: "fw-card-parts",
|
|
3204
|
+
children: parts.map((part, i) => /* @__PURE__ */ jsx("span", { children: part }, i))
|
|
3205
|
+
})]);
|
|
3206
|
+
}
|
|
3207
|
+
const latency = row.modelMs;
|
|
3208
|
+
if (latency?.length) out.push(["LLM latency", /* @__PURE__ */ jsxs("span", {
|
|
3209
|
+
className: "fw-card-parts",
|
|
3210
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [formatSpanDuration(percentile(latency, .5)), " median"] }), latency.length >= 10 && /* @__PURE__ */ jsxs("span", { children: [formatSpanDuration(percentile(latency, .95)), " p95"] })]
|
|
3211
|
+
})]);
|
|
3212
|
+
const cost = span.costUsd ?? row.rollupCostUsd;
|
|
3213
|
+
if (cost !== void 0) out.push(["Cost", rowCostText(row, cost)]);
|
|
3214
|
+
const counts = row.counts;
|
|
3215
|
+
if (counts?.model) out.push(["LLM calls", counts.model.toLocaleString()]);
|
|
3216
|
+
if (counts?.tool) {
|
|
3217
|
+
const tools = Object.entries(counts.tools).sort(([a, x], [b, y]) => y - x || a.localeCompare(b));
|
|
3218
|
+
const rest = tools.length - TOOLS_SHOWN;
|
|
3219
|
+
out.push(["Tool calls", /* @__PURE__ */ jsxs(Fragment, { children: [counts.tool.toLocaleString(), /* @__PURE__ */ jsxs("span", {
|
|
3220
|
+
className: "fw-card-tools",
|
|
3221
|
+
title: tools.map(([tool, n]) => `${tool} ${n}`).join(", "),
|
|
3222
|
+
children: [tools.slice(0, rest > 1 ? TOOLS_SHOWN : tools.length).map(([tool, n]) => /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", { children: tool }), /* @__PURE__ */ jsx("span", { children: n.toLocaleString() })] }, tool)), rest > 1 && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsxs("span", { children: [rest, " more tools"] }), /* @__PURE__ */ jsx("span", { children: tools.slice(TOOLS_SHOWN).reduce((sum, [, n]) => sum + n, 0).toLocaleString() })] })]
|
|
3223
|
+
})] })]);
|
|
3224
|
+
}
|
|
3225
|
+
if (counts?.failed) out.push(["Failures", plural(counts.failed, "failed step")]);
|
|
3226
|
+
const restarts = span.marks?.filter((mark) => mark.kind === "restart").length ?? 0;
|
|
3227
|
+
if (counts?.retried || restarts) out.push(["Retries", [counts?.retried ? plural(counts.retried, "step retried", "steps retried") : "", restarts ? plural(restarts, "restart") : ""].filter(Boolean).join(" · ")]);
|
|
3228
|
+
if (full && row.hasChildren) out.push(["Steps", row.descendants.toLocaleString()]);
|
|
3229
|
+
for (const fact of span.facts ?? []) if (!out.some(([label]) => label.toLowerCase() === fact.label.toLowerCase())) out.push([fact.label, fact.value]);
|
|
3230
|
+
return out;
|
|
3231
|
+
}
|
|
3232
|
+
function Table({ rows }) {
|
|
3233
|
+
return /* @__PURE__ */ jsx("dl", {
|
|
3234
|
+
className: "fw-card-table",
|
|
3235
|
+
children: rows.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
/** The span card: what ran, for how long, on what tokens and at what recorded cost, then its input and output. */
|
|
3123
3239
|
function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
|
|
3124
3240
|
const span = row.span;
|
|
3125
3241
|
const status = spanRunning(span) ? "Running" : row.ok ? "Done" : "Failed";
|
|
3126
|
-
const tokens = span.kind === "model" || span.kind === "pipeline" || span.promptTokens !== void 0;
|
|
3127
3242
|
const owner = agent && agent.id !== row.id ? agent : void 0;
|
|
3128
|
-
const
|
|
3129
|
-
|
|
3130
|
-
|
|
3243
|
+
const frame = {
|
|
3244
|
+
id,
|
|
3245
|
+
role: pinned ? "dialog" : "tooltip",
|
|
3246
|
+
"aria-label": pinned ? `${span.name} details` : void 0,
|
|
3247
|
+
style: {
|
|
3248
|
+
left,
|
|
3249
|
+
top
|
|
3250
|
+
},
|
|
3251
|
+
onKeyDown: (event) => {
|
|
3252
|
+
if (event.key === "Escape") {
|
|
3253
|
+
event.stopPropagation();
|
|
3254
|
+
onClose();
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3131
3257
|
};
|
|
3132
|
-
const
|
|
3258
|
+
const close = pinned && /* @__PURE__ */ jsx("button", {
|
|
3259
|
+
type: "button",
|
|
3260
|
+
className: "fw-icon-button fw-card-close",
|
|
3261
|
+
"aria-label": "Close details",
|
|
3262
|
+
onClick: onClose,
|
|
3263
|
+
autoFocus: true,
|
|
3264
|
+
children: /* @__PURE__ */ jsx(CloseGlyph, {})
|
|
3265
|
+
});
|
|
3133
3266
|
if (compact) {
|
|
3134
3267
|
const who = owner ?? row;
|
|
3135
|
-
const
|
|
3136
|
-
const outcome = subject.outcome;
|
|
3137
|
-
const tokenCount = subject.promptTokens !== void 0 || subject.completionTokens !== void 0 ? (subject.promptTokens ?? 0) + (subject.completionTokens ?? 0) : void 0;
|
|
3138
|
-
const cost = subject.costUsd ?? who.rollupCostUsd;
|
|
3139
|
-
const cells = [[subject.kind === "model" ? "Thinking" : KIND_LABEL[subject.kind], lasted(subject)]];
|
|
3140
|
-
for (const fact of (subject.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
|
|
3141
|
-
cells.push(["Started", `+${formatSpanDuration(Math.max(0, subject.startMs))}`]);
|
|
3142
|
-
if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
|
|
3143
|
-
if (cost !== void 0) cells.push(["Cost", rowCostText(who, cost)]);
|
|
3144
|
-
cells.splice(6);
|
|
3268
|
+
const outcome = who.span.outcome;
|
|
3145
3269
|
const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
|
|
3146
|
-
const
|
|
3270
|
+
const named = span.name && span.name.toLowerCase() !== kind.toLowerCase() && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : "";
|
|
3271
|
+
const stop = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
|
|
3272
|
+
const step = owner && span.endMs > span.startMs ? `${kind}${named} · ${formatSpanDuration(stop - span.startMs)}${row.ok ? "" : " · failed"}` : void 0;
|
|
3147
3273
|
const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
|
|
3148
3274
|
return /* @__PURE__ */ jsxs("div", {
|
|
3149
|
-
|
|
3150
|
-
role: pinned ? "dialog" : "tooltip",
|
|
3151
|
-
"aria-label": pinned ? `${span.name} details` : void 0,
|
|
3275
|
+
...frame,
|
|
3152
3276
|
className: "fw-card fw-card-compact",
|
|
3153
3277
|
"data-tone": toneKey(who.tone),
|
|
3154
3278
|
"data-outcome": outcome?.tone,
|
|
3155
|
-
style: {
|
|
3156
|
-
left,
|
|
3157
|
-
top
|
|
3158
|
-
},
|
|
3159
|
-
onKeyDown: (event) => {
|
|
3160
|
-
if (event.key === "Escape") {
|
|
3161
|
-
event.stopPropagation();
|
|
3162
|
-
onClose();
|
|
3163
|
-
}
|
|
3164
|
-
},
|
|
3165
3279
|
children: [
|
|
3166
3280
|
/* @__PURE__ */ jsxs("div", {
|
|
3167
3281
|
className: "fw-card-agent",
|
|
@@ -3183,28 +3297,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
3183
3297
|
className: `fw-pill fw-pill-${status.toLowerCase()}`,
|
|
3184
3298
|
children: status
|
|
3185
3299
|
}),
|
|
3186
|
-
|
|
3187
|
-
type: "button",
|
|
3188
|
-
className: "fw-icon-button",
|
|
3189
|
-
style: {
|
|
3190
|
-
width: 20,
|
|
3191
|
-
height: 20
|
|
3192
|
-
},
|
|
3193
|
-
"aria-label": "Close details",
|
|
3194
|
-
onClick: onClose,
|
|
3195
|
-
autoFocus: true,
|
|
3196
|
-
children: /* @__PURE__ */ jsx(CloseGlyph, {})
|
|
3197
|
-
})
|
|
3300
|
+
close
|
|
3198
3301
|
]
|
|
3199
3302
|
}),
|
|
3200
|
-
|
|
3201
|
-
className: "fw-card-detail",
|
|
3202
|
-
children: who.span.detail
|
|
3203
|
-
}),
|
|
3204
|
-
/* @__PURE__ */ jsx("dl", {
|
|
3205
|
-
className: "fw-card-cells",
|
|
3206
|
-
children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
|
|
3207
|
-
}),
|
|
3303
|
+
/* @__PURE__ */ jsx(Table, { rows: metrics(who, clockMs, false) }),
|
|
3208
3304
|
step && /* @__PURE__ */ jsx("p", {
|
|
3209
3305
|
className: "fw-card-step",
|
|
3210
3306
|
children: step
|
|
@@ -3217,35 +3313,16 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
3217
3313
|
});
|
|
3218
3314
|
}
|
|
3219
3315
|
return /* @__PURE__ */ jsxs("div", {
|
|
3220
|
-
|
|
3221
|
-
role: pinned ? "dialog" : "tooltip",
|
|
3222
|
-
"aria-label": pinned ? `${span.name} details` : void 0,
|
|
3316
|
+
...frame,
|
|
3223
3317
|
className: "fw-card",
|
|
3224
3318
|
"data-tone": toneKey(row.tone),
|
|
3225
|
-
style: {
|
|
3226
|
-
left,
|
|
3227
|
-
top
|
|
3228
|
-
},
|
|
3229
|
-
onKeyDown: (event) => {
|
|
3230
|
-
if (event.key === "Escape") {
|
|
3231
|
-
event.stopPropagation();
|
|
3232
|
-
onClose();
|
|
3233
|
-
}
|
|
3234
|
-
},
|
|
3235
3319
|
children: [
|
|
3236
3320
|
owner && /* @__PURE__ */ jsxs("div", {
|
|
3237
3321
|
className: "fw-card-agent",
|
|
3238
|
-
children: [
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
}),
|
|
3243
|
-
owner.name,
|
|
3244
|
-
owner.span.detail && /* @__PURE__ */ jsxs("span", {
|
|
3245
|
-
className: "fw-muted",
|
|
3246
|
-
children: [" ", owner.span.detail]
|
|
3247
|
-
})
|
|
3248
|
-
]
|
|
3322
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3323
|
+
className: "fw-agent-dot",
|
|
3324
|
+
"aria-hidden": true
|
|
3325
|
+
}), owner.name]
|
|
3249
3326
|
}),
|
|
3250
3327
|
/* @__PURE__ */ jsxs("div", {
|
|
3251
3328
|
className: "fw-card-head",
|
|
@@ -3258,42 +3335,18 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
3258
3335
|
name: span.name
|
|
3259
3336
|
})
|
|
3260
3337
|
}),
|
|
3261
|
-
/* @__PURE__ */
|
|
3262
|
-
className: "fw-card-
|
|
3263
|
-
children:
|
|
3264
|
-
className: "fw-card-name",
|
|
3265
|
-
children: keepWords(span.name)
|
|
3266
|
-
}), span.detail && /* @__PURE__ */ jsx("div", {
|
|
3267
|
-
className: "fw-muted",
|
|
3268
|
-
children: span.detail
|
|
3269
|
-
})]
|
|
3338
|
+
/* @__PURE__ */ jsx("div", {
|
|
3339
|
+
className: "fw-card-name",
|
|
3340
|
+
children: keepWords(span.name)
|
|
3270
3341
|
}),
|
|
3271
3342
|
/* @__PURE__ */ jsx("span", {
|
|
3272
3343
|
className: `fw-pill fw-pill-${status.toLowerCase()}`,
|
|
3273
3344
|
children: status
|
|
3274
3345
|
}),
|
|
3275
|
-
|
|
3276
|
-
type: "button",
|
|
3277
|
-
className: "fw-icon-button",
|
|
3278
|
-
"aria-label": "Close details",
|
|
3279
|
-
onClick: onClose,
|
|
3280
|
-
autoFocus: true,
|
|
3281
|
-
children: /* @__PURE__ */ jsx(CloseGlyph, {})
|
|
3282
|
-
})
|
|
3283
|
-
]
|
|
3284
|
-
}),
|
|
3285
|
-
/* @__PURE__ */ jsxs("dl", {
|
|
3286
|
-
className: "fw-card-stats",
|
|
3287
|
-
children: [
|
|
3288
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
|
|
3289
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
|
|
3290
|
-
span.costUsd !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: rowCostText(row, span.costUsd) })] }),
|
|
3291
|
-
tokens && span.promptTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
|
|
3292
|
-
tokens && span.completionTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
|
|
3293
|
-
(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)),
|
|
3294
|
-
row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
|
|
3346
|
+
close
|
|
3295
3347
|
]
|
|
3296
3348
|
}),
|
|
3349
|
+
/* @__PURE__ */ jsx(Table, { rows: metrics(row, clockMs, true) }),
|
|
3297
3350
|
span.input && /* @__PURE__ */ jsxs("div", {
|
|
3298
3351
|
className: "fw-card-text",
|
|
3299
3352
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -3319,26 +3372,26 @@ const at = (startMs, endMs) => ({
|
|
|
3319
3372
|
"--s": startMs,
|
|
3320
3373
|
"--e": Number.isFinite(endMs) ? endMs : OPEN_END
|
|
3321
3374
|
});
|
|
3322
|
-
const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ?
|
|
3375
|
+
const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ? 80 : 0) + (costliest ? 90 : 0);
|
|
3323
3376
|
function Segment({ mark, events, agentView, ending }) {
|
|
3324
3377
|
const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
3325
|
-
const named = agentView && mark.kind === "tool" && mark.count === 1 && mark.
|
|
3378
|
+
const named = agentView && mark.kind === "tool" && (mark.count === 1 ? mark.name : mark.tool && `${mark.tool} ×${mark.count}`);
|
|
3326
3379
|
return /* @__PURE__ */ jsx("span", {
|
|
3327
3380
|
className: `fw-seg fw-seg-${mark.kind}${mark.alt ? " fw-seg-alt" : ""}`,
|
|
3328
3381
|
"data-tone": toneKey(mark.tone),
|
|
3329
3382
|
style: at(mark.startMs, mark.endMs),
|
|
3330
3383
|
title: mark.name ? `${mark.name}${mark.count > 1 ? ` +${mark.count - 1} more` : ""}` : void 0,
|
|
3331
|
-
onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget) : void 0,
|
|
3384
|
+
onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget, event.clientX) : void 0,
|
|
3332
3385
|
onClick: agentView ? (event) => {
|
|
3333
3386
|
event.stopPropagation();
|
|
3334
|
-
events.onPin(mark.id, event.currentTarget);
|
|
3387
|
+
events.onPin(mark.id, event.currentTarget, event.clientX);
|
|
3335
3388
|
} : void 0,
|
|
3336
3389
|
children: glyph ? /* @__PURE__ */ jsx("span", {
|
|
3337
3390
|
className: "fw-glyph",
|
|
3338
3391
|
children: glyph
|
|
3339
3392
|
}) : named && /* @__PURE__ */ jsx("span", {
|
|
3340
3393
|
className: "fw-seg-name",
|
|
3341
|
-
children:
|
|
3394
|
+
children: named
|
|
3342
3395
|
})
|
|
3343
3396
|
});
|
|
3344
3397
|
}
|
|
@@ -3365,8 +3418,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3365
3418
|
const kind = !row.ok ? "failed" : row.kind;
|
|
3366
3419
|
const marks = isAgent || row.hasChildren || row.id.startsWith("run:") ? folded && !agentView ? row.segments : row.own : [];
|
|
3367
3420
|
const label = endLabel(row, clockMs);
|
|
3368
|
-
const summary =
|
|
3369
|
-
const inspect = (event) => props.onInspect(row.id, event.currentTarget);
|
|
3421
|
+
const summary = [...span.detail && span.detail !== props.sharedDetail ? [span.detail] : [], ...agentSummary(row)];
|
|
3422
|
+
const inspect = (event) => props.onInspect(row.id, event.currentTarget, event.clientX);
|
|
3370
3423
|
const family = agentView && row.id.startsWith("family:");
|
|
3371
3424
|
const step = agentView && row.stepOf !== void 0;
|
|
3372
3425
|
const opens = family || agentView && isAgent && row.own.length > 0;
|
|
@@ -3388,7 +3441,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3388
3441
|
className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${step ? " fw-step" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
|
|
3389
3442
|
style: {
|
|
3390
3443
|
"--row-y": `${top}px`,
|
|
3391
|
-
"--fw-enter-delay": `${motion?.enter ?? 0}ms
|
|
3444
|
+
"--fw-enter-delay": `${motion?.enter ?? 0}ms`,
|
|
3445
|
+
"--fw-end-w": `${endLabelWidth(label, slowest, costliest)}px`
|
|
3392
3446
|
},
|
|
3393
3447
|
onFocus: (event) => {
|
|
3394
3448
|
if (event.target === event.currentTarget) props.onActivate(row);
|
|
@@ -3400,7 +3454,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3400
3454
|
return;
|
|
3401
3455
|
}
|
|
3402
3456
|
const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
|
|
3403
|
-
if (bar) props.onPin(row.id, bar);
|
|
3457
|
+
if (bar) props.onPin(row.id, bar, event.target instanceof Element && event.target.closest(".fw-lane") ? event.clientX : void 0);
|
|
3404
3458
|
},
|
|
3405
3459
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3406
3460
|
role: "gridcell",
|
|
@@ -3479,21 +3533,20 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3479
3533
|
]
|
|
3480
3534
|
}), agentView && /* @__PURE__ */ jsxs("span", {
|
|
3481
3535
|
className: "fw-subtitle",
|
|
3482
|
-
children: [
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
/* @__PURE__ */ jsx("span", {
|
|
3536
|
+
children: [renderIcon && /* @__PURE__ */ jsx("span", {
|
|
3537
|
+
className: "fw-detail-icon",
|
|
3538
|
+
"aria-hidden": true,
|
|
3539
|
+
children: renderIcon(span)
|
|
3540
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
3541
|
+
className: "fw-subtitle-tail",
|
|
3542
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3489
3543
|
className: "fw-subtitle-text",
|
|
3490
|
-
children:
|
|
3491
|
-
}),
|
|
3492
|
-
summary[1] && /* @__PURE__ */ jsxs("span", {
|
|
3544
|
+
children: summary[0]
|
|
3545
|
+
}), summary.slice(1).map((part) => /* @__PURE__ */ jsxs("span", {
|
|
3493
3546
|
className: "fw-subtitle-extra",
|
|
3494
|
-
children: ["· ",
|
|
3495
|
-
})
|
|
3496
|
-
]
|
|
3547
|
+
children: ["· ", part]
|
|
3548
|
+
}, part))]
|
|
3549
|
+
})]
|
|
3497
3550
|
})]
|
|
3498
3551
|
}),
|
|
3499
3552
|
folded && /* @__PURE__ */ jsxs("span", {
|
|
@@ -3532,7 +3585,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3532
3585
|
onClick: (event) => {
|
|
3533
3586
|
event.stopPropagation();
|
|
3534
3587
|
if (family) props.onToggle(row);
|
|
3535
|
-
else props.onPin(row.id, event.currentTarget);
|
|
3588
|
+
else props.onPin(row.id, event.currentTarget, event.clientX);
|
|
3536
3589
|
},
|
|
3537
3590
|
children: [!isAgent && !row.ok && /* @__PURE__ */ jsx("span", {
|
|
3538
3591
|
className: "fw-glyph",
|
|
@@ -3550,10 +3603,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3550
3603
|
}, `${mark.id}-${i}`)),
|
|
3551
3604
|
/* @__PURE__ */ jsxs("span", {
|
|
3552
3605
|
className: "fw-end",
|
|
3553
|
-
style:
|
|
3554
|
-
...at(span.startMs, running ? OPEN_END : span.endMs),
|
|
3555
|
-
"--fw-end-w": `${endLabelWidth(label, slowest, costliest)}px`
|
|
3556
|
-
},
|
|
3606
|
+
style: at(span.startMs, running ? OPEN_END : span.endMs),
|
|
3557
3607
|
children: [
|
|
3558
3608
|
/* @__PURE__ */ jsx("span", {
|
|
3559
3609
|
className: "fw-duration",
|
|
@@ -3817,22 +3867,6 @@ function FlowBody(props) {
|
|
|
3817
3867
|
trackPx,
|
|
3818
3868
|
view
|
|
3819
3869
|
]);
|
|
3820
|
-
const pauseLabels = useMemo(() => {
|
|
3821
|
-
const shown = /* @__PURE__ */ new Set();
|
|
3822
|
-
let freeFrom = -Infinity;
|
|
3823
|
-
for (const pause of [...props.pauses ?? []].sort((a, b) => a.startMs - b.startMs)) {
|
|
3824
|
-
const width = duration(pause.endMs - pause.startMs).length * 7 + 24;
|
|
3825
|
-
const centre = ((pause.startMs + pause.endMs) / 2 - win.startMs) / msPerPx;
|
|
3826
|
-
if (centre - width / 2 < freeFrom) continue;
|
|
3827
|
-
shown.add(pause);
|
|
3828
|
-
freeFrom = centre + width / 2 + 8;
|
|
3829
|
-
}
|
|
3830
|
-
return shown;
|
|
3831
|
-
}, [
|
|
3832
|
-
props.pauses,
|
|
3833
|
-
win.startMs,
|
|
3834
|
-
msPerPx
|
|
3835
|
-
]);
|
|
3836
3870
|
useEffect(() => {
|
|
3837
3871
|
const element = scroller.current;
|
|
3838
3872
|
if (!element) return;
|
|
@@ -4014,18 +4048,21 @@ function FlowBody(props) {
|
|
|
4014
4048
|
props.onScrub(marker.at);
|
|
4015
4049
|
}
|
|
4016
4050
|
}, `e-${marker.at}-${marker.label}`)),
|
|
4017
|
-
props.pauses?.
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4051
|
+
props.pauses?.map((pause) => {
|
|
4052
|
+
const said = `${pause.label} ${duration(pause.endMs - pause.startMs)}${pause.detail ? `: ${pause.detail}` : ""}`;
|
|
4053
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4054
|
+
className: "fw-pause-mark",
|
|
4055
|
+
style: {
|
|
4056
|
+
"--s": pause.startMs,
|
|
4057
|
+
"--e": pause.endMs
|
|
4058
|
+
},
|
|
4059
|
+
title: said,
|
|
4060
|
+
"aria-label": said,
|
|
4061
|
+
role: "img",
|
|
4062
|
+
tabIndex: 0,
|
|
4063
|
+
onPointerDown: (event) => event.stopPropagation()
|
|
4064
|
+
}, `pm-${pause.startMs}`);
|
|
4065
|
+
}),
|
|
4029
4066
|
jumped && /* @__PURE__ */ jsx("span", {
|
|
4030
4067
|
className: "fw-event-label",
|
|
4031
4068
|
"data-tone": jumped.tone ?? "neutral",
|
|
@@ -4074,14 +4111,24 @@ function FlowBody(props) {
|
|
|
4074
4111
|
/* @__PURE__ */ jsxs("div", {
|
|
4075
4112
|
className: "fw-gridlayer",
|
|
4076
4113
|
"aria-hidden": true,
|
|
4077
|
-
children: [
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4114
|
+
children: [
|
|
4115
|
+
props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
|
|
4116
|
+
className: "fw-pause",
|
|
4117
|
+
style: {
|
|
4118
|
+
"--s": pause.startMs,
|
|
4119
|
+
"--e": pause.endMs
|
|
4120
|
+
}
|
|
4121
|
+
}, `p-${pause.startMs}`)),
|
|
4122
|
+
ticks.map((tick) => /* @__PURE__ */ jsx("span", {
|
|
4123
|
+
className: "fw-gridline",
|
|
4124
|
+
style: { "--s": tick }
|
|
4125
|
+
}, tick)),
|
|
4126
|
+
props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
4127
|
+
className: "fw-marker-line",
|
|
4128
|
+
"data-tone": marker.tone ?? "neutral",
|
|
4129
|
+
style: { "--s": marker.at }
|
|
4130
|
+
}, `l-${marker.at}-${marker.label}`))
|
|
4131
|
+
]
|
|
4085
4132
|
}),
|
|
4086
4133
|
/* @__PURE__ */ jsxs("div", {
|
|
4087
4134
|
ref: scroller,
|
|
@@ -4126,16 +4173,9 @@ function FlowBody(props) {
|
|
|
4126
4173
|
rowHeight,
|
|
4127
4174
|
render: row
|
|
4128
4175
|
}),
|
|
4129
|
-
props.markers?.length
|
|
4176
|
+
props.markers?.length ? /* @__PURE__ */ jsx("div", {
|
|
4130
4177
|
className: "fw-marker-layer",
|
|
4131
|
-
children:
|
|
4132
|
-
className: "fw-pause",
|
|
4133
|
-
style: {
|
|
4134
|
-
"--s": pause.startMs,
|
|
4135
|
-
"--e": pause.endMs
|
|
4136
|
-
},
|
|
4137
|
-
title: `${pause.label}, ${duration(pause.endMs - pause.startMs)}`
|
|
4138
|
-
}, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
4178
|
+
children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
|
|
4139
4179
|
className: "fw-marker",
|
|
4140
4180
|
"data-tone": marker.tone ?? "neutral",
|
|
4141
4181
|
style: { "--s": marker.at },
|
|
@@ -4144,7 +4184,7 @@ function FlowBody(props) {
|
|
|
4144
4184
|
className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
|
|
4145
4185
|
children: marker.label
|
|
4146
4186
|
})
|
|
4147
|
-
}, `${marker.at}-${marker.label}`))
|
|
4187
|
+
}, `${marker.at}-${marker.label}`))
|
|
4148
4188
|
}) : null,
|
|
4149
4189
|
showCursor && props.cursorLine !== false && /* @__PURE__ */ jsx("div", {
|
|
4150
4190
|
className: "fw-cursor-layer",
|
|
@@ -4440,6 +4480,7 @@ function useHostable(value, onChange, initial) {
|
|
|
4440
4480
|
report.current?.(resolved);
|
|
4441
4481
|
}, [controlled])];
|
|
4442
4482
|
}
|
|
4483
|
+
const CARD_WIDTH = 320;
|
|
4443
4484
|
/** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
|
|
4444
4485
|
function summaryCells(trace, at, durationMs) {
|
|
4445
4486
|
const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
|
|
@@ -4617,7 +4658,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4617
4658
|
const pipelines = rows.filter((row) => row.kind === "pipeline");
|
|
4618
4659
|
return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
|
|
4619
4660
|
}, [rows]);
|
|
4620
|
-
const
|
|
4661
|
+
const details = new Set(agents.flatMap((row) => row.span.detail ? [row.span.detail] : []));
|
|
4662
|
+
const sharedDetail = agents.length > 1 && details.size === 1 ? [...details][0] : void 0;
|
|
4621
4663
|
const [flippedFamilies, setFlippedFamilies] = useState(/* @__PURE__ */ new Set());
|
|
4622
4664
|
const openFamilies = useMemo(() => defaultOpenFamilies(agents, UNFOLD_ROWS, flippedFamilies), [agents, flippedFamilies]);
|
|
4623
4665
|
const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
|
|
@@ -4732,8 +4774,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4732
4774
|
}, limit));
|
|
4733
4775
|
const latest = useRef({
|
|
4734
4776
|
toggle: (_row) => {},
|
|
4735
|
-
pin: (_target, _element) => {},
|
|
4736
|
-
inspect: (_target, _element) => {}
|
|
4777
|
+
pin: (_target, _element, _x) => {},
|
|
4778
|
+
inspect: (_target, _element, _x) => {}
|
|
4737
4779
|
});
|
|
4738
4780
|
const toggleNow = (row) => {
|
|
4739
4781
|
const flip = (open) => new Set(open.has(row.id) ? [...open].filter((id) => id !== row.id) : [...open, row.id]);
|
|
@@ -4757,25 +4799,43 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4757
4799
|
};
|
|
4758
4800
|
latest.current.toggle = toggleNow;
|
|
4759
4801
|
const toggle = useCallback((row) => latest.current.toggle(row), []);
|
|
4760
|
-
const place = (target, element, pinned) => {
|
|
4802
|
+
const place = (target, element, pinned, x) => {
|
|
4761
4803
|
const host = root.current?.getBoundingClientRect();
|
|
4762
4804
|
if (!host) return;
|
|
4763
4805
|
const rect = element.getBoundingClientRect();
|
|
4764
|
-
const
|
|
4765
|
-
const left = Math.max(8, Math.min(rect.left - host.left, host.width -
|
|
4806
|
+
const width = Math.min(CARD_WIDTH, host.width - 16);
|
|
4807
|
+
const left = Math.max(8, Math.min((x ?? rect.left + 24) - host.left - 24, host.width - width - 8));
|
|
4766
4808
|
clearTimeout(hideTimer.current);
|
|
4767
4809
|
setCard({
|
|
4768
4810
|
target,
|
|
4769
4811
|
pinned,
|
|
4770
4812
|
left,
|
|
4771
|
-
|
|
4772
|
-
|
|
4813
|
+
anchorTop: rect.top - host.top,
|
|
4814
|
+
anchorBottom: rect.bottom - host.top,
|
|
4815
|
+
above: view === "agents"
|
|
4773
4816
|
});
|
|
4774
4817
|
};
|
|
4775
|
-
|
|
4776
|
-
if (!card
|
|
4818
|
+
useLayoutEffect(() => {
|
|
4819
|
+
if (!card || card.fitted || !root.current) return;
|
|
4820
|
+
const box = document.getElementById(cardId)?.getBoundingClientRect();
|
|
4821
|
+
if (!box) return;
|
|
4822
|
+
const hostTop = root.current.getBoundingClientRect().top;
|
|
4823
|
+
const room = {
|
|
4824
|
+
above: hostTop + card.anchorTop - 16,
|
|
4825
|
+
below: globalThis.innerHeight - (hostTop + card.anchorBottom) - 16
|
|
4826
|
+
};
|
|
4827
|
+
const preferred = card.above ? room.above : room.below;
|
|
4828
|
+
const other = card.above ? room.below : room.above;
|
|
4829
|
+
setCard({
|
|
4830
|
+
...card,
|
|
4831
|
+
fitted: true,
|
|
4832
|
+
above: box.height <= preferred || box.height > other && preferred >= other ? card.above : !card.above
|
|
4833
|
+
});
|
|
4834
|
+
}, [card, cardId]);
|
|
4835
|
+
latest.current.inspect = (target, element, x) => {
|
|
4836
|
+
if (!card?.pinned) place(target, element, false, x);
|
|
4777
4837
|
};
|
|
4778
|
-
const inspect = useCallback((target, element) => latest.current.inspect(target, element), []);
|
|
4838
|
+
const inspect = useCallback((target, element, x) => latest.current.inspect(target, element, x), []);
|
|
4779
4839
|
const leave = useCallback(() => {
|
|
4780
4840
|
clearTimeout(hideTimer.current);
|
|
4781
4841
|
hideTimer.current = setTimeout(() => setCard((previous) => previous?.pinned ? previous : null), 140);
|
|
@@ -4788,19 +4848,19 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4788
4848
|
document.addEventListener("pointermove", move);
|
|
4789
4849
|
return () => document.removeEventListener("pointermove", move);
|
|
4790
4850
|
}, [card, leave]);
|
|
4791
|
-
latest.current.pin = (target, element) => {
|
|
4851
|
+
latest.current.pin = (target, element, x) => {
|
|
4792
4852
|
if (card?.pinned && card.target === target) {
|
|
4793
4853
|
closeCard();
|
|
4794
4854
|
return;
|
|
4795
4855
|
}
|
|
4796
|
-
place(target, element, true);
|
|
4856
|
+
place(target, element, true, x);
|
|
4797
4857
|
const row = byId.get(target);
|
|
4798
4858
|
if (row) {
|
|
4799
4859
|
onSelect?.(row.span);
|
|
4800
4860
|
zoomTo(row);
|
|
4801
4861
|
}
|
|
4802
4862
|
};
|
|
4803
|
-
const pin = useCallback((target, element) => latest.current.pin(target, element), []);
|
|
4863
|
+
const pin = useCallback((target, element, x) => latest.current.pin(target, element, x), []);
|
|
4804
4864
|
const selection = selectedId ?? (card?.pinned ? card.target : void 0);
|
|
4805
4865
|
const closeCard = () => {
|
|
4806
4866
|
const target = card?.target;
|
|
@@ -5053,7 +5113,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
5053
5113
|
agent: cardAgent,
|
|
5054
5114
|
clockMs,
|
|
5055
5115
|
left: card.left,
|
|
5056
|
-
top: card.
|
|
5116
|
+
top: card.above ? card.anchorTop - 8 : card.anchorBottom + 8,
|
|
5057
5117
|
pinned: card.pinned,
|
|
5058
5118
|
onClose: closeCard,
|
|
5059
5119
|
renderIcon,
|