@usetheo/ui 0.22.1 → 0.26.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.
Files changed (48) hide show
  1. package/dist/components/composites/annotation-input/annotation-input.d.ts +38 -0
  2. package/dist/components/composites/annotation-input/index.d.ts +4 -0
  3. package/dist/components/composites/annotation-input/types.d.ts +31 -0
  4. package/dist/components/composites/attributes-table/attributes-table.d.ts +20 -0
  5. package/dist/components/composites/attributes-table/index.d.ts +1 -0
  6. package/dist/components/composites/histogram/histogram.d.ts +19 -0
  7. package/dist/components/composites/histogram/index.d.ts +2 -0
  8. package/dist/components/composites/io-cards/index.d.ts +2 -0
  9. package/dist/components/composites/io-cards/io-cards.d.ts +17 -0
  10. package/dist/components/composites/io-cards/message-card.d.ts +21 -0
  11. package/dist/components/composites/percentile-chart/index.d.ts +2 -0
  12. package/dist/components/composites/percentile-chart/percentile-chart.d.ts +37 -0
  13. package/dist/components/composites/session-summary/index.d.ts +1 -0
  14. package/dist/components/composites/session-summary/session-summary.d.ts +18 -0
  15. package/dist/components/composites/session-timeline/index.d.ts +1 -0
  16. package/dist/components/composites/session-timeline/session-timeline.d.ts +22 -0
  17. package/dist/components/composites/span-graph/index.d.ts +1 -0
  18. package/dist/components/composites/span-graph/span-graph.d.ts +19 -0
  19. package/dist/components/composites/span-tree/index.d.ts +2 -0
  20. package/dist/components/composites/span-tree/span-tree-row.d.ts +22 -0
  21. package/dist/components/composites/span-tree/span-tree-virtual.d.ts +7 -0
  22. package/dist/components/composites/span-tree/span-tree.d.ts +34 -0
  23. package/dist/components/composites/span-waterfall/index.d.ts +1 -0
  24. package/dist/components/composites/span-waterfall/span-waterfall.d.ts +28 -0
  25. package/dist/components/composites/trace-compare/index.d.ts +1 -0
  26. package/dist/components/composites/trace-compare/trace-compare.d.ts +27 -0
  27. package/dist/components/composites/trace-transcript/index.d.ts +1 -0
  28. package/dist/components/composites/trace-transcript/trace-transcript.d.ts +26 -0
  29. package/dist/components/primitives/combobox/combobox.d.ts +3 -3
  30. package/dist/index.d.ts +30 -0
  31. package/dist/index.js +2177 -1
  32. package/dist/lib/chart/histogram.d.ts +21 -0
  33. package/dist/lib/chart/index.d.ts +1 -0
  34. package/dist/lib/session/aggregate.d.ts +18 -0
  35. package/dist/lib/session/index.d.ts +2 -0
  36. package/dist/lib/session/types.d.ts +30 -0
  37. package/dist/lib/trace/align.d.ts +41 -0
  38. package/dist/lib/trace/attributes.d.ts +11 -0
  39. package/dist/lib/trace/bar-layout.d.ts +52 -0
  40. package/dist/lib/trace/chat.d.ts +14 -0
  41. package/dist/lib/trace/cost.d.ts +16 -0
  42. package/dist/lib/trace/duration.d.ts +18 -0
  43. package/dist/lib/trace/flatten.d.ts +28 -0
  44. package/dist/lib/trace/graph-layout.d.ts +52 -0
  45. package/dist/lib/trace/index.d.ts +9 -0
  46. package/dist/lib/trace/types.d.ts +80 -0
  47. package/dist/test/fixtures/trace.d.ts +19 -0
  48. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -38,6 +38,495 @@ var twMerge = extendTailwindMerge({
38
38
  function cn(...inputs) {
39
39
  return twMerge(clsx(inputs));
40
40
  }
41
+
42
+ // src/lib/trace/duration.ts
43
+ function toNs(value) {
44
+ if (typeof value === "bigint") return value;
45
+ if (typeof value !== "string" || value === "") return null;
46
+ try {
47
+ return BigInt(value);
48
+ } catch {
49
+ const ms = Date.parse(value);
50
+ return Number.isNaN(ms) ? null : BigInt(ms) * 1000000n;
51
+ }
52
+ }
53
+ function durationMs(span) {
54
+ const start = toNs(span.startTime);
55
+ const end = toNs(span.endTime ?? null);
56
+ if (start === null || end === null) return null;
57
+ const ms = (end - start) / 1000000n;
58
+ return ms < 0n ? null : Number(ms);
59
+ }
60
+ function isSpanError(status) {
61
+ return status === "ERROR";
62
+ }
63
+ function formatDurationMs(ms) {
64
+ if (ms === null) return "in-flight";
65
+ if (ms >= 6e4) return `${trimZeros(ms / 6e4)}min`;
66
+ if (ms >= 1e3) return `${trimZeros(ms / 1e3)}s`;
67
+ return `${Math.round(ms)}ms`;
68
+ }
69
+ function trimZeros(n) {
70
+ return Number.parseFloat(n.toFixed(2)).toString();
71
+ }
72
+
73
+ // src/lib/trace/chat.ts
74
+ function asChat(value) {
75
+ try {
76
+ const parsed = JSON.parse(value);
77
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((m) => m !== null && typeof m === "object" && "role" in m && "content" in m)) {
78
+ return parsed;
79
+ }
80
+ } catch {
81
+ }
82
+ return null;
83
+ }
84
+ function prettyValue(value) {
85
+ try {
86
+ return JSON.stringify(JSON.parse(value), null, 2);
87
+ } catch {
88
+ return value;
89
+ }
90
+ }
91
+ var REDACTED = "<REDACTED>";
92
+ function isRedactedThinking(m) {
93
+ if (m.content === REDACTED) return true;
94
+ if (Array.isArray(m.content)) {
95
+ return m.content.some((b) => {
96
+ if (b === null || typeof b !== "object") return false;
97
+ const block = b;
98
+ const type = typeof block.type === "string" ? block.type : "";
99
+ if (type === "redacted_thinking") return true;
100
+ if (type === "thinking") {
101
+ return block.thinking === REDACTED || block.thinking === void 0 || block.thinking === "";
102
+ }
103
+ return false;
104
+ });
105
+ }
106
+ return false;
107
+ }
108
+ function contentText(content) {
109
+ if (typeof content === "string") return content;
110
+ if (content === null || content === void 0) return "";
111
+ if (Array.isArray(content)) {
112
+ return content.map((b) => {
113
+ if (typeof b === "string") return b;
114
+ if (b !== null && typeof b === "object") {
115
+ const block = b;
116
+ if (typeof block.text === "string") return block.text;
117
+ if (typeof block.thinking === "string") return block.thinking;
118
+ }
119
+ return "";
120
+ }).filter(Boolean).join("\n\n");
121
+ }
122
+ return JSON.stringify(content, null, 2);
123
+ }
124
+
125
+ // src/lib/trace/attributes.ts
126
+ function groupByNamespace(attrs) {
127
+ const buckets = /* @__PURE__ */ new Map();
128
+ for (const [key, value] of Object.entries(attrs)) {
129
+ const dot = key.indexOf(".");
130
+ const ns = dot > 0 ? key.slice(0, dot) : "general";
131
+ let bucket = buckets.get(ns);
132
+ if (!bucket) {
133
+ bucket = [];
134
+ buckets.set(ns, bucket);
135
+ }
136
+ bucket.push([key, value]);
137
+ }
138
+ return [...buckets.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([namespace, entries]) => ({ namespace, entries }));
139
+ }
140
+
141
+ // src/lib/trace/cost.ts
142
+ var COST_ATTRIBUTE_KEYS = ["gen_ai.usage.cost", "gen_ai.usage.cost_usd", "cost_usd", "cost"];
143
+ function spanCostUsd(span) {
144
+ if (typeof span.costUsd === "number" && Number.isFinite(span.costUsd)) {
145
+ return span.costUsd > 0 ? span.costUsd : 0;
146
+ }
147
+ const attrs = span.attributes ?? {};
148
+ for (const key of COST_ATTRIBUTE_KEYS) {
149
+ if (!(key in attrs)) continue;
150
+ const raw = attrs[key];
151
+ const n = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : Number.NaN;
152
+ if (Number.isFinite(n)) return n > 0 ? n : 0;
153
+ }
154
+ return 0;
155
+ }
156
+ function aggregateCost(span) {
157
+ const seen = /* @__PURE__ */ new Set();
158
+ const walk = (s) => {
159
+ if (seen.has(s.id)) return 0;
160
+ seen.add(s.id);
161
+ let total = spanCostUsd(s);
162
+ for (const c of s.children ?? []) total += walk(c);
163
+ return total;
164
+ };
165
+ return walk(span);
166
+ }
167
+
168
+ // src/lib/trace/flatten.ts
169
+ var VIRTUALIZE_THRESHOLD = 200;
170
+ function flattenAll(root) {
171
+ const out = [];
172
+ const seen = /* @__PURE__ */ new Set();
173
+ const walk = (span) => {
174
+ if (seen.has(span.id)) return;
175
+ seen.add(span.id);
176
+ out.push(span);
177
+ for (const c of span.children ?? []) walk(c);
178
+ };
179
+ walk(root);
180
+ return out;
181
+ }
182
+ function flattenVisible(root, collapsed) {
183
+ const out = [];
184
+ const seen = /* @__PURE__ */ new Set();
185
+ const walk = (span, depth) => {
186
+ if (seen.has(span.id)) return;
187
+ seen.add(span.id);
188
+ out.push({ span, depth });
189
+ if (!collapsed.has(span.id)) for (const c of span.children ?? []) walk(c, depth + 1);
190
+ };
191
+ walk(root, 0);
192
+ return out;
193
+ }
194
+ var TRANSCRIPT_PREVIEW_CHARS = 140;
195
+ var SUBAGENT_MIN_CHILDREN = 2;
196
+ function transcriptPreview(span) {
197
+ const oneLine = (span.outputValue ?? "").replace(/\s+/g, " ").trim();
198
+ return oneLine.length > TRANSCRIPT_PREVIEW_CHARS ? `${oneLine.slice(0, TRANSCRIPT_PREVIEW_CHARS)}\u2026` : oneLine;
199
+ }
200
+ function toTranscriptRows(root) {
201
+ const rows = [];
202
+ const walk = (span) => {
203
+ const children = span.children ?? [];
204
+ rows.push({
205
+ kind: "span",
206
+ spanId: span.id,
207
+ role: span.kind ?? span.name ?? "span",
208
+ preview: transcriptPreview(span),
209
+ stats: {
210
+ inputTokens: span.inputTokens ?? 0,
211
+ outputTokens: span.outputTokens ?? 0,
212
+ costUsd: spanCostUsd(span),
213
+ durationMs: durationMs(span)
214
+ }
215
+ });
216
+ if (children.length >= SUBAGENT_MIN_CHILDREN) {
217
+ rows.push({ kind: "group-header", spanId: span.id, groupId: span.id });
218
+ }
219
+ for (const c of children) walk(c);
220
+ };
221
+ walk(root);
222
+ return rows;
223
+ }
224
+
225
+ // src/lib/trace/align.ts
226
+ function traceMetrics(root) {
227
+ const all = flattenAll(root);
228
+ let totalTokens = 0;
229
+ let hasError = false;
230
+ for (const s of all) {
231
+ totalTokens += (s.inputTokens ?? 0) + (s.outputTokens ?? 0);
232
+ if (isSpanError(s.status)) hasError = true;
233
+ }
234
+ return {
235
+ durationMs: durationMs(root),
236
+ totalTokens,
237
+ costUsd: aggregateCost(root),
238
+ hasError,
239
+ spanCount: all.length
240
+ };
241
+ }
242
+ function numericDelta(from, to) {
243
+ const deltaPct = from !== null && to !== null && from !== 0 ? (to - from) / Math.abs(from) * 100 : null;
244
+ return { from, to, deltaPct };
245
+ }
246
+ function spanTokens(s) {
247
+ return (s.inputTokens ?? 0) + (s.outputTokens ?? 0);
248
+ }
249
+ function computeDelta(a, b) {
250
+ return {
251
+ durationMs: numericDelta(durationMs(a), durationMs(b)),
252
+ tokens: numericDelta(spanTokens(a), spanTokens(b)),
253
+ costUsd: numericDelta(a.costUsd ?? 0, b.costUsd ?? 0)
254
+ };
255
+ }
256
+ function alignSpanTrees(a, b) {
257
+ const rows = [];
258
+ const seen = /* @__PURE__ */ new Set();
259
+ const walk = (an, bn, depth) => {
260
+ const anchor = an ?? bn;
261
+ if (!anchor) return;
262
+ const guardId = `${an?.id ?? ""}|${bn?.id ?? ""}`;
263
+ if (seen.has(guardId)) return;
264
+ seen.add(guardId);
265
+ const status = an && bn ? "matched" : an ? "only-in-a" : "only-in-b";
266
+ rows.push({
267
+ key: anchor.name,
268
+ depth,
269
+ a: an,
270
+ b: bn,
271
+ status,
272
+ delta: an && bn ? computeDelta(an, bn) : void 0
273
+ });
274
+ const aChildren = an?.children ?? [];
275
+ const bChildren = bn?.children ?? [];
276
+ const bUsed = /* @__PURE__ */ new Set();
277
+ for (const ac of aChildren) {
278
+ if (ac.id === an?.id) continue;
279
+ const j = bChildren.findIndex((bc, idx) => !bUsed.has(idx) && bc.name === ac.name);
280
+ if (j >= 0) {
281
+ bUsed.add(j);
282
+ walk(ac, bChildren[j], depth + 1);
283
+ } else {
284
+ walk(ac, void 0, depth + 1);
285
+ }
286
+ }
287
+ for (let idx = 0; idx < bChildren.length; idx++) {
288
+ if (!bUsed.has(idx) && bChildren[idx]?.id !== bn?.id)
289
+ walk(void 0, bChildren[idx], depth + 1);
290
+ }
291
+ };
292
+ walk(a, b, 0);
293
+ return rows;
294
+ }
295
+
296
+ // src/lib/trace/bar-layout.ts
297
+ function computeTraceBounds(root) {
298
+ let start = null;
299
+ let end = null;
300
+ for (const span of flattenAll(root)) {
301
+ const s2 = toNs(span.startTime);
302
+ if (s2 === null) continue;
303
+ if (start === null || s2 < start) start = s2;
304
+ const e = toNs(span.endTime ?? null) ?? s2;
305
+ const eff = e >= s2 ? e : s2;
306
+ if (end === null || eff > end) end = eff;
307
+ }
308
+ const s = start ?? 0n;
309
+ return { startNs: s, endNs: end ?? s };
310
+ }
311
+ function computeBarLayout(spanStart, spanEnd, traceStartNs, traceEndNs) {
312
+ const window2 = traceEndNs > traceStartNs ? traceEndNs - traceStartNs : 1n;
313
+ const start = toNs(spanStart);
314
+ if (start === null) return { leftPct: 0, widthPct: 0, unbounded: true };
315
+ const end = toNs(spanEnd ?? null);
316
+ const skewed = end !== null && end < start;
317
+ const unbounded = end === null || skewed;
318
+ const effEnd = end !== null && !skewed ? end : start;
319
+ const pct = (delta) => delta > 0n ? Number(delta * 10000n / window2) / 100 : 0;
320
+ const leftPct = Math.min(Math.max(pct(start - traceStartNs), 0), 100);
321
+ const rawWidth = effEnd > start ? pct(effEnd - start) : 0;
322
+ const widthPct = Math.min(Math.max(rawWidth, 0), 100 - leftPct);
323
+ return { leftPct, widthPct, unbounded };
324
+ }
325
+ var NICE_MANTISSAS = [1n, 2n, 5n];
326
+ var MIN_TICK_PX = 70;
327
+ function trimZeros2(n) {
328
+ return Number.parseFloat(n.toFixed(2)).toString();
329
+ }
330
+ function labelForNs(offsetNs) {
331
+ const ns = Number(offsetNs);
332
+ if (ns >= 6e10) return `${trimZeros2(ns / 6e10)}min`;
333
+ if (ns >= 1e9) return `${trimZeros2(ns / 1e9)}s`;
334
+ return `${trimZeros2(ns / 1e6)}ms`;
335
+ }
336
+ function niceStep(targetNs) {
337
+ if (!(targetNs > 0)) return 0n;
338
+ let pow = 1n;
339
+ for (let guard = 0; guard < 32; guard++) {
340
+ for (const m of NICE_MANTISSAS) {
341
+ const candidate = m * pow;
342
+ if (Number(candidate) >= targetNs) return candidate;
343
+ }
344
+ pow *= 10n;
345
+ }
346
+ return pow;
347
+ }
348
+ function niceAxisTicks(startNs, endNs, pxWidth) {
349
+ if (endNs <= startNs || !(pxWidth > 0)) return [];
350
+ const spanNs = endNs - startNs;
351
+ const maxTicks = Math.max(1, Math.floor(pxWidth / MIN_TICK_PX));
352
+ const step = niceStep(Number(spanNs) / maxTicks);
353
+ if (step <= 0n) return [];
354
+ const ticks = [];
355
+ const spanNum = Number(spanNs);
356
+ for (let offset = 0n; offset <= spanNs; offset += step) {
357
+ ticks.push({
358
+ offsetNs: offset,
359
+ label: labelForNs(offset),
360
+ leftPct: Number(offset) / spanNum * 100
361
+ });
362
+ }
363
+ return ticks;
364
+ }
365
+ function packRows(spans) {
366
+ const rows = /* @__PURE__ */ new Map();
367
+ const rowEnds = [];
368
+ const ordered = [...spans].sort(
369
+ (a, b) => a.startNs < b.startNs ? -1 : a.startNs > b.startNs ? 1 : 0
370
+ );
371
+ for (const s of ordered) {
372
+ const end = s.endNs >= s.startNs ? s.endNs : s.startNs;
373
+ let placed = -1;
374
+ for (let r = 0; r < rowEnds.length; r++) {
375
+ const rowEnd = rowEnds[r];
376
+ if (rowEnd !== void 0 && rowEnd <= s.startNs) {
377
+ placed = r;
378
+ break;
379
+ }
380
+ }
381
+ if (placed === -1) {
382
+ placed = rowEnds.length;
383
+ rowEnds.push(end);
384
+ } else {
385
+ rowEnds[placed] = end;
386
+ }
387
+ rows.set(s.id, placed);
388
+ }
389
+ return rows;
390
+ }
391
+
392
+ // src/lib/trace/graph-layout.ts
393
+ function attr(span, key) {
394
+ const a = span.attributes;
395
+ if (a == null || typeof a !== "object") return void 0;
396
+ const v = a[key];
397
+ return typeof v === "string" ? v : void 0;
398
+ }
399
+ function deriveSpanKind(span) {
400
+ const operationName = attr(span, "gen_ai.operation.name");
401
+ const toolName = attr(span, "gen_ai.tool.name");
402
+ const hasChildren = (span.children?.length ?? 0) > 0;
403
+ if (toolName != null || operationName === "execute_tool") return "tool";
404
+ if (operationName === "retrieve" || operationName === "search") return "retriever";
405
+ if (operationName === "embeddings" || operationName === "embedding") return "embedding";
406
+ if (operationName === "chat" || operationName === "text_completion" || span.model != null)
407
+ return "llm";
408
+ if (hasChildren) return "chain";
409
+ return "unknown";
410
+ }
411
+ var GRAPH_NODE_CAP = 200;
412
+ var NODE_X_GAP = 168;
413
+ var NODE_Y_GAP = 96;
414
+ function buildLayeredGraph(root, selectedId, cap = GRAPH_NODE_CAP) {
415
+ const seenIds = /* @__PURE__ */ new Set();
416
+ const all = flattenAll(root).filter((s) => {
417
+ if (seenIds.has(s.id)) return false;
418
+ seenIds.add(s.id);
419
+ return true;
420
+ });
421
+ const count = all.length;
422
+ if (count > cap) {
423
+ return { nodes: [], edges: [], width: 0, height: 0, truncated: true, count };
424
+ }
425
+ const byId = new Map(all.map((s) => [s.id, s]));
426
+ const parentOf = /* @__PURE__ */ new Map();
427
+ const childrenOf = /* @__PURE__ */ new Map();
428
+ for (const s of all) {
429
+ if (s.id === root.id) continue;
430
+ const p = s.parentId;
431
+ const eff = p != null && p !== s.id && byId.has(p) ? p : root.id;
432
+ parentOf.set(s.id, eff);
433
+ let bucket = childrenOf.get(eff);
434
+ if (!bucket) {
435
+ bucket = [];
436
+ childrenOf.set(eff, bucket);
437
+ }
438
+ bucket.push(s.id);
439
+ }
440
+ const depthOf = /* @__PURE__ */ new Map();
441
+ const orderOf = /* @__PURE__ */ new Map();
442
+ const perDepth = /* @__PURE__ */ new Map();
443
+ const visited = /* @__PURE__ */ new Set([root.id]);
444
+ const queue = [{ id: root.id, depth: 0 }];
445
+ let maxDepth = 0;
446
+ while (queue.length > 0) {
447
+ const item = queue.shift();
448
+ if (!item) break;
449
+ const order = perDepth.get(item.depth) ?? 0;
450
+ perDepth.set(item.depth, order + 1);
451
+ depthOf.set(item.id, item.depth);
452
+ orderOf.set(item.id, order);
453
+ maxDepth = Math.max(maxDepth, item.depth);
454
+ for (const child of childrenOf.get(item.id) ?? []) {
455
+ if (visited.has(child)) continue;
456
+ visited.add(child);
457
+ queue.push({ id: child, depth: item.depth + 1 });
458
+ }
459
+ }
460
+ for (const s of all) {
461
+ if (depthOf.has(s.id)) continue;
462
+ const order = perDepth.get(1) ?? 0;
463
+ perDepth.set(1, order + 1);
464
+ depthOf.set(s.id, 1);
465
+ orderOf.set(s.id, order);
466
+ parentOf.set(s.id, root.id);
467
+ maxDepth = Math.max(maxDepth, 1);
468
+ }
469
+ const pathSet = new Set(computePath(root.id, selectedId, parentOf, depthOf, byId));
470
+ const nodes = all.map((s) => {
471
+ const depth = depthOf.get(s.id) ?? 0;
472
+ const order = orderOf.get(s.id) ?? 0;
473
+ return {
474
+ spanId: s.id,
475
+ name: s.name,
476
+ kind: deriveSpanKind(s),
477
+ depth,
478
+ order,
479
+ x: order * NODE_X_GAP,
480
+ y: depth * NODE_Y_GAP,
481
+ error: isSpanError(s.status),
482
+ selected: selectedId != null && s.id === selectedId,
483
+ onPath: pathSet.has(s.id)
484
+ };
485
+ });
486
+ const edges = all.filter((s) => s.id !== root.id).map((s) => {
487
+ const from = parentOf.get(s.id) ?? root.id;
488
+ return { from, to: s.id, onPath: pathSet.has(from) && pathSet.has(s.id) };
489
+ });
490
+ const width = Math.max(1, ...Array.from(perDepth.values())) * NODE_X_GAP;
491
+ const height = (maxDepth + 1) * NODE_Y_GAP;
492
+ return { nodes, edges, width, height, truncated: false, count };
493
+ }
494
+ function computePath(rootId, selectedId, parentOf, depthOf, byId) {
495
+ let target = selectedId != null && byId.has(selectedId) ? selectedId : void 0;
496
+ if (target === void 0) {
497
+ let deepest = rootId;
498
+ let best = -1;
499
+ for (const [id, d] of depthOf) {
500
+ if (d > best) {
501
+ best = d;
502
+ deepest = id;
503
+ }
504
+ }
505
+ target = deepest;
506
+ }
507
+ const chain = [];
508
+ const seen = /* @__PURE__ */ new Set();
509
+ let cur = target;
510
+ while (cur != null && !seen.has(cur)) {
511
+ seen.add(cur);
512
+ chain.push(cur);
513
+ if (cur === rootId) break;
514
+ cur = parentOf.get(cur);
515
+ }
516
+ return chain;
517
+ }
518
+ var KIND_LABEL = {
519
+ llm: "LLM",
520
+ tool: "Tool",
521
+ retriever: "Retriever",
522
+ embedding: "Embedding",
523
+ agent: "Agent",
524
+ chain: "Chain",
525
+ reranker: "Reranker",
526
+ evaluator: "Evaluator",
527
+ guardrail: "Guardrail",
528
+ unknown: "Span"
529
+ };
41
530
  var buttonVariants = cva(
42
531
  [
43
532
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg",
@@ -5971,5 +6460,1692 @@ function CommandPalette({
5971
6460
  ] })
5972
6461
  ] }) });
