@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
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, R as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, a as AnthropicRequestSchema } from "./router-CO9_4CVh.mjs";
2
+ import { C as CapturedLogSchema, R as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, a as AnthropicRequestSchema } from "./router-BrdjOUEW.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -9,16 +9,11 @@ import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as P
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
11
11
  import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
12
- import "../_libs/modelcontextprotocol__server.mjs";
13
- import { C as Check, X, D as Download, S as Settings, a as ChevronDown, U as Upload, b as Scan, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, T as Trash2, R as Rows3, i as Columns2, j as Minus, k as Pencil, E as Equal, l as EyeOff, m as Eye, n as ExternalLink, o as RotateCw, G as GitCompareArrows, p as RotateCcw, q as CircleCheckBig, W as Wrench, r as Globe, F as FileTerminal, s as Radio, t as CircleQuestionMark, u as Server, v as Gauge, w as Lock, x as Wifi, y as WifiOff, A as ArrowUp, z as ArrowDown, B as TriangleAlert, H as CircleStop, I as ChevronsUp, J as ChevronsDown, K as Brain, N as Terminal } from "../_libs/lucide-react.mjs";
14
- import { M as Markdown } from "../_libs/react-markdown.mjs";
12
+ import { C as Check, X, D as Download, S as Settings, a as ChevronDown, U as Upload, b as Scan, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, T as Trash2, E as EyeOff, i as Eye, j as ExternalLink, R as RotateCw, k as Pencil, G as GitCompareArrows, l as Minus, m as CircleCheckBig, O as OctagonAlert, n as TriangleAlert, W as Wrench, o as Globe, F as FileTerminal, p as Radio, q as ChevronsUp, r as ChevronsDown, s as RotateCcw, t as CircleQuestionMark, u as Server, v as Gauge, w as Lock, x as Wifi, y as WifiOff, A as ArrowUp, z as ArrowDown, B as Rows3, H as Columns2 } from "../_libs/lucide-react.mjs";
15
13
  import { u as union, d as object, a as array, l as literal, b as string, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
16
14
  import { R as Root2$1, L as List, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
17
15
  import { P as Provider, R as Root3, T as Trigger$3, a as Portal$2, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
18
16
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
19
- import { R as Root$1 } from "../_libs/radix-ui__react-separator.mjs";
20
- import { R as Root$2, C as CollapsibleTrigger$1, a as CollapsibleContent$1 } from "../_libs/radix-ui__react-collapsible.mjs";
21
- import { R as Root$3, V as Viewport$1, C as Corner, S as ScrollAreaScrollbar, a as ScrollAreaThumb } from "../_libs/radix-ui__react-scroll-area.mjs";
22
17
  import "../_libs/tanstack__react-router.mjs";
23
18
  import "../_libs/tiny-warning.mjs";
24
19
  import "../_libs/tanstack__router-core.mjs";
@@ -61,6 +56,7 @@ import "../_libs/mimic-function.mjs";
61
56
  import "../_libs/semver.mjs";
62
57
  import "../_libs/uint8array-extras.mjs";
63
58
  import "node:child_process";
59
+ import "../_libs/modelcontextprotocol__server.mjs";
64
60
  import "../_libs/use-sync-external-store.mjs";
65
61
  import "../_libs/dequal.mjs";
66
62
  import "../_libs/readable-stream.mjs";
@@ -112,57 +108,6 @@ import "../_libs/radix-ui__react-arrow.mjs";
112
108
  import "../_libs/radix-ui__react-use-size.mjs";
113
109
  import "../_libs/radix-ui__react-use-previous.mjs";
114
110
  import "../_libs/@radix-ui/react-visually-hidden+[...].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
111
  import "../_libs/radix-ui__react-roving-focus.mjs";
167
112
  const ApiErrorSchema = object({
168
113
  error: string()
@@ -327,10 +272,10 @@ function OnboardingBanner() {
327
272
  onClick: () => {
328
273
  void markSeen();
329
274
  },
330
- className: "inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-md border border-amber-500/40 text-amber-700 dark:text-amber-300 hover:bg-amber-500/10 transition-colors shrink-0",
275
+ className: "inline-flex items-center gap-1.5 text-xs h-8 px-3 rounded-md border border-amber-500/40 text-amber-700 dark:text-amber-300 hover:bg-amber-500/10 transition-colors shrink-0",
331
276
  "aria-label": "Dismiss onboarding tip",
332
277
  children: [
333
- /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3" }),
278
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5" }),
334
279
  "Got it"
335
280
  ]
336
281
  }
@@ -390,7 +335,7 @@ async function exportLogsAsZip(logs) {
390
335
  document.body.removeChild(anchor);
391
336
  URL.revokeObjectURL(url);
392
337
  }
393
- const version = "1.16.3";
338
+ const version = "1.16.5";
394
339
  const packageJson = {
395
340
  version
396
341
  };
@@ -398,9 +343,8 @@ function cn(...inputs) {
398
343
  return twMerge(clsx(inputs));
399
344
  }
400
345
  function formatTokens(count) {
401
- if (count >= 1048576) return (count / 1048576).toFixed(1).replace(/\.0$/, "") + "M";
402
- if (count >= 1024) return (count / 1024).toFixed(1).replace(/\.0$/, "") + "K";
403
- return count.toString();
346
+ if (count >= 1e6) return (count / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
347
+ return (count / 1e3).toFixed(1).replace(/\.0$/, "") + "K";
404
348
  }
405
349
  function getStatusCategory(status) {
406
350
  if (status === null) return "pending";
@@ -629,11 +573,14 @@ function ConversationHeader({
629
573
  {
630
574
  role: "button",
631
575
  tabIndex: 0,
576
+ "data-nav-id": `conv-${conversationId}`,
577
+ "data-nav-action": expanded ? "collapse" : "expand",
632
578
  className: cn(
633
579
  "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
634
580
  "hover:bg-muted/50",
635
581
  "select-none",
636
- "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10"
582
+ "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
583
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none"
637
584
  ),
638
585
  onClick: onToggle,
639
586
  onKeyDown: (e) => {
@@ -709,7 +656,7 @@ function ConversationHeader({
709
656
  onClick: handleClearClick,
710
657
  "aria-label": `Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`,
711
658
  title: "Clear this group",
712
- className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-6 rounded hover:bg-muted cursor-pointer",
659
+ className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer",
713
660
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" })
714
661
  }
715
662
  ),
@@ -783,7 +730,7 @@ function groupLogsByConversation(logs) {
783
730
  result.sort((a, b) => (a.logs[0]?.timestamp ?? "").localeCompare(b.logs[0]?.timestamp ?? ""));
784
731
  return result;
785
732
  }
786
- function isRecord$1(value) {
733
+ function isRecord(value) {
787
734
  return typeof value === "object" && value !== null && !Array.isArray(value);
788
735
  }
789
736
  function extractStopReason(log) {
@@ -793,14 +740,14 @@ function extractStopReason(log) {
793
740
  if (typeof json === "string") {
794
741
  json = JSON.parse(json);
795
742
  }
796
- if (!isRecord$1(json)) return null;
743
+ if (!isRecord(json)) return null;
797
744
  if (typeof json.stop_reason === "string") {
798
745
  if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
799
746
  return json.stop_reason;
800
747
  }
801
748
  return null;
802
749
  }
803
- if (Array.isArray(json.choices) && json.choices.length > 0 && isRecord$1(json.choices[0]) && typeof json.choices[0].finish_reason === "string" && json.choices[0].finish_reason === "stop") {
750
+ if (Array.isArray(json.choices) && json.choices.length > 0 && isRecord(json.choices[0]) && typeof json.choices[0].finish_reason === "string" && json.choices[0].finish_reason === "stop") {
804
751
  return "stop";
805
752
  }
806
753
  return null;
@@ -1151,8 +1098,17 @@ const crabVariants = [
1151
1098
  Crab11,
1152
1099
  Crab12
1153
1100
  ];
1154
- function getCrabVariant(index) {
1155
- return crabVariants[Math.abs(index) % crabVariants.length] ?? Crab1;
1101
+ function getCrabVariant(index2) {
1102
+ return crabVariants[Math.abs(index2) % crabVariants.length] ?? Crab1;
1103
+ }
1104
+ function getInteriorCrabVariantIndex(seed) {
1105
+ const interiorCount = crabVariants.length - 2;
1106
+ if (interiorCount <= 0) return 0;
1107
+ const normalizedSeed = Number.isFinite(seed) ? Math.trunc(seed) : 0;
1108
+ return 1 + (normalizedSeed % interiorCount + interiorCount) % interiorCount;
1109
+ }
1110
+ function getInteriorCrabVariant(seed) {
1111
+ return getCrabVariant(getInteriorCrabVariantIndex(seed));
1156
1112
  }
1157
1113
  const AnthropicLogoSvg = "data:image/svg+xml,%3csvg%20height='2500'%20viewBox='0%206.603%201192.672%201193.397'%20width='2500'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='m233.96%20800.215%20234.684-131.678%203.947-11.436-3.947-6.363h-11.436l-39.221-2.416-134.094-3.624-116.296-4.832-112.67-6.04-28.35-6.04-26.577-35.035%202.738-17.477%2023.84-16.027%2034.147%202.98%2075.463%205.155%20113.235%207.812%2082.147%204.832%20121.692%2012.644h19.329l2.738-7.812-6.604-4.832-5.154-4.832-117.182-79.41-126.845-83.92-66.443-48.321-35.92-24.484-18.12-22.953-7.813-50.093%2032.618-35.92%2043.812%202.98%2011.195%202.98%2044.375%2034.147%2094.792%2073.37%20123.786%2091.167%2018.12%2015.06%207.249-5.154.886-3.624-8.135-13.61-67.329-121.692-71.838-123.785-31.974-51.302-8.456-30.765c-2.98-12.645-5.154-23.275-5.154-36.242l37.127-50.416%2020.537-6.604%2049.53%206.604%2020.86%2018.121%2030.765%2070.39%2049.852%20110.818%2077.315%20150.684%2022.631%2044.698%2012.08%2041.396%204.51%2012.645h7.813v-7.248l6.362-84.886%2011.759-104.215%2011.436-134.094%203.946-37.772%2018.685-45.262%2037.127-24.482%2028.994%2013.852%2023.839%2034.148-3.303%2022.067-14.174%2092.134-27.785%20144.323-18.121%2096.644h10.55l12.08-12.08%2048.887-64.913%2082.147-102.685%2036.242-40.752%2042.282-45.02%2027.14-21.423h51.303l37.772%2056.135-16.913%2057.986-52.832%2067.007-43.812%2056.779-62.82%2084.563-39.22%2067.651%203.623%205.396%209.343-.886%20141.906-30.201%2076.671-13.852%2091.49-15.705%2041.396%2019.329%204.51%2019.65-16.269%2040.189-97.852%2024.16-114.764%2022.954-170.9%2040.43-2.093%201.53%202.416%202.98%2076.993%207.248%2032.94%201.771h80.617l150.12%2011.195%2039.222%2025.933%2023.517%2031.732-3.946%2024.16-60.403%2030.766-81.503-19.33-190.228-45.26-65.235-16.27h-9.02v5.397l54.362%2053.154%2099.624%2089.96%20124.752%20115.973%206.362%2028.671-16.027%2022.63-16.912-2.415-109.611-82.47-42.282-37.127-95.758-80.618h-6.363v8.456l22.067%2032.296%20116.537%20175.167%206.04%2053.719-8.456%2017.476-30.201%2010.55-33.181-6.04-68.215-95.758-70.39-107.84-56.778-96.644-6.926%203.947-33.503%20360.886-15.705%2018.443-36.243%2013.852-30.201-22.953-16.027-37.127%2016.027-73.37%2019.329-95.758%2015.704-76.107%2014.175-94.55%208.456-31.41-.563-2.094-6.927.886-71.275%2097.852-108.402%20146.497-85.772%2091.812-20.537%208.134-35.597-18.443%203.301-32.94%2019.893-29.315%20118.712-151.007%2071.597-93.583%2046.228-54.04-.322-7.813h-2.738l-315.302%20204.725-56.135%207.248-24.16-22.63%202.98-37.128%2011.435-12.08%2094.792-65.236-.322.323z'%20fill='%23d97757'/%3e%3c/svg%3e";
1158
1114
  const OpenAILogoSvg = "data:image/svg+xml,%3csvg%20height='2500'%20viewBox='-1%20-.1%20949.1%20959.8'%20width='2474'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='m925.8%20456.3c10.4%2023.2%2017%2048%2019.7%2073.3%202.6%2025.3%201.3%2050.9-4.1%2075.8-5.3%2024.9-14.5%2048.8-27.3%2070.8-8.4%2014.7-18.3%2028.5-29.7%2041.2-11.3%2012.6-23.9%2024-37.6%2034-13.8%2010-28.5%2018.4-44.1%2025.3-15.5%206.8-31.7%2012-48.3%2015.4-7.8%2024.2-19.4%2047.1-34.4%2067.7-14.9%2020.6-33%2038.7-53.6%2053.6-20.6%2015-43.4%2026.6-67.6%2034.4-24.2%207.9-49.5%2011.8-75%2011.8-16.9.1-33.9-1.7-50.5-5.1-16.5-3.5-32.7-8.8-48.2-15.7s-30.2-15.5-43.9-25.5c-13.6-10-26.2-21.5-37.4-34.2-25%205.4-50.6%206.7-75.9%204.1-25.3-2.7-50.1-9.3-73.4-19.7-23.2-10.3-44.7-24.3-63.6-41.4s-35-37.1-47.7-59.1c-8.5-14.7-15.5-30.2-20.8-46.3s-8.8-32.7-10.6-49.6c-1.8-16.8-1.7-33.8.1-50.7%201.8-16.8%205.5-33.4%2010.8-49.5-17-18.9-31-40.4-41.4-63.6-10.3-23.3-17-48-19.6-73.3-2.7-25.3-1.3-50.9%204-75.8s14.5-48.8%2027.3-70.8c8.4-14.7%2018.3-28.6%2029.6-41.2s24-24%2037.7-34%2028.5-18.5%2044-25.3c15.6-6.9%2031.8-12%2048.4-15.4%207.8-24.3%2019.4-47.1%2034.3-67.7%2015-20.6%2033.1-38.7%2053.7-53.7%2020.6-14.9%2043.4-26.5%2067.6-34.4%2024.2-7.8%2049.5-11.8%2075-11.7%2016.9-.1%2033.9%201.6%2050.5%205.1s32.8%208.7%2048.3%2015.6c15.5%207%2030.2%2015.5%2043.9%2025.5%2013.7%2010.1%2026.3%2021.5%2037.5%2034.2%2024.9-5.3%2050.5-6.6%2075.8-4s50%209.3%2073.3%2019.6c23.2%2010.4%2044.7%2024.3%2063.6%2041.4%2018.9%2017%2035%2036.9%2047.7%2059%208.5%2014.6%2015.5%2030.1%2020.8%2046.3%205.3%2016.1%208.9%2032.7%2010.6%2049.6%201.8%2016.9%201.8%2033.9-.1%2050.8-1.8%2016.9-5.5%2033.5-10.8%2049.6%2017.1%2018.9%2031%2040.3%2041.4%2063.6zm-333.2%20426.9c21.8-9%2041.6-22.3%2058.3-39s30-36.5%2039-58.4c9-21.8%2013.7-45.2%2013.7-68.8v-223q-.1-.3-.2-.7-.1-.3-.3-.6-.2-.3-.5-.5-.3-.3-.6-.4l-80.7-46.6v269.4c0%202.7-.4%205.5-1.1%208.1-.7%202.7-1.7%205.2-3.1%207.6s-3%204.6-5%206.5a32.1%2032.1%200%200%201%20-6.5%205l-191.1%20110.3c-1.6%201-4.3%202.4-5.7%203.2%207.9%206.7%2016.5%2012.6%2025.5%2017.8%209.1%205.2%2018.5%209.6%2028.3%2013.2%209.8%203.5%2019.9%206.2%2030.1%208%2010.3%201.8%2020.7%202.7%2031.1%202.7%2023.6%200%2047-4.7%2068.8-13.8zm-455.1-151.4c11.9%2020.5%2027.6%2038.3%2046.3%2052.7%2018.8%2014.4%2040.1%2024.9%2062.9%2031s46.6%207.7%2070%204.6%2045.9-10.7%2066.4-22.5l193.2-111.5.5-.5q.2-.2.3-.6.2-.3.3-.6v-94l-233.2%20134.9c-2.4%201.4-4.9%202.4-7.5%203.2-2.7.7-5.4%201-8.2%201-2.7%200-5.4-.3-8.1-1-2.6-.8-5.2-1.8-7.6-3.2l-191.1-110.4c-1.7-1-4.2-2.5-5.6-3.4-1.8%2010.3-2.7%2020.7-2.7%2031.1s1%2020.8%202.8%2031.1c1.8%2010.2%204.6%2020.3%208.1%2030.1%203.6%209.8%208%2019.2%2013.2%2028.2zm-50.2-417c-11.8%2020.5-19.4%2043.1-22.5%2066.5s-1.5%2047.1%204.6%2070c6.1%2022.8%2016.6%2044.1%2031%2062.9%2014.4%2018.7%2032.3%2034.4%2052.7%2046.2l193.1%20111.6q.3.1.7.2h.7q.4%200%20.7-.2.3-.1.6-.3l81-46.8-233.2-134.6c-2.3-1.4-4.5-3.1-6.5-5a32.1%2032.1%200%200%201%20-5-6.5c-1.3-2.4-2.4-4.9-3.1-7.6-.7-2.6-1.1-5.3-1-8.1v-227.1c-9.8%203.6-19.3%208-28.3%2013.2-9%205.3-17.5%2011.3-25.5%2018-7.9%206.7-15.3%2014.1-22%2022.1-6.7%207.9-12.6%2016.5-17.8%2025.5zm663.3%20154.4c2.4%201.4%204.6%203%206.6%205%201.9%201.9%203.6%204.1%205%206.5%201.3%202.4%202.4%205%203.1%207.6.6%202.7%201%205.4.9%208.2v227.1c32.1-11.8%2060.1-32.5%2080.8-59.7%2020.8-27.2%2033.3-59.7%2036.2-93.7s-3.9-68.2-19.7-98.5-39.9-55.5-69.5-72.5l-193.1-111.6q-.3-.1-.7-.2h-.7q-.3.1-.7.2-.3.1-.6.3l-80.6%2046.6%20233.2%20134.7zm80.5-121h-.1v.1zm-.1-.1c5.8-33.6%201.9-68.2-11.3-99.7-13.1-31.5-35-58.6-63-78.2-28-19.5-61-30.7-95.1-32.2-34.2-1.4-68%206.9-97.6%2023.9l-193.1%20111.5q-.3.2-.5.5l-.4.6q-.1.3-.2.7-.1.3-.1.7v93.2l233.2-134.7c2.4-1.4%205-2.4%207.6-3.2%202.7-.7%205.4-1%208.1-1%202.8%200%205.5.3%208.2%201%202.6.8%205.1%201.8%207.5%203.2l191.1%20110.4c1.7%201%204.2%202.4%205.6%203.3zm-505.3-103.2c0-2.7.4-5.4%201.1-8.1.7-2.6%201.7-5.2%203.1-7.6%201.4-2.3%203-4.5%205-6.5%201.9-1.9%204.1-3.6%206.5-4.9l191.1-110.3c1.8-1.1%204.3-2.5%205.7-3.2-26.2-21.9-58.2-35.9-92.1-40.2-33.9-4.4-68.3%201-99.2%2015.5-31%2014.5-57.2%2037.6-75.5%2066.4-18.3%2028.9-28%2062.3-28%2096.5v223q.1.4.2.7.1.3.3.6.2.3.5.6.2.2.6.4l80.7%2046.6zm43.8%20294.7%20103.9%2060%20103.9-60v-119.9l-103.8-60-103.9%2060z'/%3e%3c/svg%3e";
@@ -1276,334 +1232,70 @@ function TooltipContent({
1276
1232
  }
1277
1233
  ) });
1278
1234
  }
1279
- function classifyValue(value) {
1280
- if (value === null) return "null";
1281
- if (Array.isArray(value)) return "array";
1282
- switch (typeof value) {
1283
- case "string":
1284
- return "string";
1285
- case "number":
1286
- return "number";
1287
- case "boolean":
1288
- return "boolean";
1289
- case "object":
1290
- return "object";
1291
- case "bigint":
1292
- case "symbol":
1293
- case "undefined":
1294
- case "function":
1295
- return "object";
1296
- }
1297
- }
1298
- function isExpandable(value) {
1299
- return value !== null && (Array.isArray(value) || typeof value === "object");
1300
- }
1301
- function getPropertyValue(obj, key) {
1302
- const descriptor = Object.getOwnPropertyDescriptor(obj, key);
1303
- if (descriptor === void 0) return void 0;
1304
- return descriptor.value;
1305
- }
1306
- function getEntries(value) {
1307
- if (Array.isArray(value)) {
1308
- return value.map((item, index) => [String(index), item]);
1309
- }
1310
- if (typeof value === "object" && value !== null) {
1311
- return Object.keys(value).map((key) => {
1312
- const raw = getPropertyValue(value, key);
1313
- return [key, safeJsonValue(raw)];
1314
- });
1315
- }
1316
- return [];
1317
- }
1318
- function getItemCount(value) {
1319
- if (Array.isArray(value)) return value.length;
1320
- if (typeof value === "object" && value !== null) return Object.keys(value).length;
1321
- return 0;
1322
- }
1323
- const STRING_TRUNCATE_LIMIT = 120;
1324
- function StringValue({ text }) {
1325
- const [expanded, setExpanded] = reactExports.useState(false);
1326
- const isLong = text.length > STRING_TRUNCATE_LIMIT;
1327
- if (!isLong) {
1328
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400 break-all", children: [
1329
- '"',
1330
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "prose prose-sm dark:prose-invert inline max-w-none [&_p]:inline [&_p]:my-0 [&_code]:text-emerald-300 [&_a]:text-emerald-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: text }) }),
1331
- '"'
1332
- ] });
1333
- }
1334
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400 break-all", children: [
1335
- '"',
1336
- expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1337
- "span",
1235
+ function JsonExpansionButton({
1236
+ policy,
1237
+ isExpanded,
1238
+ isPending,
1239
+ onToggle
1240
+ }) {
1241
+ if (policy === null) return null;
1242
+ const label = isPending ? "Updating..." : isExpanded ? "Collapse all" : "Expand all";
1243
+ const tooltip = isExpanded ? "Collapse all JSON nodes" : "Expand all JSON nodes";
1244
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1245
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1246
+ Button,
1338
1247
  {
1339
- className: "cursor-pointer prose prose-sm dark:prose-invert inline max-w-none [&_p]:inline [&_p]:my-0 [&_code]:text-emerald-300 [&_a]:text-emerald-300",
1248
+ variant: "outline",
1249
+ size: "sm",
1250
+ className: "h-8 text-xs",
1340
1251
  onClick: (e) => {
1341
1252
  e.stopPropagation();
1342
- setExpanded(false);
1343
- },
1344
- onKeyDown: (e) => {
1345
- if (e.key === "Enter" || e.key === " ") {
1346
- e.stopPropagation();
1347
- setExpanded(false);
1348
- }
1253
+ onToggle();
1349
1254
  },
1350
- role: "button",
1351
- tabIndex: 0,
1352
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: text })
1353
- }
1354
- ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { delayDuration: 300, children: [
1355
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1356
- TooltipTrigger,
1357
- {
1358
- onClick: (e) => {
1359
- e.stopPropagation();
1360
- setExpanded(true);
1361
- },
1362
- className: "text-left cursor-pointer",
1363
- children: [
1364
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: text.slice(0, STRING_TRUNCATE_LIMIT) }),
1365
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400/50", children: "…" })
1366
- ]
1367
- }
1368
- ),
1369
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1370
- TooltipContent,
1371
- {
1372
- side: "bottom",
1373
- className: "max-w-md text-xs p-2 break-words whitespace-pre-wrap",
1374
- children: [
1375
- text.slice(0, 500),
1376
- text.length > 500 ? "…" : ""
1377
- ]
1378
- }
1379
- )
1380
- ] }),
1381
- '"'
1382
- ] });
1383
- }
1384
- function PrimitiveValue({ value }) {
1385
- if (value === null) {
1386
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-rose-400 italic", children: "null" });
1387
- }
1388
- switch (typeof value) {
1389
- case "string":
1390
- return /* @__PURE__ */ jsxRuntimeExports.jsx(StringValue, { text: value });
1391
- case "number":
1392
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: value });
1393
- case "boolean":
1394
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: value ? "true" : "false" });
1395
- case "object":
1396
- case "bigint":
1397
- case "symbol":
1398
- case "undefined":
1399
- case "function":
1400
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground", children: JSON.stringify(value) });
1401
- }
1402
- }
1403
- function CopyValueButton({ value }) {
1404
- const [copied, setCopied] = reactExports.useState(false);
1405
- function handleCopy(e) {
1406
- e.stopPropagation();
1407
- void window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
1408
- setCopied(true);
1409
- setTimeout(() => {
1410
- setCopied(false);
1411
- }, 2e3);
1412
- });
1413
- }
1414
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1415
- "button",
1416
- {
1417
- type: "button",
1418
- onClick: handleCopy,
1419
- className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors",
1420
- title: "Copy JSON",
1421
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
1422
- }
1423
- );
1424
- }
1425
- function CopyButton$1({ value }) {
1426
- const [copied, setCopied] = reactExports.useState(false);
1427
- function handleCopy(e) {
1428
- e.stopPropagation();
1429
- void window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
1430
- setCopied(true);
1431
- setTimeout(() => {
1432
- setCopied(false);
1433
- }, 2e3);
1434
- });
1435
- }
1436
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1437
- "button",
1438
- {
1439
- type: "button",
1440
- onClick: handleCopy,
1441
- className: "opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0",
1442
- title: "Copy to clipboard",
1443
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3 text-muted-foreground" })
1444
- }
1445
- );
1446
- }
1447
- function hasExpandableDescendant(value) {
1448
- if (!isExpandable(value)) return false;
1449
- return getEntries(value).some(([, v]) => isExpandable(v));
1450
- }
1451
- function ExpandCollapseButton({
1452
- allExpanded,
1453
- onClick
1454
- }) {
1455
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1456
- "button",
1457
- {
1458
- type: "button",
1459
- onClick,
1460
- className: "opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0",
1461
- title: allExpanded ? "Collapse all" : "Expand all",
1462
- children: allExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsUp, { className: "size-3.5 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsDown, { className: "size-3.5 text-muted-foreground" })
1463
- }
1464
- );
1465
- }
1466
- const JsonNode = reactExports.memo(function JsonNode2({
1467
- name,
1468
- value,
1469
- level,
1470
- defaultExpandDepth,
1471
- isArrayItem
1472
- }) {
1473
- const [expanded, setExpanded] = reactExports.useState(level < defaultExpandDepth);
1474
- const [childResetKey, setChildResetKey] = reactExports.useState(0);
1475
- const [childDepthOverride, setChildDepthOverride] = reactExports.useState(null);
1476
- const [allExpanded, setAllExpanded] = reactExports.useState(false);
1477
- const expandable = isExpandable(value);
1478
- const dataType = classifyValue(value);
1479
- const openBracket = dataType === "array" ? "[" : "{";
1480
- const closeBracket = dataType === "array" ? "]" : "}";
1481
- function toggleDeepExpansion() {
1482
- if (allExpanded) {
1483
- setExpanded(false);
1484
- setChildDepthOverride(0);
1485
- setChildResetKey((k) => k + 1);
1486
- setAllExpanded(false);
1487
- } else {
1488
- setExpanded(true);
1489
- setChildDepthOverride(Number.POSITIVE_INFINITY);
1490
- setChildResetKey((k) => k + 1);
1491
- setAllExpanded(true);
1492
- }
1493
- }
1494
- function handleExpandAll(e) {
1495
- e.stopPropagation();
1496
- toggleDeepExpansion();
1497
- }
1498
- const effectiveChildDepth = childDepthOverride ?? defaultExpandDepth;
1499
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn(level > 0 && "border-l border-border/50 ml-2"), children: [
1500
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1501
- "div",
1502
- {
1503
- className: cn(
1504
- "flex items-start gap-1 py-0.5 px-1 -ml-1 rounded-sm group/row",
1505
- expandable && "cursor-pointer hover:bg-muted/50"
1506
- ),
1507
- onClick: expandable ? () => {
1508
- if (expanded) {
1509
- setAllExpanded(false);
1510
- }
1511
- setExpanded(!expanded);
1512
- } : void 0,
1513
- onKeyDown: expandable ? (e) => {
1514
- if (e.key === "Enter" || e.key === " ") {
1515
- e.preventDefault();
1516
- setExpanded(!expanded);
1517
- }
1518
- } : void 0,
1519
- onDoubleClick: expandable && hasExpandableDescendant(value) ? () => toggleDeepExpansion() : void 0,
1520
- role: expandable ? "button" : void 0,
1521
- tabIndex: expandable ? 0 : void 0,
1255
+ disabled: isPending,
1256
+ "aria-pressed": isExpanded,
1522
1257
  children: [
1523
- expandable ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-4 h-5 flex items-center justify-center shrink-0", children: expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-4 shrink-0" }),
1524
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("shrink-0", isArrayItem ? "text-muted-foreground" : "text-cyan-400"), children: isArrayItem ? name : `"${name}"` }),
1525
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground shrink-0", children: expandable ? "" : ":" }),
1526
- expandable ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
1527
- openBracket,
1528
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 text-xs", children: [
1529
- " ",
1530
- getItemCount(value),
1531
- " ",
1532
- getItemCount(value) === 1 ? "item" : "items",
1533
- " ",
1534
- closeBracket
1535
- ] })
1536
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PrimitiveValue, { value }) }),
1537
- expandable && hasExpandableDescendant(value) && /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandCollapseButton, { allExpanded, onClick: handleExpandAll }),
1538
- /* @__PURE__ */ jsxRuntimeExports.jsx(CopyButton$1, { value })
1258
+ isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsUp, { className: "size-3.5 mr-1" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsDown, { className: "size-3.5 mr-1" }),
1259
+ label
1539
1260
  ]
