@tonyclaw/llm-inspector 1.16.3 → 1.16.5

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 (58) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-C1w4KUGZ.js +1 -0
  3. package/.output/public/assets/ReplayDialog-DR2Sgq_g.js +1 -0
  4. package/.output/public/assets/RequestAnatomy-DAre35kj.js +1 -0
  5. package/.output/public/assets/ResponseView-ackes7_g.js +1 -0
  6. package/.output/public/assets/StreamingChunkSequence-GrXwIGKA.js +1 -0
  7. package/.output/public/assets/index-BGzHFOEX.css +1 -0
  8. package/.output/public/assets/index-DX88k9br.js +101 -0
  9. package/.output/public/assets/json-viewer-C_QUhGeu.js +14 -0
  10. package/.output/public/assets/{main-Cpts3Ifr.js → main-CDMdNDY_.js} +1 -1
  11. package/.output/server/_libs/lucide-react.mjs +96 -76
  12. package/.output/server/_ssr/CompareDrawer-ftkJxyk6.mjs +1040 -0
  13. package/.output/server/_ssr/ReplayDialog-DcmE3lj5.mjs +321 -0
  14. package/.output/server/_ssr/RequestAnatomy-rK_LNMdG.mjs +351 -0
  15. package/.output/server/_ssr/ResponseView-CbQ4n-aJ.mjs +601 -0
  16. package/.output/server/_ssr/StreamingChunkSequence-84FZkIzv.mjs +301 -0
  17. package/.output/server/_ssr/{index-CjvQZBI0.mjs → index-CDjLoMsk.mjs} +1036 -2352
  18. package/.output/server/_ssr/index.mjs +2 -2
  19. package/.output/server/_ssr/json-viewer-B-qpM5xC.mjs +510 -0
  20. package/.output/server/_ssr/{router-CO9_4CVh.mjs → router-BrdjOUEW.mjs} +24 -14
  21. package/.output/server/{_tanstack-start-manifest_v-D-9SW7K3.mjs → _tanstack-start-manifest_v-DmOZEcJ3.mjs} +1 -1
  22. package/.output/server/index.mjs +72 -30
  23. package/package.json +1 -1
  24. package/src/components/OnboardingBanner.tsx +2 -2
  25. package/src/components/ProxyViewer.tsx +38 -26
  26. package/src/components/ProxyViewerContainer.tsx +3 -24
  27. package/src/components/proxy-viewer/CompareDrawer.tsx +6 -6
  28. package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
  29. package/src/components/proxy-viewer/ConversationHeader.tsx +4 -1
  30. package/src/components/proxy-viewer/LogEntry.tsx +230 -163
  31. package/src/components/proxy-viewer/LogEntryHeader.tsx +134 -36
  32. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +1 -1
  33. package/src/components/proxy-viewer/ThreadConnector.tsx +17 -2
  34. package/src/components/proxy-viewer/TurnGroup.tsx +94 -71
  35. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +98 -0
  36. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +196 -0
  37. package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +53 -0
  38. package/src/components/proxy-viewer/anatomy/types.ts +39 -0
  39. package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +114 -0
  40. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +4 -24
  41. package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
  42. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +6 -4
  43. package/src/components/proxy-viewer/lazy.ts +37 -0
  44. package/src/components/proxy-viewer/log-formats/anthropic.ts +146 -0
  45. package/src/components/proxy-viewer/log-formats/openai.ts +127 -0
  46. package/src/components/proxy-viewer/log-formats/types.ts +7 -0
  47. package/src/components/proxy-viewer/log-formats/unknown.ts +4 -0
  48. package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
  49. package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
  50. package/src/components/proxy-viewer/viewerState.ts +8 -0
  51. package/src/components/ui/crab-variants.tsx +11 -0
  52. package/src/components/ui/json-expansion-button.tsx +56 -0
  53. package/src/components/ui/json-viewer-bulk.ts +97 -0
  54. package/src/components/ui/json-viewer.tsx +129 -118
  55. package/src/lib/utils.ts +2 -3
  56. package/src/routes/api/logs.stream.ts +26 -16
  57. package/.output/public/assets/index-DRRCmu5p.css +0 -1
  58. package/.output/public/assets/index-DfjhkDNi.js +0 -107