5973
6462
  }
6463
+ function SpanTreeRow({
6464
+ span,
6465
+ depth,
6466
+ selected,
6467
+ collapsed,
6468
+ hasChildren,
6469
+ posinset,
6470
+ setsize,
6471
+ showMetrics = true,
6472
+ bounds,
6473
+ onSelect,
6474
+ onToggle,
6475
+ renderBadge
6476
+ }) {
6477
+ const dur = durationMs(span);
6478
+ const error = isSpanError(span.status);
6479
+ return /* @__PURE__ */ jsxs(
6480
+ "div",
6481
+ {
6482
+ role: "treeitem",
6483
+ "data-slot": "span-tree-row",
6484
+ "aria-level": depth + 1,
6485
+ "aria-posinset": posinset,
6486
+ "aria-setsize": setsize,
6487
+ "aria-selected": selected,
6488
+ "aria-expanded": hasChildren ? !collapsed : void 0,
6489
+ className: "flex items-center gap-2",
6490
+ style: { paddingLeft: `${depth * 16}px` },
6491
+ children: [
6492
+ hasChildren ? /* @__PURE__ */ jsx(
6493
+ "button",
6494
+ {
6495
+ type: "button",
6496
+ "aria-label": collapsed ? `Expand ${span.name}` : `Collapse ${span.name}`,
6497
+ "aria-expanded": !collapsed,
6498
+ onClick: () => onToggle(span.id),
6499
+ className: "shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted/50",
6500
+ children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
6501
+ }
6502
+ ) : /* @__PURE__ */ jsx("span", { className: "w-4 shrink-0", "aria-hidden": "true" }),
6503
+ /* @__PURE__ */ jsxs(
6504
+ "button",
6505
+ {
6506
+ type: "button",
6507
+ tabIndex: -1,
6508
+ "aria-current": selected ? "true" : void 0,
6509
+ onClick: () => onSelect(span.id),
6510
+ className: cn(
6511
+ "flex min-w-0 flex-1 items-center gap-2 rounded-md py-1 pr-2 text-left text-body-sm hover:bg-muted/50",
6512
+ selected && "bg-muted font-medium"
6513
+ ),
6514
+ children: [
6515
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-mono text-xs", children: span.name }),
6516
+ showMetrics && dur !== null ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground text-xs", children: formatDurationMs(dur) }) : null,
6517
+ span.model ? /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", className: "shrink-0", children: span.model }) : null,
6518
+ showMetrics && (span.inputTokens !== void 0 || span.outputTokens !== void 0) ? /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground text-xs", children: [
6519
+ span.inputTokens ?? 0,
6520
+ "\u2192",
6521
+ span.outputTokens ?? 0
6522
+ ] }) : null,
6523
+ error ? /* @__PURE__ */ jsx(BadgeWithDot, { variant: "destructive", className: "shrink-0", children: "error" }) : null,
6524
+ renderBadge?.(span),
6525
+ bounds ? /* @__PURE__ */ jsx(InlineBar, { span, bounds, error }) : null
6526
+ ]
6527
+ }
6528
+ )
6529
+ ]
6530
+ }
6531
+ );
6532
+ }
6533
+ function InlineBar({
6534
+ span,
6535
+ bounds,
6536
+ error
6537
+ }) {
6538
+ const { leftPct, widthPct, unbounded } = computeBarLayout(
6539
+ span.startTime,
6540
+ span.endTime,
6541
+ bounds.startNs,
6542
+ bounds.endNs
6543
+ );
6544
+ return /* @__PURE__ */ jsx(
6545
+ "span",
6546
+ {
6547
+ className: "relative ml-2 block h-4 min-w-[64px] flex-1 overflow-hidden rounded-sm bg-muted/30",
6548
+ "aria-hidden": "true",
6549
+ children: /* @__PURE__ */ jsx(
6550
+ "span",
6551
+ {
6552
+ "data-slot": "span-tree-bar",
6553
+ "data-error": error ? "true" : void 0,
6554
+ "data-unbounded": unbounded ? "true" : void 0,
6555
+ style: { left: `${leftPct}%`, width: `max(${widthPct}%, 2px)` },
6556
+ className: cn(
6557
+ "absolute top-0.5 bottom-0.5 rounded-sm",
6558
+ error ? "bg-destructive" : "bg-primary/60",
6559
+ unbounded && "border border-primary/60 border-dashed bg-transparent"
6560
+ )
6561
+ }
6562
+ )
6563
+ }
6564
+ );
6565
+ }
6566
+ function SpanTreeVirtual({
6567
+ flat,
6568
+ selectedId,
6569
+ collapsed,
6570
+ showMetrics,
6571
+ bounds,
6572
+ onSelect,
6573
+ onToggle,
6574
+ renderBadge
6575
+ }) {
6576
+ const parentRef = useRef(null);
6577
+ const virtualizer = useVirtualizer({
6578
+ count: flat.length,
6579
+ getScrollElement: () => parentRef.current,
6580
+ estimateSize: () => 32,
6581
+ overscan: 20,
6582
+ // A default viewport so rows render before the real ResizeObserver measures the element
6583
+ // (SSR/jsdom have no layout). The real observer overrides this in the browser.
6584
+ initialRect: { width: 320, height: 640 }
6585
+ });
6586
+ useEffect(() => {
6587
+ const index = flat.findIndex((f) => f.span.id === selectedId);
6588
+ if (index >= 0) virtualizer.scrollToIndex(index, { align: "center", behavior: "auto" });
6589
+ }, [selectedId]);
6590
+ return /* @__PURE__ */ jsx("div", { ref: parentRef, "data-slot": "span-tree-virtual", className: "max-h-[70vh] overflow-auto", children: /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative", width: "100%" }, children: virtualizer.getVirtualItems().map((vi) => {
6591
+ const row = flat[vi.index];
6592
+ if (!row) return null;
6593
+ return /* @__PURE__ */ jsx(
6594
+ "div",
6595
+ {
6596
+ "data-index": vi.index,
6597
+ ref: virtualizer.measureElement,
6598
+ style: {
6599
+ position: "absolute",
6600
+ top: 0,
6601
+ left: 0,
6602
+ width: "100%",
6603
+ transform: `translateY(${vi.start}px)`
6604
+ },
6605
+ children: /* @__PURE__ */ jsx(
6606
+ SpanTreeRow,
6607
+ {
6608
+ span: row.span,
6609
+ depth: row.depth,
6610
+ selected: row.span.id === selectedId,
6611
+ collapsed: collapsed.has(row.span.id),
6612
+ hasChildren: (row.span.children?.length ?? 0) > 0,
6613
+ posinset: vi.index + 1,
6614
+ setsize: flat.length,
6615
+ showMetrics,
6616
+ bounds,
6617
+ onSelect,
6618
+ onToggle,
6619
+ renderBadge
6620
+ }
6621
+ )
6622
+ },
6623
+ `${vi.index}-${row.span.id}`
6624
+ );
6625
+ }) }) });
6626
+ }
6627
+ function SpanTree({
6628
+ root,
6629
+ selectedId,
6630
+ onSelect,
6631
+ collapsed,
6632
+ onToggleCollapse,
6633
+ showMetrics = true,
6634
+ bounds,
6635
+ virtualizeThreshold = VIRTUALIZE_THRESHOLD,
6636
+ renderBadge,
6637
+ className,
6638
+ "aria-label": ariaLabel = "Trace span tree"
6639
+ }) {
6640
+ const flat = useMemo(() => flattenVisible(root, collapsed), [root, collapsed]);
6641
+ const virtualized = flat.length >= virtualizeThreshold;
6642
+ const ctx = {
6643
+ selectedId,
6644
+ collapsed,
6645
+ showMetrics,
6646
+ bounds,
6647
+ onSelect,
6648
+ onToggle: onToggleCollapse,
6649
+ renderBadge
6650
+ };
6651
+ const onKeyDown = (e) => {
6652
+ const idx = flat.findIndex((f) => f.span.id === selectedId);
6653
+ const go = (i) => {
6654
+ const target = flat[i];
6655
+ if (target) onSelect(target.span.id);
6656
+ };
6657
+ if (e.key === "ArrowDown") {
6658
+ e.preventDefault();
6659
+ go(Math.min(idx + 1, flat.length - 1));
6660
+ } else if (e.key === "ArrowUp") {
6661
+ e.preventDefault();
6662
+ go(Math.max(idx - 1, 0));
6663
+ } else if (e.key === "Home") {
6664
+ e.preventDefault();
6665
+ go(0);
6666
+ } else if (e.key === "End") {
6667
+ e.preventDefault();
6668
+ go(flat.length - 1);
6669
+ } else if ((e.key === "Enter" || e.key === " ") && idx >= 0) {
6670
+ e.preventDefault();
6671
+ go(idx);
6672
+ }
6673
+ };
6674
+ return /* @__PURE__ */ jsx(
6675
+ "div",
6676
+ {
6677
+ role: "tree",
6678
+ "data-slot": "span-tree",
6679
+ "aria-label": ariaLabel,
6680
+ tabIndex: 0,
6681
+ onKeyDown,
6682
+ className: cn("outline-none focus-visible:ring-2 focus-visible:ring-ring", className),
6683
+ children: virtualized ? /* @__PURE__ */ jsx(SpanTreeVirtual, { flat, ...ctx }) : /* @__PURE__ */ jsx(RecursiveNode, { span: root, depth: 0, posinset: 1, setsize: 1, ctx })
6684
+ }
6685
+ );
6686
+ }
6687
+ function RecursiveNode({
6688
+ span,
6689
+ depth,
6690
+ posinset,
6691
+ setsize,
6692
+ ctx
6693
+ }) {
6694
+ const isCollapsed = ctx.collapsed.has(span.id);
6695
+ const children = span.children ?? [];
6696
+ return /* @__PURE__ */ jsxs("div", { children: [
6697
+ /* @__PURE__ */ jsx(
6698
+ SpanTreeRow,
6699
+ {
6700
+ span,
6701
+ depth,
6702
+ selected: span.id === ctx.selectedId,
6703
+ collapsed: isCollapsed,
6704
+ hasChildren: children.length > 0,
6705
+ posinset,
6706
+ setsize,
6707
+ showMetrics: ctx.showMetrics,
6708
+ bounds: ctx.bounds,
6709
+ onSelect: ctx.onSelect,
6710
+ onToggle: ctx.onToggle,
6711
+ renderBadge: ctx.renderBadge
6712
+ }
6713
+ ),
6714
+ !isCollapsed && children.length > 0 ? /* @__PURE__ */ jsx(
6715
+ "div",
6716
+ {
6717
+ role: "group",
6718
+ children: children.map((c, i) => /* @__PURE__ */ jsx(
6719
+ RecursiveNode,
6720
+ {
6721
+ span: c,
6722
+ depth: depth + 1,
6723
+ posinset: i + 1,
6724
+ setsize: children.length,
6725
+ ctx
6726
+ },
6727
+ `${i}-${c.id}`
6728
+ ))
6729
+ }
6730
+ ) : null
6731
+ ] });
6732
+ }
6733
+ var AXIS_PX = 600;
6734
+ var defaultTimestamp = (ns) => new Date(Number(ns / 1000000n)).toISOString();
6735
+ var defaultCost = (usd) => `$${usd.toFixed(4)}`;
6736
+ function SpanWaterfall({
6737
+ root,
6738
+ bounds,
6739
+ selectedId,
6740
+ onSelect,
6741
+ collapsed,
6742
+ formatTimestamp = defaultTimestamp,
6743
+ formatCostUsd = defaultCost,
6744
+ className,
6745
+ "aria-label": ariaLabel = "Trace waterfall"
6746
+ }) {
6747
+ const laneRef = useRef(null);
6748
+ const [needle, setNeedle] = useState(null);
6749
+ const window2 = useMemo(() => bounds ?? computeTraceBounds(root), [bounds, root]);
6750
+ const visible = useMemo(() => flattenVisible(root, collapsed), [root, collapsed]);
6751
+ const rows = useMemo(() => {
6752
+ const items = visible.map((f) => {
6753
+ const startNs = toNs(f.span.startTime);
6754
+ if (startNs === null) return null;
6755
+ return { id: f.span.id, startNs, endNs: toNs(f.span.endTime ?? null) ?? startNs };
6756
+ }).filter((x) => x !== null);
6757
+ return packRows(items);
6758
+ }, [visible]);
6759
+ const spanNs = window2.endNs > window2.startNs ? window2.endNs - window2.startNs : 0n;
6760
+ const ticks = useMemo(() => niceAxisTicks(window2.startNs, window2.endNs, AXIS_PX), [window2]);
6761
+ const onMove = (e) => {
6762
+ if (spanNs <= 0n) return;
6763
+ const rect = laneRef.current?.getBoundingClientRect();
6764
+ const width = rect?.width ?? 0;
6765
+ const frac = width > 0 ? Math.min(Math.max((e.clientX - (rect?.left ?? 0)) / width, 0), 1) : 0;
6766
+ const offsetNs = BigInt(Math.round(frac * Number(spanNs)));
6767
+ setNeedle({ leftPct: frac * 100, label: formatTimestamp(window2.startNs + offsetNs) });
6768
+ };
6769
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "span-waterfall", "aria-label": ariaLabel, className: cn("space-y-1", className), children: [
6770
+ ticks.length > 0 ? /* @__PURE__ */ jsx(
6771
+ "div",
6772
+ {
6773
+ className: "relative mb-1 h-4 w-full text-[10px] text-muted-foreground",
6774
+ "aria-hidden": "true",
6775
+ children: ticks.map((t) => /* @__PURE__ */ jsx(
6776
+ "span",
6777
+ {
6778
+ "data-slot": "span-waterfall-tick",
6779
+ className: "-translate-x-1/2 absolute whitespace-nowrap",
6780
+ style: { left: `${Math.min(t.leftPct, 100)}%` },
6781
+ children: t.label
6782
+ },
6783
+ t.offsetNs.toString()
6784
+ ))
6785
+ }
6786
+ ) : null,
6787
+ /* @__PURE__ */ jsxs(
6788
+ "div",
6789
+ {
6790
+ ref: laneRef,
6791
+ className: "relative space-y-0.5",
6792
+ onMouseMove: onMove,
6793
+ onMouseLeave: () => setNeedle(null),
6794
+ children: [
6795
+ needle ? /* @__PURE__ */ jsx(
6796
+ "span",
6797
+ {
6798
+ "data-slot": "span-waterfall-needle",
6799
+ "aria-hidden": "true",
6800
+ className: "-top-4 -translate-x-1/2 pointer-events-none absolute z-10 whitespace-nowrap rounded bg-foreground px-1 text-[10px] text-background",
6801
+ style: { left: `${Math.min(needle.leftPct, 100)}%` },
6802
+ children: needle.label
6803
+ }
6804
+ ) : null,
6805
+ visible.map((f) => /* @__PURE__ */ jsx(
6806
+ WaterfallRow,
6807
+ {
6808
+ span: f.span,
6809
+ bounds: window2,
6810
+ row: rows.get(f.span.id) ?? 0,
6811
+ selected: f.span.id === selectedId,
6812
+ onSelect,
6813
+ formatCostUsd
6814
+ },
6815
+ f.span.id
6816
+ ))
6817
+ ]
6818
+ }
6819
+ )
6820
+ ] });
6821
+ }
6822
+ function WaterfallRow({
6823
+ span,
6824
+ bounds,
6825
+ row,
6826
+ selected,
6827
+ onSelect,
6828
+ formatCostUsd
6829
+ }) {
6830
+ const { leftPct, widthPct, unbounded } = computeBarLayout(
6831
+ span.startTime,
6832
+ span.endTime,
6833
+ bounds.startNs,
6834
+ bounds.endNs
6835
+ );
6836
+ const error = isSpanError(span.status);
6837
+ const hasChildren = (span.children?.length ?? 0) > 0;
6838
+ const rollupCost = hasChildren ? aggregateCost(span) : 0;
6839
+ const dur = durationMs(span);
6840
+ const tip = `${span.name} \xB7 ${formatDurationMs(dur)}`;
6841
+ return /* @__PURE__ */ jsxs(
6842
+ "button",
6843
+ {
6844
+ type: "button",
6845
+ "data-slot": "span-waterfall-row",
6846
+ "aria-label": `Timeline: ${tip}`,
6847
+ "aria-current": selected ? "true" : void 0,
6848
+ onClick: () => onSelect(span.id),
6849
+ title: tip,
6850
+ className: "flex w-full items-center gap-2 rounded-sm py-0.5 text-left hover:bg-muted/40",
6851
+ children: [
6852
+ /* @__PURE__ */ jsx(
6853
+ "span",
6854
+ {
6855
+ "aria-hidden": "true",
6856
+ className: "min-w-0 shrink-0 basis-40 truncate font-mono text-xs",
6857
+ style: { paddingLeft: `${row * 8}px` },
6858
+ children: span.name
6859
+ }
6860
+ ),
6861
+ /* @__PURE__ */ jsx(
6862
+ "span",
6863
+ {
6864
+ className: "relative block h-4 flex-1 overflow-hidden rounded-sm bg-muted/30",
6865
+ "aria-hidden": "true",
6866
+ children: /* @__PURE__ */ jsx(
6867
+ "span",
6868
+ {
6869
+ "data-slot": "span-waterfall-bar",
6870
+ "data-error": error ? "true" : void 0,
6871
+ "data-selected": selected ? "true" : void 0,
6872
+ "data-unbounded": unbounded ? "true" : void 0,
6873
+ style: { left: `${leftPct}%`, width: `${widthPct}%`, minWidth: "2px" },
6874
+ className: cn(
6875
+ "absolute top-0.5 bottom-0.5 rounded-sm",
6876
+ error ? "bg-destructive" : "bg-primary/60",
6877
+ selected && "ring-2 ring-primary ring-offset-1",
6878
+ unbounded && "border border-primary/60 border-dashed bg-transparent"
6879
+ )
6880
+ }
6881
+ )
6882
+ }
6883
+ ),
6884
+ hasChildren && rollupCost > 0 ? /* @__PURE__ */ jsxs(BadgeWithDot, { variant: "outline", className: "shrink-0 font-mono text-[10px]", children: [
6885
+ "\u2211 ",
6886
+ formatCostUsd(rollupCost)
6887
+ ] }) : null
6888
+ ]
6889
+ }
6890
+ );
6891
+ }
6892
+ function AttributesTable({
6893
+ attrs,
6894
+ maskedKeys,
6895
+ canReveal = false,
6896
+ promoted = [],
6897
+ defaultOpen = true
6898
+ }) {
6899
+ const safe = attrs ?? {};
6900
+ const groups = groupByNamespace(safe);
6901
+ const badges = promoted.filter((k) => k in safe).map((k) => ({ key: k, value: safe[k] }));
6902
+ if (groups.length === 0) {
6903
+ return /* @__PURE__ */ jsx(EmptyState, { title: "No attributes", description: "This span records none." });
6904
+ }
6905
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-slot": "attributes-table", children: [
6906
+ badges.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5", "data-testid": "attributes-badges", children: badges.map((b) => /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", title: b.key, children: typeof b.value === "string" ? b.value : JSON.stringify(b.value) }, b.key)) }) : null,
6907
+ groups.map((group) => /* @__PURE__ */ jsx(
6908
+ NamespaceCard,
6909
+ {
6910
+ group,
6911
+ canReveal,
6912
+ maskedKeys,
6913
+ defaultOpen
6914
+ },
6915
+ group.namespace
6916
+ ))
6917
+ ] });
6918
+ }
6919
+ function NamespaceCard({
6920
+ group,
6921
+ canReveal,
6922
+ maskedKeys,
6923
+ defaultOpen
6924
+ }) {
6925
+ const [open, setOpen] = useState(defaultOpen);
6926
+ return /* @__PURE__ */ jsxs(Card, { size: "sm", children: [
6927
+ /* @__PURE__ */ jsx(Card.Header, { className: "p-0", children: /* @__PURE__ */ jsxs(
6928
+ Button,
6929
+ {
6930
+ type: "button",
6931
+ variant: "ghost",
6932
+ size: "sm",
6933
+ "aria-expanded": open,
6934
+ "aria-label": `${open ? "Collapse" : "Expand"} ${group.namespace}`,
6935
+ onClick: () => setOpen((o) => !o),
6936
+ className: "w-full justify-start gap-1.5",
6937
+ children: [
6938
+ open ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" }),
6939
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-xs", children: `${group.namespace} (${group.entries.length})` })
6940
+ ]
6941
+ }
6942
+ ) }),
6943
+ open ? /* @__PURE__ */ jsx(Card.Body, { className: "pt-1", children: /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-1 gap-1 text-xs", children: group.entries.map(([key, value]) => /* @__PURE__ */ jsx(
6944
+ AttributeRow,
6945
+ {
6946
+ attrKey: key,
6947
+ value,
6948
+ masked: maskedKeys?.(key) ?? false,
6949
+ canReveal
6950
+ },
6951
+ key
6952
+ )) }) }) : null
6953
+ ] });
6954
+ }
6955
+ function AttributeRow({
6956
+ attrKey,
6957
+ value,
6958
+ masked,
6959
+ canReveal
6960
+ }) {
6961
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
6962
+ /* @__PURE__ */ jsx("dt", { className: "w-40 shrink-0 truncate font-mono text-muted-foreground", title: attrKey, children: attrKey }),
6963
+ /* @__PURE__ */ jsx("dd", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: masked ? /* @__PURE__ */ jsx(MaskedValue, { value: String(value), canReveal }) : /* @__PURE__ */ jsx(PlainValue, { value, attrKey }) })
6964
+ ] });
6965
+ }
6966
+ function PlainValue({ value, attrKey }) {
6967
+ if (value !== null && typeof value === "object") {
6968
+ return /* @__PURE__ */ jsx(JsonViewer, { value, collapsed: 1, enableCopy: true });
6969
+ }
6970
+ const text = String(value);
6971
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6972
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-mono", children: text }),
6973
+ /* @__PURE__ */ jsx(CopyButton, { value: text, "aria-label": `Copy ${attrKey}`, variant: "ghost", size: "sm" })
6974
+ ] });
6975
+ }
6976
+ function MaskedValue({ value, canReveal }) {
6977
+ const [revealed, setRevealed] = useState(false);
6978
+ if (revealed && canReveal) {
6979
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6980
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-mono", children: value }),
6981
+ /* @__PURE__ */ jsx(CopyButton, { value, "aria-label": "Copy value", variant: "ghost", size: "sm" })
6982
+ ] });
6983
+ }
6984
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6985
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", "aria-label": "Masked value", children: "\u2022\u2022\u2022" }),
6986
+ canReveal ? /* @__PURE__ */ jsx(
6987
+ Button,
6988
+ {
6989
+ type: "button",
6990
+ variant: "secondary",
6991
+ size: "sm",
6992
+ "aria-label": "Reveal masked value",
6993
+ title: "Reveal",
6994
+ onClick: () => setRevealed(true),
6995
+ children: "Reveal"
6996
+ }
6997
+ ) : /* @__PURE__ */ jsx(
6998
+ "span",
6999
+ {
7000
+ className: "text-muted-foreground text-xs",
7001
+ title: "You do not have permission to reveal this value",
7002
+ children: "masked"
7003
+ }
7004
+ )
7005
+ ] });
7006
+ }
7007
+ function roleVariant(role) {
7008
+ if (role === "user") return "primary";
7009
+ if (role === "assistant") return "accent";
7010
+ if (role === "system") return "default";
7011
+ return "outline";
7012
+ }
7013
+ function PlainText({ text }) {
7014
+ return /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words text-body-sm", children: text });
7015
+ }
7016
+ function MessageCard({ message, renderMarkdown }) {
7017
+ const [showRaw, setShowRaw] = useState(false);
7018
+ const redacted = isRedactedThinking(message);
7019
+ const body = contentText(message.content);
7020
+ return /* @__PURE__ */ jsx(Card, { size: "sm", "data-slot": "io-card", "data-testid": "io-card", children: /* @__PURE__ */ jsxs(Card.Body, { className: "space-y-2", children: [
7021
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7022
+ /* @__PURE__ */ jsx(BadgeWithDot, { variant: roleVariant(message.role), children: message.role }),
7023
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
7024
+ /* @__PURE__ */ jsx(
7025
+ Button,
7026
+ {
7027
+ type: "button",
7028
+ variant: "ghost",
7029
+ size: "sm",
7030
+ "aria-label": showRaw ? "Hide raw JSON" : "Show raw JSON",
7031
+ onClick: () => setShowRaw((r) => !r),
7032
+ children: showRaw ? "Hide JSON" : "Raw JSON"
7033
+ }
7034
+ ),
7035
+ /* @__PURE__ */ jsx(
7036
+ CopyButton,
7037
+ {
7038
+ value: JSON.stringify(message),
7039
+ "aria-label": "Copy message JSON",
7040
+ variant: "ghost",
7041
+ size: "sm"
7042
+ }
7043
+ )
7044
+ ] })
7045
+ ] }),
7046
+ redacted ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs italic", children: "[Redacted thinking]" }) : body ? renderMarkdown ? renderMarkdown(body) : /* @__PURE__ */ jsx(PlainText, { text: body }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: "\u2014" }),
7047
+ showRaw ? /* @__PURE__ */ jsx(JsonViewer, { value: message, collapsed: 1, enableCopy: true }) : null
7048
+ ] }) });
7049
+ }
7050
+ function ToolCallCard({ call }) {
7051
+ const name = call.function?.name ?? "(unnamed tool)";
7052
+ const args = call.function?.arguments ?? "";
7053
+ return /* @__PURE__ */ jsx(Card, { size: "sm", "data-slot": "io-tool-call", children: /* @__PURE__ */ jsxs(Card.Body, { className: "space-y-2", children: [
7054
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
7055
+ /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", children: "tool call" }),
7056
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-xs", children: name }),
7057
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground", children: call.id }),
7058
+ /* @__PURE__ */ jsx("div", { className: "ml-auto", children: /* @__PURE__ */ jsx(CopyButton, { value: args, "aria-label": "Copy tool arguments", variant: "ghost", size: "sm" }) })
7059
+ ] }),
7060
+ args ? /* @__PURE__ */ jsx(JsonViewerText, { text: prettyValue(args) }) : null
7061
+ ] }) });
7062
+ }
7063
+ function ToolResultCard({ message }) {
7064
+ const id = message.tool_call_id ?? "";
7065
+ const text = contentText(message.content);
7066
+ return /* @__PURE__ */ jsx(Card, { size: "sm", "data-slot": "io-tool-result", children: /* @__PURE__ */ jsxs(Card.Body, { className: "space-y-2", children: [
7067
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
7068
+ /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", children: "tool result" }),
7069
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
7070
+ "\u21B3 ",
7071
+ id
7072
+ ] }),
7073
+ /* @__PURE__ */ jsx("div", { className: "ml-auto", children: /* @__PURE__ */ jsx(CopyButton, { value: text, "aria-label": "Copy tool result", variant: "ghost", size: "sm" }) })
7074
+ ] }),
7075
+ /* @__PURE__ */ jsx(JsonViewerText, { text: prettyValue(text) })
7076
+ ] }) });
7077
+ }
7078
+ function JsonViewerText({ text }) {
7079
+ try {
7080
+ return /* @__PURE__ */ jsx(JsonViewer, { value: JSON.parse(text), collapsed: 1, enableCopy: true });
7081
+ } catch {
7082
+ return /* @__PURE__ */ jsx("pre", { className: "overflow-auto rounded-md border p-2 font-mono text-xs", children: text });
7083
+ }
7084
+ }
7085
+ function MessageItem({ message, renderMarkdown }) {
7086
+ if (message.role === "tool" && message.tool_call_id) {
7087
+ return /* @__PURE__ */ jsx(ToolResultCard, { message });
7088
+ }
7089
+ const calls = message.tool_calls ?? [];
7090
+ if (calls.length > 0) {
7091
+ const hasBody = contentText(message.content) !== "" || isRedactedThinking(message);
7092
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
7093
+ hasBody ? /* @__PURE__ */ jsx(MessageCard, { message, renderMarkdown }) : null,
7094
+ calls.map((c) => /* @__PURE__ */ jsx(ToolCallCard, { call: c }, c.id))
7095
+ ] });
7096
+ }
7097
+ return /* @__PURE__ */ jsx(MessageCard, { message, renderMarkdown });
7098
+ }
7099
+ var REDACTED2 = "<REDACTED>";
7100
+ var COLLAPSE_THRESHOLD = 8;
7101
+ var HEAD_TAIL = 3;
7102
+ function IOCards({ value, renderMarkdown, "data-testid": testId }) {
7103
+ if (value === void 0 || value === null || value === "") {
7104
+ return /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: "\u2014" });
7105
+ }
7106
+ if (value === REDACTED2) {
7107
+ return /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", "data-testid": testId, children: "Redacted by the client (prompt logging is off)." });
7108
+ }
7109
+ const chat = asChat(value);
7110
+ if (chat) {
7111
+ return /* @__PURE__ */ jsx("div", { "data-slot": "io-cards", "data-testid": testId, children: /* @__PURE__ */ jsx(MessageFeed, { messages: chat, renderMarkdown }) });
7112
+ }
7113
+ return /* @__PURE__ */ jsx(NonChatFallback, { value, testId });
7114
+ }
7115
+ function NonChatFallback({ value, testId }) {
7116
+ try {
7117
+ return /* @__PURE__ */ jsx(JsonViewer, { value: JSON.parse(value), collapsed: 1, enableCopy: true });
7118
+ } catch {
7119
+ return /* @__PURE__ */ jsx("pre", { "data-testid": testId, className: "overflow-auto rounded-md border p-2 font-mono text-xs", children: prettyValue(value) });
7120
+ }
7121
+ }
7122
+ function MessageFeed({
7123
+ messages,
7124
+ renderMarkdown
7125
+ }) {
7126
+ const [expanded, setExpanded] = useState(false);
7127
+ const collapsible = messages.length > COLLAPSE_THRESHOLD;
7128
+ if (!collapsible || expanded) {
7129
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
7130
+ messages.map((m, i) => (
7131
+ // biome-ignore lint/suspicious/noArrayIndexKey: chat messages carry no stable id; the transcript is append-only so index is the natural key
7132
+ /* @__PURE__ */ jsx(MessageItem, { message: m, renderMarkdown }, i)
7133
+ )),
7134
+ collapsible && expanded ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setExpanded(false), children: "Show less" }) : null
7135
+ ] });
7136
+ }
7137
+ const head = messages.slice(0, HEAD_TAIL);
7138
+ const tail = messages.slice(messages.length - HEAD_TAIL);
7139
+ const hiddenCount = messages.length - head.length - tail.length;
7140
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
7141
+ head.map((m, i) => (
7142
+ // biome-ignore lint/suspicious/noArrayIndexKey: chat messages carry no stable id (append-only feed)
7143
+ /* @__PURE__ */ jsx(MessageItem, { message: m, renderMarkdown }, `h-${i}`)
7144
+ )),
7145
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setExpanded(true), children: `Show ${hiddenCount} more` }),
7146
+ tail.map((m, i) => (
7147
+ // biome-ignore lint/suspicious/noArrayIndexKey: chat messages carry no stable id (append-only feed)
7148
+ /* @__PURE__ */ jsx(MessageItem, { message: m, renderMarkdown }, `t-${i}`)
7149
+ ))
7150
+ ] });
7151
+ }
7152
+ var defaultCost2 = (usd) => `$${usd.toFixed(4)}`;
7153
+ function TraceTranscript({
7154
+ root,
7155
+ rows: rowsProp,
7156
+ selectedId,
7157
+ onSelect,
7158
+ collapsedGroups,
7159
+ onToggleGroup,
7160
+ formatCostUsd = defaultCost2,
7161
+ virtualizeThreshold = 200,
7162
+ className,
7163
+ "aria-label": ariaLabel = "Trace transcript"
7164
+ }) {
7165
+ const allRows = useMemo(() => rowsProp ?? toTranscriptRows(root), [rowsProp, root]);
7166
+ const hidden = useMemo(() => collectHidden(root, collapsedGroups), [root, collapsedGroups]);
7167
+ const visible = useMemo(() => allRows.filter((r) => !hidden.has(r.spanId)), [allRows, hidden]);
7168
+ if (visible.length === 0) {
7169
+ return /* @__PURE__ */ jsx(EmptyState, { title: "No transcript", description: "This trace has no spans to show." });
7170
+ }
7171
+ const virtualized = visible.length >= virtualizeThreshold;
7172
+ const ctx = { selectedId, onSelect, collapsedGroups, onToggleGroup, formatCostUsd };
7173
+ return /* @__PURE__ */ jsx("div", { "data-slot": "trace-transcript", "aria-label": ariaLabel, className: cn("space-y-2", className), children: virtualized ? /* @__PURE__ */ jsx(VirtualFeed, { rows: visible, ...ctx }) : visible.map((row) => /* @__PURE__ */ jsx(Row3, { row, ...ctx }, `${row.kind}-${row.spanId}`)) });
7174
+ }
7175
+ function collectHidden(root, collapsed) {
7176
+ const hidden = /* @__PURE__ */ new Set();
7177
+ if (!collapsed || collapsed.size === 0) return hidden;
7178
+ const hideSubtree = (span) => {
7179
+ for (const c of span.children ?? []) {
7180
+ hidden.add(c.id);
7181
+ hideSubtree(c);
7182
+ }
7183
+ };
7184
+ const walk = (span) => {
7185
+ if (collapsed.has(span.id)) hideSubtree(span);
7186
+ for (const c of span.children ?? []) walk(c);
7187
+ };
7188
+ walk(root);
7189
+ return hidden;
7190
+ }
7191
+ function Row3({ row, ...ctx }) {
7192
+ if (row.kind === "group-header") {
7193
+ const collapsed = ctx.collapsedGroups?.has(row.groupId ?? "") ?? false;
7194
+ return /* @__PURE__ */ jsxs(
7195
+ "button",
7196
+ {
7197
+ type: "button",
7198
+ "data-slot": "transcript-group",
7199
+ "aria-expanded": !collapsed,
7200
+ "aria-label": collapsed ? "Expand group" : "Collapse group",
7201
+ onClick: () => ctx.onToggleGroup?.(row.groupId ?? ""),
7202
+ className: "flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-muted-foreground text-xs hover:bg-muted/40",
7203
+ children: [
7204
+ collapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }),
7205
+ "subagent group"
7206
+ ]
7207
+ }
7208
+ );
7209
+ }
7210
+ const selected = row.spanId === ctx.selectedId;
7211
+ const stats = row.stats;
7212
+ return /* @__PURE__ */ jsxs(
7213
+ "button",
7214
+ {
7215
+ type: "button",
7216
+ "data-slot": "transcript-span",
7217
+ "aria-current": selected ? "true" : void 0,
7218
+ onClick: () => ctx.onSelect(row.spanId),
7219
+ className: cn(
7220
+ "flex w-full flex-col gap-1 rounded-md border border-border p-2 text-left hover:bg-muted/40",
7221
+ selected && "bg-muted ring-1 ring-primary"
7222
+ ),
7223
+ children: [
7224
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7225
+ /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", className: "shrink-0", children: row.role ?? "span" }),
7226
+ stats ? /* @__PURE__ */ jsxs("span", { className: "ml-auto shrink-0 font-mono text-[10px] text-muted-foreground", children: [
7227
+ stats.inputTokens,
7228
+ "\u2192",
7229
+ stats.outputTokens,
7230
+ " \xB7 ",
7231
+ ctx.formatCostUsd(stats.costUsd),
7232
+ " \xB7",
7233
+ " ",
7234
+ formatDurationMs(stats.durationMs)
7235
+ ] }) : null
7236
+ ] }),
7237
+ row.preview ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 whitespace-pre-wrap break-words text-body-sm text-muted-foreground", children: row.preview }) : null
7238
+ ]
7239
+ }
7240
+ );
7241
+ }
7242
+ function VirtualFeed({ rows, ...ctx }) {
7243
+ const parentRef = useRef(null);
7244
+ const virtualizer = useVirtualizer({
7245
+ count: rows.length,
7246
+ getScrollElement: () => parentRef.current,
7247
+ estimateSize: () => 64,
7248
+ overscan: 12,
7249
+ initialRect: { width: 480, height: 640 }
7250
+ });
7251
+ return /* @__PURE__ */ jsx(
7252
+ "div",
7253
+ {
7254
+ ref: parentRef,
7255
+ "data-slot": "trace-transcript-virtual",
7256
+ className: "max-h-[70vh] overflow-auto",
7257
+ children: /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative", width: "100%" }, children: virtualizer.getVirtualItems().map((vi) => {
7258
+ const row = rows[vi.index];
7259
+ if (!row) return null;
7260
+ return /* @__PURE__ */ jsx(
7261
+ "div",
7262
+ {
7263
+ "data-index": vi.index,
7264
+ ref: virtualizer.measureElement,
7265
+ style: {
7266
+ position: "absolute",
7267
+ top: 0,
7268
+ left: 0,
7269
+ width: "100%",
7270
+ transform: `translateY(${vi.start}px)`,
7271
+ paddingBottom: 8
7272
+ },
7273
+ children: /* @__PURE__ */ jsx(Row3, { row, ...ctx })
7274
+ },
7275
+ `${vi.index}-${row.spanId}`
7276
+ );
7277
+ }) })
7278
+ }
7279
+ );
7280
+ }
7281
+ var NODE_W = 132;
7282
+ var NODE_H = 44;
7283
+ var PAD2 = 24;
7284
+ function SpanGraph({
7285
+ root,
7286
+ selectedId,
7287
+ onSelect,
7288
+ nodeCap = 200,
7289
+ className,
7290
+ "aria-label": ariaLabel = "Agent graph"
7291
+ }) {
7292
+ const graph = useMemo(
7293
+ () => buildLayeredGraph(root, selectedId ?? void 0, nodeCap),
7294
+ [root, selectedId, nodeCap]
7295
+ );
7296
+ if (graph.truncated) {
7297
+ return /* @__PURE__ */ jsxs(
7298
+ "div",
7299
+ {
7300
+ "data-slot": "span-graph",
7301
+ className: cn(
7302
+ "rounded-md border border-border border-dashed p-4 text-center text-body-sm text-muted-foreground",
7303
+ className
7304
+ ),
7305
+ children: [
7306
+ "Graph too large to render (",
7307
+ graph.count,
7308
+ " spans > ",
7309
+ nodeCap,
7310
+ " cap). Use the tree or waterfall view instead."
7311
+ ]
7312
+ }
7313
+ );
7314
+ }
7315
+ const width = graph.width + PAD2 * 2 + NODE_W;
7316
+ const height = graph.height + PAD2 * 2;
7317
+ const byId = new Map(graph.nodes.map((n) => [n.spanId, n]));
7318
+ return /* @__PURE__ */ jsx("div", { "data-slot": "span-graph", className: cn("overflow-auto", className), children: /* @__PURE__ */ jsxs(
7319
+ "svg",
7320
+ {
7321
+ role: "group",
7322
+ "aria-label": ariaLabel,
7323
+ width,
7324
+ height,
7325
+ viewBox: `0 0 ${width} ${height}`,
7326
+ className: "max-w-full",
7327
+ children: [
7328
+ /* @__PURE__ */ jsx("title", { children: ariaLabel }),
7329
+ graph.edges.map((e) => {
7330
+ const from = byId.get(e.from);
7331
+ const to = byId.get(e.to);
7332
+ if (!from || !to) return null;
7333
+ return /* @__PURE__ */ jsx(
7334
+ "line",
7335
+ {
7336
+ "data-slot": "span-graph-edge",
7337
+ "data-on-path": e.onPath ? "true" : void 0,
7338
+ x1: from.x + PAD2 + NODE_W / 2,
7339
+ y1: from.y + PAD2 + NODE_H,
7340
+ x2: to.x + PAD2 + NODE_W / 2,
7341
+ y2: to.y + PAD2,
7342
+ className: cn("stroke-1", e.onPath ? "stroke-primary" : "stroke-border")
7343
+ },
7344
+ `${e.from}-${e.to}`
7345
+ );
7346
+ }),
7347
+ graph.nodes.map((n) => /* @__PURE__ */ jsxs(
7348
+ "g",
7349
+ {
7350
+ "data-slot": "span-graph-node",
7351
+ "data-span-id": n.spanId,
7352
+ "data-error": n.error ? "true" : void 0,
7353
+ "data-selected": n.selected ? "true" : void 0,
7354
+ transform: `translate(${n.x + PAD2}, ${n.y + PAD2})`,
7355
+ onClick: () => onSelect(n.spanId),
7356
+ onKeyDown: (e) => {
7357
+ if (e.key === "Enter" || e.key === " ") {
7358
+ e.preventDefault();
7359
+ onSelect(n.spanId);
7360
+ }
7361
+ },
7362
+ tabIndex: 0,
7363
+ role: "button",
7364
+ "aria-label": `${KIND_LABEL[n.kind]}: ${n.name}`,
7365
+ "aria-current": n.selected ? "true" : void 0,
7366
+ className: "cursor-pointer outline-none focus-visible:[&>rect]:stroke-ring",
7367
+ children: [
7368
+ /* @__PURE__ */ jsx(
7369
+ "rect",
7370
+ {
7371
+ width: NODE_W,
7372
+ height: NODE_H,
7373
+ rx: 6,
7374
+ className: cn(
7375
+ "fill-card stroke-1",
7376
+ n.error ? "stroke-destructive" : "stroke-border",
7377
+ n.selected && "stroke-2 stroke-primary"
7378
+ )
7379
+ }
7380
+ ),
7381
+ /* @__PURE__ */ jsx("circle", { cx: 12, cy: NODE_H / 2, r: 4, className: dotClass(n.kind) }),
7382
+ /* @__PURE__ */ jsx("text", { x: 24, y: NODE_H / 2 - 2, className: "fill-foreground font-mono text-[10px]", children: truncate(n.name, 16) }),
7383
+ /* @__PURE__ */ jsx("text", { x: 24, y: NODE_H / 2 + 12, className: "fill-muted-foreground text-[9px]", children: KIND_LABEL[n.kind] })
7384
+ ]
7385
+ },
7386
+ n.spanId
7387
+ ))
7388
+ ]
7389
+ }
7390
+ ) });
7391
+ }
7392
+ function truncate(s, max) {
7393
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
7394
+ }
7395
+ function dotClass(kind) {
7396
+ switch (kind) {
7397
+ case "llm":
7398
+ return "fill-primary";
7399
+ case "tool":
7400
+ return "fill-warning";
7401
+ case "retriever":
7402
+ return "fill-info";
7403
+ case "embedding":
7404
+ return "fill-accent";
7405
+ case "chain":
7406
+ return "fill-success";
7407
+ default:
7408
+ return "fill-muted-foreground";
7409
+ }
7410
+ }
7411
+ var defaultCost3 = (usd) => `$${usd.toFixed(4)}`;
7412
+ function TraceCompare({
7413
+ laneA,
7414
+ laneB,
7415
+ align = alignSpanTrees,
7416
+ formatCostUsd = defaultCost3,
7417
+ className
7418
+ }) {
7419
+ const pending = laneA.pending || laneB.pending;
7420
+ const rows = useMemo(
7421
+ () => pending ? [] : align(laneA.root, laneB.root),
7422
+ [pending, align, laneA.root, laneB.root]
7423
+ );
7424
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), "data-slot": "trace-compare", children: [
7425
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 lg:grid-cols-2", children: [
7426
+ /* @__PURE__ */ jsx(LaneCard, { lane: laneA, formatCostUsd }),
7427
+ /* @__PURE__ */ jsx(LaneCard, { lane: laneB, formatCostUsd })
7428
+ ] }),
7429
+ pending ? null : /* @__PURE__ */ jsx(DiffTable, { rows, formatCostUsd })
7430
+ ] });
7431
+ }
7432
+ function LaneCard({
7433
+ lane,
7434
+ formatCostUsd
7435
+ }) {
7436
+ const m = useMemo(() => lane.pending ? null : traceMetrics(lane.root), [lane]);
7437
+ return /* @__PURE__ */ jsxs(Card, { size: "sm", children: [
7438
+ /* @__PURE__ */ jsx(Card.Header, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7439
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-sm", children: lane.id }),
7440
+ m?.hasError ? /* @__PURE__ */ jsx(BadgeWithDot, { variant: "destructive", children: "error" }) : null
7441
+ ] }) }),
7442
+ /* @__PURE__ */ jsx(Card.Body, { children: lane.pending || !m ? /* @__PURE__ */ jsxs("div", { "data-slot": "lane-pending", className: "space-y-2", children: [
7443
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-2/3" }),
7444
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/2" })
7445
+ ] }) : /* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-3 gap-2 text-xs", children: [
7446
+ /* @__PURE__ */ jsx(Stat, { label: "Duration", value: formatDurationMs(m.durationMs) }),
7447
+ /* @__PURE__ */ jsx(Stat, { label: "Tokens", value: String(m.totalTokens) }),
7448
+ /* @__PURE__ */ jsx(Stat, { label: "Cost", value: formatCostUsd(m.costUsd) })
7449
+ ] }) })
7450
+ ] });
7451
+ }
7452
+ function Stat({ label, value }) {
7453
+ return /* @__PURE__ */ jsxs("div", { children: [
7454
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: label }),
7455
+ /* @__PURE__ */ jsx("dd", { className: "font-medium font-mono", children: value })
7456
+ ] });
7457
+ }
7458
+ function DiffTable({
7459
+ rows,
7460
+ formatCostUsd
7461
+ }) {
7462
+ return /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
7463
+ /* @__PURE__ */ jsx("thead", { className: "bg-muted/40 text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
7464
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-left font-medium", children: "Span" }),
7465
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-right font-medium", children: "Duration" }),
7466
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-right font-medium", children: "Tokens" }),
7467
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-right font-medium", children: "Cost" })
7468
+ ] }) }),
7469
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsxs("tr", { "data-slot": "diff-row", className: "border-border border-t", children: [
7470
+ /* @__PURE__ */ jsxs(
7471
+ "th",
7472
+ {
7473
+ scope: "row",
7474
+ className: "px-2 py-1 text-left font-mono font-normal",
7475
+ style: { paddingLeft: `${row.depth * 12 + 8}px` },
7476
+ children: [
7477
+ row.key,
7478
+ row.status !== "matched" ? /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", className: "ml-2", children: row.status === "only-in-a" ? "only in A" : "only in B" }) : null
7479
+ ]
7480
+ }
7481
+ ),
7482
+ /* @__PURE__ */ jsx(
7483
+ DeltaCell,
7484
+ {
7485
+ delta: row.delta?.durationMs,
7486
+ format: (n) => formatDurationMs(n),
7487
+ lowerIsBetter: true
7488
+ }
7489
+ ),
7490
+ /* @__PURE__ */ jsx(
7491
+ DeltaCell,
7492
+ {
7493
+ delta: row.delta?.tokens,
7494
+ format: (n) => String(Math.round(n ?? 0)),
7495
+ lowerIsBetter: true
7496
+ }
7497
+ ),
7498
+ /* @__PURE__ */ jsx(
7499
+ DeltaCell,
7500
+ {
7501
+ delta: row.delta?.costUsd,
7502
+ format: (n) => formatCostUsd(n ?? 0),
7503
+ lowerIsBetter: true
7504
+ }
7505
+ )
7506
+ ] }, `${row.key}-${i}`)) })
7507
+ ] }) });
7508
+ }
7509
+ function DeltaCell({
7510
+ delta,
7511
+ format,
7512
+ lowerIsBetter
7513
+ }) {
7514
+ if (!delta) {
7515
+ return /* @__PURE__ */ jsx("td", { className: "px-2 py-1 text-right text-muted-foreground", children: "\u2014" });
7516
+ }
7517
+ const { from, to, deltaPct } = delta;
7518
+ const worse = deltaPct !== null && (lowerIsBetter ? deltaPct > 0 : deltaPct < 0);
7519
+ return /* @__PURE__ */ jsxs("td", { className: "px-2 py-1 text-right", children: [
7520
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", children: format(from) }),
7521
+ /* @__PURE__ */ jsx("span", { className: "mx-1 text-muted-foreground", children: "\u2192" }),
7522
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: format(to) }),
7523
+ deltaPct !== null && Math.abs(deltaPct) >= 0.5 ? /* @__PURE__ */ jsxs(
7524
+ "span",
7525
+ {
7526
+ "data-slot": "delta-badge",
7527
+ className: cn("ml-1 font-mono text-[10px]", worse ? "text-destructive" : "text-success"),
7528
+ children: [
7529
+ deltaPct > 0 ? "+" : "",
7530
+ deltaPct.toFixed(0),
7531
+ "%"
7532
+ ]
7533
+ }
7534
+ ) : null
7535
+ ] });
7536
+ }
7537
+
7538
+ // src/lib/session/aggregate.ts
7539
+ function sessionBounds(items) {
7540
+ let minStart = null;
7541
+ let maxEnd = null;
7542
+ for (const it of items) {
7543
+ const start = toNs(it.startTime);
7544
+ if (start === null) continue;
7545
+ if (minStart === null || start < minStart) minStart = start;
7546
+ const end = toNs(it.endTime ?? null) ?? start;
7547
+ const eff = end >= start ? end : start;
7548
+ if (maxEnd === null || eff > maxEnd) maxEnd = eff;
7549
+ }
7550
+ const s = minStart ?? 0n;
7551
+ return { startNs: s, endNs: maxEnd ?? s };
7552
+ }
7553
+ function aggregateSession(items) {
7554
+ let totalCostUsd = 0;
7555
+ let totalTokens = 0;
7556
+ let errorCount = 0;
7557
+ const models = /* @__PURE__ */ new Set();
7558
+ for (const it of items) {
7559
+ if (typeof it.costUsd === "number" && Number.isFinite(it.costUsd) && it.costUsd > 0) {
7560
+ totalCostUsd += it.costUsd;
7561
+ }
7562
+ if (typeof it.totalTokens === "number" && Number.isFinite(it.totalTokens) && it.totalTokens > 0) {
7563
+ totalTokens += it.totalTokens;
7564
+ }
7565
+ if (it.status === "ERROR") errorCount++;
7566
+ if (it.model) models.add(it.model);
7567
+ }
7568
+ const { startNs, endNs } = sessionBounds(items);
7569
+ const windowMs = Number((endNs - startNs) / 1000000n);
7570
+ return {
7571
+ traceCount: items.length,
7572
+ windowMs,
7573
+ totalCostUsd,
7574
+ totalTokens,
7575
+ errorCount,
7576
+ models: [...models].sort()
7577
+ };
7578
+ }
7579
+ var defaultCost4 = (usd) => `$${usd.toFixed(4)}`;
7580
+ function SessionSummary({
7581
+ items,
7582
+ metrics,
7583
+ formatCostUsd = defaultCost4,
7584
+ className,
7585
+ "aria-label": ariaLabel = "Session summary"
7586
+ }) {
7587
+ const m = useMemo(() => metrics ?? aggregateSession(items ?? []), [metrics, items]);
7588
+ return /* @__PURE__ */ jsxs(
7589
+ "dl",
7590
+ {
7591
+ "data-slot": "session-summary",
7592
+ "aria-label": ariaLabel,
7593
+ className: cn(
7594
+ "grid grid-cols-2 gap-3 rounded-md border border-border bg-muted/20 p-3 text-xs sm:grid-cols-3 lg:grid-cols-6",
7595
+ className
7596
+ ),
7597
+ children: [
7598
+ /* @__PURE__ */ jsx(Stat2, { label: "Traces", value: String(m.traceCount) }),
7599
+ /* @__PURE__ */ jsx(
7600
+ Stat2,
7601
+ {
7602
+ label: "Duration",
7603
+ value: m.windowMs > 0 ? formatDurationMs(m.windowMs) : "\u2014",
7604
+ testId: "session-duration"
7605
+ }
7606
+ ),
7607
+ /* @__PURE__ */ jsx(Stat2, { label: "Cost", value: formatCostUsd(m.totalCostUsd), testId: "session-cost" }),
7608
+ /* @__PURE__ */ jsx(Stat2, { label: "Tokens", value: String(m.totalTokens), testId: "session-tokens" }),
7609
+ /* @__PURE__ */ jsxs("div", { children: [
7610
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Errors" }),
7611
+ /* @__PURE__ */ jsx(
7612
+ "dd",
7613
+ {
7614
+ "data-slot": "session-error",
7615
+ "data-error": m.errorCount > 0 ? "true" : void 0,
7616
+ className: cn(
7617
+ "font-medium font-mono tabular-nums",
7618
+ m.errorCount > 0 && "text-destructive"
7619
+ ),
7620
+ children: m.errorCount
7621
+ }
7622
+ )
7623
+ ] }),
7624
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7625
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Models" }),
7626
+ /* @__PURE__ */ jsx("dd", { "data-testid": "session-models", className: "flex flex-wrap gap-1", children: m.models.length > 0 ? m.models.map((model) => /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", className: "font-mono", children: model }, model)) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" }) })
7627
+ ] })
7628
+ ]
7629
+ }
7630
+ );
7631
+ }
7632
+ function Stat2({ label, value, testId }) {
7633
+ return /* @__PURE__ */ jsxs("div", { children: [
7634
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: label }),
7635
+ /* @__PURE__ */ jsx("dd", { "data-testid": testId, className: "font-medium font-mono tabular-nums", children: value })
7636
+ ] });
7637
+ }
7638
+ var defaultTimestamp2 = (ns) => new Date(Number(ns / 1000000n)).toISOString();
7639
+ var defaultCost5 = (usd) => `$${usd.toFixed(4)}`;
7640
+ function SessionTimeline({
7641
+ items,
7642
+ selectedId,
7643
+ onSelect,
7644
+ formatTimestamp = defaultTimestamp2,
7645
+ formatCostUsd = defaultCost5,
7646
+ virtualizeThreshold = 200,
7647
+ className,
7648
+ "aria-label": ariaLabel = "Session timeline"
7649
+ }) {
7650
+ const sorted = useMemo(() => {
7651
+ const withNs = items.map((it) => ({ it, ns: toNs(it.startTime) ?? 0n }));
7652
+ withNs.sort((a, b) => a.ns < b.ns ? -1 : a.ns > b.ns ? 1 : a.it.id < b.it.id ? -1 : 1);
7653
+ return withNs.map((w) => w.it);
7654
+ }, [items]);
7655
+ const bounds = useMemo(() => sessionBounds(sorted), [sorted]);
7656
+ if (sorted.length === 0) {
7657
+ return /* @__PURE__ */ jsx(EmptyState, { title: "No traces", description: "This session has no traces to replay." });
7658
+ }
7659
+ const virtualized = sorted.length >= virtualizeThreshold;
7660
+ const ctx = { bounds, selectedId, onSelect, formatTimestamp, formatCostUsd };
7661
+ return (
7662
+ // biome-ignore lint/a11y/useSemanticElements: role="list" keeps the virtualized wrapper divs valid (a <div> cannot be an <li>)
7663
+ /* @__PURE__ */ jsx(
7664
+ "div",
7665
+ {
7666
+ role: "list",
7667
+ "data-slot": "session-timeline",
7668
+ "aria-label": ariaLabel,
7669
+ className: cn("space-y-1", className),
7670
+ children: virtualized ? /* @__PURE__ */ jsx(VirtualRows, { items: sorted, ...ctx }) : sorted.map((it) => /* @__PURE__ */ jsx(Row4, { item: it, ...ctx }, it.id))
7671
+ }
7672
+ )
7673
+ );
7674
+ }
7675
+ function Row4({ item, ...ctx }) {
7676
+ const { leftPct, widthPct, unbounded } = computeBarLayout(
7677
+ item.startTime,
7678
+ item.endTime,
7679
+ ctx.bounds.startNs,
7680
+ ctx.bounds.endNs
7681
+ );
7682
+ const error = isSpanError(item.status);
7683
+ const selected = item.id === ctx.selectedId;
7684
+ const startNs = toNs(item.startTime);
7685
+ const dur = durationMs({ startTime: item.startTime, endTime: item.endTime });
7686
+ return (
7687
+ // biome-ignore lint/a11y/useSemanticElements: pairs with the role="list" container (virtualized wrappers forbid <li>)
7688
+ /* @__PURE__ */ jsx("div", { role: "listitem", children: /* @__PURE__ */ jsxs(
7689
+ "button",
7690
+ {
7691
+ type: "button",
7692
+ "data-slot": "session-timeline-row",
7693
+ "aria-current": selected ? "true" : void 0,
7694
+ onClick: () => ctx.onSelect(item.id),
7695
+ className: cn(
7696
+ "flex w-full items-center gap-3 rounded-md p-2 text-left hover:bg-muted/40",
7697
+ selected && "bg-muted ring-1 ring-primary"
7698
+ ),
7699
+ children: [
7700
+ /* @__PURE__ */ jsx("span", { className: "w-32 shrink-0 font-mono text-[10px] text-muted-foreground", children: startNs !== null ? ctx.formatTimestamp(startNs) : "\u2014" }),
7701
+ /* @__PURE__ */ jsx("span", { "data-testid": "session-trace-name", className: "w-40 shrink-0 truncate font-mono text-xs", children: item.name ?? item.id }),
7702
+ /* @__PURE__ */ jsx(
7703
+ "span",
7704
+ {
7705
+ className: "relative block h-4 flex-1 overflow-hidden rounded-sm bg-muted/30",
7706
+ "aria-hidden": "true",
7707
+ children: /* @__PURE__ */ jsx(
7708
+ "span",
7709
+ {
7710
+ "data-slot": "session-timeline-bar",
7711
+ "data-error": error ? "true" : void 0,
7712
+ "data-unbounded": unbounded ? "true" : void 0,
7713
+ style: { left: `${leftPct}%`, width: `${widthPct}%`, minWidth: "2px" },
7714
+ className: cn(
7715
+ "absolute top-0.5 bottom-0.5 rounded-sm",
7716
+ error ? "bg-destructive" : "bg-primary/60",
7717
+ unbounded && "border border-primary/60 border-dashed bg-transparent"
7718
+ )
7719
+ }
7720
+ )
7721
+ }
7722
+ ),
7723
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground tabular-nums", children: dur !== null ? formatDurationMs(dur) : "\u2014" }),
7724
+ item.costUsd !== void 0 ? /* @__PURE__ */ jsx(BadgeWithDot, { variant: "outline", className: "shrink-0 font-mono text-[10px]", children: ctx.formatCostUsd(item.costUsd) }) : null,
7725
+ error ? /* @__PURE__ */ jsx(BadgeWithDot, { variant: "destructive", className: "shrink-0", children: "error" }) : null
7726
+ ]
7727
+ }
7728
+ ) })
7729
+ );
7730
+ }
7731
+ function VirtualRows({ items, ...ctx }) {
7732
+ const parentRef = useRef(null);
7733
+ const virtualizer = useVirtualizer({
7734
+ count: items.length,
7735
+ getScrollElement: () => parentRef.current,
7736
+ estimateSize: () => 44,
7737
+ overscan: 12,
7738
+ initialRect: { width: 640, height: 640 }
7739
+ });
7740
+ return /* @__PURE__ */ jsx(
7741
+ "div",
7742
+ {
7743
+ ref: parentRef,
7744
+ "data-slot": "session-timeline-virtual",
7745
+ className: "max-h-[70vh] overflow-auto",
7746
+ children: /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative", width: "100%" }, children: virtualizer.getVirtualItems().map((vi) => {
7747
+ const item = items[vi.index];
7748
+ if (!item) return null;
7749
+ return /* @__PURE__ */ jsx(
7750
+ "div",
7751
+ {
7752
+ "data-index": vi.index,
7753
+ ref: virtualizer.measureElement,
7754
+ style: {
7755
+ position: "absolute",
7756
+ top: 0,
7757
+ left: 0,
7758
+ width: "100%",
7759
+ transform: `translateY(${vi.start}px)`
7760
+ },
7761
+ children: /* @__PURE__ */ jsx(Row4, { item, ...ctx })
7762
+ },
7763
+ `${vi.index}-${item.id}`
7764
+ );
7765
+ }) })
7766
+ }
7767
+ );
7768
+ }
7769
+
7770
+ // src/lib/chart/histogram.ts
7771
+ function computeHistogram(values, binCount) {
7772
+ if (!Number.isFinite(binCount) || binCount <= 0) return [];
7773
+ const finite = values.filter((v) => Number.isFinite(v));
7774
+ if (finite.length === 0) return [];
7775
+ const n = Math.floor(binCount);
7776
+ let min = Number.POSITIVE_INFINITY;
7777
+ let max = Number.NEGATIVE_INFINITY;
7778
+ for (const v of finite) {
7779
+ if (v < min) min = v;
7780
+ if (v > max) max = v;
7781
+ }
7782
+ const span = max - min;
7783
+ const width = span / n;
7784
+ const bins = Array.from({ length: n }, (_, i) => ({
7785
+ lo: span === 0 ? min : min + i * width,
7786
+ hi: span === 0 ? min : min + (i + 1) * width,
7787
+ count: 0
7788
+ }));
7789
+ for (const v of finite) {
7790
+ const idx = span === 0 ? 0 : Math.min(n - 1, Math.floor((v - min) / width));
7791
+ const bin = bins[idx];
7792
+ if (bin) bin.count += 1;
7793
+ }
7794
+ return bins;
7795
+ }
7796
+ var VIEW_W2 = 600;
7797
+ var PAD3 = { top: 8, right: 8, bottom: 4, left: 40 };
7798
+ var BAR_GAP = 2;
7799
+ var Histogram = forwardRef((props, ref) => {
7800
+ const {
7801
+ title,
7802
+ height = 180,
7803
+ valueFormatter = (v) => String(v),
7804
+ className,
7805
+ bins: binsProp,
7806
+ values,
7807
+ binCount,
7808
+ ...rest
7809
+ } = props;
7810
+ const bins = binsProp ?? computeHistogram(values ?? [], binCount ?? 0);
7811
+ const hasData = bins.length > 0 && bins.some((b) => b.count > 0);
7812
+ if (!hasData) {
7813
+ return /* @__PURE__ */ jsxs(
7814
+ "figure",
7815
+ {
7816
+ "data-slot": "histogram",
7817
+ ref,
7818
+ className: cn("space-y-1", className),
7819
+ "aria-label": `${title} \u2014 no data`,
7820
+ ...rest,
7821
+ children: [
7822
+ /* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
7823
+ /* @__PURE__ */ jsx(
7824
+ "div",
7825
+ {
7826
+ "data-slot": "histogram-empty",
7827
+ className: "flex items-center justify-center rounded-md border border-dashed text-body-sm text-muted-foreground",
7828
+ style: { height },
7829
+ children: "No data to distribute."
7830
+ }
7831
+ )
7832
+ ]
7833
+ }
7834
+ );
7835
+ }
7836
+ const countMax = niceMax(bins.map((b) => b.count)) || 1;
7837
+ const yScale = linScale([0, countMax], [height - PAD3.bottom, PAD3.top]);
7838
+ const plotW = VIEW_W2 - PAD3.left - PAD3.right;
7839
+ const slotW = plotW / bins.length;
7840
+ const barW = Math.max(1, slotW - BAR_GAP);
7841
+ return /* @__PURE__ */ jsxs("figure", { "data-slot": "histogram", ref, className: cn("space-y-1", className), ...rest, children: [
7842
+ /* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
7843
+ /* @__PURE__ */ jsxs(
7844
+ "svg",
7845
+ {
7846
+ viewBox: `0 0 ${VIEW_W2} ${height}`,
7847
+ preserveAspectRatio: "none",
7848
+ role: "img",
7849
+ "aria-label": `${title} distribution`,
7850
+ className: "w-full",
7851
+ style: { height },
7852
+ children: [
7853
+ /* @__PURE__ */ jsx(
7854
+ "line",
7855
+ {
7856
+ x1: PAD3.left,
7857
+ y1: height - PAD3.bottom,
7858
+ x2: VIEW_W2 - PAD3.right,
7859
+ y2: height - PAD3.bottom,
7860
+ className: "stroke-border",
7861
+ strokeWidth: 1
7862
+ }
7863
+ ),
7864
+ /* @__PURE__ */ jsx("text", { x: 4, y: PAD3.top + 8, className: "fill-muted-foreground text-[10px]", children: valueFormatter(countMax) }),
7865
+ /* @__PURE__ */ jsx("text", { x: 4, y: height - PAD3.bottom, className: "fill-muted-foreground text-[10px]", children: "0" }),
7866
+ bins.map((b, i) => {
7867
+ const y = yScale(b.count);
7868
+ const barH = Math.max(0, height - PAD3.bottom - y);
7869
+ return /* @__PURE__ */ jsx(
7870
+ "rect",
7871
+ {
7872
+ "data-slot": "histogram-bar",
7873
+ "data-count": b.count,
7874
+ x: PAD3.left + i * slotW + BAR_GAP / 2,
7875
+ y,
7876
+ width: barW,
7877
+ height: barH,
7878
+ className: "fill-primary",
7879
+ children: /* @__PURE__ */ jsx("title", { children: `${valueFormatter(b.lo)}\u2013${valueFormatter(b.hi)}: ${b.count}` })
7880
+ },
7881
+ i
7882
+ );
7883
+ })
7884
+ ]
7885
+ }
7886
+ ),
7887
+ /* @__PURE__ */ jsxs("table", { "data-slot": "histogram-table", className: "sr-only", children: [
7888
+ /* @__PURE__ */ jsxs("caption", { children: [
7889
+ title,
7890
+ " \u2014 data table"
7891
+ ] }),
7892
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
7893
+ /* @__PURE__ */ jsx("th", { children: "Range" }),
7894
+ /* @__PURE__ */ jsx("th", { children: "Count" })
7895
+ ] }) }),
7896
+ /* @__PURE__ */ jsx("tbody", { children: bins.map((b, i) => (
7897
+ // biome-ignore lint/suspicious/noArrayIndexKey: bins are positional buckets — index IS the identity
7898
+ /* @__PURE__ */ jsxs("tr", { children: [
7899
+ /* @__PURE__ */ jsx("td", { children: `${valueFormatter(b.lo)}\u2013${valueFormatter(b.hi)}` }),
7900
+ /* @__PURE__ */ jsx("td", { children: b.count })
7901
+ ] }, i)
7902
+ )) })
7903
+ ] })
7904
+ ] });
7905
+ });
7906
+ Histogram.displayName = "Histogram";
7907
+ var VIEW_W3 = 600;
7908
+ var PAD4 = { top: 8, right: 8, bottom: 4, left: 40 };
7909
+ function bandPath(upper, lower, xScale, yScale) {
7910
+ const runs = [];
7911
+ let run = [];
7912
+ for (let i = 0; i < upper.length; i++) {
7913
+ const u = upper[i];
7914
+ const l = lower[i];
7915
+ if (u !== void 0 && l !== void 0 && Number.isFinite(u) && Number.isFinite(l)) {
7916
+ run.push(i);
7917
+ } else if (run.length > 0) {
7918
+ runs.push(run);
7919
+ run = [];
7920
+ }
7921
+ }
7922
+ if (run.length > 0) runs.push(run);
7923
+ return runs.map((idxs) => {
7924
+ const top = idxs.map(
7925
+ (i, k) => `${k === 0 ? "M" : "L"}${xScale(i).toFixed(2)},${yScale(upper[i]).toFixed(2)}`
7926
+ );
7927
+ const bottom = idxs.slice().reverse().map((i) => `L${xScale(i).toFixed(2)},${yScale(lower[i]).toFixed(2)}`);
7928
+ return `${top.join(" ")} ${bottom.join(" ")} Z`;
7929
+ }).join(" ");
7930
+ }
7931
+ var PercentileChart = forwardRef((props, ref) => {
7932
+ const {
7933
+ title,
7934
+ buckets,
7935
+ height = 180,
7936
+ valueFormatter = (v) => String(v),
7937
+ className,
7938
+ ...rest
7939
+ } = props;
7940
+ const hasData = buckets.length > 0;
7941
+ if (!hasData) {
7942
+ return /* @__PURE__ */ jsxs(
7943
+ "figure",
7944
+ {
7945
+ "data-slot": "percentile-chart",
7946
+ ref,
7947
+ className: cn("space-y-1", className),
7948
+ "aria-label": `${title} \u2014 no data`,
7949
+ ...rest,
7950
+ children: [
7951
+ /* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
7952
+ /* @__PURE__ */ jsx(
7953
+ "div",
7954
+ {
7955
+ "data-slot": "percentile-empty",
7956
+ className: "flex items-center justify-center rounded-md border border-dashed text-body-sm text-muted-foreground",
7957
+ style: { height },
7958
+ children: "No data in this time range."
7959
+ }
7960
+ )
7961
+ ]
7962
+ }
7963
+ );
7964
+ }
7965
+ const p50 = buckets.map((b) => b.p50);
7966
+ const p95 = buckets.map((b) => b.p95);
7967
+ const p99 = buckets.map((b) => b.p99);
7968
+ const yMax = niceMax([...p99]) || 1;
7969
+ const xScale = linScale([0, Math.max(1, buckets.length - 1)], [PAD4.left, VIEW_W3 - PAD4.right]);
7970
+ const yScale = linScale([0, yMax], [height - PAD4.bottom, PAD4.top]);
7971
+ const p50Points = p50.map((y, x) => ({ x, y }));
7972
+ return /* @__PURE__ */ jsxs("figure", { "data-slot": "percentile-chart", ref, className: cn("space-y-1", className), ...rest, children: [
7973
+ /* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
7974
+ /* @__PURE__ */ jsxs(
7975
+ "svg",
7976
+ {
7977
+ viewBox: `0 0 ${VIEW_W3} ${height}`,
7978
+ preserveAspectRatio: "none",
7979
+ role: "img",
7980
+ "aria-label": `${title} percentiles`,
7981
+ className: "w-full",
7982
+ style: { height },
7983
+ children: [
7984
+ /* @__PURE__ */ jsx(
7985
+ "line",
7986
+ {
7987
+ x1: PAD4.left,
7988
+ y1: height - PAD4.bottom,
7989
+ x2: VIEW_W3 - PAD4.right,
7990
+ y2: height - PAD4.bottom,
7991
+ className: "stroke-border",
7992
+ strokeWidth: 1
7993
+ }
7994
+ ),
7995
+ /* @__PURE__ */ jsx("text", { x: 4, y: PAD4.top + 8, className: "fill-muted-foreground text-[10px]", children: valueFormatter(yMax) }),
7996
+ /* @__PURE__ */ jsx("text", { x: 4, y: height - PAD4.bottom, className: "fill-muted-foreground text-[10px]", children: "0" }),
7997
+ /* @__PURE__ */ jsx(
7998
+ "path",
7999
+ {
8000
+ "data-slot": "percentile-band",
8001
+ "data-band": "p95-p99",
8002
+ d: bandPath(p99, p95, xScale, yScale),
8003
+ className: "fill-primary",
8004
+ fillOpacity: 0.15,
8005
+ children: /* @__PURE__ */ jsx("title", { children: "p95\u2013p99" })
8006
+ }
8007
+ ),
8008
+ /* @__PURE__ */ jsx(
8009
+ "path",
8010
+ {
8011
+ "data-slot": "percentile-band",
8012
+ "data-band": "p50-p95",
8013
+ d: bandPath(p95, p50, xScale, yScale),
8014
+ className: "fill-primary",
8015
+ fillOpacity: 0.3,
8016
+ children: /* @__PURE__ */ jsx("title", { children: "p50\u2013p95" })
8017
+ }
8018
+ ),
8019
+ /* @__PURE__ */ jsx(
8020
+ "path",
8021
+ {
8022
+ "data-slot": "percentile-p50-line",
8023
+ d: seriesPath(p50Points, xScale, yScale),
8024
+ fill: "none",
8025
+ className: "stroke-primary",
8026
+ strokeWidth: 1.5,
8027
+ children: /* @__PURE__ */ jsx("title", { children: "p50" })
8028
+ }
8029
+ )
8030
+ ]
8031
+ }
8032
+ ),
8033
+ /* @__PURE__ */ jsxs("table", { "data-slot": "percentile-table", className: "sr-only", children: [
8034
+ /* @__PURE__ */ jsxs("caption", { children: [
8035
+ title,
8036
+ " \u2014 data table"
8037
+ ] }),
8038
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
8039
+ /* @__PURE__ */ jsx("th", { children: "Bucket" }),
8040
+ /* @__PURE__ */ jsx("th", { children: "p50" }),
8041
+ /* @__PURE__ */ jsx("th", { children: "p95" }),
8042
+ /* @__PURE__ */ jsx("th", { children: "p99" })
8043
+ ] }) }),
8044
+ /* @__PURE__ */ jsx("tbody", { children: buckets.map((b, i) => (
8045
+ // biome-ignore lint/suspicious/noArrayIndexKey: buckets are positional time slots — index IS the identity
8046
+ /* @__PURE__ */ jsxs("tr", { children: [
8047
+ /* @__PURE__ */ jsx("td", { children: b.label ?? i + 1 }),
8048
+ /* @__PURE__ */ jsx("td", { children: Number.isFinite(b.p50) ? valueFormatter(b.p50) : "\u2014" }),
8049
+ /* @__PURE__ */ jsx("td", { children: Number.isFinite(b.p95) ? valueFormatter(b.p95) : "\u2014" }),
8050
+ /* @__PURE__ */ jsx("td", { children: Number.isFinite(b.p99) ? valueFormatter(b.p99) : "\u2014" })
8051
+ ] }, i)
8052
+ )) })
8053
+ ] })
8054
+ ] });
8055
+ });
8056
+ PercentileChart.displayName = "PercentileChart";
8057
+ function AnnotationInput(props) {
8058
+ const { name, description, required, disabled, className } = props;
8059
+ const generatedId = useId();
8060
+ const controlId = props.id ?? generatedId;
8061
+ const descId = description ? `${controlId}-desc` : void 0;
8062
+ const isCategorical = props.config.type === "categorical";
8063
+ const labelId = isCategorical ? `${controlId}-label` : void 0;
8064
+ const label = /* @__PURE__ */ jsx(Label, { id: labelId, htmlFor: isCategorical ? void 0 : controlId, required, children: name });
8065
+ const a11y = {
8066
+ "aria-required": required || void 0,
8067
+ "aria-describedby": descId,
8068
+ disabled
8069
+ };
8070
+ let control = null;
8071
+ if (props.config.type === "categorical") {
8072
+ const { config, value, onValueChange } = props;
8073
+ control = /* @__PURE__ */ jsx(
8074
+ RadioGroup,
8075
+ {
8076
+ value: value ?? "",
8077
+ onValueChange: (v) => onValueChange(v === "" ? null : v),
8078
+ "aria-labelledby": labelId,
8079
+ className: "gap-1.5",
8080
+ ...a11y,
8081
+ children: config.options.map((opt) => {
8082
+ const optId = `${controlId}-${opt.label}`;
8083
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
8084
+ /* @__PURE__ */ jsx(RadioGroup.Item, { value: opt.label, id: optId, disabled }),
8085
+ /* @__PURE__ */ jsx(Label, { htmlFor: optId, className: "font-normal", children: opt.label })
8086
+ ] }, opt.label);
8087
+ })
8088
+ }
8089
+ );
8090
+ } else if (props.config.type === "continuous") {
8091
+ const { config, value, onValueChange } = props;
8092
+ const onNumber = (e) => {
8093
+ const raw = e.target.value;
8094
+ if (raw === "") return onValueChange(null);
8095
+ const n = Number(raw);
8096
+ onValueChange(Number.isFinite(n) ? n : null);
8097
+ };
8098
+ control = /* @__PURE__ */ jsx(
8099
+ Input,
8100
+ {
8101
+ ...a11y,
8102
+ id: controlId,
8103
+ type: "number",
8104
+ min: config.min,
8105
+ max: config.max,
8106
+ step: config.step,
8107
+ value: value ?? "",
8108
+ onChange: onNumber
8109
+ }
8110
+ );
8111
+ } else if (props.config.type === "freeform") {
8112
+ const { config, value, onValueChange } = props;
8113
+ const onText = (e) => onValueChange(e.target.value === "" ? null : e.target.value);
8114
+ control = /* @__PURE__ */ jsx(
8115
+ Textarea,
8116
+ {
8117
+ ...a11y,
8118
+ id: controlId,
8119
+ maxLength: config.maxLength,
8120
+ value: value ?? "",
8121
+ onChange: onText
8122
+ }
8123
+ );
8124
+ }
8125
+ return /* @__PURE__ */ jsxs(
8126
+ "div",
8127
+ {
8128
+ "data-slot": "annotation-input",
8129
+ "data-type": props.config.type,
8130
+ className: cn("space-y-1.5", className),
8131
+ children: [
8132
+ label,
8133
+ description ? /* @__PURE__ */ jsx("p", { id: descId, className: "text-body-sm text-muted-foreground", children: description }) : null,
8134
+ control
8135
+ ]
8136
+ }
8137
+ );
8138
+ }
8139
+
8140
+ // src/components/composites/annotation-input/types.ts
8141
+ function isCategoricalConfig(c) {
8142
+ return c.type === "categorical";
8143
+ }
8144
+ function isContinuousConfig(c) {
8145
+ return c.type === "continuous";
8146
+ }
8147
+ function isFreeformConfig(c) {
8148
+ return c.type === "freeform";
8149
+ }
5974
8150
 