1540
1261
  }
1541
- ),
1542
- expandable && expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-4", children: [
1543
- getEntries(value).map(([key, childValue]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1544
- JsonNode2,
1545
- {
1546
- name: key,
1547
- value: childValue,
1548
- level: level + 1,
1549
- defaultExpandDepth: effectiveChildDepth,
1550
- isArrayItem: dataType === "array"
1551
- },
1552
- key
1553
- )),
1554
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground py-0.5 px-1", children: closeBracket })
1555
- ] }, childResetKey)
1556
- ] });
1557
- });
1558
- function JsonViewer({
1559
- data,
1560
- defaultExpandDepth = 2,
1561
- className,
1562
- showCopy = false
1563
- }) {
1564
- const expandable = isExpandable(data);
1565
- if (!expandable) {
1566
- return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("font-mono text-xs leading-relaxed", className), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
1567
- /* @__PURE__ */ jsxRuntimeExports.jsx(PrimitiveValue, { value: data }),
1568
- showCopy && /* @__PURE__ */ jsxRuntimeExports.jsx(CopyValueButton, { value: data })
1569
- ] }) }) });
1570
- }
1571
- const dataType = classifyValue(data);
1572
- const isArray = dataType === "array";
1573
- return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("font-mono text-xs leading-relaxed", className), children: [
1574
- showCopy && /* @__PURE__ */ jsxRuntimeExports.jsx(CopyValueButton, { value: data }),
1575
- getEntries(data).map(([key, childValue]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1576
- JsonNode,
1577
- {
1578
- name: key,
1579
- value: childValue,
1580
- level: 0,
1581
- defaultExpandDepth,
1582
- isArrayItem: isArray
1583
- },
1584
- key
1585
- ))
1262
+ ) }),
1263
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: tooltip })
1586
1264
  ] }) });
1587
1265
  }
1588
- function JsonViewerFromString({
1589
- text,
1590
- defaultExpandDepth = 2,
1591
- className
1592
- }) {
1266
+ function getJsonExpansionPolicy(_value) {
1267
+ return { depth: Number.POSITIVE_INFINITY };
1268
+ }
1269
+ function parseJsonText(text) {
1593
1270
  try {
1594
1271
  const parsed = JSON.parse(text);
1595
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1596
- JsonViewer,
1597
- {
1598
- data: safeJsonValue(parsed),
1599
- defaultExpandDepth,
1600
- className
1601
- }
1602
- );
1272
+ return { kind: "json", data: safeJsonValue(parsed) };
1603
1273
  } catch {
1604
- return /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: cn("font-mono text-xs whitespace-pre-wrap break-words", className), children: text });
1274
+ return { kind: "text" };
1605
1275
  }
1606
1276
  }
1277
+ function useJsonBulkExpansion(text) {
1278
+ const parsed = reactExports.useMemo(() => text === null ? null : parseJsonText(text), [text]);
1279
+ const parsedData = parsed?.kind === "json" ? parsed.data : null;
1280
+ const policy = reactExports.useMemo(
1281
+ () => parsedData === null ? null : getJsonExpansionPolicy(),
1282
+ [parsedData]
1283
+ );
1284
+ const [isExpanded, setIsExpanded] = reactExports.useState(false);
1285
+ const [bulkDepth, setBulkDepth] = reactExports.useState(0);
1286
+ const [bulkRevision, setBulkRevision] = reactExports.useState(0);
1287
+ const [isPending, startTransition] = reactExports.useTransition();
1288
+ const toggle = reactExports.useCallback(() => {
1289
+ const nextExpanded = !isExpanded;
1290
+ const targetDepth = nextExpanded && policy !== null ? policy.depth : 0;
1291
+ startTransition(() => {
1292
+ setIsExpanded(nextExpanded);
1293
+ setBulkDepth(targetDepth);
1294
+ setBulkRevision((current) => current + 1);
1295
+ });
1296
+ }, [isExpanded, policy]);
1297
+ return { parsedData, policy, isExpanded, toggle, isPending, bulkDepth, bulkRevision };
1298
+ }
1607
1299
  function safeJsonValue(value) {
1608
1300
  if (value === null || value === void 0) return null;
1609
1301
  switch (typeof value) {
@@ -1709,6 +1401,88 @@ function TabsContent({
1709
1401
  }
1710
1402
  );
1711
1403
  }
1404
+ const LazyCompareDrawer = reactExports.lazy(
1405
+ () => import("./CompareDrawer-ftkJxyk6.mjs").then((m) => ({ default: m.CompareDrawer }))
1406
+ );
1407
+ const LazyReplayDialog = reactExports.lazy(
1408
+ () => import("./ReplayDialog-DcmE3lj5.mjs").then((m) => ({ default: m.ReplayDialog }))
1409
+ );
1410
+ const LazyRequestAnatomy = reactExports.lazy(
1411
+ () => import("./RequestAnatomy-rK_LNMdG.mjs").then((m) => ({ default: m.RequestAnatomy }))
1412
+ );
1413
+ const LazyResponseView = reactExports.lazy(
1414
+ () => import("./ResponseView-CbQ4n-aJ.mjs").then((m) => ({ default: m.ResponseView }))
1415
+ );
1416
+ const LazyStreamingChunkSequence = reactExports.lazy(
1417
+ () => import("./StreamingChunkSequence-84FZkIzv.mjs").then((m) => ({
1418
+ default: m.StreamingChunkSequence
1419
+ }))
1420
+ );
1421
+ const LazyJsonViewer = reactExports.lazy(
1422
+ () => import("./json-viewer-B-qpM5xC.mjs").then((m) => ({ default: m.JsonViewer }))
1423
+ );
1424
+ const LazyJsonViewerFromString = reactExports.lazy(
1425
+ () => import("./json-viewer-B-qpM5xC.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1426
+ );
1427
+ const HIGHLIGHT_DURATION_MS = 1200;
1428
+ const MAX_HIGHLIGHT_ATTEMPTS = 12;
1429
+ function useAnatomyJump(options) {
1430
+ const { containerRef, setExpandToPath, ensureTabActive, highlightMs } = options;
1431
+ const highlightTimer = reactExports.useRef(null);
1432
+ return reactExports.useCallback(
1433
+ (segment2) => {
1434
+ ensureTabActive?.();
1435
+ setExpandToPath(segment2.path);
1436
+ const applyHighlight = (target) => {
1437
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
1438
+ target.classList.add(
1439
+ "ring-2",
1440
+ "ring-primary/60",
1441
+ "ring-offset-1",
1442
+ "ring-offset-background",
1443
+ "rounded-sm",
1444
+ "transition-shadow"
1445
+ );
1446
+ if (highlightTimer.current !== null) clearTimeout(highlightTimer.current);
1447
+ highlightTimer.current = setTimeout(() => {
1448
+ target.classList.remove(
1449
+ "ring-2",
1450
+ "ring-primary/60",
1451
+ "ring-offset-1",
1452
+ "ring-offset-background",
1453
+ "rounded-sm",
1454
+ "transition-shadow"
1455
+ );
1456
+ setExpandToPath(null);
1457
+ }, highlightMs ?? HIGHLIGHT_DURATION_MS);
1458
+ };
1459
+ const escape = (value) => {
1460
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
1461
+ return CSS.escape(value);
1462
+ }
1463
+ return value.replace(/(["'\\[\](){}])/g, "\\$1");
1464
+ };
1465
+ const selector = `[data-anatomy-path="${escape(segment2.path)}"]`;
1466
+ const tryFindTarget = (attemptsLeft) => {
1467
+ const container = containerRef.current;
1468
+ if (container !== null) {
1469
+ const target = container.querySelector(selector);
1470
+ if (target !== null) {
1471
+ applyHighlight(target);
1472
+ return;
1473
+ }
1474
+ }
1475
+ if (attemptsLeft > 0) {
1476
+ window.requestAnimationFrame(() => tryFindTarget(attemptsLeft - 1));
1477
+ } else {
1478
+ setExpandToPath(null);
1479
+ }
1480
+ };
1481
+ window.requestAnimationFrame(() => tryFindTarget(MAX_HIGHLIGHT_ATTEMPTS));
1482
+ },
1483
+ [containerRef, ensureTabActive, highlightMs, setExpandToPath]
1484
+ );
1485
+ }
1712
1486
  function normalizeHeaders(headers) {
1713
1487
  if (headers === void 0) return [];
1714
1488
  return Object.entries(headers).map((entry) => [entry[0].toLowerCase(), entry[1]]).sort(([a], [b]) => a.localeCompare(b));
@@ -2036,16 +1810,16 @@ function SplitRows({ lines }) {
2036
1810
  )) });
2037
1811
  }
2038
1812
  const DiffView = reactExports.memo(DiffViewInner);
2039
- function formatElapsed$1(ms) {
2040
- if (ms < 1e3) return `${ms}ms`;
2041
- return `${(ms / 1e3).toFixed(1)}s`;
2042
- }
2043
1813
  const STATUS_BADGE_CLASSES = {
2044
1814
  success: "bg-emerald-500/15 text-emerald-400 border-emerald-500/25",
2045
1815
  client_error: "bg-amber-500/15 text-amber-400 border-amber-500/25",
2046
- server_error: "",
1816
+ server_error: "bg-rose-500/15 text-rose-400 border-rose-500/25",
2047
1817
  pending: "bg-muted text-muted-foreground border-border"
2048
1818
  };
1819
+ function formatElapsed$1(ms) {
1820
+ if (ms < 1e3) return `${ms}ms`;
1821
+ return `${(ms / 1e3).toFixed(1)}s`;
1822
+ }
2049
1823
  function CacheTrendIndicator({ trend }) {
2050
1824
  if (trend === null) return null;
2051
1825
  const isUp = trend.direction === "up";
@@ -2066,7 +1840,12 @@ const LogEntryHeader = reactExports.memo(function({
2066
1840
  expanded,
2067
1841
  onToggle,
2068
1842
  responseToolNames = null,
2069
- cacheTrend = null
1843
+ cacheTrend = null,
1844
+ onReplay,
1845
+ onCopyRequest,
1846
+ requestCopied = false,
1847
+ onToggleRequestExpansion,
1848
+ requestExpansionState = null
2070
1849
  }) {
2071
1850
  const statusCategory = getStatusCategory(log.responseStatus);
2072
1851
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
@@ -2076,10 +1855,13 @@ const LogEntryHeader = reactExports.memo(function({
2076
1855
  {
2077
1856
  role: "button",
2078
1857
  tabIndex: 0,
1858
+ "data-nav-id": `log-${log.id}`,
1859
+ "data-nav-action": expanded ? "collapse" : "expand",
2079
1860
  className: cn(
2080
1861
  "flex items-center gap-2 px-3 py-1 cursor-pointer transition-colors",
2081
1862
  "hover:bg-muted/50",
2082
- "select-none"
1863
+ "select-none",
1864
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none"
2083
1865
  ),
2084
1866
  onClick: onToggle,
2085
1867
  onKeyDown: (e) => {
@@ -2097,34 +1879,20 @@ const LogEntryHeader = reactExports.memo(function({
2097
1879
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4" }) }) }),
2098
1880
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.model })
2099
1881
  ] }),
2100
- statusCategory !== "success" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: statusCategory === "server_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2101
- Badge,
2102
- {
2103
- variant: "destructive",
2104
- className: "text-[10px] px-1.5 py-0 h-4 font-mono tabular-nums",
2105
- children: log.responseStatus
2106
- }
2107
- ) : statusCategory === "pending" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1882
+ statusCategory !== "success" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2108
1883
  Badge,
2109
1884
  {
2110
1885
  variant: "outline",
2111
1886
  className: cn(
2112
- "text-[10px] px-1.5 py-0 h-4 font-mono tabular-nums",
1887
+ "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums gap-1",
2113
1888
  STATUS_BADGE_CLASSES[statusCategory]
2114
1889
  ),
2115
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3 animate-spin" })
2116
- }
2117
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2118
- Badge,
2119
- {
2120
- variant: "outline",
2121
- className: cn(
2122
- "text-[10px] px-1.5 py-0 h-4 font-mono tabular-nums",
2123
- STATUS_BADGE_CLASSES[statusCategory]
2124
- ),
2125
- children: log.responseStatus
1890
+ children: [
1891
+ statusCategory === "server_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(OctagonAlert, { className: "size-3", "aria-label": "Server error" }) : statusCategory === "client_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Client error" }) : statusCategory === "pending" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3 animate-spin", "aria-label": "Pending" }) : null,
1892
+ log.responseStatus
1893
+ ]
2126
1894
  }
2127
- ) }),
1895
+ ),
2128
1896
  log.elapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