@@ -0,0 +1,1040 @@
1
+ import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
+ import { g as getLogFormatAdapter, r as resolveLogFormat, a as getConversationId, c as cn, B as Badge, f as formatTokens } from "./index-CDjLoMsk.mjs";
3
+ import { JsonViewerFromString } from "./json-viewer-B-qpM5xC.mjs";
4
+ import "./router-BrdjOUEW.mjs";
5
+ import "../_libs/modelcontextprotocol__server.mjs";
6
+ import "../_libs/jszip.mjs";
7
+ import { X, B as Rows3, H as Columns2, l as Minus, P as Plus, k as Pencil, f as ChevronRight, I as Equal, C as Check, c as Copy } from "../_libs/lucide-react.mjs";
8
+ import "../_libs/swr.mjs";
9
+ import "../_libs/use-sync-external-store.mjs";
10
+ import "../_libs/dequal.mjs";
11
+ import "../_libs/clsx.mjs";
12
+ import "../_libs/tailwind-merge.mjs";
13
+ import "../_libs/class-variance-authority.mjs";
14
+ import "../_libs/radix-ui__react-dialog.mjs";
15
+ import "../_libs/radix-ui__primitive.mjs";
16
+ import "../_libs/radix-ui__react-compose-refs.mjs";
17
+ import "../_libs/radix-ui__react-context.mjs";
18
+ import "../_libs/radix-ui__react-id.mjs";
19
+ import "../_libs/@radix-ui/react-use-layout-effect+[...].mjs";
20
+ import "../_libs/@radix-ui/react-use-controllable-state+[...].mjs";
21
+ import "../_libs/@radix-ui/react-dismissable-layer+[...].mjs";
22
+ import "../_libs/radix-ui__react-primitive.mjs";
23
+ import "../_libs/react-dom.mjs";
24
+ import "util";
25
+ import "async_hooks";
26
+ import "stream";
27
+ import "crypto";
28
+ import "../_libs/radix-ui__react-slot.mjs";
29
+ import "../_libs/@radix-ui/react-use-callback-ref+[...].mjs";
30
+ import "../_libs/@radix-ui/react-use-escape-keydown+[...].mjs";
31
+ import "../_libs/radix-ui__react-focus-scope.mjs";
32
+ import "../_libs/radix-ui__react-portal.mjs";
33
+ import "../_libs/radix-ui__react-presence.mjs";
34
+ import "../_libs/radix-ui__react-focus-guards.mjs";
35
+ import "../_libs/react-remove-scroll.mjs";
36
+ import "tslib";
37
+ import "../_libs/react-remove-scroll-bar.mjs";
38
+ import "../_libs/react-style-singleton.mjs";
39
+ import "../_libs/get-nonce.mjs";
40
+ import "../_libs/use-sidecar.mjs";
41
+ import "../_libs/use-callback-ref.mjs";
42
+ import "../_libs/aria-hidden.mjs";
43
+ import "../_libs/diff.mjs";
44
+ import "../_libs/tanstack__react-virtual.mjs";
45
+ import "../_libs/tanstack__virtual-core.mjs";
46
+ import "../_libs/radix-ui__react-select.mjs";
47
+ import "../_libs/radix-ui__number.mjs";
48
+ import "../_libs/radix-ui__react-collection.mjs";
49
+ import "../_libs/radix-ui__react-direction.mjs";
50
+ import "../_libs/radix-ui__react-popper.mjs";
51
+ import "../_libs/floating-ui__react-dom.mjs";
52
+ import "../_libs/floating-ui__dom.mjs";
53
+ import "../_libs/floating-ui__core.mjs";
54
+ import "../_libs/floating-ui__utils.mjs";
55
+ import "../_libs/radix-ui__react-arrow.mjs";
56
+ import "../_libs/radix-ui__react-use-size.mjs";
57
+ import "../_libs/radix-ui__react-use-previous.mjs";
58
+ import "../_libs/@radix-ui/react-visually-hidden+[...].mjs";
59
+ import "../_libs/zod.mjs";
60
+ import "../_libs/radix-ui__react-tabs.mjs";
61
+ import "../_libs/radix-ui__react-roving-focus.mjs";
62
+ import "../_libs/radix-ui__react-tooltip.mjs";
63
+ import "../_libs/tanstack__react-router.mjs";
64
+ import "../_libs/tiny-warning.mjs";
65
+ import "../_libs/tanstack__router-core.mjs";
66
+ import "../_libs/cookie-es.mjs";
67
+ import "../_libs/tanstack__history.mjs";
68
+ import "../_libs/tiny-invariant.mjs";
69
+ import "../_libs/seroval.mjs";
70
+ import "../_libs/seroval-plugins.mjs";
71
+ import "node:stream/web";
72
+ import "node:stream";
73
+ import "../_libs/isbot.mjs";
74
+ import "node:fs";
75
+ import "node:fs/promises";
76
+ import "node:buffer";
77
+ import "node:path";
78
+ import "../_libs/conf.mjs";
79
+ import "node:util";
80
+ import "node:process";
81
+ import "node:crypto";
82
+ import "node:assert";
83
+ import "../_libs/dot-prop.mjs";
84
+ import "../_libs/env-paths.mjs";
85
+ import "node:os";
86
+ import "../_libs/atomically.mjs";
87
+ import "../_libs/stubborn-fs.mjs";
88
+ import "../_libs/stubborn-utils.mjs";
89
+ import "../_libs/when-exit.mjs";
90
+ import "../_libs/ajv.mjs";
91
+ import "../_libs/fast-deep-equal.mjs";
92
+ import "../_libs/json-schema-traverse.mjs";
93
+ import "../_libs/fast-uri.mjs";
94
+ import "../_libs/ajv-formats.mjs";
95
+ import "../_libs/debounce-fn.mjs";
96
+ import "../_libs/mimic-function.mjs";
97
+ import "../_libs/semver.mjs";
98
+ import "../_libs/uint8array-extras.mjs";
99
+ import "node:child_process";
100
+ import "../_libs/readable-stream.mjs";
101
+ import "events";
102
+ import "node:string_decoder";
103
+ import "../_libs/process-nextick-args.mjs";
104
+ import "../_libs/isarray.mjs";
105
+ import "../_libs/safe-buffer.mjs";
106
+ import "buffer";
107
+ import "../_libs/core-util-is.mjs";
108
+ import "../_libs/inherits.mjs";
109
+ import "../_libs/util-deprecate.mjs";
110
+ import "../_libs/lie.mjs";
111
+ import "../_libs/immediate.mjs";
112
+ import "../_libs/setimmediate.mjs";
113
+ import "../_libs/pako.mjs";
114
+ import "../_libs/react-markdown.mjs";
115
+ import "../_libs/devlop.mjs";
116
+ import "../_libs/unified.mjs";
117
+ import "../_libs/bail.mjs";
118
+ import "../_libs/extend.mjs";
119
+ import "../_libs/is-plain-obj.mjs";
120
+ import "../_libs/trough.mjs";
121
+ import "../_libs/vfile.mjs";
122
+ import "../_libs/vfile-message.mjs";
123
+ import "../_libs/unist-util-stringify-position.mjs";
124
+ import "node:url";
125
+ import "../_libs/remark-parse.mjs";
126
+ import "../_libs/mdast-util-from-markdown.mjs";
127
+ import "../_libs/micromark-util-decode-numeric-character-reference+[...].mjs";
128
+ import "../_libs/micromark-util-decode-string.mjs";
129
+ import "../_libs/decode-named-character-reference+[...].mjs";
130
+ import "../_libs/character-entities.mjs";
131
+ import "../_libs/micromark-util-normalize-identifier+[...].mjs";
132
+ import "../_libs/micromark.mjs";
133
+ import "../_libs/micromark-util-combine-extensions+[...].mjs";
134
+ import "../_libs/micromark-util-chunked.mjs";
135
+ import "../_libs/micromark-factory-space.mjs";
136
+ import "../_libs/micromark-util-character.mjs";
137
+ import "../_libs/micromark-core-commonmark.mjs";
138
+ import "../_libs/micromark-util-classify-character+[...].mjs";
139
+ import "../_libs/micromark-util-resolve-all.mjs";
140
+ import "../_libs/micromark-util-subtokenize.mjs";
141
+ import "../_libs/micromark-factory-destination.mjs";
142
+ import "../_libs/micromark-factory-label.mjs";
143
+ import "../_libs/micromark-factory-title.mjs";
144
+ import "../_libs/micromark-factory-whitespace.mjs";
145
+ import "../_libs/micromark-util-html-tag-name.mjs";
146
+ import "../_libs/mdast-util-to-string.mjs";
147
+ import "../_libs/remark-rehype.mjs";
148
+ import "../_libs/mdast-util-to-hast.mjs";
149
+ import "../_libs/ungap__structured-clone.mjs";
150
+ import "../_libs/micromark-util-sanitize-uri.mjs";
151
+ import "../_libs/unist-util-position.mjs";
152
+ import "../_libs/trim-lines.mjs";
153
+ import "../_libs/unist-util-visit.mjs";
154
+ import "../_libs/unist-util-visit-parents.mjs";
155
+ import "../_libs/unist-util-is.mjs";
156
+ import "../_libs/hast-util-to-jsx-runtime.mjs";
157
+ import "../_libs/comma-separated-tokens.mjs";
158
+ import "../_libs/property-information.mjs";
159
+ import "../_libs/space-separated-tokens.mjs";
160
+ import "../_libs/style-to-js.mjs";
161
+ import "../_libs/style-to-object.mjs";
162
+ import "../_libs/inline-style-parser.mjs";
163
+ import "../_libs/hast-util-whitespace.mjs";
164
+ import "../_libs/estree-util-is-identifier-name.mjs";
165
+ import "../_libs/html-url-attributes.mjs";
166
+ const ROOT_PATH = "";
167
+ function formatPath(segments) {
168
+ if (segments.length === 0) return ROOT_PATH;
169
+ let out = "";
170
+ for (let i = 0; i < segments.length; i++) {
171
+ const seg = segments[i];
172
+ if (seg === void 0) continue;
173
+ if (typeof seg === "number") {
174
+ out += `[${seg}]`;
175
+ } else if (i === 0) {
176
+ out += seg;
177
+ } else {
178
+ out += `.${seg}`;
179
+ }
180
+ }
181
+ return out;
182
+ }
183
+ function isPlainObject(value) {
184
+ return typeof value === "object" && value !== null && !Array.isArray(value);
185
+ }
186
+ function normalizeRequest(raw) {
187
+ if (typeof raw === "string") {
188
+ try {
189
+ return toNode(JSON.parse(raw));
190
+ } catch {
191
+ return { kind: "primitive", value: raw };
192
+ }
193
+ }
194
+ return toNode(raw);
195
+ }
196
+ function toNode(value) {
197
+ if (value === null) return { kind: "primitive", value: null };
198
+ if (typeof value === "string") return { kind: "primitive", value };
199
+ if (typeof value === "number") return { kind: "primitive", value };
200
+ if (typeof value === "boolean") return { kind: "primitive", value };
201
+ if (Array.isArray(value)) {
202
+ return { kind: "array", value: value.map((v) => toNode(v)) };
203
+ }
204
+ if (isPlainObject(value)) {
205
+ const out = {};
206
+ for (const k of Object.keys(value).sort()) {
207
+ out[k] = toNode(value[k]);
208
+ }
209
+ return { kind: "object", value: out };
210
+ }
211
+ return { kind: "primitive", value: null };
212
+ }
213
+ function diffTrees(left, right) {
214
+ const ops = [];
215
+ walk([], left, right, ops);
216
+ return ops;
217
+ }
218
+ function walk(segments, left, right, out) {
219
+ const path = formatPath(segments);
220
+ if (nodeEqual(left, right)) {
221
+ out.push({ kind: "equal", path, value: left });
222
+ return;
223
+ }
224
+ if (left.kind !== right.kind) {
225
+ out.push({ kind: "changed", path, left, right });
226
+ return;
227
+ }
228
+ if (left.kind === "primitive" && right.kind === "primitive") {
229
+ out.push({ kind: "changed", path, left, right });
230
+ return;
231
+ }
232
+ if (left.kind === "object" && right.kind === "object") {
233
+ const leftKeys = Object.keys(left.value);
234
+ const rightKeys = Object.keys(right.value);
235
+ const rightKeySet = new Set(rightKeys);
236
+ for (const k of leftKeys) {
237
+ const lChild = left.value[k];
238
+ if (lChild === void 0) continue;
239
+ if (!rightKeySet.has(k)) {
240
+ out.push({
241
+ kind: "removed",
242
+ path: formatPath([...segments, k]),
243
+ value: lChild
244
+ });
245
+ } else {
246
+ const rChild = right.value[k];
247
+ if (rChild === void 0) continue;
248
+ walk([...segments, k], lChild, rChild, out);
249
+ }
250
+ }
251
+ for (const k of rightKeys) {
252
+ if (leftKeys.includes(k)) continue;
253
+ const rChild = right.value[k];
254
+ if (rChild === void 0) continue;
255
+ out.push({
256
+ kind: "added",
257
+ path: formatPath([...segments, k]),
258
+ value: rChild
259
+ });
260
+ }
261
+ return;
262
+ }
263
+ if (left.kind === "array" && right.kind === "array") {
264
+ const minLen = Math.min(left.value.length, right.value.length);
265
+ for (let i = 0; i < minLen; i++) {
266
+ const lChild = left.value[i];
267
+ const rChild = right.value[i];
268
+ if (lChild === void 0 || rChild === void 0) continue;
269
+ walk([...segments, i], lChild, rChild, out);
270
+ }
271
+ for (let i = minLen; i < right.value.length; i++) {
272
+ const rChild = right.value[i];
273
+ if (rChild === void 0) continue;
274
+ out.push({
275
+ kind: "added",
276
+ path: formatPath([...segments, i]),
277
+ value: rChild
278
+ });
279
+ }
280
+ for (let i = minLen; i < left.value.length; i++) {
281
+ const lChild = left.value[i];
282
+ if (lChild === void 0) continue;
283
+ out.push({
284
+ kind: "removed",
285
+ path: formatPath([...segments, i]),
286
+ value: lChild
287
+ });
288
+ }
289
+ }
290
+ }
291
+ function nodeEqual(a, b) {
292
+ if (a.kind !== b.kind) return false;
293
+ if (a.kind === "primitive" && b.kind === "primitive") {
294
+ return a.value === b.value;
295
+ }
296
+ if (a.kind === "array" && b.kind === "array") {
297
+ if (a.value.length !== b.value.length) return false;
298
+ for (let i = 0; i < a.value.length; i++) {
299
+ const ai = a.value[i];
300
+ const bi = b.value[i];
301
+ if (ai === void 0 || bi === void 0) return false;
302
+ if (!nodeEqual(ai, bi)) return false;
303
+ }
304
+ return true;
305
+ }
306
+ if (a.kind === "object" && b.kind === "object") {
307
+ const aKeys = Object.keys(a.value);
308
+ const bKeys = Object.keys(b.value);
309
+ if (aKeys.length !== bKeys.length) return false;
310
+ for (const k of aKeys) {
311
+ const av = a.value[k];
312
+ const bv = b.value[k];
313
+ if (av === void 0 || bv === void 0) return false;
314
+ if (!nodeEqual(av, bv)) return false;
315
+ }
316
+ return true;
317
+ }
318
+ return false;
319
+ }
320
+ function previewNode(node, maxLen = 80) {
321
+ let s;
322
+ switch (node.kind) {
323
+ case "primitive":
324
+ s = node.value === null ? "null" : JSON.stringify(node.value);
325
+ break;
326
+ case "array":
327
+ s = `[… ${node.value.length} items]`;
328
+ break;
329
+ case "object":
330
+ s = `{… ${Object.keys(node.value).length} keys}`;
331
+ break;
332
+ }
333
+ if (s.length > maxLen) s = `${s.slice(0, maxLen - 1)}…`;
334
+ return s;
335
+ }
336
+ function nodeToJsonString(node, indent = 2) {
337
+ return JSON.stringify(nodeToJsonValue(node), null, indent);
338
+ }
339
+ function nodeToJsonValue(node) {
340
+ switch (node.kind) {
341
+ case "primitive":
342
+ return node.value;
343
+ case "array":
344
+ return node.value.map(nodeToJsonValue);
345
+ case "object": {
346
+ const out = {};
347
+ for (const [k, v] of Object.entries(node.value)) {
348
+ out[k] = nodeToJsonValue(v);
349
+ }
350
+ return out;
351
+ }
352
+ }
353
+ }
354
+ function parentPath(path) {
355
+ if (path === "") return "";
356
+ for (let i = path.length - 1; i >= 0; i--) {
357
+ const ch = path[i];
358
+ if (ch === "." || ch === "[") {
359
+ return path.substring(0, i);
360
+ }
361
+ }
362
+ return "";
363
+ }
364
+ function isDeepEqual(op) {
365
+ return op.kind === "equal" && (op.value.kind === "object" || op.value.kind === "array");
366
+ }
367
+ function groupContiguousEquals(ops) {
368
+ const out = [];
369
+ let i = 0;
370
+ while (i < ops.length) {
371
+ const op = ops[i];
372
+ if (op !== void 0 && isDeepEqual(op)) {
373
+ const startParent = parentPath(op.path);
374
+ let j = i + 1;
375
+ while (j < ops.length) {
376
+ const next = ops[j];
377
+ if (next === void 0) break;
378
+ if (!isDeepEqual(next)) break;
379
+ if (parentPath(next.path) !== startParent) break;
380
+ j++;
381
+ }
382
+ if (j - i > 1) {
383
+ const equalOps = [];
384
+ for (let k = i; k < j; k++) {
385
+ const eop = ops[k];
386
+ if (eop !== void 0 && eop.kind === "equal") {
387
+ equalOps.push(eop);
388
+ }
389
+ }
390
+ out.push({ kind: "equal-run", ops: equalOps });
391
+ i = j;
392
+ continue;
393
+ }
394
+ }
395
+ if (op !== void 0) {
396
+ out.push({ kind: "single", op });
397
+ }
398
+ i++;
399
+ }
400
+ return out;
401
+ }
402
+ const KIND_VISUAL = {
403
+ added: {
404
+ icon: Plus,
405
+ accent: "text-emerald-600 dark:text-emerald-400",
406
+ bg: "bg-emerald-500/5 hover:bg-emerald-500/10",
407
+ border: "border-l-emerald-500",
408
+ label: "ADDED"
409
+ },
410
+ removed: {
411
+ icon: Minus,
412
+ accent: "text-rose-600 dark:text-rose-400",
413
+ bg: "bg-rose-500/5 hover:bg-rose-500/10",
414
+ border: "border-l-rose-500",
415
+ label: "REMOVED"
416
+ },
417
+ changed: {
418
+ icon: Pencil,
419
+ accent: "text-amber-600 dark:text-amber-400",
420
+ bg: "bg-amber-500/5 hover:bg-amber-500/10",
421
+ border: "border-l-amber-500",
422
+ label: "CHANGED"
423
+ },
424
+ equal: {
425
+ icon: Equal,
426
+ accent: "text-muted-foreground/70",
427
+ bg: "bg-muted/20 hover:bg-muted/30",
428
+ border: "border-l-muted-foreground/20",
429
+ label: "EQUAL"
430
+ }
431
+ };
432
+ function EqualRunRow({
433
+ ops,
434
+ expanded,
435
+ onToggle
436
+ }) {
437
+ const first = ops[0];
438
+ const last = ops[ops.length - 1];
439
+ if (first === void 0 || last === void 0) {
440
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground/40 text-xs", children: "—" });
441
+ }
442
+ const firstPath = first.path;
443
+ const lastPath = last.path;
444
+ const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
445
+ const summary = first.value.kind === "array" ? `${ops.length} equal arrays` : first.value.kind === "object" ? `${ops.length} equal objects` : "equal";
446
+ const v = KIND_VISUAL.equal;
447
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("border-l-4 rounded-sm", v.border, v.bg), children: [
448
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
449
+ "button",
450
+ {
451
+ type: "button",
452
+ onClick: onToggle,
453
+ className: "w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",
454
+ children: [
455
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
456
+ ChevronRight,
457
+ {
458
+ className: cn("size-3 transition-transform shrink-0", expanded && "rotate-90")
459
+ }
460
+ ),
461
+ /* @__PURE__ */ jsxRuntimeExports.jsx(v.icon, { className: cn("size-3 shrink-0", v.accent) }),
462
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1", title: `${firstPath} … ${lastPath}`, children: label }),
463
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-[10px] uppercase tracking-wider shrink-0", v.accent), children: v.label }),
464
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 shrink-0", children: [
465
+ "(",
466
+ summary,
467
+ ")"
468
+ ] })
469
+ ]
470
+ }
471
+ ),
472
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-5 mt-1 mb-2 space-y-2 pr-2", children: ops.map((op) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border/50 rounded p-2 bg-muted/20", children: [
473
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
474
+ /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 0 })
475
+ ] }, op.path)) })
476
+ ] });
477
+ }
478
+ function UnifiedOpRow({
479
+ op,
480
+ idx,
481
+ copiedPath,
482
+ onCopyPath,
483
+ expanded,
484
+ onToggle
485
+ }) {
486
+ const v = KIND_VISUAL[op.kind];
487
+ const Icon = v.icon;
488
+ const isExpandable = op.kind === "added" || op.kind === "removed" ? op.value.kind === "object" || op.value.kind === "array" : op.kind === "changed" ? op.left.kind === "object" || op.left.kind === "array" || op.right.kind === "object" || op.right.kind === "array" : false;
489
+ const preview = op.kind === "changed" ? [
490
+ {
491
+ text: previewNode(op.left, 400),
492
+ tone: "text-rose-700 dark:text-rose-300 line-through"
493
+ },
494
+ { text: previewNode(op.right, 400), tone: "text-emerald-700 dark:text-emerald-300" }
495
+ ] : op.kind === "removed" ? [
496
+ {
497
+ text: previewNode(op.value, 400),
498
+ tone: "text-rose-700 dark:text-rose-300 line-through"
499
+ }
500
+ ] : op.kind === "added" ? [{ text: previewNode(op.value, 400), tone: "text-emerald-700 dark:text-emerald-300" }] : [{ text: previewNode(op.value, 400), tone: "text-muted-foreground" }];
501
+ const justCopied = copiedPath === op.path && op.path !== "";
502
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
503
+ "div",
504
+ {
505
+ "data-diff-idx": idx,
506
+ "data-diff-kind": op.kind,
507
+ className: cn("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors", v.border, v.bg),
508
+ children: [
509
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
510
+ "button",
511
+ {
512
+ type: "button",
513
+ onClick: onToggle,
514
+ disabled: !isExpandable,
515
+ className: cn(
516
+ "w-full flex items-center gap-2 text-xs text-left rounded-sm",
517
+ isExpandable ? "cursor-pointer" : "cursor-default"
518
+ ),
519
+ "aria-expanded": isExpandable ? expanded : void 0,
520
+ "aria-label": isExpandable ? expanded ? `Collapse ${op.path || "root"}` : `Expand ${op.path || "root"}` : void 0,
521
+ children: [
522
+ isExpandable ? /* @__PURE__ */ jsxRuntimeExports.jsx(
523
+ ChevronRight,
524
+ {
525
+ className: cn(
526
+ "size-3 shrink-0 transition-transform",
527
+ v.accent,
528
+ expanded && "rotate-90"
529
+ )
530
+ }
531
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "size-3 shrink-0", "aria-hidden": "true" }),
532
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: cn("size-3.5 shrink-0", v.accent), strokeWidth: 2.5 }),
533
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1 min-w-0", title: op.path || "(root)", children: op.path === "" ? "(root)" : op.path }),
534
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
535
+ "span",
536
+ {
537
+ className: cn(
538
+ "text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",
539
+ v.accent,
540
+ op.kind === "equal" ? "bg-muted/40" : "bg-background/60"
541
+ ),
542
+ children: v.label
543
+ }
544
+ ),
545
+ op.path !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx(
546
+ "span",
547
+ {
548
+ role: "button",
549
+ tabIndex: 0,
550
+ onClick: (e) => {
551
+ e.stopPropagation();
552
+ onCopyPath(op.path);
553
+ },
554
+ onKeyDown: (e) => {
555
+ if (e.key === "Enter" || e.key === " ") {
556
+ e.stopPropagation();
557
+ e.preventDefault();
558
+ onCopyPath(op.path);
559
+ }
560
+ },
561
+ className: cn(
562
+ "shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
563
+ justCopied ? "text-emerald-500" : "text-muted-foreground/50 hover:text-foreground hover:bg-muted"
564
+ ),
565
+ "aria-label": justCopied ? "Copied" : "Copy",
566
+ title: justCopied ? "Copied!" : "Copy",
567
+ children: justCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
568
+ }
569
+ )
570
+ ]
571
+ }
572
+ ),
573
+ preview.map((p, i) => (
574
+ // biome-ignore lint/suspicious/noArrayIndexKey: preview list is rebuilt on every render and is positional
575
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("font-mono text-xs mt-1 break-all pl-5", p.tone), children: p.text }, i)
576
+ )),
577
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
578
+ "div",
579
+ {
580
+ className: "overflow-hidden transition-all duration-200",
581
+ style: { maxHeight: expanded && isExpandable ? "2000px" : "0" },
582
+ "aria-hidden": !expanded,
583
+ children: expanded && isExpandable && op.kind !== "equal" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedSubtree, { op }) : null
584
+ }
585
+ )
586
+ ]
587
+ }
588
+ );
589
+ }
590
+ function ExpandedSubtree({ op }) {
591
+ if (op.kind === "added" || op.kind === "removed") {
592
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 0 }) });
593
+ }
594
+ const leftIsStructured = op.left.kind === "object" || op.left.kind === "array";
595
+ const rightIsStructured = op.right.kind === "object" || op.right.kind === "array";
596
+ if (!leftIsStructured && !rightIsStructured) {
597
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 text-xs text-muted-foreground/70 italic", children: "Primitive values are shown inline above." });
598
+ }
599
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2", children: [
600
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-rose-500/30 rounded p-2 bg-rose-500/5", children: [
601
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-rose-500 mb-1", children: "Old" }),
602
+ /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.left), defaultExpandDepth: 0 })
603
+ ] }),
604
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-emerald-500/30 rounded p-2 bg-emerald-500/5", children: [
605
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-emerald-500 mb-1", children: "New" }),
606
+ /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.right), defaultExpandDepth: 0 })
607
+ ] })
608
+ ] });
609
+ }
610
+ function SummaryChips({
611
+ counts,
612
+ onJumpTo
613
+ }) {
614
+ const total = counts.added + counts.removed + counts.changed;
615
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap", children: [
616
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-medium", children: [
617
+ total,
618
+ " ",
619
+ total === 1 ? "change" : "changes"
620
+ ] }),
621
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
622
+ "button",
623
+ {
624
+ type: "button",
625
+ onClick: () => onJumpTo("removed"),
626
+ disabled: counts.removed === 0,
627
+ className: cn(
628
+ "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
629
+ counts.removed > 0 ? "border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
630
+ ),
631
+ title: counts.removed > 0 ? "Jump to first removed" : "No removals",
632
+ children: [
633
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
634
+ counts.removed,
635
+ " removed"
636
+ ]
637
+ }
638
+ ),
639
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
640
+ "button",
641
+ {
642
+ type: "button",
643
+ onClick: () => onJumpTo("added"),
644
+ disabled: counts.added === 0,
645
+ className: cn(
646
+ "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
647
+ counts.added > 0 ? "border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
648
+ ),
649
+ title: counts.added > 0 ? "Jump to first added" : "No additions",
650
+ children: [
651
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-3" }),
652
+ counts.added,
653
+ " added"
654
+ ]
655
+ }
656
+ ),
657
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
658
+ "button",
659
+ {
660
+ type: "button",
661
+ onClick: () => onJumpTo("changed"),
662
+ disabled: counts.changed === 0,
663
+ className: cn(
664
+ "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
665
+ counts.changed > 0 ? "border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
666
+ ),
667
+ title: counts.changed > 0 ? "Jump to first changed" : "No changes",
668
+ children: [
669
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "size-3" }),
670
+ counts.changed,
671
+ " changed"
672
+ ]
673
+ }
674
+ )
675
+ ] });
676
+ }
677
+ function ModeToggle({
678
+ mode,
679
+ onChange
680
+ }) {
681
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "inline-flex rounded-md border border-border overflow-hidden", children: [
682
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
683
+ "button",
684
+ {
685
+ type: "button",
686
+ onClick: () => onChange("unified"),
687
+ "aria-pressed": mode === "unified",
688
+ className: cn(
689
+ "flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",
690
+ mode === "unified" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
691
+ ),
692
+ title: "Unified view (single column, emphasized diffs)",
693
+ children: [
694
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Rows3, { className: "size-3" }),
695
+ "Unified"
696
+ ]
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
700
+ "button",
701
+ {
702
+ type: "button",
703
+ onClick: () => onChange("split"),
704
+ "aria-pressed": mode === "split",
705
+ className: cn(
706
+ "flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",
707
+ mode === "split" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
708
+ ),
709
+ title: "Split view (path | left | right)",
710
+ children: [
711
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Columns2, { className: "size-3" }),
712
+ "Split"
713
+ ]
714
+ }
715
+ )
716
+ ] });
717
+ }
718
+ function SideSummary({ log, side }) {
719
+ const conversationId = getConversationId(log);
720
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 space-y-1 text-xs", children: [
721
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
722
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
723
+ Badge,
724
+ {
725
+ variant: "outline",
726
+ className: cn(
727
+ "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
728
+ side === "left" ? "border-rose-500/40 text-rose-400" : "border-emerald-500/40 text-emerald-400"
729
+ ),
730
+ children: side === "left" ? "← Left" : "Right →"
731
+ }
732
+ ),
733
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-blue-400/80", children: [
734
+ "#",
735
+ log.id
736
+ ] }),
737
+ log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground truncate", children: log.model })
738
+ ] }),
739
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-muted-foreground font-mono", children: [
740
+ log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400", children: [
741
+ "Cache +",
742
+ formatTokens(log.cacheCreationInputTokens)
743
+ ] }),
744
+ log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-purple-400", children: [
745
+ "Cache ~",
746
+ formatTokens(log.cacheReadInputTokens)
747
+ ] }),
748
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: log.timestamp, children: log.timestamp })
749
+ ] }),
750
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-muted-foreground/70 font-mono truncate", title: conversationId, children: [
751
+ "session: ",
752
+ conversationId
753
+ ] })
754
+ ] });
755
+ }
756
+ function CompareDrawer({ left, right, onClose }) {
757
+ const ops = reactExports.useMemo(() => {
758
+ const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
759
+ left.rawRequestBody
760
+ );
761
+ const rightRequest = getLogFormatAdapter(resolveLogFormat(right)).analyzeRequest(
762
+ right.rawRequestBody
763
+ );
764
+ const l = normalizeRequest(leftRequest.comparisonValue);
765
+ const r = normalizeRequest(rightRequest.comparisonValue);
766
+ return diffTrees(l, r);
767
+ }, [
768
+ left.apiFormat,
769
+ left.path,
770
+ left.rawRequestBody,
771
+ right.apiFormat,
772
+ right.path,
773
+ right.rawRequestBody
774
+ ]);
775
+ const grouped = reactExports.useMemo(() => groupContiguousEquals(ops), [ops]);
776
+ const counts = reactExports.useMemo(() => {
777
+ let added = 0;
778
+ let removed = 0;
779
+ let changed = 0;
780
+ for (const g of grouped) {
781
+ if (g.kind !== "single") continue;
782
+ switch (g.op.kind) {
783
+ case "added":
784
+ added++;
785
+ break;
786
+ case "removed":
787
+ removed++;
788
+ break;
789
+ case "changed":
790
+ changed++;
791
+ break;
792
+ }
793
+ }
794
+ return { added, removed, changed };
795
+ }, [grouped]);
796
+ const [expandedRuns, setExpandedRuns] = reactExports.useState(/* @__PURE__ */ new Set());
797
+ const toggleRun = (idx) => {
798
+ setExpandedRuns((prev) => {
799
+ const next = new Set(prev);
800
+ if (next.has(idx)) next.delete(idx);
801
+ else next.add(idx);
802
+ return next;
803
+ });
804
+ };
805
+ const [expandedRows, setExpandedRows] = reactExports.useState(/* @__PURE__ */ new Set());
806
+ const toggleRow = (idx) => {
807
+ setExpandedRows((prev) => {
808
+ const next = new Set(prev);
809
+ if (next.has(idx)) next.delete(idx);
810
+ else next.add(idx);
811
+ return next;
812
+ });
813
+ };
814
+ reactExports.useEffect(() => {
815
+ setExpandedRows(/* @__PURE__ */ new Set());
816
+ }, [left.id, right.id]);
817
+ const [mode, setMode] = reactExports.useState("unified");
818
+ const bodyRef = reactExports.useRef(null);
819
+ const [copiedPath, setCopiedPath] = reactExports.useState(null);
820
+ const copyResetTimer = reactExports.useRef(null);
821
+ const onCopyPath = (path) => {
822
+ void window.navigator.clipboard.writeText(path).then(() => {
823
+ setCopiedPath(path);
824
+ if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
825
+ copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
826
+ });
827
+ };
828
+ reactExports.useEffect(() => {
829
+ return () => {
830
+ if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
831
+ };
832
+ }, []);
833
+ const jumpToKind = (kind) => {
834
+ const idx = grouped.findIndex((g) => g.kind === "single" && g.op.kind === kind);
835
+ if (idx === -1) return;
836
+ const root = bodyRef.current;
837
+ if (root === null) return;
838
+ const el = root.querySelector(`[data-diff-idx="${idx}"]`);
839
+ if (el !== null) {
840
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
841
+ }
842
+ };
843
+ reactExports.useEffect(() => {
844
+ const onKey = (e) => {
845
+ if (e.key === "Escape") onClose();
846
+ };
847
+ document.addEventListener("keydown", onKey);
848
+ const prevOverflow = document.body.style.overflow;
849
+ document.body.style.overflow = "hidden";
850
+ return () => {
851
+ document.removeEventListener("keydown", onKey);
852
+ document.body.style.overflow = prevOverflow;
853
+ };
854
+ }, [onClose]);
855
+ const sameSession = getConversationId(left) === getConversationId(right);
856
+ const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
857
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
858
+ "div",
859
+ {
860
+ className: "fixed inset-0 z-50 flex justify-end",
861
+ role: "dialog",
862
+ "aria-modal": "true",
863
+ "aria-label": "Compare two log requests",
864
+ children: [
865
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
866
+ "button",
867
+ {
868
+ type: "button",
869
+ onClick: onClose,
870
+ "aria-label": "Close compare drawer",
871
+ className: "absolute inset-0 bg-black/40 cursor-default",
872
+ tabIndex: -1
873
+ }
874
+ ),
875
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
876
+ "div",
877
+ {
878
+ className: cn(
879
+ "relative bg-background border-l border-border shadow-xl",
880
+ "w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"
881
+ ),
882
+ onClick: (e) => e.stopPropagation(),
883
+ onKeyDown: (e) => e.stopPropagation(),
884
+ children: [
885
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-4 px-4 py-3 border-b border-border", children: [
886
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-4 min-w-0", children: [
887
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: left, side: "left" }),
888
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: right, side: "right" })
889
+ ] }),
890
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
891
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ModeToggle, { mode, onChange: setMode }),
892
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
893
+ "button",
894
+ {
895
+ type: "button",
896
+ onClick: onClose,
897
+ "aria-label": "Close",
898
+ className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",
899
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
900
+ }
901
+ )
902
+ ] })
903
+ ] }),
904
+ !sameSession && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border", children: "Heads up: the two selected logs are from different sessions." }),
905
+ allEqual ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm", children: "The two Request payloads are identical." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
906
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChips, { counts, onJumpTo: jumpToKind }),
907
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: bodyRef, className: "flex-1 min-h-0 overflow-y-auto", children: mode === "unified" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 space-y-0.5", children: grouped.map((g, i) => {
908
+ if (g.kind === "equal-run") {
909
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
910
+ EqualRunRow,
911
+ {
912
+ ops: g.ops,
913
+ expanded: expandedRuns.has(i),
914
+ onToggle: () => toggleRun(i)
915
+ },
916
+ `r${i}`
917
+ );
918
+ }
919
+ const op = g.op;
920
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
921
+ UnifiedOpRow,
922
+ {
923
+ op,
924
+ idx: i,
925
+ copiedPath,
926
+ onCopyPath,
927
+ expanded: expandedRows.has(i),
928
+ onToggle: () => toggleRow(i)
929
+ },
930
+ `o${i}`
931
+ );
932
+ }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(SplitBody, { grouped, left, right }) })
933
+ ] })
934
+ ]
935
+ }
936
+ )
937
+ ]
938
+ }
939
+ );
940
+ }
941
+ function SplitBody({
942
+ grouped,
943
+ left,
944
+ right
945
+ }) {
946
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs", children: [
947
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground", children: [
948
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Path" }),
949
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
950
+ "Left (Log #",
951
+ left.id,
952
+ ")"
953
+ ] }),
954
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
955
+ "Right (Log #",
956
+ right.id,
957
+ ")"
958
+ ] })
959
+ ] }),
960
+ grouped.map((g, i) => {
961
+ if (g.kind === "equal-run") {
962
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
963
+ "div",
964
+ {
965
+ className: "col-span-3 px-2 py-1 text-xs text-muted-foreground/60",
966
+ children: [
967
+ g.ops.length,
968
+ " equal siblings collapsed — switch to Unified to expand"
969
+ ]
970
+ },
971
+ i
972
+ );
973
+ }
974
+ const op = g.op;
975
+ if (op.kind === "equal") {
976
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
977
+ "div",
978
+ {
979
+ className: "col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",
980
+ children: [
981
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs truncate", title: op.path, children: op.path }),
982
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) }),
983
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) })
984
+ ]
985
+ },
986
+ i
987
+ );
988
+ }
989
+ if (op.kind === "added") {
990
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
991
+ "div",
992
+ {
993
+ className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",
994
+ children: [
995
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
996
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-emerald-300/90", children: [
997
+ "+ ",
998
+ previewNode(op.value, 400)
999
+ ] })
1000
+ ]
1001
+ },
1002
+ i
1003
+ );
1004
+ }
1005
+ if (op.kind === "removed") {
1006
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1007
+ "div",
1008
+ {
1009
+ className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",
1010
+ children: [
1011
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
1012
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-rose-300/90 line-through", children: [
1013
+ "− ",
1014
+ previewNode(op.value, 400)
1015
+ ] })
1016
+ ]
1017
+ },
1018
+ i
1019
+ );
1020
+ }
1021
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1022
+ "div",
1023
+ {
1024
+ className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",
1025
+ children: [
1026
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
1027
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1028
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-rose-300/90 break-all line-through", children: previewNode(op.left, 400) }),
1029
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-300/90 break-all", children: previewNode(op.right, 400) })
1030
+ ] })
1031
+ ]
1032
+ },
1033
+ i
1034
+ );
1035
+ })
1036
+ ] });
1037
+ }
1038
+ export {
1039
+ CompareDrawer
1040
+ };