5975
- export { AccountMenu, ActionBar, Alert, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, DescriptionList, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FileDropzone, FormField, Input, JsonViewer, Label, LoginSplit, MetricCard, MetricsPanel, PageShell, Pagination, PinInput, PlanBadge, PreviewEnvCard, Progress, ProjectCard, RadioGroup, RollbackUI, ScrollArea, Select, Sheet, Sidebar, Skeleton, Slider, SocialAuthRow, StatTile, StatusDot, StatusIndicator, Stepper, Switch, Table, Tabs, TaskHeader, Textarea, Timestamp, Toast, Toaster, TooltipWithStatics as Tooltip, TopNav, TrendChart, UpdateBanner, avatarVariants, badgeVariants, buttonVariants, cn, computePageRange, deriveSteps, isNearBottom, linScale, matchesAccept, niceMax, seriesPath, sheetVariants, useStickToBottom, useToast, validateFiles };
8151
+ export { AccountMenu, ActionBar, Alert, AnnotationInput, AttributesTable, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, DescriptionList, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FileDropzone, FormField, GRAPH_NODE_CAP, Histogram, IOCards, Input, JsonViewer, KIND_LABEL, Label, LoginSplit, MessageCard, MessageItem, MetricCard, MetricsPanel, PageShell, Pagination, PercentileChart, PinInput, PlanBadge, PreviewEnvCard, Progress, ProjectCard, RadioGroup, RollbackUI, ScrollArea, Select, SessionSummary, SessionTimeline, Sheet, Sidebar, Skeleton, Slider, SocialAuthRow, SpanGraph, SpanTree, SpanTreeRow, SpanWaterfall, StatTile, StatusDot, StatusIndicator, Stepper, Switch, Table, Tabs, TaskHeader, Textarea, Timestamp, Toast, Toaster, TooltipWithStatics as Tooltip, TopNav, TraceCompare, TraceTranscript, TrendChart, UpdateBanner, VIRTUALIZE_THRESHOLD, aggregateCost, aggregateSession, alignSpanTrees, asChat, avatarVariants, badgeVariants, buildLayeredGraph, buttonVariants, cn, computeBarLayout, computeHistogram, computePageRange, computeTraceBounds, contentText, deriveSpanKind, deriveSteps, durationMs, flattenAll, flattenVisible, groupByNamespace, isCategoricalConfig, isContinuousConfig, isFreeformConfig, isNearBottom, isRedactedThinking, isSpanError, linScale, matchesAccept, niceAxisTicks, niceMax, packRows, prettyValue, seriesPath, sessionBounds, sheetVariants, spanCostUsd, toNs, toTranscriptRows, traceMetrics, useStickToBottom, useToast, validateFiles };