2129
1897
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
2130
1898
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(log.elapsedMs) })
@@ -2213,350 +1981,182 @@ const LogEntryHeader = reactExports.memo(function({
2213
1981
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request used SSE streaming" })
2214
1982
  ] }),
2215
1983
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
1984
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1985
+ "span",
1986
+ {
1987
+ className: "flex items-center gap-1.5 shrink-0",
1988
+ onClick: (e) => e.stopPropagation(),
1989
+ onKeyDown: (e) => e.stopPropagation(),
1990
+ children: [
1991
+ requestExpansionState !== null && onToggleRequestExpansion !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1992
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1993
+ Button,
1994
+ {
1995
+ variant: "outline",
1996
+ size: "icon",
1997
+ className: "size-8",
1998
+ onClick: onToggleRequestExpansion,
1999
+ disabled: requestExpansionState.isPending,
2000
+ "aria-pressed": requestExpansionState.isExpanded,
2001
+ "aria-label": requestExpansionState.isExpanded ? "Collapse all JSON" : "Expand all JSON",
2002
+ children: requestExpansionState.isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsUp, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsDown, { className: "size-3.5" })
2003
+ }
2004
+ ) }),
2005
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: requestExpansionState.isExpanded ? "Collapse all JSON nodes" : "Expand all JSON nodes" })
2006
+ ] }),
2007
+ onCopyRequest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2008
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2009
+ Button,
2010
+ {
2011
+ variant: "outline",
2012
+ size: "icon",
2013
+ className: "size-8",
2014
+ onClick: onCopyRequest,
2015
+ "aria-label": "Copy request body",
2016
+ children: requestCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
2017
+ }
2018
+ ) }),
2019
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: requestCopied ? "Copied to clipboard" : "Copy request body" })
2020
+ ] }),
2021
+ onReplay !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2022
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2023
+ Button,
2024
+ {
2025
+ variant: "outline",
2026
+ size: "icon",
2027
+ className: "size-8",
2028
+ onClick: onReplay,
2029
+ "aria-label": "Replay request",
2030
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3.5" })
2031
+ }
2032
+ ) }),
2033
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Re-send this request to the provider" })
2034
+ ] })
2035
+ ]
2036
+ }
2037
+ ),
2216
2038
  expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" })
2217
2039
  ]
2218
2040
  }
2219
2041
  ) });
2220
2042
  });
2221
- function Separator({
2222
- className,
2223
- orientation = "horizontal",
2224
- decorative = true,
2225
- ...props
2226
- }) {
2227
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2228
- Root$1,
2229
- {
2230
- "data-slot": "separator",
2231
- decorative,
2232
- orientation,
2233
- className: cn(
2234
- "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
2235
- className
2236
- ),
2237
- ...props
2238
- }
2239
- );
2043
+ function forEachCodePoint(text, visit) {
2044
+ for (const ch of Array.from(text)) {
2045
+ visit(ch);
2046
+ }
2240
2047
  }
2241
- function Collapsible({
2242
- ...props
2243
- }) {
2244
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root$2, { "data-slot": "collapsible", ...props });
2048
+ const CJK_REGEX = /[\u3000-\u9FFF\uAC00-\uD7FF\uF900-\uFAFF\uFF00-\uFFEF\u{1F300}-\u{1FAFF}]/u;
2049
+ function estimateTokens(text) {
2050
+ if (text.length === 0) return 0;
2051
+ let cjk = 0;
2052
+ let other = 0;
2053
+ forEachCodePoint(text, (ch) => {
2054
+ if (CJK_REGEX.test(ch)) {
2055
+ cjk += 1;
2056
+ } else {
2057
+ other += 1;
2058
+ }
2059
+ });
2060
+ return Math.ceil(cjk * 1) + Math.ceil(other / 4);
2245
2061
  }
2246
- function CollapsibleTrigger({
2247
- ...props
2248
- }) {
2249
- return /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleTrigger$1, { "data-slot": "collapsible-trigger", ...props });
2062
+ function countCharacters(text) {
2063
+ if (text.length === 0) return 0;
2064
+ let n = 0;
2065
+ forEachCodePoint(text, () => {
2066
+ n += 1;
2067
+ });
2068
+ return n;
2250
2069
  }
2251
- function CollapsibleContent({
2252
- ...props
2253
- }) {
2254
- return /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent$1, { "data-slot": "collapsible-content", ...props });
2070
+ function emptyRequestAnalysis(rawBody) {
2071
+ return {
2072
+ parsed: null,
2073
+ comparisonValue: rawBody,
2074
+ messageCount: null,
2075
+ toolCount: null
2076
+ };
2255
2077
  }
2256
- function ScrollArea({
2257
- className,
2258
- children,
2259
- ...props
2260
- }) {
2261
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2262
- Root$3,
2263
- {
2264
- "data-slot": "scroll-area",
2265
- className: cn("relative", className),
2266
- ...props,
2267
- children: [
2268
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2269
- Viewport$1,
2270
- {
2271
- "data-slot": "scroll-area-viewport",
2272
- className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
2273
- children
2078
+ const EMPTY_RESPONSE_ANALYSIS = {
2079
+ parsed: null,
2080
+ toolNames: null
2081
+ };
2082
+ function contentToText$1(content) {
2083
+ if (typeof content === "string") return content;
2084
+ if (!Array.isArray(content)) return "";
2085
+ const parts = [];
2086
+ for (const block of content) {
2087
+ if (block === null || typeof block !== "object") continue;
2088
+ const b = block;
2089
+ const type = b.type;
2090
+ switch (type) {
2091
+ case "text":
2092
+ if (typeof b.text === "string") parts.push(b.text);
2093
+ break;
2094
+ case "thinking":
2095
+ case "think":
2096
+ if (typeof b.thinking === "string") parts.push(b.thinking);
2097
+ break;
2098
+ case "tool_use": {
2099
+ const name = typeof b.name === "string" ? b.name : "";
2100
+ const input = b.input !== void 0 ? JSON.stringify(b.input) : "";
2101
+ parts.push(`${name} ${input}`.trim());
2102
+ break;
2103
+ }
2104
+ case "tool_result": {
2105
+ const inner = b.content;
2106
+ if (typeof inner === "string") {
2107
+ parts.push(inner);
2108
+ } else if (Array.isArray(inner)) {
2109
+ for (const item of inner) {
2110
+ if (item !== null && typeof item === "object") {
2111
+ const t = item.text;
2112
+ if (typeof t === "string") parts.push(t);
2113
+ }
2274
2114
  }
2275
- ),
2276
- /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollBar, {}),
2277
- /* @__PURE__ */ jsxRuntimeExports.jsx(Corner, {})
2278
- ]
2115
+ }
2116
+ break;
2117
+ }
2118
+ case "image":
2119
+ parts.push("[image]");
2120
+ break;
2279
2121
  }
2280
- );
2281
- }
2282
- function ScrollBar({
2283
- className,
2284
- orientation = "vertical",
2285
- ...props
2286
- }) {
2287
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2288
- ScrollAreaScrollbar,
2289
- {
2290
- "data-slot": "scroll-area-scrollbar",
2291
- orientation,
2292
- className: cn(
2293
- "flex touch-none p-px transition-colors select-none",
2294
- orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
2295
- orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
2296
- className
2297
- ),
2298
- ...props,
2299
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2300
- ScrollAreaThumb,
2301
- {
2302
- "data-slot": "scroll-area-thumb",
2303
- className: "bg-border relative flex-1 rounded-full"
2304
- }
2305
- )
2306
- }
2307
- );
2308
- }
2309
- function assertNever(_value) {
2310
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {});
2311
- }
2312
- function SystemReminderBlock({ text }) {
2313
- const [open, setOpen] = reactExports.useState(false);
2314
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(Collapsible, { open, onOpenChange: setOpen, children: [
2315
- /* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 py-0.5 cursor-pointer hover:opacity-80 transition-opacity group", children: [
2316
- open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" }),
2317
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground text-xs italic select-none opacity-60", children: "[system-reminder]" })
2318
- ] }),
2319
- /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 pt-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: text }) }) }) })
2320
- ] });
2321
- }
2322
- const THINKING_TAG_REGEX = /<think>([\s\S]*?)<\/think>/gi;
2323
- const THINKING_TAG_REGEX_SINGLE = /<think>([\s\S]*?)<\/think>/i;
2324
- function extractThinkingFromContent(text) {
2325
- const match = THINKING_TAG_REGEX_SINGLE.exec(text);
2326
- if (!match || match[1] === void 0) {
2327
- return { thinking: null, remainingText: text };
2328
- }
2329
- const thinking = match[1].trim();
2330
- const remainingText = text.replace(THINKING_TAG_REGEX, "").trim();
2331
- return { thinking, remainingText };
2332
- }
2333
- const TextBlock = reactExports.memo(function TextBlock2({ text }) {
2334
- if (text.includes("<system-reminder>")) {
2335
- return /* @__PURE__ */ jsxRuntimeExports.jsx(SystemReminderBlock, { text });
2336
- }
2337
- const { thinking, remainingText } = extractThinkingFromContent(text);
2338
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2339
- thinking !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingBlock, { thinking }),
2340
- remainingText.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: remainingText }) }),
2341
- thinking === null && remainingText.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty text block" })
2342
- ] });
2343
- });
2344
- const ThinkingBlock = reactExports.memo(function ThinkingBlock2({
2345
- thinking
2346
- }) {
2347
- const [open, setOpen] = reactExports.useState(false);
2348
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-l-2 border-purple-500/40 my-1", children: [
2349
- /* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-purple-500/5 transition-colors rounded-r-sm group", children: [
2350
- /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { className: "size-3.5 text-purple-400 shrink-0" }),
2351
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-medium text-purple-400", children: "Thinking" }),
2352
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2353
- Badge,
2354
- {
2355
- variant: "ghost",
2356
- className: "text-[10px] text-muted-foreground px-1.5 py-0 h-4 font-mono",
2357
- children: [
2358
- thinking.length.toLocaleString(),
2359
- " chars"
2360
- ]
2361
- }
2362
- ),
2363
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
2364
- open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" })
2365
- ] }),
2366
- /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono leading-relaxed", children: thinking }) }) }) })
2367
- ] }) });
2368
- });
2369
- const ToolUseBlock = reactExports.memo(function ToolUseBlock2({
2370
- name,
2371
- input
2372
- }) {
2373
- const [open, setOpen] = reactExports.useState(false);
2374
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-l-2 border-blue-500/40 my-1", children: [
2375
- /* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-blue-500/5 transition-colors rounded-r-sm group", children: [
2376
- /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5 text-blue-400 shrink-0" }),
2377
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "text-[10px] font-mono px-1.5 py-0 h-4", children: name }),
2378
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
2379
- open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" })
2380
- ] }),
2381
- /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(input), defaultExpandDepth: 2 }) }) }) })
2382
- ] }) });
2383
- });
2384
- const ResponseContentBlockRenderer = reactExports.memo(function ResponseContentBlockRenderer2({
2385
- block
2386
- }) {
2387
- switch (block.type) {
2388
- case "text":
2389
- return /* @__PURE__ */ jsxRuntimeExports.jsx(TextBlock, { text: block.text });
2390
- case "thinking":
2391
- case "think":
2392
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingBlock, { thinking: block.thinking });
2393
- case "tool_use":
2394
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseBlock, { name: block.name, input: block.input });
2395
- default:
2396
- return assertNever();
2397
- }
2398
- });
2399
- const StructuredResponseViewAnthropic = reactExports.memo(function StructuredResponseViewAnthropic2({
2400
- response
2401
- }) {
2402
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
2403
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
2404
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "secondary", className: "text-[10px] px-1.5 py-0 h-5 font-mono", children: response.model }),
2405
- response.stop_reason !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2406
- Badge,
2407
- {
2408
- variant: "outline",
2409
- className: "text-[10px] px-1.5 py-0 h-5 font-mono flex items-center gap-1",
2410
- children: [
2411
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleStop, { className: "size-2.5" }),
2412
- response.stop_reason
2413
- ]
2414
- }
2415
- ),
2416
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs", children: [
2417
- /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3" }),
2418
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
2419
- formatTokens(response.usage.input_tokens ?? 0),
2420
- " in /",
2421
- " ",
2422
- formatTokens(response.usage.output_tokens ?? 0),
2423
- " out"
2424
- ] }),
2425
- response.usage.cache_creation_input_tokens !== void 0 && response.usage.cache_creation_input_tokens !== null && response.usage.cache_creation_input_tokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
2426
- "Cache +",
2427
- formatTokens(response.usage.cache_creation_input_tokens)
2428
- ] }),
2429
- response.usage.cache_read_input_tokens !== void 0 && response.usage.cache_read_input_tokens !== null && response.usage.cache_read_input_tokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
2430
- "Cache ~",
2431
- formatTokens(response.usage.cache_read_input_tokens)
2432
- ] })
2433
- ] })
2434
- ] }),
2435
- /* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { className: "opacity-50" }),
2436
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2437
- response.content.map((block, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(ResponseContentBlockRenderer, { block }, i)),
2438
- response.content.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
2439
- ] })
2440
- ] });
2441
- });
2442
- function parseToolArguments(raw) {
2443
- if (raw === void 0 || raw === "") return {};
2444
- try {
2445
- return JSON.parse(raw);
2446
- } catch {
2447
- return null;
2448
2122
  }
2449
- }
2450
- function OpenAIToolCallBlock({ call }) {
2451
- const [open, setOpen] = reactExports.useState(false);
2452
- const name = call.function.name ?? "(unnamed tool)";
2453
- const parsed = parseToolArguments(call.function.arguments);
2454
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-l-2 border-blue-500/40 my-1", children: [
2455
- /* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-blue-500/5 transition-colors rounded-r-sm group", children: [
2456
- /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5 text-blue-400 shrink-0" }),
2457
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "text-[10px] font-mono px-1.5 py-0 h-4", children: name }),
2458
- call.id !== void 0 && call.id !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-muted-foreground/60 truncate", children: call.id }),
2459
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
2460
- open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" })
2461
- ] }),
2462
- /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: parsed === null ? (
2463
- // JSON.parse failed — show the raw string so the user can
2464
- // still see what the model tried to call.
2465
- /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words text-rose-300/90", children: call.function.arguments })
2466
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(parsed), defaultExpandDepth: 2 }) }) }) })
2467
- ] }) });
2468
- }
2469
- const OpenAIResponseView = reactExports.memo(function OpenAIResponseView2({
2470
- response
2471
- }) {
2472
- const choice = response.choices[0];
2473
- const message = choice?.message;
2474
- const toolCalls = message?.tool_calls ?? [];
2475
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
2476
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
2477
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "secondary", className: "text-[10px] px-1.5 py-0 h-5 font-mono", children: response.model }),
2478
- choice?.finish_reason !== null && choice?.finish_reason !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2479
- Badge,
2480
- {
2481
- variant: "outline",
2482
- className: "text-[10px] px-1.5 py-0 h-5 font-mono flex items-center gap-1",
2483
- children: [
2484
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleStop, { className: "size-2.5" }),
2485
- choice.finish_reason
2486
- ]
2487
- }
2488
- ),
2489
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs", children: [
2490
- /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3" }),
2491
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
2492
- formatTokens(response.usage.prompt_tokens ?? 0),
2493
- " in /",
2494
- " ",
2495
- formatTokens(response.usage.completion_tokens ?? 0),
2496
- " out"
2497
- ] })
2498
- ] })
2499
- ] }),
2500
- /* @__PURE__ */ jsxRuntimeExports.jsx(Separator, { className: "opacity-50" }),
2501
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2502
- message?.reasoning_content !== null && message?.reasoning_content !== void 0 && message.reasoning_content.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingBlock, { thinking: message.reasoning_content }),
2503
- message?.content !== null && message?.content !== void 0 && message.content.length > 0 && (() => {
2504
- const hasReasoningField = message.reasoning_content !== null && message.reasoning_content !== void 0 && message.reasoning_content.length > 0;
2505
- const { thinking, remainingText } = extractThinkingFromContent(message.content);
2506
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2507
- thinking !== null && !hasReasoningField && /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingBlock, { thinking }),
2508
- remainingText.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: remainingText }) })
2509
- ] });
2510
- })(),
2511
- toolCalls.map((call, i) => (
2512
- // biome-ignore lint/suspicious/noArrayIndexKey: tool_calls is the positionally stable list from the response
2513
- /* @__PURE__ */ jsxRuntimeExports.jsx(OpenAIToolCallBlock, { call }, call.id ?? `tc-${i}`)
2514
- )),
2515
- message?.function_call !== null && message?.function_call !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-blue-500/30 rounded-md p-3 bg-blue-500/5", children: [
2516
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-blue-400 font-mono mb-1", children: "function_call" }),
2517
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono text-xs", children: [
2518
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-300", children: message.function_call.name }),
2519
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
2520
- "(",
2521
- message.function_call.arguments,
2522
- ")"
2523
- ] })
2524
- ] })
2525
- ] }),
2526
- (message?.content === null || message?.content === void 0 || message.content.length === 0) && (message?.reasoning_content === null || message?.reasoning_content === void 0 || message.reasoning_content.length === 0) && (message?.function_call === null || message?.function_call === void 0) && toolCalls.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
2527
- ] })
2528
- ] });
2529
- });
2530
- function isRecord(value) {
2531
- return typeof value === "object" && value !== null && !Array.isArray(value);
2532
- }
2533
- function isOpenAIResponse(response) {
2534
- return isRecord(response) && response.object === "chat.completion";
2535
- }
2536
- function isAnthropicResponse(response) {
2537
- return isRecord(response) && response.type === "message" && Array.isArray(response.content);
2538
- }
2539
- function formatViewFor(apiFormat, response) {
2540
- if (apiFormat === "openai" && isOpenAIResponse(response)) {
2541
- return /* @__PURE__ */ jsxRuntimeExports.jsx(OpenAIResponseView, { response });
2123
+ return parts.join("\n");
2124
+ }
2125
+ function systemToText(system) {
2126
+ if (typeof system === "string") return system;
2127
+ if (!Array.isArray(system)) return "";
2128
+ const parts = [];
2129
+ for (const block of system) {
2130
+ if (block !== null && typeof block === "object") {
2131
+ const t = block.text;
2132
+ const type = block.type;
2133
+ if (type === "text" && typeof t === "string") parts.push(t);
2134
+ }
2542
2135
  }
2543
- if (apiFormat === "anthropic" && isAnthropicResponse(response)) {
2544
- return /* @__PURE__ */ jsxRuntimeExports.jsx(StructuredResponseViewAnthropic, { response });
2136
+ return parts.join("\n");
2137
+ }
2138
+ function toolsToText$1(tools) {
2139
+ if (!Array.isArray(tools)) return "";
2140
+ const parts = [];
2141
+ for (const tool of tools) {
2142
+ if (tool === null || typeof tool !== "object") continue;
2143
+ const t = tool;
2144
+ if (typeof t.name === "string") parts.push(t.name);
2145
+ if (typeof t.description === "string") parts.push(t.description);
2146
+ if (t.input_schema !== void 0) parts.push(JSON.stringify(t.input_schema));
2545
2147
  }
2546
- return null;
2148
+ return parts.join("\n");
2547
2149
  }
2548
- function emptyRequestAnalysis(rawBody) {
2150
+ function segment$1(role, label, text, path) {
2549
2151
  return {
2550
- parsed: null,
2551
- comparisonValue: rawBody,
2552
- messageCount: null,
2553
- toolCount: null
2152
+ role,
2153
+ label,
2154
+ text,
2155
+ size: estimateTokens(text),
2156
+ characters: countCharacters(text),
2157
+ path
2554
2158
  };
2555
2159
  }
2556
- const EMPTY_RESPONSE_ANALYSIS = {
2557
- parsed: null,
2558
- toolNames: null
2559
- };
2560
2160
  const anthropicLogFormatAdapter = {
2561
2161
  format: "anthropic",
2562
2162
  analyzeRequest(rawBody) {
@@ -2589,8 +2189,92 @@ const anthropicLogFormatAdapter = {
2589
2189
  } catch {
2590
2190
  return EMPTY_RESPONSE_ANALYSIS;
2591
2191
  }
2192
+ },
2193
+ anatomySegments(parsed) {
2194
+ if (parsed === null || typeof parsed !== "object") return null;
2195
+ const body = parsed;
2196
+ const segments = [];
2197
+ if (body.system !== void 0) {
2198
+ const text = systemToText(body.system);
2199
+ if (text.length > 0) {
2200
+ segments.push(segment$1("system", "system", text, "/system"));
2201
+ }
2202
+ }
2203
+ if (Array.isArray(body.messages)) {
2204
+ body.messages.forEach((message, index2) => {
2205
+ if (message === null || typeof message !== "object") return;
2206
+ const m = message;
2207
+ const role = m.role === "user" || m.role === "assistant" ? m.role : "user";
2208
+ const text = contentToText$1(m.content);
2209
+ segments.push(segment$1(role, `[${index2}] ${role}`, text, `/messages/${index2}`));
2210
+ });
2211
+ }
2212
+ if (Array.isArray(body.tools) && body.tools.length > 0) {
2213
+ const text = toolsToText$1(body.tools);
2214
+ segments.push(segment$1("tools", "tools", text, "/tools"));
2215
+ }
2216
+ return segments.length > 0 ? segments : null;
2592
2217
  }
2593
2218
  };
2219
+ function contentToText(content) {
2220
+ if (typeof content === "string") return content;
2221
+ if (!Array.isArray(content)) return "";
2222
+ const parts = [];
2223
+ for (const block of content) {
2224
+ if (block === null || typeof block !== "object") continue;
2225
+ const b = block;
2226
+ const type = b.type;
2227
+ if (type === "text" && typeof b.text === "string") {
2228
+ parts.push(b.text);
2229
+ } else if (type === "image_url") {
2230
+ parts.push("[image]");
2231
+ }
2232
+ }
2233
+ return parts.join("\n");
2234
+ }
2235
+ function messageToText(message) {
2236
+ const parts = [];
2237
+ const text = contentToText(message.content);
2238
+ if (text.length > 0) parts.push(text);
2239
+ const reasoning = typeof message.reasoning_content === "string" ? message.reasoning_content : typeof message.thinking === "string" ? message.thinking : typeof message.think === "string" ? message.think : "";
2240
+ if (reasoning.length > 0) parts.push(reasoning);
2241
+ const toolCalls = message.tool_calls;
2242
+ if (Array.isArray(toolCalls)) {
2243
+ for (const call of toolCalls) {
2244
+ if (call === null || typeof call !== "object") continue;
2245
+ const fn = call.function;
2246
+ if (fn === void 0) continue;
2247
+ const name = typeof fn.name === "string" ? fn.name : "";
2248
+ const args = typeof fn.arguments === "string" ? fn.arguments : "";
2249
+ parts.push(`${name} ${args}`.trim());
2250
+ }
2251
+ }
2252
+ return parts.join("\n");
2253
+ }
2254
+ function toolsToText(tools) {
2255
+ if (!Array.isArray(tools)) return "";
2256
+ const parts = [];
2257
+ for (const tool of tools) {
2258
+ if (tool === null || typeof tool !== "object") continue;
2259
+ const t = tool;
2260
+ const fn = t.function;
2261
+ if (fn === void 0) continue;
2262
+ if (typeof fn.name === "string") parts.push(fn.name);
2263
+ if (typeof fn.description === "string") parts.push(fn.description);
2264
+ if (fn.parameters !== void 0) parts.push(JSON.stringify(fn.parameters));
2265
+ }
2266
+ return parts.join("\n");
2267
+ }
2268
+ function segment(role, label, text, path) {
2269
+ return {
2270
+ role,
2271
+ label,
2272
+ text,
2273
+ size: estimateTokens(text),
2274
+ characters: countCharacters(text),
2275
+ path
2276
+ };
2277
+ }
2594
2278
  const openAILogFormatAdapter = {
2595
2279
  format: "openai",
2596
2280
  analyzeRequest(rawBody) {
@@ -2617,6 +2301,25 @@ const openAILogFormatAdapter = {
2617
2301
  parsed,
2618
2302
  toolNames: toolNames.length > 0 ? toolNames : null
2619
2303
  };
2304
+ },
2305
+ anatomySegments(parsed) {
2306
+ if (parsed === null || typeof parsed !== "object") return null;
2307
+ const body = parsed;
2308
+ const segments = [];
2309
+ if (Array.isArray(body.messages)) {
2310
+ body.messages.forEach((message, index2) => {
2311
+ if (message === null || typeof message !== "object") return;
2312
+ const m = message;
2313
+ const role = m.role === "user" || m.role === "assistant" || m.role === "system" || m.role === "tool" ? m.role : "user";
2314
+ const text = messageToText(m);
2315
+ segments.push(segment(role, `[${index2}] ${role}`, text, `/messages/${index2}`));
2316
+ });
2317
+ }
2318
+ if (Array.isArray(body.tools) && body.tools.length > 0) {
2319
+ const text = toolsToText(body.tools);
2320
+ segments.push(segment("tools", "tools", text, "/tools"));
2321
+ }
2322
+ return segments.length > 0 ? segments : null;
2620
2323
  }
2621
2324
  };
2622
2325
  const unknownLogFormatAdapter = {
@@ -2626,6 +2329,9 @@ const unknownLogFormatAdapter = {
2626
2329
  },
2627
2330
  analyzeResponse() {
2628
2331
  return EMPTY_RESPONSE_ANALYSIS;
2332
+ },
2333
+ anatomySegments() {
2334
+ return null;
2629
2335
  }
2630
2336
  };
2631
2337
  const ADAPTERS = {
@@ -2640,391 +2346,6 @@ function resolveLogFormat(log) {
2640
2346
  const pathFormat = requestFormatForPath(log.path);
2641
2347
  return pathFormat === "unknown" ? log.apiFormat : pathFormat;
2642
2348
  }
2643
- function getStatusClasses(category) {
2644
- switch (category) {
2645
- case "success":
2646
- return "text-emerald-400";
2647
- case "client_error":
2648
- return "text-amber-400";
2649
- case "server_error":
2650
- return "text-red-400";
2651
- case "pending":
2652
- return "text-muted-foreground";
2653
- }
2654
- }
2655
- function StatusIndicator({ status }) {
2656
- const category = getStatusCategory(status);
2657
- const classes = getStatusClasses(category);
2658
- if (status === null) {
2659
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground italic", children: "pending" });
2660
- }
2661
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: cn("flex items-center gap-1 text-xs font-mono font-semibold", classes), children: [
2662
- category === "server_error" && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3" }),
2663
- status
2664
- ] });
2665
- }
2666
- function ErrorResponseView({ text }) {
2667
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-red-500/30 bg-red-500/5 p-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-xs text-red-300 whitespace-pre-wrap font-mono leading-relaxed overflow-auto max-h-[60vh]", children: text }) });
2668
- }
2669
- function MarkdownFallbackView({ text }) {
2670
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: text }) });
2671
- }
2672
- const ResponseView = reactExports.memo(function ResponseView2({
2673
- responseText,
2674
- responseStatus,
2675
- streaming,
2676
- inputTokens,
2677
- outputTokens,
2678
- cacheCreationInputTokens,
2679
- cacheReadInputTokens,
2680
- apiFormat,
2681
- error
2682
- }) {
2683
- const resolvedFormat = apiFormat ?? "unknown";
2684
- const parsed = reactExports.useMemo(
2685
- () => getLogFormatAdapter(resolvedFormat).analyzeResponse(responseText).parsed,
2686
- [resolvedFormat, responseText]
2687
- );
2688
- if (responseText === null && error === void 0) {
2689
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 py-3", children: [
2690
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
2691
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground italic", children: "No response" })
2692
- ] });
2693
- }
2694
- const isHttpError = responseStatus !== null && responseStatus >= 400;
2695
- if (isHttpError) {
2696
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2697
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
2698
- /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText ?? "" }),
2699
- error !== void 0 && error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
2700
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2701
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: error })
2702
- ] })
2703
- ] });
2704
- }
2705
- if (error !== void 0 && error !== null) {
2706
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2707
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
2708
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
2709
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2710
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: error })
2711
- ] }),
2712
- responseText !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText }) })
2713
- ] });
2714
- }
2715
- if (parsed !== null) {
2716
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2717
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
2718
- formatViewFor(resolvedFormat, parsed)
2719
- ] });
2720
- }
2721
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2722
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
2723
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
2724
- streaming && (inputTokens !== null || outputTokens !== null) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs", children: [
2725
- /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3" }),
2726
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
2727
- inputTokens !== null ? formatTokens(inputTokens) : "—",
2728
- " in /",
2729
- " ",
2730
- outputTokens !== null ? formatTokens(outputTokens) : "—",
2731
- " out"
2732
- ] }),
2733
- cacheCreationInputTokens !== null && cacheCreationInputTokens !== void 0 && cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
2734
- "Cache +",
2735
- formatTokens(cacheCreationInputTokens)
2736
- ] }),
2737
- cacheReadInputTokens !== null && cacheReadInputTokens !== void 0 && cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
2738
- "Cache ~",
2739
- formatTokens(cacheReadInputTokens)
2740
- ] })
2741
- ] })
2742
- ] }),
2743
- /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownFallbackView, { text: responseText ?? "" })
2744
- ] });
2745
- });
2746
- const ReplayResultSchema = object({
2747
- success: boolean(),
2748
- error: string().optional(),
2749
- responseStatus: number().optional(),
2750
- responseText: string().optional(),
2751
- inputTokens: number().optional(),
2752
- outputTokens: number().optional(),
2753
- elapsedMs: number().optional(),
2754
- streaming: boolean().optional()
2755
- });
2756
- function ReplayDialog({ log, open, onOpenChange }) {
2757
- const [modifiedBody, setModifiedBody] = reactExports.useState(() => {
2758
- return log.rawRequestBody ?? "{}";
2759
- });
2760
- const [replayResult, setReplayResult] = reactExports.useState(null);
2761
- const [loading, setLoading] = reactExports.useState(false);
2762
- const [error, setError] = reactExports.useState(null);
2763
- async function handleReplay() {
2764
- setLoading(true);
2765
- setError(null);
2766
- setReplayResult(null);
2767
- try {
2768
- const res = await fetch(`/api/logs/${log.id}/replay`, {
2769
- method: "POST",
2770
- headers: { "Content-Type": "application/json" },
2771
- body: JSON.stringify({ modifiedBody })
2772
- });
2773
- const json = await res.json();
2774
- const parsed = ReplayResultSchema.safeParse(json);
2775
- if (!parsed.success) {
2776
- setError("Invalid response from server");
2777
- setLoading(false);
2778
- return;
2779
- }
2780
- const data = parsed.data;
2781
- setReplayResult(data);
2782
- if (!data.success) {
2783
- setError(data.error ?? "Replay failed");
2784
- }
2785
- } catch (err) {
2786
- setError(err instanceof Error ? err.message : "Network error");
2787
- } finally {
2788
- setLoading(false);
2789
- }
2790
- }
2791
- function handleClose() {
2792
- setReplayResult(null);
2793
- setError(null);
2794
- onOpenChange(false);
2795
- }
2796
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "max-w-4xl max-h-[85vh] overflow-auto", children: [
2797
- /* @__PURE__ */ jsxRuntimeExports.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { className: "flex items-center gap-2", children: [
2798
- /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-4" }),
2799
- "Replay Request #",
2800
- log.id
2801
- ] }) }),
2802
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "modified", children: [
2803
- /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { children: [
2804
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "modified", children: "Modified Request" }),
2805
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "original", children: "Original Response" }),
2806
- replayResult && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "replay", children: "Replay Response" })
2807
- ] }),
2808
- /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, { value: "modified", className: "space-y-4", children: [
2809
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2810
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-sm font-medium mb-2 block", children: "Request Body (JSON)" }),
2811
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2812
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2813
- "textarea",
2814
- {
2815
- className: "w-full h-64 p-3 font-mono text-xs bg-muted rounded-md border border-input resize-none focus:outline-none focus:ring-2 focus:ring-ring",
2816
- value: modifiedBody,
2817
- onChange: (e) => setModifiedBody(e.target.value),
2818
- spellCheck: false
2819
- }
2820
- ) }),
2821
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Edit the request body before re-sending to the provider" })
2822
- ] }) })
2823
- ] }),
2824
- error !== null && error !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-md", children: error }),
2825
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2826
- Button,
2827
- {
2828
- onClick: () => {
2829
- void handleReplay();
2830
- },
2831
- disabled: loading,
2832
- children: loading ? "Replaying..." : "Replay"
2833
- }
2834
- ) }),
2835
- replayResult && replayResult.success && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "parsed", children: [
2836
- /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { children: [
2837
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" }),
2838
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" })
2839
- ] }),
2840
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2841
- ResponseView,
2842
- {
2843
- responseText: replayResult.responseText ?? null,
2844
- responseStatus: replayResult.responseStatus ?? null,
2845
- streaming: replayResult.streaming ?? false,
2846
- inputTokens: replayResult.inputTokens ?? null,
2847
- outputTokens: replayResult.outputTokens ?? null,
2848
- apiFormat: log.apiFormat
2849
- }
2850
- ) }),
2851
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap bg-muted p-3 rounded-md max-h-96 overflow-auto", children: replayResult.responseText ?? "No response" }) })
2852
- ] })
2853
- ] }),
2854
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "original", children: log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "parsed", children: [
2855
- /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { children: [
2856
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" }),
2857
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" })
2858
- ] }),
2859
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2860
- ResponseView,
2861
- {
2862
- responseText: log.responseText,
2863
- responseStatus: log.responseStatus,
2864
- streaming: log.streaming,
2865
- inputTokens: log.inputTokens,
2866
- outputTokens: log.outputTokens,
2867
- cacheCreationInputTokens: log.cacheCreationInputTokens,
2868
- cacheReadInputTokens: log.cacheReadInputTokens,
2869
- apiFormat: log.apiFormat
2870
- }
2871
- ) }),
2872
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap bg-muted p-3 rounded-md max-h-96 overflow-auto", children: log.responseText }) })
2873
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground italic", children: "No original response" }) }),
2874
- replayResult && replayResult.success && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "replay", children: replayResult.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "parsed", children: [
2875
- /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { children: [
2876
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" }),
2877
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" })
2878
- ] }),
2879
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2880
- ResponseView,
2881
- {
2882
- responseText: replayResult.responseText ?? null,
2883
- responseStatus: replayResult.responseStatus ?? null,
2884
- streaming: replayResult.streaming ?? false,
2885
- inputTokens: replayResult.inputTokens ?? null,
2886
- outputTokens: replayResult.outputTokens ?? null,
2887
- apiFormat: log.apiFormat
2888
- }
2889
- ) }),
2890
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap bg-muted p-3 rounded-md max-h-96 overflow-auto", children: replayResult.responseText }) })
2891
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground italic", children: "No replay response" }) })
2892
- ] })
2893
- ] }) });
2894
- }
2895
- const StreamingChunkSequence = reactExports.memo(function StreamingChunkSequence2({
2896
- logId,
2897
- truncated
2898
- }) {
2899
- const [containerExpanded, setContainerExpanded] = reactExports.useState(false);
2900
- const [chunkState, setChunkState] = reactExports.useState({ status: "idle" });
2901
- const [expandedIndices, setExpandedIndices] = reactExports.useState(/* @__PURE__ */ new Set());
2902
- reactExports.useEffect(() => {
2903
- if (!containerExpanded || chunkState.status !== "idle") return;
2904
- let cancelled = false;
2905
- setChunkState({ status: "loading" });
2906
- fetch(`/api/logs/${logId}/chunks`).then((res) => {
2907
- if (!res.ok) {
2908
- return Promise.reject(new Error("Chunks not found"));
2909
- }
2910
- return res.json();
2911
- }).then((data) => {
2912
- if (!cancelled) {
2913
- setChunkState({ status: "success", chunks: data.chunks });
2914
- }
2915
- }).catch(() => {
2916
- if (!cancelled) {
2917
- setChunkState({ status: "error", message: "Chunk data unavailable" });
2918
- }
2919
- });
2920
- return () => {
2921
- cancelled = true;
2922
- };
2923
- }, [containerExpanded, logId]);
2924
- const groups = reactExports.useMemo(() => {
2925
- if (chunkState.status !== "success") return [];
2926
- const map = /* @__PURE__ */ new Map();
2927
- for (const chunk of chunkState.chunks) {
2928
- const existing = map.get(chunk.index);
2929
- if (existing) {
2930
- existing.push(chunk);
2931
- } else {
2932
- map.set(chunk.index, [chunk]);
2933
- }
2934
- }
2935
- return Array.from(map.entries()).map(([index, chunks]) => ({ index, chunks })).sort((a, b) => a.index - b.index);
2936
- }, [chunkState]);
2937
- const toggleIndex = (index) => {
2938
- setExpandedIndices((prev) => {
2939
- const next = new Set(prev);
2940
- if (next.has(index)) {
2941
- next.delete(index);
2942
- } else {
2943
- next.add(index);
2944
- }
2945
- return next;
2946
- });
2947
- };
2948
- function renderBody() {
2949
- if (chunkState.status === "idle" || chunkState.status === "loading") {
2950
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 py-2 text-xs text-muted-foreground", children: [
2951
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3 animate-spin" }),
2952
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Loading chunks..." })
2953
- ] });
2954
- }
2955
- if (chunkState.status === "error") {
2956
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-2 text-xs text-muted-foreground italic", children: chunkState.message });
2957
- }
2958
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 space-y-1", children: [
2959
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-muted-foreground font-mono mb-2", children: [
2960
- groups.length,
2961
- " index group",
2962
- groups.length !== 1 ? "s" : "",
2963
- " available"
2964
- ] }),
2965
- groups.map((group) => {
2966
- const isExpanded = expandedIndices.has(group.index);
2967
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-border bg-background", children: [
2968
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2969
- "button",
2970
- {
2971
- type: "button",
2972
- className: "flex items-center gap-2 w-full px-2 py-1.5 text-left hover:bg-muted/50 transition-colors cursor-pointer",
2973
- onClick: () => toggleIndex(group.index),
2974
- children: [
2975
- isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" }),
2976
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-muted-foreground font-mono", children: [
2977
- "[",
2978
- group.index,
2979
- "] ",
2980
- group.chunks[0]?.type ?? ""
2981
- ] }),
2982
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-muted-foreground font-mono", children: [
2983
- group.chunks.length,
2984
- " chunk",
2985
- group.chunks.length !== 1 ? "s" : ""
2986
- ] })
2987
- ]
2988
- }
2989
- ),
2990
- isExpanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2 pb-2 space-y-1", children: group.chunks.map((chunk) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-border bg-muted/20 p-2", children: [
2991
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
2992
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-muted-foreground font-mono", children: [
2993
- "+",
2994
- chunk.timestamp,
2995
- "ms"
2996
- ] }),
2997
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: chunk.type })
2998
- ] }),
2999
- /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: chunk, defaultExpandDepth: 1, showCopy: true })
3000
- ] }, chunk.index)) })
3001
- ] }, group.index);
3002
- })
3003
- ] });
3004
- }
3005
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
3006
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3007
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3008
- "button",
3009
- {
3010
- type: "button",
3011
- className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
3012
- onClick: () => setContainerExpanded((v) => !v),
3013
- children: [
3014
- containerExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3" }),
3015
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Raw SSE Events" }),
3016
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "text-[9px] px-1 py-0 h-4 font-mono ml-1", children: [
3017
- logId,
3018
- truncated === true ? "+" : ""
3019
- ] })
3020
- ]
3021
- }
3022
- ) }),
3023
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Server-Sent Events streaming chunks from the provider" })
3024
- ] }) }),
3025
- containerExpanded === true ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-border bg-muted/20 overflow-auto max-h-64", children: renderBody() }) : null
3026
- ] });
3027
- });
3028
2349
  function shouldShowRawRequestTab(apiFormat, viewMode, strip) {
3029
2350
  return apiFormat === "anthropic" && viewMode === "full" && strip;
3030
2351
  }
@@ -3034,6 +2355,9 @@ function shouldShowHeadersDiffButton(viewMode, hasRawHeaders) {
3034
2355
  function shouldShowRequestDiffButton(apiFormat, viewMode, strip, hasRawRequest) {
3035
2356
  return apiFormat === "anthropic" && viewMode === "full" && strip && hasRawRequest;
3036
2357
  }
2358
+ function TabFallback() {
2359
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1", "aria-hidden": "true" });
2360
+ }
3037
2361
  function CopyButton({
3038
2362
  text,
3039
2363
  label,
@@ -3041,39 +2365,39 @@ function CopyButton({
3041
2365
  onCopy
3042
2366
  }) {
3043
2367
  if (text === null) return null;
3044
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
3045
- "button",
3046
- {
3047
- type: "button",
3048
- onClick: onCopy,
3049
- className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1 rounded hover:bg-muted",
3050
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3051
- /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }),
3052
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500", children: "Copied!" })
3053
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3054
- /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" }),
3055
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: label })
3056
- ] })
3057
- }
3058
- );
3059
- }
3060
- function DiffToggleButton({
2368
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2369
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2370
+ Button,
2371
+ {
2372
+ variant: "outline",
2373
+ size: "sm",
2374
+ className: "h-8 text-xs",
2375
+ onClick: onCopy,
2376
+ "aria-label": label,
2377
+ children: [
2378
+ copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 mr-1 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5 mr-1" }),
2379
+ copied ? "Copied!" : label
2380
+ ]
2381
+ }
2382
+ ) }),
2383
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: copied ? "Copied to clipboard" : label })
2384
+ ] });
2385
+ }
2386
+ function DiffToggleButton({
3061
2387
  active,
3062
2388
  onClick
3063
2389
  }) {
3064
2390
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3065
2391
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3066
- "button",
2392
+ Button,
3067
2393
  {
3068
- type: "button",
2394
+ variant: active ? "default" : "outline",
2395
+ size: "sm",
2396
+ className: "h-8 text-xs",
3069
2397
  onClick,
3070
2398
  "aria-pressed": active,
3071
- className: cn(
3072
- "flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
3073
- active ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground hover:bg-muted"
3074
- ),
3075
2399
  children: [
3076
- /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3" }),
2400
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3.5 mr-1" }),
3077
2401
  active ? "Showing diff" : "Diff with Raw"
3078
2402
  ]
3079
2403
  }
@@ -3135,6 +2459,8 @@ const LogEntry = reactExports.memo(function({
3135
2459
  const [headersDiff, setHeadersDiff] = reactExports.useState(false);
3136
2460
  const [requestDiff, setRequestDiff] = reactExports.useState(false);
3137
2461
  const [activeTab, setActiveTab] = reactExports.useState("request");
2462
+ const [expandToPath, setExpandToPath] = reactExports.useState(null);
2463
+ const requestJsonRef = reactExports.useRef(null);
3138
2464
  const resolvedFormat = resolveLogFormat(log);
3139
2465
  const adapter = getLogFormatAdapter(resolvedFormat);
3140
2466
  const requestAnalysis = reactExports.useMemo(
@@ -3155,6 +2481,23 @@ const LogEntry = reactExports.memo(function({
3155
2481
  const requestCopy = useCopyFeedback(displayedRequestBody);
3156
2482
  const rawRequestCopy = useCopyFeedback(log.rawRequestBody);
3157
2483
  const responseCopy = useCopyFeedback(log.responseText);
2484
+ const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
2485
+ const rawRequestExpansion = useJsonBulkExpansion(log.rawRequestBody);
2486
+ const anatomySegments = reactExports.useMemo(
2487
+ () => requestExpansion.parsedData !== null ? adapter.anatomySegments(requestExpansion.parsedData) : null,
2488
+ [adapter, requestExpansion.parsedData]
2489
+ );
2490
+ const anatomyPaths = reactExports.useMemo(() => {
2491
+ if (anatomySegments === null) return void 0;
2492
+ return new Set(anatomySegments.map((s) => s.path));
2493
+ }, [anatomySegments]);
2494
+ const jumpToAnatomySegment = useAnatomyJump({
2495
+ containerRef: requestJsonRef,
2496
+ setExpandToPath,
2497
+ ensureTabActive: () => {
2498
+ if (activeTab !== "request") setActiveTab("request");
2499
+ }
2500
+ });
3158
2501
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(TooltipProvider, { children: [
3159
2502
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border rounded-lg mb-1 overflow-hidden", children: [
3160
2503
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -3166,7 +2509,19 @@ const LogEntry = reactExports.memo(function({
3166
2509
  expanded,
3167
2510
  onToggle: () => setExpanded(!expanded),
3168
2511
  responseToolNames: responseAnalysis.toolNames,
3169
- cacheTrend
2512
+ cacheTrend,
2513
+ onReplay: onCompareWithPrevious === void 0 ? void 0 : () => {
2514
+ setReplayOpen(true);
2515
+ },
2516
+ onCopyRequest: displayedRequestBody === null ? void 0 : (e) => {
2517
+ requestCopy.copy(e);
2518
+ },
2519
+ requestCopied: requestCopy.copied,
2520
+ onToggleRequestExpansion: requestExpansion.toggle,
2521
+ requestExpansionState: requestExpansion.policy === null ? null : {
2522
+ isExpanded: requestExpansion.isExpanded,
2523
+ isPending: requestExpansion.isPending
2524
+ }
3170
2525
  }
3171
2526
  ),
3172
2527
  expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [
@@ -3175,23 +2530,47 @@ const LogEntry = reactExports.memo(function({
3175
2530
  viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }),
3176
2531
  shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }),
3177
2532
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
2533
+ anatomySegments !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "anatomy", children: "Anatomy" }),
3178
2534
  viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
3179
2535
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
3180
2536
  ] }),
3181
- shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
3182
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3183
- CopyButton,
2537
+ shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pt-1 pb-3", children: [
2538
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2539
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2540
+ JsonExpansionButton,
2541
+ {
2542
+ policy: rawRequestExpansion.policy,
2543
+ isExpanded: rawRequestExpansion.isExpanded,
2544
+ isPending: rawRequestExpansion.isPending,
2545
+ onToggle: rawRequestExpansion.toggle
2546
+ }
2547
+ ),
2548
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2549
+ CopyButton,
2550
+ {
2551
+ text: log.rawRequestBody,
2552
+ label: "Copy",
2553
+ copied: rawRequestCopy.copied,
2554
+ onCopy: rawRequestCopy.copy
2555
+ }
2556
+ )
2557
+ ] }),
2558
+ log.rawRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" }) : rawRequestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2559
+ LazyJsonViewer,
3184
2560
  {
3185
- text: log.rawRequestBody,
3186
- label: "Copy Raw Request",
3187
- copied: rawRequestCopy.copied,
3188
- onCopy: rawRequestCopy.copy
2561
+ data: rawRequestExpansion.parsedData,
2562
+ bulkDepth: rawRequestExpansion.bulkDepth,
2563
+ bulkRevision: rawRequestExpansion.bulkRevision
3189
2564
  }
3190
- ) }),
3191
- log.rawRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.rawRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2565
+ ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words", children: log.rawRequestBody })
3192
2566
  ] }) }),
3193
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
3194
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2567
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pt-1 pb-3", children: [
2568
+ (shouldShowRequestDiffButton(
2569
+ resolvedFormat,
2570
+ viewMode,
2571
+ strip,
2572
+ log.rawRequestBody !== null
2573
+ ) || onCompareWithPrevious !== void 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
3195
2574
  shouldShowRequestDiffButton(
3196
2575
  resolvedFormat,
3197
2576
  viewMode,
@@ -3213,7 +2592,7 @@ const LogEntry = reactExports.memo(function({
3213
2592
  {
3214
2593
  variant: "outline",
3215
2594
  size: "sm",
3216
- className: "h-7 text-xs",
2595
+ className: "h-8 text-xs",
3217
2596
  onClick: (e) => {
3218
2597
  e.stopPropagation();
3219
2598
  onCompareWithPrevious(log);
@@ -3225,35 +2604,7 @@ const LogEntry = reactExports.memo(function({
3225
2604
  }
3226
2605
  ) }),
3227
2606
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Compare this request with the immediately preceding one" })
3228
- ] }),
3229
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3230
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3231
- Button,
3232
- {
3233
- variant: "outline",
3234
- size: "sm",
3235
- className: "h-7 text-xs",
3236
- onClick: (e) => {
3237
- e.stopPropagation();
3238
- setReplayOpen(true);
3239
- },
3240
- children: [
3241
- /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3 mr-1" }),
3242
- "Replay"
3243
- ]
3244
- }
3245
- ) }),
3246
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Re-send this request to the provider" })
3247
- ] }),
3248
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3249
- CopyButton,
3250
- {
3251
- text: displayedRequestBody,
3252
- label: "Copy Request",
3253
- copied: requestCopy.copied,
3254
- onCopy: requestCopy.copy
3255
- }
3256
- )
2607
+ ] })
3257
2608
  ] }),
3258
2609
  requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
3259
2610
  RequestDiffContent,
@@ -3262,9 +2613,27 @@ const LogEntry = reactExports.memo(function({
3262
2613
  displayedBody: displayedRequestBody,
3263
2614
  emptyLabel: "No transformation applied — raw and sent request bodies are identical."
3264
2615
  }
3265
- ) : displayedRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: displayedRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2616
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: requestJsonRef, children: displayedRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" }) : requestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2617
+ LazyJsonViewer,
2618
+ {
2619
+ data: requestExpansion.parsedData,
2620
+ bulkDepth: requestExpansion.bulkDepth,
2621
+ bulkRevision: requestExpansion.bulkRevision,
2622
+ anatomyPaths,
2623
+ expandToPath
2624
+ }
2625
+ ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words", children: displayedRequestBody }) })
3266
2626
  ] }) }),
3267
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2627
+ anatomySegments !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "anatomy", children: activeTab === "anatomy" && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2628
+ LazyRequestAnatomy,
2629
+ {
2630
+ parsed: null,
2631
+ inputTokens: log.inputTokens ?? null,
2632
+ segments: anatomySegments,
2633
+ onSegmentActivate: jumpToAnatomySegment
2634
+ }
2635
+ ) }) }),
2636
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pt-1 pb-3", children: [
3268
2637
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
3269
2638
  viewMode,
3270
2639
  log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
@@ -3293,14 +2662,14 @@ const LogEntry = reactExports.memo(function({
3293
2662
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
3294
2663
  ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
3295
2664
  ] }) }),
3296
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: activeTab === "raw-headers" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2665
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: activeTab === "raw-headers" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 pt-1 pb-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
3297
2666
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
3298
2667
  key,
3299
2668
  ":"
3300
2669
  ] }),
3301
2670
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
3302
2671
  ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
3303
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: activeTab === "raw" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
2672
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: activeTab === "raw" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pt-1 pb-3 space-y-3", children: [
3304
2673
  log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
3305
2674
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
3306
2675
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
@@ -3309,22 +2678,22 @@ const LogEntry = reactExports.memo(function({
3309
2678
  CopyButton,
3310
2679
  {
3311
2680
  text: log.responseText,
3312
- label: "Copy Response",
2681
+ label: "Copy",
3313
2682
  copied: responseCopy.copied,
3314
2683
  onCopy: responseCopy.copy
3315
2684
  }
3316
2685
  ) }),
3317
- log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.responseText, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
3318
- log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
3319
- StreamingChunkSequence,
2686
+ log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(LazyJsonViewerFromString, { text: log.responseText, defaultExpandDepth: 0 }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
2687
+ log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2688
+ LazyStreamingChunkSequence,
3320
2689
  {
3321
2690
  logId: log.id,
3322
2691
  truncated: log.streamingChunksPath !== null
3323
2692
  }
3324
- )
2693
+ ) })
3325
2694
  ] }) }),
3326
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3327
- ResponseView,
2695
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 pt-1 pb-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2696
+ LazyResponseView,
3328
2697
  {
3329
2698
  responseText: log.responseText,
3330
2699
  responseStatus: log.responseStatus,
@@ -3336,10 +2705,10 @@ const LogEntry = reactExports.memo(function({
3336
2705
  apiFormat: resolvedFormat,
3337
2706
  error: log.error
3338
2707
  }
3339
- ) }) })
2708
+ ) }) }) })
3340
2709
  ] }) })
3341
2710
  ] }),
3342
- /* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
2711
+ /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(LazyReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen }) })
3343
2712
  ] });
3344
2713
  });
3345
2714
  function ThreadConnector({
@@ -3347,13 +2716,16 @@ function ThreadConnector({
3347
2716
  isPending,
3348
2717
  isFirst,
3349
2718
  isTurnStart,
2719
+ isOnlyEntry = false,
3350
2720
  crabIndex = 0,
3351
2721
  collapsible = false,
3352
2722
  onToggle
3353
2723
  }) {
3354
2724
  const isBoundary = stopReason === "end_turn" || stopReason === "stop";
2725
+ const isFusedBoundary = isOnlyEntry && isTurnStart && isBoundary;
3355
2726
  const isRunning = isPending && !isBoundary;
3356
2727
  const Crab = reactExports.useMemo(() => getCrabVariant(crabIndex), [crabIndex]);
2728
+ const FusedCrab = reactExports.useMemo(() => getInteriorCrabVariant(crabIndex), [crabIndex]);
3357
2729
  const interactiveProps = collapsible && onToggle ? {
3358
2730
  role: "button",
3359
2731
  tabIndex: 0,
@@ -3371,7 +2743,16 @@ function ThreadConnector({
3371
2743
  } : {};
3372
2744
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center w-6 shrink-0 pt-0.5 pb-0.5", children: [
3373
2745
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center h-[calc(0.75rem-8px)]", children: !isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 bg-muted-foreground/30 h-full" }) }),
3374
- isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2746
+ isFusedBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: "Start and end of turn", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2747
+ FusedCrab,
2748
+ {
2749
+ className: cn(
2750
+ "size-3.5 text-[#80FF00]",
2751
+ "animate-crab-settle",
2752
+ "drop-shadow-[0_0_4px_rgba(128,255,0,0.5)]"
2753
+ )
2754
+ }
2755
+ ) }) : isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx(
3375
2756
  "span",
3376
2757
  {
3377
2758
  title: stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)",
@@ -3408,6 +2789,43 @@ function ThreadConnector({
3408
2789
  ) })
3409
2790
  ] });
3410
2791
  }
2792
+ function shouldRenderConversationContent(standalone, expanded) {
2793
+ return standalone || expanded;
2794
+ }
2795
+ function isTurnCollapsible(entryCount, isComplete, isPending) {
2796
+ return entryCount > 1 && isComplete && !isPending;
2797
+ }
2798
+ function buildTurnGroups(logs) {
2799
+ const groups = [];
2800
+ let entries = [];
2801
+ let turnIndex = 0;
2802
+ for (const log of logs) {
2803
+ entries.push({ log, stopReason: extractStopReason(log) });
2804
+ const current = entries[entries.length - 1];
2805
+ if (current !== void 0 && isTurnBoundary(current.stopReason)) {
2806
+ groups.push({ entries, turnIndex });
2807
+ entries = [];
2808
+ turnIndex += 1;
2809
+ }
2810
+ }
2811
+ if (entries.length > 0) groups.push({ entries, turnIndex });
2812
+ return groups;
2813
+ }
2814
+ function buildValidPredecessors(groups) {
2815
+ const predecessors = /* @__PURE__ */ new Map();
2816
+ for (const group of groups) {
2817
+ for (let index2 = 1; index2 < group.logs.length; index2 += 1) {
2818
+ const current = group.logs[index2];
2819
+ const previous = group.logs[index2 - 1];
2820
+ if (current === void 0 || previous === void 0) continue;
2821
+ const currentFormat = resolveLogFormat(current);
2822
+ const previousFormat = resolveLogFormat(previous);
2823
+ if (currentFormat === "unknown" || currentFormat !== previousFormat) continue;
2824
+ predecessors.set(current.id, previous);
2825
+ }
2826
+ }
2827
+ return predecessors;
2828
+ }
3411
2829
  function formatElapsed(ms) {
3412
2830
  if (ms < 1e3) return `${ms}ms`;
3413
2831
  return `${(ms / 1e3).toFixed(1)}s`;
@@ -3425,15 +2843,18 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3425
2843
  const lastStop = entries[lastIdx]?.stopReason ?? null;
3426
2844
  const isComplete = lastStop !== null ? isTurnBoundary(lastStop) : false;
3427
2845
  const isPending = entries[lastIdx]?.log.responseStatus === null;
3428
- const collapsible = isComplete && !isPending;
2846
+ const isSingleLog = entries.length === 1;
2847
+ const collapsible = isTurnCollapsible(entries.length, isComplete, isPending);
3429
2848
  const [collapsed, setCollapsed] = reactExports.useState(false);
3430
- const prevCompleteRef = reactExports.useRef(isComplete);
2849
+ const prevCompleteRef = reactExports.useRef(false);
3431
2850
  reactExports.useEffect(() => {
3432
- if (isComplete && !prevCompleteRef.current) {
2851
+ if (!collapsible) {
2852
+ setCollapsed(false);
2853
+ } else if (isComplete && !prevCompleteRef.current) {
3433
2854
  setCollapsed(true);
3434
2855
  }
3435
2856
  prevCompleteRef.current = isComplete;
3436
- }, [isComplete]);
2857
+ }, [collapsible, isComplete]);
3437
2858
  const toggleCollapse = reactExports.useCallback(() => {
3438
2859
  if (collapsible) setCollapsed((prev) => !prev);
3439
2860
  }, [collapsible]);
@@ -3498,129 +2919,137 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3498
2919
  ref: containerRef,
3499
2920
  className: cn("border rounded-lg", isPending ? "border-amber-500/10" : "border-transparent"),
3500
2921
  children: collapsed ? (
3501
- /* ---- Collapsed: dual-crab + summary ---- */
3502
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
3503
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-6 shrink-0 flex flex-col items-center pt-0.5 pb-0.5", children: [
3504
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center h-[calc(0.75rem-8px)]" }),
3505
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3506
- "span",
3507
- {
3508
- role: "button",
3509
- tabIndex: 0,
3510
- title: "Start of turn — click to expand",
3511
- className: "cursor-pointer",
3512
- onClick: (e) => {
3513
- e.stopPropagation();
3514
- toggleCollapse();
3515
- },
3516
- onKeyDown: (e) => {
3517
- if (e.key === "Enter" || e.key === " ") {
3518
- e.preventDefault();
3519
- toggleCollapse();
3520
- }
3521
- },
3522
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3523
- StartCrab,
2922
+ /* ---- Collapsed: dual-crab (+ summary card for multi-log turns) ---- */
2923
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2924
+ "div",
2925
+ {
2926
+ "data-nav-id": `turn-collapsed-${entries[0]?.log.id ?? turnIndex}`,
2927
+ "data-nav-action": "expand",
2928
+ role: "button",
2929
+ tabIndex: 0,
2930
+ className: "flex items-stretch cursor-pointer focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none rounded-lg",
2931
+ onClick: toggleCollapse,
2932
+ onKeyDown: (e) => {
2933
+ if (e.key === "Enter" || e.key === " ") {
2934
+ e.preventDefault();
2935
+ toggleCollapse();
2936
+ }
2937
+ },
2938
+ children: [
2939
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 shrink-0 flex items-start pt-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono tabular-nums leading-none select-none", children: turnIndex + 1 }) }),
2940
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-6 shrink-0 flex flex-col items-center pt-0.5 pb-0.5", children: [
2941
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center h-[calc(0.75rem-8px)]" }),
2942
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2943
+ "span",
3524
2944
  {
3525
- className: cn(
3526
- "size-3.5 text-emerald-400",
3527
- "animate-crab-appear",
3528
- "drop-shadow-[0_0_4px_rgba(52,211,153,0.5)]"
2945
+ role: "button",
2946
+ tabIndex: 0,
2947
+ title: "Start of turn — click to expand",
2948
+ className: "cursor-pointer",
2949
+ onClick: (e) => {
2950
+ e.stopPropagation();
2951
+ toggleCollapse();
2952
+ },
2953
+ onKeyDown: (e) => {
2954
+ if (e.key === "Enter" || e.key === " ") {
2955
+ e.preventDefault();
2956
+ toggleCollapse();
2957
+ }
2958
+ },
2959
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2960
+ StartCrab,
2961
+ {
2962
+ className: cn(
2963
+ "size-3.5 text-emerald-400",
2964
+ "animate-crab-appear drop-shadow-[0_0_4px_rgba(52,211,153,0.5)]"
2965
+ )
2966
+ }
3529
2967
  )
3530
2968
  }
3531
- )
3532
- }
3533
- ),
3534
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex justify-center min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 bg-muted-foreground/30 h-full" }) }),
3535
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3536
- "span",
3537
- {
3538
- role: "button",
3539
- tabIndex: 0,
3540
- title: "End of Turn — click to expand",
3541
- className: "cursor-pointer",
3542
- onClick: (e) => {
3543
- e.stopPropagation();
3544
- toggleCollapse();
3545
- },
3546
- onKeyDown: (e) => {
3547
- if (e.key === "Enter" || e.key === " ") {
3548
- e.preventDefault();
3549
- toggleCollapse();
3550
- }
3551
- },
3552
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3553
- EndCrab,
2969
+ ),
2970
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex justify-center min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 bg-muted-foreground/30 h-full" }) }),
2971
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2972
+ "span",
3554
2973
  {
3555
- className: cn(
3556
- "size-3.5 text-amber-400",
3557
- "animate-crab-settle",
3558
- "drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]"
2974
+ role: "button",
2975
+ tabIndex: 0,
2976
+ title: "End of Turn — click to expand",
2977
+ className: "cursor-pointer",
2978
+ onClick: (e) => {
2979
+ e.stopPropagation();
2980
+ toggleCollapse();
2981
+ },
2982
+ onKeyDown: (e) => {
2983
+ if (e.key === "Enter" || e.key === " ") {
2984
+ e.preventDefault();
2985
+ toggleCollapse();
2986
+ }
2987
+ },
2988
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2989
+ EndCrab,
2990
+ {
2991
+ className: cn(
2992
+ "size-3.5 text-amber-400",
2993
+ "animate-crab-settle drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]"
2994
+ )
2995
+ }
3559
2996
  )
3560
2997
  }
3561
2998
  )
3562
- }
3563
- )
3564
- ] }),
3565
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
3566
- "div",
3567
- {
3568
- className: cn(
3569
- "flex-1 min-w-0 mb-0.5 rounded-lg border border-border py-1 px-3 flex items-center gap-3 text-xs cursor-pointer",
3570
- bgClass
3571
- ),
3572
- onClick: toggleCollapse,
3573
- role: "button",
3574
- tabIndex: 0,
3575
- onKeyDown: (e) => {
3576
- if (e.key === "Enter" || e.key === " ") {
3577
- e.preventDefault();
3578
- toggleCollapse();
3579
- }
3580
- },
3581
- children: [
3582
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono font-semibold tabular-nums shrink-0", children: [
3583
- "#",
3584
- entries[0]?.log.id ?? "?",
3585
- " ~ #",
3586
- entries[lastIdx]?.log.id ?? "?"
3587
- ] }),
3588
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground shrink-0", children: [
3589
- entries.length,
3590
- " request",
3591
- entries.length > 1 ? "s" : ""
3592
- ] }),
3593
- uniqueProviders.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-center gap-0.5 shrink-0", children: uniqueProviders.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: p, className: "size-4" }, p)) }),
3594
- aggregate.hasElapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground shrink-0", children: [
3595
- /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3596
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.totalElapsed) })
3597
- ] }),
3598
- aggregate.hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 shrink-0", children: [
3599
- /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
3600
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
3601
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400", children: [
3602
- "IN ",
3603
- formatTokens(aggregate.totalInput)
2999
+ ] }),
3000
+ entries.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3001
+ "div",
3002
+ {
3003
+ className: cn(
3004
+ "flex-1 min-w-0 mb-0.5 rounded-lg border border-border py-1 px-3 flex items-center gap-3 text-xs",
3005
+ bgClass
3006
+ ),
3007
+ children: [
3008
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono font-semibold tabular-nums shrink-0", children: [
3009
+ "#",
3010
+ entries[0]?.log.id ?? "?",
3011
+ " ~ #",
3012
+ entries[lastIdx]?.log.id ?? "?"
3604
3013
  ] }),
3605
- " / ",
3606
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-amber-400", children: [
3607
- "OUT ",
3608
- formatTokens(aggregate.totalOutput)
3609
- ] })
3610
- ] })
3611
- ] }),
3612
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
3613
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" })
3614
- ]
3615
- }
3616
- )
3617
- ] })
3014
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground shrink-0", children: [
3015
+ entries.length,
3016
+ " request",
3017
+ entries.length > 1 ? "s" : ""
3018
+ ] }),
3019
+ uniqueProviders.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-center gap-0.5 shrink-0", children: uniqueProviders.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: p, className: "size-4" }, p)) }),
3020
+ aggregate.hasElapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground shrink-0", children: [
3021
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3022
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.totalElapsed) })
3023
+ ] }),
3024
+ aggregate.hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 shrink-0", children: [
3025
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
3026
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
3027
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400", children: [
3028
+ "IN ",
3029
+ formatTokens(aggregate.totalInput)
3030
+ ] }),
3031
+ " / ",
3032
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-amber-400", children: [
3033
+ "OUT ",
3034
+ formatTokens(aggregate.totalOutput)
3035
+ ] })
3036
+ ] })
3037
+ ] }),
3038
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
3039
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" })
3040
+ ]
3041
+ }
3042
+ )
3043
+ ]
3044
+ }
3045
+ )
3618
3046
  ) : (
3619
3047
  /* ---- Expanded: full entries ---- */
3620
3048
  entries.map((entry, visibleIdx) => {
3621
3049
  const { log, stopReason: reason } = entry;
3622
3050
  const isTurnStart = visibleIdx === 0;
3623
3051
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
3052
+ isTurnStart ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 shrink-0 flex items-start pt-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono tabular-nums leading-none select-none", children: turnIndex + 1 }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 shrink-0" }),
3624
3053
  /* @__PURE__ */ jsxRuntimeExports.jsx(
3625
3054
  ThreadConnector,
3626
3055
  {
@@ -3628,8 +3057,9 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3628
3057
  isPending: log.responseStatus === null,
3629
3058
  isFirst: visibleIdx === 0,
3630
3059
  isTurnStart,
3060
+ isOnlyEntry: isSingleLog,
3631
3061
  crabIndex: log.id % 12,
3632
- collapsible: collapsible && entries.length > 1 && isTurnStart,
3062
+ collapsible: collapsible && isTurnStart,
3633
3063
  onToggle: toggleCollapse
3634
3064
  }
3635
3065
  ),
@@ -3649,40 +3079,6 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3649
3079
  }
3650
3080
  );
3651
3081
  });
3652
- function shouldRenderConversationContent(standalone, expanded) {
3653
- return standalone || expanded;
3654
- }
3655
- function buildTurnGroups(logs) {
3656
- const groups = [];
3657
- let entries = [];
3658
- let turnIndex = 0;
3659
- for (const log of logs) {
3660
- entries.push({ log, stopReason: extractStopReason(log) });
3661
- const current = entries[entries.length - 1];
3662
- if (current !== void 0 && isTurnBoundary(current.stopReason)) {
3663
- groups.push({ entries, turnIndex });
3664
- entries = [];
3665
- turnIndex += 1;
3666
- }
3667
- }
3668
- if (entries.length > 0) groups.push({ entries, turnIndex });
3669
- return groups;
3670
- }
3671
- function buildValidPredecessors(groups) {
3672
- const predecessors = /* @__PURE__ */ new Map();
3673
- for (const group of groups) {
3674
- for (let index = 1; index < group.logs.length; index += 1) {
3675
- const current = group.logs[index];
3676
- const previous = group.logs[index - 1];
3677
- if (current === void 0 || previous === void 0) continue;
3678
- const currentFormat = resolveLogFormat(current);
3679
- const previousFormat = resolveLogFormat(previous);
3680
- if (currentFormat === "unknown" || currentFormat !== previousFormat) continue;
3681
- predecessors.set(current.id, previous);
3682
- }
3683
- }
3684
- return predecessors;
3685
- }
3686
3082
  function computeStats(logs) {
3687
3083
  let totalInput = 0;
3688
3084
  let totalOutput = 0;
@@ -3729,7 +3125,7 @@ const ConversationGroup = reactExports.memo(function({
3729
3125
  onClear: () => onClearGroup(group.logs.map((l) => l.id))
3730
3126
  }
3731
3127
  ),
3732
- shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-[70vh] overflow-y-auto", children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3128
+ shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3733
3129
  TurnGroup,
3734
3130
  {
3735
3131
  entries: tg.entries,
@@ -4031,13 +3427,13 @@ function ImportWizardDialog({
4031
3427
  scan();
4032
3428
  }
4033
3429
  }, [open, scan]);
4034
- const toggleProvider = reactExports.useCallback((index) => {
3430
+ const toggleProvider = reactExports.useCallback((index2) => {
4035
3431
  setSelected((prev) => {
4036
3432
  const next = new Set(prev);
4037
- if (next.has(index)) {
4038
- next.delete(index);
3433
+ if (next.has(index2)) {
3434
+ next.delete(index2);
4039
3435
  } else {
4040
- next.add(index);
3436
+ next.add(index2);
4041
3437
  }
4042
3438
  return next;
4043
3439
  });
@@ -4562,10 +3958,10 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
4562
3958
  const modelRowRefs = reactExports.useRef([]);
4563
3959
  reactExports.useEffect(() => {
4564
3960
  if (openModelDropdown === null) return;
4565
- const index = openModelDropdown;
3961
+ const index2 = openModelDropdown;
4566
3962
  function handleClick(e) {
4567
3963
  if (!(e.target instanceof Node)) return;
4568
- const ref = modelRowRefs.current[index];
3964
+ const ref = modelRowRefs.current[index2];
4569
3965
  if (ref !== null && ref !== void 0 && !ref.contains(e.target)) {
4570
3966
  setOpenModelDropdown(null);
4571
3967
  }
@@ -5507,968 +4903,240 @@ function SettingsDialog() {
5507
4903
  isLoading,
5508
4904
  externalTestResults: testResults,
5509
4905
  externalTestingProviders: testingProviders,
5510
- externalTestingTimeLeft: testingTimeLeft,
5511
- onProvidersMutate: () => {
5512
- return mutate();
5513
- },
5514
- onTestResultsChange: handleTestResultsChange,
5515
- onTestingProvidersChange: handleTestingProvidersChange,
5516
- onTestingTimeLeftChange: handleTestingTimeLeftChange
5517
- }
5518
- ) }),
5519
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "proxy", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProxySettingsTab, {}) })
5520
- ] })
5521
- ] })
5522
- ] })
5523
- ] });
5524
- }
5525
- function ProxySettingsTab() {
5526
- const { strip, isLoading, setStrip } = useStripConfig();
5527
- const [error, setError] = reactExports.useState(null);
5528
- const [pending, setPending] = reactExports.useState(false);
5529
- const handleToggle = reactExports.useCallback(
5530
- async (next) => {
5531
- setError(null);
5532
- setPending(true);
5533
- try {
5534
- await setStrip(next);
5535
- } catch (err) {
5536
- setError(err instanceof Error ? err.message : String(err));
5537
- } finally {
5538
- setPending(false);
5539
- }
5540
- },
5541
- [setStrip]
5542
- );
5543
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
5544
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
5545
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold", children: "Claude Code billing header" }),
5546
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground", children: [
5547
- "When enabled, the proxy strips the synthetic ",
5548
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "x-anthropic-billing-header" }),
5549
- " text block that Claude Code prepends to the system prompt before forwarding to upstream. Improves prefix-cache hit rates against third-party upstreams. The default at startup is controlled by the ",
5550
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "LLM_INSPECTOR_STRIP_CLAUDE_CODE_BILLING_HEADER" }),
5551
- " environment variable; your choice is persisted to ",
5552
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "config.json" }),
5553
- " and overrides the env var for subsequent requests."
5554
- ] })
5555
- ] }),
5556
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-3", children: [
5557
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5558
- "input",
5559
- {
5560
- type: "checkbox",
5561
- role: "switch",
5562
- checked: strip,
5563
- disabled: isLoading || pending,
5564
- onChange: (e) => {
5565
- void handleToggle(e.currentTarget.checked);
5566
- },
5567
- className: "size-4 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
5568
- }
5569
- ),
5570
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
5571
- ] }),
5572
- error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
5573
- "Failed to save: ",
5574
- error
5575
- ] })
5576
- ] });
5577
- }
5578
- function computeCacheTrends(groups) {
5579
- const result = /* @__PURE__ */ new Map();
5580
- for (const group of groups) {
5581
- const logs = group.logs;
5582
- for (let i = 1; i < logs.length; i++) {
5583
- const prev = logs[i - 1];
5584
- const curr = logs[i];
5585
- if (prev === void 0 || curr === void 0) continue;
5586
- result.set(curr.id, {
5587
- creation: compareField(prev.cacheCreationInputTokens, curr.cacheCreationInputTokens),
5588
- read: compareField(prev.cacheReadInputTokens, curr.cacheReadInputTokens)
5589
- });
5590
- }
5591
- }
5592
- return result;
5593
- }
5594
- function compareField(previous, current) {
5595
- if (current === null) return null;
5596
- if (previous === null) return null;
5597
- if (current > previous) return { direction: "up", delta: current - previous };
5598
- if (current < previous) return { direction: "down", delta: previous - current };
5599
- return null;
5600
- }
5601
- const ROOT_PATH = "";
5602
- function formatPath(segments) {
5603
- if (segments.length === 0) return ROOT_PATH;
5604
- let out = "";
5605
- for (let i = 0; i < segments.length; i++) {
5606
- const seg = segments[i];
5607
- if (seg === void 0) continue;
5608
- if (typeof seg === "number") {
5609
- out += `[${seg}]`;
5610
- } else if (i === 0) {
5611
- out += seg;
5612
- } else {
5613
- out += `.${seg}`;
5614
- }
5615
- }
5616
- return out;
5617
- }
5618
- function isPlainObject(value) {
5619
- return typeof value === "object" && value !== null && !Array.isArray(value);
5620
- }
5621
- function normalizeRequest(raw) {
5622
- if (typeof raw === "string") {
5623
- try {
5624
- return toNode(JSON.parse(raw));
5625
- } catch {
5626
- return { kind: "primitive", value: raw };
5627
- }
5628
- }
5629
- return toNode(raw);
5630
- }
5631
- function toNode(value) {
5632
- if (value === null) return { kind: "primitive", value: null };
5633
- if (typeof value === "string") return { kind: "primitive", value };
5634
- if (typeof value === "number") return { kind: "primitive", value };
5635
- if (typeof value === "boolean") return { kind: "primitive", value };
5636
- if (Array.isArray(value)) {
5637
- return { kind: "array", value: value.map((v) => toNode(v)) };
5638
- }
5639
- if (isPlainObject(value)) {
5640
- const out = {};
5641
- for (const k of Object.keys(value).sort()) {
5642
- out[k] = toNode(value[k]);
5643
- }
5644
- return { kind: "object", value: out };
5645
- }
5646
- return { kind: "primitive", value: null };
5647
- }
5648
- function diffTrees(left, right) {
5649
- const ops = [];
5650
- walk([], left, right, ops);
5651
- return ops;
5652
- }
5653
- function walk(segments, left, right, out) {
5654
- const path = formatPath(segments);
5655
- if (nodeEqual(left, right)) {
5656
- out.push({ kind: "equal", path, value: left });
5657
- return;
5658
- }
5659
- if (left.kind !== right.kind) {
5660
- out.push({ kind: "changed", path, left, right });
5661
- return;
5662
- }
5663
- if (left.kind === "primitive" && right.kind === "primitive") {
5664
- out.push({ kind: "changed", path, left, right });
5665
- return;
5666
- }
5667
- if (left.kind === "object" && right.kind === "object") {
5668
- const leftKeys = Object.keys(left.value);
5669
- const rightKeys = Object.keys(right.value);
5670
- const rightKeySet = new Set(rightKeys);
5671
- for (const k of leftKeys) {
5672
- const lChild = left.value[k];
5673
- if (lChild === void 0) continue;
5674
- if (!rightKeySet.has(k)) {
5675
- out.push({
5676
- kind: "removed",
5677
- path: formatPath([...segments, k]),
5678
- value: lChild
5679
- });
5680
- } else {
5681
- const rChild = right.value[k];
5682
- if (rChild === void 0) continue;
5683
- walk([...segments, k], lChild, rChild, out);
5684
- }
5685
- }
5686
- for (const k of rightKeys) {
5687
- if (leftKeys.includes(k)) continue;
5688
- const rChild = right.value[k];
5689
- if (rChild === void 0) continue;
5690
- out.push({
5691
- kind: "added",
5692
- path: formatPath([...segments, k]),
5693
- value: rChild
5694
- });
5695
- }
5696
- return;
5697
- }
5698
- if (left.kind === "array" && right.kind === "array") {
5699
- const minLen = Math.min(left.value.length, right.value.length);
5700
- for (let i = 0; i < minLen; i++) {
5701
- const lChild = left.value[i];
5702
- const rChild = right.value[i];
5703
- if (lChild === void 0 || rChild === void 0) continue;
5704
- walk([...segments, i], lChild, rChild, out);
5705
- }
5706
- for (let i = minLen; i < right.value.length; i++) {
5707
- const rChild = right.value[i];
5708
- if (rChild === void 0) continue;
5709
- out.push({
5710
- kind: "added",
5711
- path: formatPath([...segments, i]),
5712
- value: rChild
5713
- });
5714
- }
5715
- for (let i = minLen; i < left.value.length; i++) {
5716
- const lChild = left.value[i];
5717
- if (lChild === void 0) continue;
5718
- out.push({
5719
- kind: "removed",
5720
- path: formatPath([...segments, i]),
5721
- value: lChild
5722
- });
5723
- }
5724
- }
5725
- }
5726
- function nodeEqual(a, b) {
5727
- if (a.kind !== b.kind) return false;
5728
- if (a.kind === "primitive" && b.kind === "primitive") {
5729
- return a.value === b.value;
5730
- }
5731
- if (a.kind === "array" && b.kind === "array") {
5732
- if (a.value.length !== b.value.length) return false;
5733
- for (let i = 0; i < a.value.length; i++) {
5734
- const ai = a.value[i];
5735
- const bi = b.value[i];
5736
- if (ai === void 0 || bi === void 0) return false;
5737
- if (!nodeEqual(ai, bi)) return false;
5738
- }
5739
- return true;
5740
- }
5741
- if (a.kind === "object" && b.kind === "object") {
5742
- const aKeys = Object.keys(a.value);
5743
- const bKeys = Object.keys(b.value);
5744
- if (aKeys.length !== bKeys.length) return false;
5745
- for (const k of aKeys) {
5746
- const av = a.value[k];
5747
- const bv = b.value[k];
5748
- if (av === void 0 || bv === void 0) return false;
5749
- if (!nodeEqual(av, bv)) return false;
5750
- }
5751
- return true;
5752
- }
5753
- return false;
5754
- }
5755
- function previewNode(node, maxLen = 80) {
5756
- let s;
5757
- switch (node.kind) {
5758
- case "primitive":
5759
- s = node.value === null ? "null" : JSON.stringify(node.value);
5760
- break;
5761
- case "array":
5762
- s = `[… ${node.value.length} items]`;
5763
- break;
5764
- case "object":
5765
- s = `{… ${Object.keys(node.value).length} keys}`;
5766
- break;
5767
- }
5768
- if (s.length > maxLen) s = `${s.slice(0, maxLen - 1)}…`;
5769
- return s;
5770
- }
5771
- function nodeToJsonString(node, indent = 2) {
5772
- return JSON.stringify(nodeToJsonValue(node), null, indent);
5773
- }
5774
- function nodeToJsonValue(node) {
5775
- switch (node.kind) {
5776
- case "primitive":
5777
- return node.value;
5778
- case "array":
5779
- return node.value.map(nodeToJsonValue);
5780
- case "object": {
5781
- const out = {};
5782
- for (const [k, v] of Object.entries(node.value)) {
5783
- out[k] = nodeToJsonValue(v);
5784
- }
5785
- return out;
5786
- }
5787
- }
5788
- }
5789
- function parentPath(path) {
5790
- if (path === "") return "";
5791
- for (let i = path.length - 1; i >= 0; i--) {
5792
- const ch = path[i];
5793
- if (ch === "." || ch === "[") {
5794
- return path.substring(0, i);
5795
- }
5796
- }
5797
- return "";
5798
- }
5799
- function isDeepEqual(op) {
5800
- return op.kind === "equal" && (op.value.kind === "object" || op.value.kind === "array");
5801
- }
5802
- function groupContiguousEquals(ops) {
5803
- const out = [];
5804
- let i = 0;
5805
- while (i < ops.length) {
5806
- const op = ops[i];
5807
- if (op !== void 0 && isDeepEqual(op)) {
5808
- const startParent = parentPath(op.path);
5809
- let j = i + 1;
5810
- while (j < ops.length) {
5811
- const next = ops[j];
5812
- if (next === void 0) break;
5813
- if (!isDeepEqual(next)) break;
5814
- if (parentPath(next.path) !== startParent) break;
5815
- j++;
5816
- }
5817
- if (j - i > 1) {
5818
- const equalOps = [];
5819
- for (let k = i; k < j; k++) {
5820
- const eop = ops[k];
5821
- if (eop !== void 0 && eop.kind === "equal") {
5822
- equalOps.push(eop);
5823
- }
5824
- }
5825
- out.push({ kind: "equal-run", ops: equalOps });
5826
- i = j;
5827
- continue;
5828
- }
5829
- }
5830
- if (op !== void 0) {
5831
- out.push({ kind: "single", op });
5832
- }
5833
- i++;
5834
- }
5835
- return out;
5836
- }
5837
- const KIND_VISUAL = {
5838
- added: {
5839
- icon: Plus,
5840
- accent: "text-emerald-600 dark:text-emerald-400",
5841
- bg: "bg-emerald-500/5 hover:bg-emerald-500/10",
5842
- border: "border-l-emerald-500",
5843
- label: "ADDED"
5844
- },
5845
- removed: {
5846
- icon: Minus,
5847
- accent: "text-rose-600 dark:text-rose-400",
5848
- bg: "bg-rose-500/5 hover:bg-rose-500/10",
5849
- border: "border-l-rose-500",
5850
- label: "REMOVED"
5851
- },
5852
- changed: {
5853
- icon: Pencil,
5854
- accent: "text-amber-600 dark:text-amber-400",
5855
- bg: "bg-amber-500/5 hover:bg-amber-500/10",
5856
- border: "border-l-amber-500",
5857
- label: "CHANGED"
5858
- },
5859
- equal: {
5860
- icon: Equal,
5861
- accent: "text-muted-foreground/70",
5862
- bg: "bg-muted/20 hover:bg-muted/30",
5863
- border: "border-l-muted-foreground/20",
5864
- label: "EQUAL"
5865
- }
5866
- };
5867
- function EqualRunRow({
5868
- ops,
5869
- expanded,
5870
- onToggle
5871
- }) {
5872
- const first = ops[0];
5873
- const last = ops[ops.length - 1];
5874
- if (first === void 0 || last === void 0) {
5875
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground/40 text-xs", children: "—" });
5876
- }
5877
- const firstPath = first.path;
5878
- const lastPath = last.path;
5879
- const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
5880
- const summary = first.value.kind === "array" ? `${ops.length} equal arrays` : first.value.kind === "object" ? `${ops.length} equal objects` : "equal";
5881
- const v = KIND_VISUAL.equal;
5882
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("border-l-4 rounded-sm", v.border, v.bg), children: [
5883
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
5884
- "button",
5885
- {
5886
- type: "button",
5887
- onClick: onToggle,
5888
- className: "w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",
5889
- children: [
5890
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5891
- ChevronRight,
5892
- {
5893
- className: cn("size-3 transition-transform shrink-0", expanded && "rotate-90")
5894
- }
5895
- ),
5896
- /* @__PURE__ */ jsxRuntimeExports.jsx(v.icon, { className: cn("size-3 shrink-0", v.accent) }),
5897
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1", title: `${firstPath} … ${lastPath}`, children: label }),
5898
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-[10px] uppercase tracking-wider shrink-0", v.accent), children: v.label }),
5899
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 shrink-0", children: [
5900
- "(",
5901
- summary,
5902
- ")"
5903
- ] })
5904
- ]
5905
- }
5906
- ),
5907
- 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: [
5908
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
5909
- /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 2 })
5910
- ] }, op.path)) })
5911
- ] });
5912
- }
5913
- function UnifiedOpRow({
5914
- op,
5915
- idx,
5916
- copiedPath,
5917
- onCopyPath,
5918
- expanded,
5919
- onToggle
5920
- }) {
5921
- const v = KIND_VISUAL[op.kind];
5922
- const Icon2 = v.icon;
5923
- const isExpandable2 = 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;
5924
- const preview = op.kind === "changed" ? [
5925
- {
5926
- text: previewNode(op.left, 400),
5927
- tone: "text-rose-700 dark:text-rose-300 line-through"
5928
- },
5929
- { text: previewNode(op.right, 400), tone: "text-emerald-700 dark:text-emerald-300" }
5930
- ] : op.kind === "removed" ? [
5931
- {
5932
- text: previewNode(op.value, 400),
5933
- tone: "text-rose-700 dark:text-rose-300 line-through"
5934
- }
5935
- ] : 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" }];
5936
- const justCopied = copiedPath === op.path && op.path !== "";
5937
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5938
- "div",
5939
- {
5940
- "data-diff-idx": idx,
5941
- "data-diff-kind": op.kind,
5942
- className: cn("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors", v.border, v.bg),
5943
- children: [
5944
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
5945
- "button",
5946
- {
5947
- type: "button",
5948
- onClick: onToggle,
5949
- disabled: !isExpandable2,
5950
- className: cn(
5951
- "w-full flex items-center gap-2 text-xs text-left rounded-sm",
5952
- isExpandable2 ? "cursor-pointer" : "cursor-default"
5953
- ),
5954
- "aria-expanded": isExpandable2 ? expanded : void 0,
5955
- "aria-label": isExpandable2 ? expanded ? `Collapse ${op.path || "root"}` : `Expand ${op.path || "root"}` : void 0,
5956
- children: [
5957
- isExpandable2 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
5958
- ChevronRight,
5959
- {
5960
- className: cn(
5961
- "size-3 shrink-0 transition-transform",
5962
- v.accent,
5963
- expanded && "rotate-90"
5964
- )
5965
- }
5966
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "size-3 shrink-0", "aria-hidden": "true" }),
5967
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { className: cn("size-3.5 shrink-0", v.accent), strokeWidth: 2.5 }),
5968
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1 min-w-0", title: op.path || "(root)", children: op.path === "" ? "(root)" : op.path }),
5969
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5970
- "span",
5971
- {
5972
- className: cn(
5973
- "text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",
5974
- v.accent,
5975
- op.kind === "equal" ? "bg-muted/40" : "bg-background/60"
5976
- ),
5977
- children: v.label
5978
- }
5979
- ),
5980
- op.path !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx(
5981
- "span",
5982
- {
5983
- role: "button",
5984
- tabIndex: 0,
5985
- onClick: (e) => {
5986
- e.stopPropagation();
5987
- onCopyPath(op.path);
5988
- },
5989
- onKeyDown: (e) => {
5990
- if (e.key === "Enter" || e.key === " ") {
5991
- e.stopPropagation();
5992
- e.preventDefault();
5993
- onCopyPath(op.path);
5994
- }
5995
- },
5996
- className: cn(
5997
- "shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
5998
- justCopied ? "text-emerald-500" : "text-muted-foreground/50 hover:text-foreground hover:bg-muted"
5999
- ),
6000
- "aria-label": justCopied ? "Copied" : "Copy path",
6001
- title: justCopied ? "Copied!" : "Copy path",
6002
- children: justCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
6003
- }
6004
- )
6005
- ]
6006
- }
6007
- ),
6008
- preview.map((p, i) => (
6009
- // biome-ignore lint/suspicious/noArrayIndexKey: preview list is rebuilt on every render and is positional
6010
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("font-mono text-xs mt-1 break-all pl-5", p.tone), children: p.text }, i)
6011
- )),
6012
- /* @__PURE__ */ jsxRuntimeExports.jsx(
6013
- "div",
6014
- {
6015
- className: "overflow-hidden transition-all duration-200",
6016
- style: { maxHeight: expanded && isExpandable2 ? "2000px" : "0" },
6017
- "aria-hidden": !expanded,
6018
- children: expanded && isExpandable2 && op.kind !== "equal" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedSubtree, { op }) : null
6019
- }
6020
- )
6021
- ]
6022
- }
6023
- );
6024
- }
6025
- function ExpandedSubtree({ op }) {
6026
- if (op.kind === "added" || op.kind === "removed") {
6027
- 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: 2 }) });
6028
- }
6029
- const leftIsStructured = op.left.kind === "object" || op.left.kind === "array";
6030
- const rightIsStructured = op.right.kind === "object" || op.right.kind === "array";
6031
- if (!leftIsStructured && !rightIsStructured) {
6032
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 text-xs text-muted-foreground/70 italic", children: "Primitive values are shown inline above." });
6033
- }
6034
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2", children: [
6035
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-rose-500/30 rounded p-2 bg-rose-500/5", children: [
6036
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-rose-500 mb-1", children: "Old" }),
6037
- /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.left), defaultExpandDepth: 2 })
6038
- ] }),
6039
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-emerald-500/30 rounded p-2 bg-emerald-500/5", children: [
6040
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-emerald-500 mb-1", children: "New" }),
6041
- /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.right), defaultExpandDepth: 2 })
6042
- ] })
6043
- ] });
6044
- }
6045
- function SummaryChips({
6046
- counts,
6047
- onJumpTo
6048
- }) {
6049
- const total = counts.added + counts.removed + counts.changed;
6050
- 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: [
6051
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-medium", children: [
6052
- total,
6053
- " ",
6054
- total === 1 ? "change" : "changes"
6055
- ] }),
6056
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
6057
- "button",
6058
- {
6059
- type: "button",
6060
- onClick: () => onJumpTo("removed"),
6061
- disabled: counts.removed === 0,
6062
- className: cn(
6063
- "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
6064
- 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"
6065
- ),
6066
- title: counts.removed > 0 ? "Jump to first removed" : "No removals",
6067
- children: [
6068
- /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
6069
- counts.removed,
6070
- " removed"
6071
- ]
6072
- }
6073
- ),
6074
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
6075
- "button",
6076
- {
6077
- type: "button",
6078
- onClick: () => onJumpTo("added"),
6079
- disabled: counts.added === 0,
6080
- className: cn(
6081
- "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
6082
- 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"
6083
- ),
6084
- title: counts.added > 0 ? "Jump to first added" : "No additions",
6085
- children: [
6086
- /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-3" }),
6087
- counts.added,
6088
- " added"
6089
- ]
6090
- }
6091
- ),
6092
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
6093
- "button",
6094
- {
6095
- type: "button",
6096
- onClick: () => onJumpTo("changed"),
6097
- disabled: counts.changed === 0,
6098
- className: cn(
6099
- "inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
6100
- 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"
6101
- ),
6102
- title: counts.changed > 0 ? "Jump to first changed" : "No changes",
6103
- children: [
6104
- /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "size-3" }),
6105
- counts.changed,
6106
- " changed"
6107
- ]
6108
- }
6109
- )
4906
+ externalTestingTimeLeft: testingTimeLeft,
4907
+ onProvidersMutate: () => {
4908
+ return mutate();
4909
+ },
4910
+ onTestResultsChange: handleTestResultsChange,
4911
+ onTestingProvidersChange: handleTestingProvidersChange,
4912
+ onTestingTimeLeftChange: handleTestingTimeLeftChange
4913
+ }
4914
+ ) }),
4915
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "proxy", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProxySettingsTab, {}) })
4916
+ ] })
4917
+ ] })
4918
+ ] })
6110
4919
  ] });
6111
4920
  }
6112
- function ModeToggle({
6113
- mode,
6114
- onChange
6115
- }) {
6116
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "inline-flex rounded-md border border-border overflow-hidden", children: [
6117
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
6118
- "button",
6119
- {
6120
- type: "button",
6121
- onClick: () => onChange("unified"),
6122
- "aria-pressed": mode === "unified",
6123
- className: cn(
6124
- "flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",
6125
- mode === "unified" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
6126
- ),
6127
- title: "Unified view (single column, emphasized diffs)",
6128
- children: [
6129
- /* @__PURE__ */ jsxRuntimeExports.jsx(Rows3, { className: "size-3" }),
6130
- "Unified"
6131
- ]
6132
- }
6133
- ),
6134
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
6135
- "button",
6136
- {
6137
- type: "button",
6138
- onClick: () => onChange("split"),
6139
- "aria-pressed": mode === "split",
6140
- className: cn(
6141
- "flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",
6142
- mode === "split" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
6143
- ),
6144
- title: "Split view (path | left | right)",
6145
- children: [
6146
- /* @__PURE__ */ jsxRuntimeExports.jsx(Columns2, { className: "size-3" }),
6147
- "Split"
6148
- ]
4921
+ function ProxySettingsTab() {
4922
+ const { strip, isLoading, setStrip } = useStripConfig();
4923
+ const [error, setError] = reactExports.useState(null);
4924
+ const [pending, setPending] = reactExports.useState(false);
4925
+ const handleToggle = reactExports.useCallback(
4926
+ async (next) => {
4927
+ setError(null);
4928
+ setPending(true);
4929
+ try {
4930
+ await setStrip(next);
4931
+ } catch (err) {
4932
+ setError(err instanceof Error ? err.message : String(err));
4933
+ } finally {
4934
+ setPending(false);
6149
4935
  }
6150
- )
6151
- ] });
6152
- }
6153
- function SideSummary({ log, side }) {
6154
- const conversationId = getConversationId(log);
6155
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 space-y-1 text-xs", children: [
6156
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
4936
+ },
4937
+ [setStrip]
4938
+ );
4939
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
4940
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
4941
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold", children: "Claude Code billing header" }),
4942
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground", children: [
4943
+ "When enabled, the proxy strips the synthetic ",
4944
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "x-anthropic-billing-header" }),
4945
+ " text block that Claude Code prepends to the system prompt before forwarding to upstream. Improves prefix-cache hit rates against third-party upstreams. The default at startup is controlled by the ",
4946
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "LLM_INSPECTOR_STRIP_CLAUDE_CODE_BILLING_HEADER" }),
4947
+ " environment variable; your choice is persisted to ",
4948
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "config.json" }),
4949
+ " and overrides the env var for subsequent requests."
4950
+ ] })
4951
+ ] }),
4952
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-3", children: [
6157
4953
  /* @__PURE__ */ jsxRuntimeExports.jsx(
6158
- Badge,
4954
+ "input",
6159
4955
  {
6160
- variant: "outline",
6161
- className: cn(
6162
- "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
6163
- side === "left" ? "border-rose-500/40 text-rose-400" : "border-emerald-500/40 text-emerald-400"
6164
- ),
6165
- children: side === "left" ? "← Left" : "Right →"
4956
+ type: "checkbox",
4957
+ role: "switch",
4958
+ checked: strip,
4959
+ disabled: isLoading || pending,
4960
+ onChange: (e) => {
4961
+ void handleToggle(e.currentTarget.checked);
4962
+ },
4963
+ className: "size-4 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
6166
4964
  }
6167
4965
  ),
6168
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-blue-400/80", children: [
6169
- "#",
6170
- log.id
6171
- ] }),
6172
- log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground truncate", children: log.model })
6173
- ] }),
6174
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-muted-foreground font-mono", children: [
6175
- log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400", children: [
6176
- "Cache +",
6177
- formatTokens(log.cacheCreationInputTokens)
6178
- ] }),
6179
- log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-purple-400", children: [
6180
- "Cache ~",
6181
- formatTokens(log.cacheReadInputTokens)
6182
- ] }),
6183
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: log.timestamp, children: log.timestamp })
4966
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
6184
4967
  ] }),
6185
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-muted-foreground/70 font-mono truncate", title: conversationId, children: [
6186
- "session: ",
6187
- conversationId
4968
+ error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
4969
+ "Failed to save: ",
4970
+ error
6188
4971
  ] })
6189
4972
  ] });
6190
4973
  }
6191
- function CompareDrawer({ left, right, onClose }) {
6192
- const ops = reactExports.useMemo(() => {
6193
- const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
6194
- left.rawRequestBody
6195
- );
6196
- const rightRequest = getLogFormatAdapter(resolveLogFormat(right)).analyzeRequest(
6197
- right.rawRequestBody
6198
- );
6199
- const l = normalizeRequest(leftRequest.comparisonValue);
6200
- const r = normalizeRequest(rightRequest.comparisonValue);
6201
- return diffTrees(l, r);
6202
- }, [
6203
- left.apiFormat,
6204
- left.path,
6205
- left.rawRequestBody,
6206
- right.apiFormat,
6207
- right.path,
6208
- right.rawRequestBody
6209
- ]);
6210
- const grouped = reactExports.useMemo(() => groupContiguousEquals(ops), [ops]);
6211
- const counts = reactExports.useMemo(() => {
6212
- let added = 0;
6213
- let removed = 0;
6214
- let changed = 0;
6215
- for (const g of grouped) {
6216
- if (g.kind !== "single") continue;
6217
- switch (g.op.kind) {
6218
- case "added":
6219
- added++;
6220
- break;
6221
- case "removed":
6222
- removed++;
6223
- break;
6224
- case "changed":
6225
- changed++;
6226
- break;
6227
- }
6228
- }
6229
- return { added, removed, changed };
6230
- }, [grouped]);
6231
- const [expandedRuns, setExpandedRuns] = reactExports.useState(/* @__PURE__ */ new Set());
6232
- const toggleRun = (idx) => {
6233
- setExpandedRuns((prev) => {
6234
- const next = new Set(prev);
6235
- if (next.has(idx)) next.delete(idx);
6236
- else next.add(idx);
6237
- return next;
6238
- });
6239
- };
6240
- const [expandedRows, setExpandedRows] = reactExports.useState(/* @__PURE__ */ new Set());
6241
- const toggleRow = (idx) => {
6242
- setExpandedRows((prev) => {
6243
- const next = new Set(prev);
6244
- if (next.has(idx)) next.delete(idx);
6245
- else next.add(idx);
6246
- return next;
6247
- });
6248
- };
6249
- reactExports.useEffect(() => {
6250
- setExpandedRows(/* @__PURE__ */ new Set());
6251
- }, [left.id, right.id]);
6252
- const [mode, setMode] = reactExports.useState("unified");
6253
- const bodyRef = reactExports.useRef(null);
6254
- const [copiedPath, setCopiedPath] = reactExports.useState(null);
6255
- const copyResetTimer = reactExports.useRef(null);
6256
- const onCopyPath = (path) => {
6257
- void window.navigator.clipboard.writeText(path).then(() => {
6258
- setCopiedPath(path);
6259
- if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
6260
- copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
6261
- });
6262
- };
6263
- reactExports.useEffect(() => {
6264
- return () => {
6265
- if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
6266
- };
6267
- }, []);
6268
- const jumpToKind = (kind) => {
6269
- const idx = grouped.findIndex((g) => g.kind === "single" && g.op.kind === kind);
6270
- if (idx === -1) return;
6271
- const root = bodyRef.current;
6272
- if (root === null) return;
6273
- const el = root.querySelector(`[data-diff-idx="${idx}"]`);
6274
- if (el !== null) {
6275
- el.scrollIntoView({ behavior: "smooth", block: "center" });
4974
+ function computeCacheTrends(groups) {
4975
+ const result = /* @__PURE__ */ new Map();
4976
+ for (const group of groups) {
4977
+ const logs = group.logs;
4978
+ for (let i = 1; i < logs.length; i++) {
4979
+ const prev = logs[i - 1];
4980
+ const curr = logs[i];
4981
+ if (prev === void 0 || curr === void 0) continue;
4982
+ result.set(curr.id, {
4983
+ creation: compareField(prev.cacheCreationInputTokens, curr.cacheCreationInputTokens),
4984
+ read: compareField(prev.cacheReadInputTokens, curr.cacheReadInputTokens)
4985
+ });
6276
4986
  }
6277
- };
4987
+ }
4988
+ return result;
4989
+ }
4990
+ function compareField(previous, current) {
4991
+ if (current === null) return null;
4992
+ if (previous === null) return null;
4993
+ if (current > previous) return { direction: "up", delta: current - previous };
4994
+ if (current < previous) return { direction: "down", delta: previous - current };
4995
+ return null;
4996
+ }
4997
+ const NAV_ATTR = "data-nav-id";
4998
+ const NAV_ACTION_ATTR = "data-nav-action";
4999
+ function findNavItems(container) {
5000
+ return Array.from(container.querySelectorAll(`[${NAV_ATTR}]`));
5001
+ }
5002
+ function isFocusTarget(node) {
5003
+ return node instanceof HTMLElement;
5004
+ }
5005
+ function findClosestNavItem(target, container) {
5006
+ let el = target;
5007
+ while (el !== null && el !== container) {
5008
+ if (el.hasAttribute(NAV_ATTR)) return el;
5009
+ el = el.parentElement;
5010
+ }
5011
+ if (target.hasAttribute(NAV_ATTR)) return target;
5012
+ return null;
5013
+ }
5014
+ function getAction(el) {
5015
+ const val = el.getAttribute(NAV_ACTION_ATTR);
5016
+ if (val === "toggle" || val === "expand" || val === "collapse") return val;
5017
+ return null;
5018
+ }
5019
+ function focusAndScroll(el) {
5020
+ el.focus({ preventScroll: true });
5021
+ el.scrollIntoView({ block: "nearest", behavior: "smooth" });
5022
+ }
5023
+ function safeItemAt(items, index2) {
5024
+ const el = items[index2];
5025
+ return el ?? null;
5026
+ }
5027
+ function isEditableTarget(target) {
5028
+ const tag = target.tagName;
5029
+ if (tag === "INPUT" || tag === "TEXTAREA") return true;
5030
+ if (target.isContentEditable) return true;
5031
+ return false;
5032
+ }
5033
+ function useKeyboardNavigation(containerRef, wrapperRef) {
5034
+ const rootRef = wrapperRef ?? containerRef;
5035
+ const handleFocusContainer = reactExports.useCallback(
5036
+ (e) => {
5037
+ const container = containerRef.current;
5038
+ if (!container) return;
5039
+ const target = e.target;
5040
+ if (!isFocusTarget(target)) return;
5041
+ if (target !== rootRef.current) return;
5042
+ if (!container.contains(target)) return;
5043
+ const items = findNavItems(container);
5044
+ const first = safeItemAt(items, 0);
5045
+ if (first !== null) focusAndScroll(first);
5046
+ },
5047
+ [containerRef, rootRef]
5048
+ );
6278
5049
  reactExports.useEffect(() => {
6279
- const onKey = (e) => {
6280
- if (e.key === "Escape") onClose();
6281
- };
6282
- document.addEventListener("keydown", onKey);
6283
- const prevOverflow = document.body.style.overflow;
6284
- document.body.style.overflow = "hidden";
6285
- return () => {
6286
- document.removeEventListener("keydown", onKey);
6287
- document.body.style.overflow = prevOverflow;
6288
- };
6289
- }, [onClose]);
6290
- const sameSession = getConversationId(left) === getConversationId(right);
6291
- const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
6292
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6293
- "div",
6294
- {
6295
- className: "fixed inset-0 z-50 flex justify-end",
6296
- role: "dialog",
6297
- "aria-modal": "true",
6298
- "aria-label": "Compare two log requests",
6299
- children: [
6300
- /* @__PURE__ */ jsxRuntimeExports.jsx(
6301
- "button",
6302
- {
6303
- type: "button",
6304
- onClick: onClose,
6305
- "aria-label": "Close compare drawer",
6306
- className: "absolute inset-0 bg-black/40 cursor-default",
6307
- tabIndex: -1
5050
+ const root = rootRef.current;
5051
+ const container = containerRef.current;
5052
+ if (!root || !container) return;
5053
+ const handleKeyDown = (e) => {
5054
+ const target = e.target;
5055
+ if (!isFocusTarget(target)) return;
5056
+ if (!container.contains(target)) return;
5057
+ if (isEditableTarget(target)) return;
5058
+ const items = findNavItems(container);
5059
+ if (items.length === 0) return;
5060
+ const current = findClosestNavItem(target, container);
5061
+ const currentIdx = current !== null ? items.indexOf(current) : -1;
5062
+ let handled = false;
5063
+ switch (e.key) {
5064
+ case "ArrowUp":
5065
+ case "W": {
5066
+ if (e.key === "W" && !e.shiftKey) break;
5067
+ e.preventDefault();
5068
+ const prevIdx = currentIdx > 0 ? currentIdx - 1 : currentIdx === -1 ? items.length - 1 : -1;
5069
+ if (prevIdx !== -1) {
5070
+ const prev = safeItemAt(items, prevIdx);
5071
+ if (prev !== null) focusAndScroll(prev);
6308
5072
  }
6309
- ),
6310
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
6311
- "div",
6312
- {
6313
- className: cn(
6314
- "relative bg-background border-l border-border shadow-xl",
6315
- "w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"
6316
- ),
6317
- onClick: (e) => e.stopPropagation(),
6318
- onKeyDown: (e) => e.stopPropagation(),
6319
- children: [
6320
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-4 px-4 py-3 border-b border-border", children: [
6321
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-4 min-w-0", children: [
6322
- /* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: left, side: "left" }),
6323
- /* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: right, side: "right" })
6324
- ] }),
6325
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
6326
- /* @__PURE__ */ jsxRuntimeExports.jsx(ModeToggle, { mode, onChange: setMode }),
6327
- /* @__PURE__ */ jsxRuntimeExports.jsx(
6328
- "button",
6329
- {
6330
- type: "button",
6331
- onClick: onClose,
6332
- "aria-label": "Close",
6333
- className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",
6334
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
6335
- }
6336
- )
6337
- ] })
6338
- ] }),
6339
- !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." }),
6340
- 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: [
6341
- /* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChips, { counts, onJumpTo: jumpToKind }),
6342
- /* @__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) => {
6343
- if (g.kind === "equal-run") {
6344
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
6345
- EqualRunRow,
6346
- {
6347
- ops: g.ops,
6348
- expanded: expandedRuns.has(i),
6349
- onToggle: () => toggleRun(i)
6350
- },
6351
- `r${i}`
6352
- );
6353
- }
6354
- const op = g.op;
6355
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
6356
- UnifiedOpRow,
6357
- {
6358
- op,
6359
- idx: i,
6360
- copiedPath,
6361
- onCopyPath,
6362
- expanded: expandedRows.has(i),
6363
- onToggle: () => toggleRow(i)
6364
- },
6365
- `o${i}`
6366
- );
6367
- }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(SplitBody, { grouped, left, right }) })
6368
- ] })
6369
- ]
5073
+ handled = true;
5074
+ break;
5075
+ }
5076
+ case "ArrowDown":
5077
+ case "S": {
5078
+ if (e.key === "S" && !e.shiftKey) break;
5079
+ e.preventDefault();
5080
+ const nextIdx = currentIdx < items.length - 1 ? currentIdx + 1 : currentIdx === -1 ? 0 : -1;
5081
+ if (nextIdx !== -1) {
5082
+ const next = safeItemAt(items, nextIdx);
5083
+ if (next !== null) focusAndScroll(next);
6370
5084
  }
6371
- )
6372
- ]
6373
- }
6374
- );
6375
- }
6376
- function SplitBody({
6377
- grouped,
6378
- left,
6379
- right
6380
- }) {
6381
- 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: [
6382
- /* @__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: [
6383
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Path" }),
6384
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
6385
- "Left (Log #",
6386
- left.id,
6387
- ")"
6388
- ] }),
6389
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
6390
- "Right (Log #",
6391
- right.id,
6392
- ")"
6393
- ] })
6394
- ] }),
6395
- grouped.map((g, i) => {
6396
- if (g.kind === "equal-run") {
6397
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6398
- "div",
6399
- {
6400
- className: "col-span-3 px-2 py-1 text-xs text-muted-foreground/60",
6401
- children: [
6402
- g.ops.length,
6403
- " equal siblings collapsed — switch to Unified to expand"
6404
- ]
6405
- },
6406
- i
6407
- );
6408
- }
6409
- const op = g.op;
6410
- if (op.kind === "equal") {
6411
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6412
- "div",
6413
- {
6414
- className: "col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",
6415
- children: [
6416
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs truncate", title: op.path, children: op.path }),
6417
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) }),
6418
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) })
6419
- ]
6420
- },
6421
- i
6422
- );
6423
- }
6424
- if (op.kind === "added") {
6425
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6426
- "div",
6427
- {
6428
- className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",
6429
- children: [
6430
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
6431
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-emerald-300/90", children: [
6432
- "+ ",
6433
- previewNode(op.value, 400)
6434
- ] })
6435
- ]
6436
- },
6437
- i
6438
- );
5085
+ handled = true;
5086
+ break;
5087
+ }
5088
+ case "ArrowLeft":
5089
+ case "A": {
5090
+ if (e.key === "A" && !e.shiftKey) break;
5091
+ if (current === null) break;
5092
+ e.preventDefault();
5093
+ const action = getAction(current);
5094
+ if (action === "collapse") {
5095
+ current.click();
5096
+ } else if (currentIdx > 0) {
5097
+ const prev = safeItemAt(items, currentIdx - 1);
5098
+ if (prev !== null) focusAndScroll(prev);
5099
+ }
5100
+ handled = true;
5101
+ break;
5102
+ }
5103
+ case "ArrowRight":
5104
+ case "D": {
5105
+ if (e.key === "D" && !e.shiftKey) break;
5106
+ if (current === null) break;
5107
+ e.preventDefault();
5108
+ const action = getAction(current);
5109
+ if (action === "expand") {
5110
+ current.click();
5111
+ } else if (currentIdx < items.length - 1) {
5112
+ const next = safeItemAt(items, currentIdx + 1);
5113
+ if (next !== null) focusAndScroll(next);
5114
+ }
5115
+ handled = true;
5116
+ break;
5117
+ }
5118
+ case " ": {
5119
+ if (current === null) break;
5120
+ e.preventDefault();
5121
+ const action = getAction(current);
5122
+ if (action !== null) current.click();
5123
+ handled = true;
5124
+ break;
5125
+ }
6439
5126
  }
6440
- if (op.kind === "removed") {
6441
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6442
- "div",
6443
- {
6444
- className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",
6445
- children: [
6446
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
6447
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-rose-300/90 line-through", children: [
6448
- "− ",
6449
- previewNode(op.value, 400)
6450
- ] })
6451
- ]
6452
- },
6453
- i
6454
- );
5127
+ if (handled) {
5128
+ e.stopPropagation();
6455
5129
  }
6456
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6457
- "div",
6458
- {
6459
- className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",
6460
- children: [
6461
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
6462
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
6463
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-rose-300/90 break-all line-through", children: previewNode(op.left, 400) }),
6464
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-300/90 break-all", children: previewNode(op.right, 400) })
6465
- ] })
6466
- ]
6467
- },
6468
- i
6469
- );
6470
- })
6471
- ] });
5130
+ };
5131
+ document.addEventListener("keydown", handleKeyDown, { capture: true });
5132
+ return () => document.removeEventListener("keydown", handleKeyDown, { capture: true });
5133
+ }, [containerRef, rootRef]);
5134
+ reactExports.useEffect(() => {
5135
+ const root = rootRef.current;
5136
+ if (!root) return;
5137
+ root.addEventListener("focus", handleFocusContainer);
5138
+ return () => root.removeEventListener("focus", handleFocusContainer);
5139
+ }, [handleFocusContainer, rootRef]);
6472
5140
  }
6473
5141
  function truncateSessionId(id) {
6474
5142
  if (id.length <= 30) return id;
@@ -6554,6 +5222,9 @@ function ProxyViewer({
6554
5222
  const [crabEntrancePhase, setCrabEntrancePhase] = reactExports.useState(
6555
5223
  "hidden"
6556
5224
  );
5225
+ const logListRef = reactExports.useRef(null);
5226
+ const logListWrapperRef = reactExports.useRef(null);
5227
+ useKeyboardNavigation(logListRef, logListWrapperRef);
6557
5228
  reactExports.useEffect(() => {
6558
5229
  const perCrabDuration = 400;
6559
5230
  const startDelay = 50;
@@ -6589,8 +5260,8 @@ function ProxyViewer({
6589
5260
  },
6590
5261
  [comparisonPredecessors]
6591
5262
  );
6592
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1200px] mx-auto flex flex-col h-screen", style: { maxHeight: "100vh" }, children: [
6593
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end px-6 pt-6 pb-8 relative", children: [
5263
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1400px] xl:max-w-[1600px] 2xl:max-w-[1800px] mx-auto px-6 pb-6", children: [
5264
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end pt-6 pb-8 relative", children: [
6594
5265
  /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-lg font-bold flex items-end gap-2 absolute left-1/2 -translate-x-1/2 whitespace-nowrap", children: [
6595
5266
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-end gap-1 group cursor-default", "aria-hidden": "true", children: [
6596
5267
  /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" }),
@@ -6633,7 +5304,7 @@ function ProxyViewer({
6633
5304
  ] }),
6634
5305
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}) })
6635
5306
  ] }),
6636
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-6 mb-4", children: [
5307
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
6637
5308
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedSession, onValueChange: onSessionChange, children: [
6638
5309
  /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[350px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All sessions" }) }),
6639
5310
  /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
@@ -6654,7 +5325,7 @@ function ProxyViewer({
6654
5325
  {
6655
5326
  type: "button",
6656
5327
  onClick: () => onViewModeChange("simple"),
6657
- className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5328
+ className: `h-8 px-3 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
6658
5329
  children: "Simple"
6659
5330
  }
6660
5331
  ),
@@ -6663,7 +5334,7 @@ function ProxyViewer({
6663
5334
  {
6664
5335
  type: "button",
6665
5336
  onClick: () => onViewModeChange("full"),
6666
- className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5337
+ className: `h-8 px-3 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
6667
5338
  children: "Full"
6668
5339
  }
6669
5340
  )
@@ -6683,10 +5354,10 @@ function ProxyViewer({
6683
5354
  void handleExport();
6684
5355
  },
6685
5356
  disabled: exporting,
6686
- className: "text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1",
5357
+ className: "h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1.5 rounded-md hover:bg-muted",
6687
5358
  title: "Export all logs as JSON ZIP",
6688
5359
  children: exporting ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Exporting..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6689
- /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
5360
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3.5" }),
6690
5361
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Export" })
6691
5362
  ] })
6692
5363
  }
@@ -6696,31 +5367,39 @@ function ProxyViewer({
6696
5367
  {
6697
5368
  type: "button",
6698
5369
  onClick: onClearAll,
6699
- className: "text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
5370
+ className: "h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer rounded-md hover:bg-muted",
6700
5371
  title: "Clear all logs",
6701
5372
  children: "Clear"
6702
5373
  }
6703
5374
  )
6704
5375
  ] }),
6705
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 px-6 pb-6", children: logs.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center text-muted-foreground py-16 space-y-4", children: [
5376
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: logs.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center text-muted-foreground py-16 space-y-4", children: [
6706
5377
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "No requests captured yet." }),
6707
5378
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs", children: "Route AI coding tools through the proxy:" }),
6708
5379
  /* @__PURE__ */ jsxRuntimeExports.jsx(CopyableCommand, { command: "LLM_BASE_URL=http://localhost:25947/proxy <your-tool>" })
6709
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto h-full flex flex-col gap-2", children: groups.map((group) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6710
- ConversationGroup,
5380
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5381
+ "div",
6711
5382
  {
6712
- group,
6713
- viewMode,
6714
- strip,
6715
- cacheTrends,
6716
- onCompareWithPrevious: handleCompareWithPrevious,
6717
- comparisonPredecessors,
6718
- onClearGroup,
6719
- standalone: groups.length === 1
6720
- },
6721
- group.id
6722
- )) }) }),
6723
- comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
5383
+ ref: logListWrapperRef,
5384
+ tabIndex: 0,
5385
+ className: "flex flex-col gap-2 focus:outline-none",
5386
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: logListRef, children: groups.map((group) => /* @__PURE__ */ jsxRuntimeExports.jsx(
5387
+ ConversationGroup,
5388
+ {
5389
+ group,
5390
+ viewMode,
5391
+ strip,
5392
+ cacheTrends,
5393
+ onCompareWithPrevious: handleCompareWithPrevious,
5394
+ comparisonPredecessors,
5395
+ onClearGroup,
5396
+ standalone: groups.length === 1
5397
+ },
5398
+ group.id
5399
+ )) })
5400
+ }
5401
+ ) }),
5402
+ comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(LazyCompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare }) })
6724
5403
  ] });
6725
5404
  }
6726
5405
  const SSEUpdateSchema = union([
@@ -6758,8 +5437,6 @@ function filterLogs(logs, selectedSession, selectedModel) {
6758
5437
  const DEBOUNCE_MS = 50;
6759
5438
  function ProxyViewerContainer() {
6760
5439
  const [allLogs, setAllLogs] = reactExports.useState([]);
6761
- const [sessions, setSessions] = reactExports.useState([]);
6762
- const [models, setModels] = reactExports.useState([]);
6763
5440
  const [selectedSession, setSelectedSession] = reactExports.useState("__all__");
6764
5441
  const [selectedModel, setSelectedModel] = reactExports.useState("__all__");
6765
5442
  const [viewMode, setViewMode] = reactExports.useState("simple");
@@ -6767,13 +5444,14 @@ function ProxyViewerContainer() {
6767
5444
  const eventSourceRef = reactExports.useRef(null);
6768
5445
  const reconnectTimeoutRef = reactExports.useRef(null);
6769
5446
  const logIndexRef = reactExports.useRef(/* @__PURE__ */ new Map());
6770
- const logsRef = reactExports.useRef([]);
6771
5447
  const pendingUpdatesRef = reactExports.useRef([]);
6772
5448
  const flushTimerRef = reactExports.useRef(null);
6773
5449
  const logs = reactExports.useMemo(
6774
5450
  () => filterLogs(allLogs, selectedSession, selectedModel),
6775
5451
  [allLogs, selectedSession, selectedModel]
6776
5452
  );
5453
+ const sessions = reactExports.useMemo(() => extractSessions(allLogs), [allLogs]);
5454
+ const models = reactExports.useMemo(() => extractModels(allLogs), [allLogs]);
6777
5455
  const flushUpdates = reactExports.useCallback(() => {
6778
5456
  flushTimerRef.current = null;
6779
5457
  const updates = pendingUpdatesRef.current;
@@ -6781,8 +5459,6 @@ function ProxyViewerContainer() {
6781
5459
  if (updates.length === 0) return;
6782
5460
  setAllLogs((prev) => {
6783
5461
  let next = prev;
6784
- let sessionsChanged = false;
6785
- let modelsChanged = false;
6786
5462
  for (const log of updates) {
6787
5463
  const idx = logIndexRef.current.get(log.id);
6788
5464
  if (idx !== void 0) {
@@ -6791,19 +5467,10 @@ function ProxyViewerContainer() {
6791
5467
  logIndexRef.current.set(log.id, next.length);
6792
5468
  next = [...next, log];
6793
5469
  }
6794
- if (log.sessionId !== null && log.sessionId !== "" && !sessions.includes(log.sessionId)) {
6795
- sessionsChanged = true;
6796
- }
6797
- if (log.model !== null && log.model !== "" && !models.includes(log.model)) {
6798
- modelsChanged = true;
6799
- }
6800
5470
  }
6801
- logsRef.current = next;
6802
- if (sessionsChanged) setSessions((s) => extractSessions(next));
6803
- if (modelsChanged) setModels((m) => extractModels(next));
6804
5471
  return next;
6805
5472
  });
6806
- }, [sessions, models]);
5473
+ }, []);
6807
5474
  const scheduleUpdate = reactExports.useCallback(
6808
5475
  (log) => {
6809
5476
  pendingUpdatesRef.current.push(log);
@@ -6841,10 +5508,7 @@ function ProxyViewerContainer() {
6841
5508
  if (log !== void 0) idx.set(log.id, i);
6842
5509
  }
6843
5510
  logIndexRef.current = idx;
6844
- logsRef.current = update.logs;
6845
5511
  setAllLogs(update.logs);
6846
- setSessions(extractSessions(update.logs));
6847
- setModels(extractModels(update.logs));
6848
5512
  setError(null);
6849
5513
  } else if (update.type === "update") {
6850
5514
  scheduleUpdate(update.log);
@@ -6888,10 +5552,7 @@ function ProxyViewerContainer() {
6888
5552
  return;
6889
5553
  }
6890
5554
  logIndexRef.current.clear();
6891
- logsRef.current = [];
6892
5555
  setAllLogs([]);
6893
- setSessions([]);
6894
- setModels([]);
6895
5556
  setError(null);
6896
5557
  } catch (err) {
6897
5558
  setError(err instanceof Error ? err.message : "Unknown error clearing logs");
@@ -6920,9 +5581,6 @@ function ProxyViewerContainer() {
6920
5581
  if (log !== void 0) idx.set(log.id, i);
6921
5582
  }
6922
5583
  logIndexRef.current = idx;
6923
- logsRef.current = remaining;
6924
- setSessions(extractSessions(remaining));
6925
- setModels(extractModels(remaining));
6926
5584
  return remaining;
6927
5585
  });
6928
5586
  setError(null);
@@ -6955,6 +5613,32 @@ function ProxyViewerContainer() {
6955
5613
  ] });
6956
5614
  }
6957
5615
  const SplitComponent = ProxyViewerContainer;
5616
+ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
5617
+ __proto__: null,
5618
+ component: SplitComponent
5619
+ }, Symbol.toStringTag, { value: "Module" }));
6958
5620
  export {
6959
- SplitComponent as component
5621
+ Badge as B,
5622
+ Dialog as D,
5623
+ Tabs as T,
5624
+ getConversationId as a,
5625
+ DialogContent as b,
5626
+ cn as c,
5627
+ DialogHeader as d,
5628
+ DialogTitle as e,
5629
+ formatTokens as f,
5630
+ getLogFormatAdapter as g,
5631
+ TabsList as h,
5632
+ TabsTrigger as i,
5633
+ TabsContent as j,
5634
+ TooltipProvider as k,
5635
+ Tooltip as l,
5636
+ TooltipTrigger as m,
5637
+ TooltipContent as n,
5638
+ Button as o,
5639
+ getStatusCategory as p,
5640
+ parseJsonText as q,
5641
+ resolveLogFormat as r,
5642
+ safeJsonValue as s,
5643
+ index as t
6960
5644
  };