@tonyclaw/llm-inspector 1.18.1 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.output/cli.js +776 -139
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-CAhlM_Gq.js → CompareDrawer-DwayZPPO.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-iv3LVMEW.js +101 -0
  5. package/.output/public/assets/{ReplayDialog-Bqu2f5HE.js → ReplayDialog-CaV1elYO.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CpVNH0CD.js → RequestAnatomy-CSfnjK7j.js} +1 -1
  7. package/.output/public/assets/{ResponseView-B_Gg37Lr.js → ResponseView-YkOL__xm.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-E2M_SS1A.js → StreamingChunkSequence-D_p6L-oB.js} +1 -1
  9. package/.output/public/assets/_sessionId-BgCVUC6R.js +1 -0
  10. package/.output/public/assets/index-CWA4S0FO.js +1 -0
  11. package/.output/public/assets/index-DeJyypsp.css +1 -0
  12. package/.output/public/assets/{json-viewer-DqhA-ODG.js → json-viewer-BB-9bqnP.js} +2 -2
  13. package/.output/public/assets/{main-DpH7JlHv.js → main-COVN451W.js} +7 -7
  14. package/.output/server/_libs/lucide-react.mjs +100 -73
  15. package/.output/server/{_sessionId-DcJ0RDNl.mjs → _sessionId-BJT5qIib.mjs} +3 -3
  16. package/.output/server/_ssr/{CompareDrawer-DajC3x7u.mjs → CompareDrawer-DNGYdUXs.mjs} +5 -5
  17. package/.output/server/_ssr/{ProxyViewerContainer-C2dnFXoC.mjs → ProxyViewerContainer-B-zDOLYE.mjs} +501 -352
  18. package/.output/server/_ssr/{ReplayDialog-BnCLuA5z.mjs → ReplayDialog-DWeqMA4y.mjs} +5 -5
  19. package/.output/server/_ssr/{RequestAnatomy-OHE3iT-f.mjs → RequestAnatomy-TOsrMu9-.mjs} +4 -4
  20. package/.output/server/_ssr/{ResponseView-NPshHwOv.mjs → ResponseView-BuqdPrzm.mjs} +5 -5
  21. package/.output/server/_ssr/{StreamingChunkSequence-BfukoR7F.mjs → StreamingChunkSequence-DuzNZkqL.mjs} +5 -5
  22. package/.output/server/_ssr/{index-CF8M0tsv.mjs → index-1nCQUt3y.mjs} +3 -3
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-CHBa-Oas.mjs → json-viewer-BL8xhHbi.mjs} +10 -6
  25. package/.output/server/_ssr/{router-B5hOtKSn.mjs → router-aCaUgVTW.mjs} +32 -17
  26. package/.output/server/{_tanstack-start-manifest_v-CFyWvIH6.mjs → _tanstack-start-manifest_v-cBRxvCjb.mjs} +1 -1
  27. package/.output/server/index.mjs +66 -66
  28. package/README.md +109 -59
  29. package/package.json +2 -1
  30. package/src/assets/logos/mcp.png +0 -0
  31. package/src/cli/detect-tools.ts +146 -0
  32. package/src/cli/onboard.ts +229 -0
  33. package/src/cli/templates/command-onboard.ts +17 -0
  34. package/src/cli/templates/skill-onboard.ts +325 -0
  35. package/src/cli.ts +185 -163
  36. package/src/components/ProxyViewer.tsx +297 -136
  37. package/src/components/ProxyViewerContainer.tsx +24 -9
  38. package/src/components/proxy-viewer/ConversationHeader.tsx +7 -22
  39. package/src/components/proxy-viewer/LogEntry.tsx +136 -157
  40. package/src/components/proxy-viewer/LogEntryHeader.tsx +147 -66
  41. package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
  42. package/src/components/ui/json-viewer.tsx +12 -0
  43. package/src/components/ui/mcp-logo.tsx +20 -0
  44. package/src/lib/sessionUrl.ts +44 -0
  45. package/src/routes/session/$sessionId.tsx +5 -57
  46. package/.output/public/assets/ProxyViewerContainer--miVHNPZ.js +0 -101
  47. package/.output/public/assets/_sessionId-P9LgC1bF.js +0 -1
  48. package/.output/public/assets/index-C0wv3YP9.css +0 -1
  49. package/.output/public/assets/index-kboKku6a.js +0 -1
@@ -1,18 +1,18 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, a as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, d as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, e as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, b as AnthropicRequestSchema } from "./router-B5hOtKSn.mjs";
2
+ import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, a as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, d as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, g as getSessionPath, e as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, b as AnthropicRequestSchema } from "./router-aCaUgVTW.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";
6
6
  import { t as twMerge } from "../_libs/tailwind-merge.mjs";
7
7
  import { c as cva } from "../_libs/class-variance-authority.mjs";
8
- import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as Portal$1, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
8
+ import { R as Root, T as Trigger$2, C as Content, a as Close, b as Title, P as Portal$2, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
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 { 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, E as ExternalLink, T as Trash2, i as TriangleAlert, j as EyeOff, k as Eye, R as RotateCw, l as Pencil, G as GitCompareArrows, m as Minus, n as CircleCheckBig, O as OctagonAlert, 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";
12
+ import { C as Check, X, P as Plus, D as Download, S as Settings, A as ArrowLeft, a as Copy, b as ChevronDown, U as Upload, c as Scan, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, T as Trash2, i as TriangleAlert, j as EyeOff, k as Eye, R as RotateCw, l as Pencil, m as Minus, n as CircleCheckBig, O as OctagonAlert, W as Wrench, G as Globe, F as FileTerminal, o as Radio, p as ChevronsUp, q as ChevronsDown, r as FileDiff, H as History, s as RotateCcw, t as GitCompareArrows, u as CircleQuestionMark, v as Server, w as Gauge, x as Lock, y as Wifi, z as WifiOff, B as ArrowUp, I as ArrowDown, J as Rows3, K as Columns2 } from "../_libs/lucide-react.mjs";
13
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";
14
- import { R as Root2$1, L as List, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
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";
14
+ import { P as Provider, R as Root3, T as Trigger$1, a as Portal$1, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
15
+ import { R as Root2$1, L as List, T as Trigger$3, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
16
16
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
17
17
  const ApiErrorSchema = object({
18
18
  error: string()
@@ -275,7 +275,7 @@ function getStatusCategory(status) {
275
275
  if (status >= 500) return "server_error";
276
276
  return "pending";
277
277
  }
278
- const version = "1.18.1";
278
+ const version = "1.19.0";
279
279
  const packageJson = {
280
280
  version
281
281
  };
@@ -322,12 +322,12 @@ function Dialog({
322
322
  function DialogTrigger({
323
323
  ...props
324
324
  }) {
325
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$1, { "data-slot": "dialog-trigger", ...props });
325
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$2, { "data-slot": "dialog-trigger", ...props });
326
326
  }
327
327
  function DialogPortal({
328
328
  ...props
329
329
  }) {
330
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { "data-slot": "dialog-portal", ...props });
330
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
331
331
  }
332
332
  function DialogOverlay({
333
333
  className,
@@ -497,14 +497,7 @@ function ConversationHeader({
497
497
  const handleOpenInNewTab = reactExports.useCallback(
498
498
  (e) => {
499
499
  e.stopPropagation();
500
- let encoded;
501
- try {
502
- encoded = btoa(conversationId).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
503
- } catch {
504
- encoded = encodeURIComponent(conversationId);
505
- }
506
- const url = `/session/${encoded}`;
507
- window.open(url, "_blank", "noopener,noreferrer");
500
+ window.open(getSessionPath(conversationId), "_blank", "noopener,noreferrer");
508
501
  },
509
502
  [conversationId]
510
503
  );
@@ -537,7 +530,7 @@ function ConversationHeader({
537
530
  {
538
531
  className: "text-purple-400/90 font-mono text-xs font-semibold shrink-0",
539
532
  title: conversationId,
540
- children: conversationId.startsWith("PID:") || conversationId.includes("|") ? conversationId : conversationId.length > 24 ? conversationId.slice(0, 12) + "" + conversationId.slice(-12) : conversationId
533
+ children: conversationId.startsWith("PID:") || conversationId.includes("|") ? conversationId : conversationId.length > 24 ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12) : conversationId
541
534
  }
542
535
  ),
543
536
  userAgent !== null && userAgent !== void 0 && userAgent !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -1163,7 +1156,7 @@ function Tooltip({ ...props }) {
1163
1156
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Root3, { "data-slot": "tooltip", ...props });
1164
1157
  }
1165
1158
  function TooltipTrigger({ ...props }) {
1166
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$3, { "data-slot": "tooltip-trigger", ...props });
1159
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$1, { "data-slot": "tooltip-trigger", ...props });
1167
1160
  }
1168
1161
  function TooltipContent({
1169
1162
  className,
@@ -1171,7 +1164,7 @@ function TooltipContent({
1171
1164
  children,
1172
1165
  ...props
1173
1166
  }) {
1174
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1167
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1175
1168
  Content2$1,
1176
1169
  {
1177
1170
  "data-slot": "tooltip-content",
@@ -1188,37 +1181,6 @@ function TooltipContent({
1188
1181
  }
1189
1182
  ) });
1190
1183
  }
1191
- function JsonExpansionButton({
1192
- policy,
1193
- isExpanded,
1194
- isPending,
1195
- onToggle
1196
- }) {
1197
- if (policy === null) return null;
1198
- const label = isPending ? "Updating..." : isExpanded ? "Collapse all" : "Expand all";
1199
- const tooltip = isExpanded ? "Collapse all JSON nodes" : "Expand all JSON nodes";
1200
- return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1201
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1202
- Button,
1203
- {
1204
- variant: "outline",
1205
- size: "sm",
1206
- className: "h-8 text-xs",
1207
- onClick: (e) => {
1208
- e.stopPropagation();
1209
- onToggle();
1210
- },
1211
- disabled: isPending,
1212
- "aria-pressed": isExpanded,
1213
- children: [
1214
- isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsUp, { className: "size-3.5 mr-1" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsDown, { className: "size-3.5 mr-1" }),
1215
- label
1216
- ]
1217
- }
1218
- ) }),
1219
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: tooltip })
1220
- ] }) });
1221
- }
1222
1184
  function getJsonExpansionPolicy(_value) {
1223
1185
  return { depth: Number.POSITIVE_INFINITY };
1224
1186
  }
@@ -1330,7 +1292,7 @@ function TabsTrigger({
1330
1292
  ...props
1331
1293
  }) {
1332
1294
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
1333
- Trigger$2,
1295
+ Trigger$3,
1334
1296
  {
1335
1297
  "data-slot": "tabs-trigger",
1336
1298
  className: cn(
@@ -1357,28 +1319,51 @@ function TabsContent({
1357
1319
  }
1358
1320
  );
1359
1321
  }
1322
+ function useCopyFeedback(text) {
1323
+ const [copied, setCopied] = reactExports.useState(false);
1324
+ const timerRef = reactExports.useRef(null);
1325
+ reactExports.useEffect(
1326
+ () => () => {
1327
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
1328
+ },
1329
+ []
1330
+ );
1331
+ const copy = reactExports.useCallback(
1332
+ (event) => {
1333
+ event.stopPropagation();
1334
+ if (text === null) return;
1335
+ void window.navigator.clipboard.writeText(text).then(() => {
1336
+ setCopied(true);
1337
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
1338
+ timerRef.current = setTimeout(() => setCopied(false), 2e3);
1339
+ });
1340
+ },
1341
+ [text]
1342
+ );
1343
+ return { copied, copy };
1344
+ }
1360
1345
  const LazyCompareDrawer = reactExports.lazy(
1361
- () => import("./CompareDrawer-DajC3x7u.mjs").then((m) => ({ default: m.CompareDrawer }))
1346
+ () => import("./CompareDrawer-DNGYdUXs.mjs").then((m) => ({ default: m.CompareDrawer }))
1362
1347
  );
1363
1348
  const LazyReplayDialog = reactExports.lazy(
1364
- () => import("./ReplayDialog-BnCLuA5z.mjs").then((m) => ({ default: m.ReplayDialog }))
1349
+ () => import("./ReplayDialog-DWeqMA4y.mjs").then((m) => ({ default: m.ReplayDialog }))
1365
1350
  );
1366
1351
  const LazyRequestAnatomy = reactExports.lazy(
1367
- () => import("./RequestAnatomy-OHE3iT-f.mjs").then((m) => ({ default: m.RequestAnatomy }))
1352
+ () => import("./RequestAnatomy-TOsrMu9-.mjs").then((m) => ({ default: m.RequestAnatomy }))
1368
1353
  );
1369
1354
  const LazyResponseView = reactExports.lazy(
1370
- () => import("./ResponseView-NPshHwOv.mjs").then((m) => ({ default: m.ResponseView }))
1355
+ () => import("./ResponseView-BuqdPrzm.mjs").then((m) => ({ default: m.ResponseView }))
1371
1356
  );
1372
1357
  const LazyStreamingChunkSequence = reactExports.lazy(
1373
- () => import("./StreamingChunkSequence-BfukoR7F.mjs").then((m) => ({
1358
+ () => import("./StreamingChunkSequence-DuzNZkqL.mjs").then((m) => ({
1374
1359
  default: m.StreamingChunkSequence
1375
1360
  }))
1376
1361
  );
1377
1362
  const LazyJsonViewer = reactExports.lazy(
1378
- () => import("./json-viewer-CHBa-Oas.mjs").then((m) => ({ default: m.JsonViewer }))
1363
+ () => import("./json-viewer-BL8xhHbi.mjs").then((m) => ({ default: m.JsonViewer }))
1379
1364
  );
1380
1365
  const LazyJsonViewerFromString = reactExports.lazy(
1381
- () => import("./json-viewer-CHBa-Oas.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1366
+ () => import("./json-viewer-BL8xhHbi.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1382
1367
  );
1383
1368
  const HIGHLIGHT_DURATION_MS = 1200;
1384
1369
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -1797,11 +1782,9 @@ const LogEntryHeader = reactExports.memo(function({
1797
1782
  onToggle,
1798
1783
  responseToolNames = null,
1799
1784
  cacheTrend = null,
1785
+ activeTab,
1786
+ tabActions,
1800
1787
  onReplay,
1801
- onCopyRequest,
1802
- requestCopied = false,
1803
- onToggleRequestExpansion,
1804
- requestExpansionState = null,
1805
1788
  slowResponseThresholdSeconds = 0
1806
1789
  }) {
1807
1790
  const statusCategory = getStatusCategory(log.responseStatus);
@@ -1965,36 +1948,74 @@ const LogEntryHeader = reactExports.memo(function({
1965
1948
  onClick: (e) => e.stopPropagation(),
1966
1949
  onKeyDown: (e) => e.stopPropagation(),
1967
1950
  children: [
1968
- requestExpansionState !== null && onToggleRequestExpansion !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1969
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1970
- Button,
1971
- {
1972
- variant: "outline",
1973
- size: "icon",
1974
- className: "size-8",
1975
- onClick: onToggleRequestExpansion,
1976
- disabled: requestExpansionState.isPending,
1977
- "aria-pressed": requestExpansionState.isExpanded,
1978
- "aria-label": requestExpansionState.isExpanded ? "Collapse all JSON" : "Expand all JSON",
1979
- children: requestExpansionState.isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsUp, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsDown, { className: "size-3.5" })
1980
- }
1981
- ) }),
1982
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: requestExpansionState.isExpanded ? "Collapse all JSON nodes" : "Expand all JSON nodes" })
1983
- ] }),
1984
- onCopyRequest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1985
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1986
- Button,
1987
- {
1988
- variant: "outline",
1989
- size: "icon",
1990
- className: "size-8",
1991
- onClick: onCopyRequest,
1992
- "aria-label": "Copy request body",
1993
- children: requestCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
1994
- }
1995
- ) }),
1996
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: requestCopied ? "Copied to clipboard" : "Copy request body" })
1997
- ] }),
1951
+ tabActions !== void 0 && activeTab !== void 0 && (() => {
1952
+ const action = tabActions[activeTab];
1953
+ if (action === void 0) return null;
1954
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1955
+ action.expansion !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1956
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1957
+ Button,
1958
+ {
1959
+ variant: "outline",
1960
+ size: "icon",
1961
+ className: "size-8",
1962
+ onClick: action.expansion.onToggle,
1963
+ disabled: action.expansion.isPending,
1964
+ "aria-pressed": action.expansion.isExpanded,
1965
+ "aria-label": action.expansion.isExpanded ? "Collapse all JSON" : "Expand all JSON",
1966
+ children: action.expansion.isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsUp, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronsDown, { className: "size-3.5" })
1967
+ }
1968
+ ) }),
1969
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: action.expansion.isExpanded ? "Collapse all JSON nodes" : "Expand all JSON nodes" })
1970
+ ] }),
1971
+ action.diffWithRaw !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1972
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1973
+ Button,
1974
+ {
1975
+ variant: "outline",
1976
+ size: "icon",
1977
+ className: cn(
1978
+ "size-8",
1979
+ action.diffWithRaw.active && "bg-accent text-accent-foreground"
1980
+ ),
1981
+ onClick: action.diffWithRaw.onToggle,
1982
+ "aria-pressed": action.diffWithRaw.active,
1983
+ "aria-label": action.diffWithRaw.active ? "Hide raw diff" : "Diff with raw",
1984
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(FileDiff, { className: "size-3.5" })
1985
+ }
1986
+ ) }),
1987
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: action.diffWithRaw.active ? "Hide diff with raw request" : "Show diff between displayed and raw request body" })
1988
+ ] }),
1989
+ action.diffWithPrevious !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1990
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1991
+ Button,
1992
+ {
1993
+ variant: "outline",
1994
+ size: "icon",
1995
+ className: "size-8",
1996
+ onClick: action.diffWithPrevious,
1997
+ "aria-label": "Diff with previous",
1998
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(History, { className: "size-3.5" })
1999
+ }
2000
+ ) }),
2001
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Compare this request with the immediately preceding one" })
2002
+ ] }),
2003
+ action.copyText !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2004
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2005
+ Button,
2006
+ {
2007
+ variant: "outline",
2008
+ size: "icon",
2009
+ className: "size-8",
2010
+ onClick: action.onCopy,
2011
+ "aria-label": action.copyCopied ? "Copied" : action.copyLabel,
2012
+ children: action.copyCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
2013
+ }
2014
+ ) }),
2015
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: action.copyCopied ? "Copied to clipboard" : action.copyLabel })
2016
+ ] })
2017
+ ] });
2018
+ })(),
1998
2019
  onReplay !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1999
2020
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2000
2021
  Button,
@@ -2335,31 +2356,6 @@ function shouldShowRequestDiffButton(apiFormat, viewMode, strip, hasRawRequest)
2335
2356
  function TabFallback() {
2336
2357
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1", "aria-hidden": "true" });
2337
2358
  }
2338
- function CopyButton({
2339
- text,
2340
- label,
2341
- copied,
2342
- onCopy
2343
- }) {
2344
- if (text === null) return null;
2345
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2346
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2347
- Button,
2348
- {
2349
- variant: "outline",
2350
- size: "sm",
2351
- className: "h-8 text-xs",
2352
- onClick: onCopy,
2353
- "aria-label": label,
2354
- children: [
2355
- copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 mr-1 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5 mr-1" }),
2356
- copied ? "Copied!" : label
2357
- ]
2358
- }
2359
- ) }),
2360
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: copied ? "Copied to clipboard" : label })
2361
- ] });
2362
- }
2363
2359
  function DiffToggleButton({
2364
2360
  active,
2365
2361
  onClick
@@ -2401,29 +2397,6 @@ const HeadersDiffContent = reactExports.memo(function({
2401
2397
  const result = reactExports.useMemo(() => computeHeadersDiff(rawHeaders, headers), [rawHeaders, headers]);
2402
2398
  return /* @__PURE__ */ jsxRuntimeExports.jsx(DiffView, { result, emptyLabel });
2403
2399
  });
2404
- function useCopyFeedback(text) {
2405
- const [copied, setCopied] = reactExports.useState(false);
2406
- const timerRef = reactExports.useRef(null);
2407
- reactExports.useEffect(
2408
- () => () => {
2409
- if (timerRef.current !== null) clearTimeout(timerRef.current);
2410
- },
2411
- []
2412
- );
2413
- const copy = reactExports.useCallback(
2414
- (event) => {
2415
- event.stopPropagation();
2416
- if (text === null) return;
2417
- void window.navigator.clipboard.writeText(text).then(() => {
2418
- setCopied(true);
2419
- if (timerRef.current !== null) clearTimeout(timerRef.current);
2420
- timerRef.current = setTimeout(() => setCopied(false), 2e3);
2421
- });
2422
- },
2423
- [text]
2424
- );
2425
- return { copied, copy };
2426
- }
2427
2400
  const LogEntry = reactExports.memo(function({
2428
2401
  log,
2429
2402
  viewMode = "simple",
@@ -2456,11 +2429,106 @@ const LogEntry = reactExports.memo(function({
2456
2429
  return stripClaudeCodeBillingHeader(log.rawRequestBody).body;
2457
2430
  }, [log.rawRequestBody, resolvedFormat, strip]);
2458
2431
  const displayedRequestBody = strippedRequestBody ?? log.rawRequestBody;
2432
+ const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
2433
+ const rawRequestExpansion = useJsonBulkExpansion(log.rawRequestBody);
2434
+ const responseExpansion = useJsonBulkExpansion(log.responseText);
2435
+ const headersText = reactExports.useMemo(
2436
+ () => log.headers && Object.keys(log.headers).length > 0 ? JSON.stringify(log.headers, null, 2) : null,
2437
+ [log.headers]
2438
+ );
2439
+ const rawHeadersText = reactExports.useMemo(
2440
+ () => log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? JSON.stringify(log.rawHeaders, null, 2) : null,
2441
+ [log.rawHeaders]
2442
+ );
2459
2443
  const requestCopy = useCopyFeedback(displayedRequestBody);
2460
2444
  const rawRequestCopy = useCopyFeedback(log.rawRequestBody);
2445
+ const headersCopy = useCopyFeedback(headersText);
2446
+ const rawHeadersCopy = useCopyFeedback(rawHeadersText);
2461
2447
  const responseCopy = useCopyFeedback(log.responseText);
2462
- const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
2463
- const rawRequestExpansion = useJsonBulkExpansion(log.rawRequestBody);
2448
+ const tabActions = reactExports.useMemo(
2449
+ () => ({
2450
+ request: {
2451
+ copyLabel: "Copy request body",
2452
+ copyText: displayedRequestBody,
2453
+ copyCopied: requestCopy.copied,
2454
+ onCopy: requestCopy.copy,
2455
+ expansion: {
2456
+ isExpanded: requestExpansion.isExpanded,
2457
+ isPending: requestExpansion.isPending,
2458
+ onToggle: requestExpansion.toggle
2459
+ },
2460
+ // "Diff with Raw" only makes sense when there's a raw body to compare
2461
+ // against (Anthropic + strip pipeline produces one). "Diff with
2462
+ // Previous" only exists when the parent wired up the compare drawer.
2463
+ diffWithRaw: shouldShowRequestDiffButton(
2464
+ resolvedFormat,
2465
+ viewMode,
2466
+ strip,
2467
+ log.rawRequestBody !== null
2468
+ ) ? { active: requestDiff, onToggle: () => setRequestDiff(!requestDiff) } : void 0,
2469
+ diffWithPrevious: onCompareWithPrevious === void 0 ? void 0 : () => {
2470
+ onCompareWithPrevious(log);
2471
+ }
2472
+ },
2473
+ "raw-request": {
2474
+ copyLabel: "Copy raw request",
2475
+ copyText: log.rawRequestBody,
2476
+ copyCopied: rawRequestCopy.copied,
2477
+ onCopy: rawRequestCopy.copy,
2478
+ expansion: {
2479
+ isExpanded: rawRequestExpansion.isExpanded,
2480
+ isPending: rawRequestExpansion.isPending,
2481
+ onToggle: rawRequestExpansion.toggle
2482
+ }
2483
+ },
2484
+ headers: {
2485
+ copyLabel: "Copy headers",
2486
+ copyText: headersText,
2487
+ copyCopied: headersCopy.copied,
2488
+ onCopy: headersCopy.copy,
2489
+ // Headers are a flat dict, no JSON tree to expand.
2490
+ expansion: null
2491
+ },
2492
+ "raw-headers": {
2493
+ copyLabel: "Copy raw headers",
2494
+ copyText: rawHeadersText,
2495
+ copyCopied: rawHeadersCopy.copied,
2496
+ onCopy: rawHeadersCopy.copy,
2497
+ expansion: null
2498
+ },
2499
+ raw: {
2500
+ copyLabel: "Copy response",
2501
+ copyText: log.responseText,
2502
+ copyCopied: responseCopy.copied,
2503
+ onCopy: responseCopy.copy,
2504
+ expansion: {
2505
+ isExpanded: responseExpansion.isExpanded,
2506
+ isPending: responseExpansion.isPending,
2507
+ onToggle: responseExpansion.toggle
2508
+ }
2509
+ }
2510
+ }),
2511
+ [
2512
+ displayedRequestBody,
2513
+ requestCopy,
2514
+ requestExpansion,
2515
+ requestDiff,
2516
+ log.rawRequestBody,
2517
+ rawRequestCopy,
2518
+ rawRequestExpansion,
2519
+ headersText,
2520
+ headersCopy,
2521
+ rawHeadersText,
2522
+ rawHeadersCopy,
2523
+ log.responseText,
2524
+ responseCopy,
2525
+ responseExpansion,
2526
+ resolvedFormat,
2527
+ viewMode,
2528
+ strip,
2529
+ onCompareWithPrevious
2530
+ ]
2531
+ );
2464
2532
  const anatomySegments = reactExports.useMemo(
2465
2533
  () => requestExpansion.parsedData !== null ? adapter.anatomySegments(requestExpansion.parsedData) : null,
2466
2534
  [adapter, requestExpansion.parsedData]
@@ -2489,17 +2557,10 @@ const LogEntry = reactExports.memo(function({
2489
2557
  responseToolNames: responseAnalysis.toolNames,
2490
2558
  cacheTrend,
2491
2559
  slowResponseThresholdSeconds,
2560
+ activeTab,
2561
+ tabActions,
2492
2562
  onReplay: onCompareWithPrevious === void 0 ? void 0 : () => {
2493
2563
  setReplayOpen(true);
2494
- },
2495
- onCopyRequest: displayedRequestBody === null ? void 0 : (e) => {
2496
- requestCopy.copy(e);
2497
- },
2498
- requestCopied: requestCopy.copied,
2499
- onToggleRequestExpansion: requestExpansion.toggle,
2500
- requestExpansionState: requestExpansion.policy === null ? null : {
2501
- isExpanded: requestExpansion.isExpanded,
2502
- isPending: requestExpansion.isPending
2503
2564
  }
2504
2565
  }
2505
2566
  ),
@@ -2513,96 +2574,31 @@ const LogEntry = reactExports.memo(function({
2513
2574
  viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
2514
2575
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
2515
2576
  ] }),
2516
- 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: [
2517
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2518
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2519
- JsonExpansionButton,
2520
- {
2521
- policy: rawRequestExpansion.policy,
2522
- isExpanded: rawRequestExpansion.isExpanded,
2523
- isPending: rawRequestExpansion.isPending,
2524
- onToggle: rawRequestExpansion.toggle
2525
- }
2526
- ),
2527
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2528
- CopyButton,
2529
- {
2530
- text: log.rawRequestBody,
2531
- label: "Copy",
2532
- copied: rawRequestCopy.copied,
2533
- onCopy: rawRequestCopy.copy
2534
- }
2535
- )
2536
- ] }),
2537
- 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(
2538
- LazyJsonViewer,
2539
- {
2540
- data: rawRequestExpansion.parsedData,
2541
- bulkDepth: rawRequestExpansion.bulkDepth,
2542
- bulkRevision: rawRequestExpansion.bulkRevision
2543
- }
2544
- ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words", children: log.rawRequestBody })
2545
- ] }) }),
2546
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pt-1 pb-3", children: [
2547
- (shouldShowRequestDiffButton(
2548
- resolvedFormat,
2549
- viewMode,
2550
- strip,
2551
- log.rawRequestBody !== null
2552
- ) || onCompareWithPrevious !== void 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2553
- shouldShowRequestDiffButton(
2554
- resolvedFormat,
2555
- viewMode,
2556
- strip,
2557
- log.rawRequestBody !== null
2558
- ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2559
- DiffToggleButton,
2560
- {
2561
- active: requestDiff,
2562
- onClick: (e) => {
2563
- e.stopPropagation();
2564
- setRequestDiff(!requestDiff);
2565
- }
2566
- }
2567
- ),
2568
- onCompareWithPrevious !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2569
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2570
- Button,
2571
- {
2572
- variant: "outline",
2573
- size: "sm",
2574
- className: "h-8 text-xs",
2575
- onClick: (e) => {
2576
- e.stopPropagation();
2577
- onCompareWithPrevious(log);
2578
- },
2579
- children: [
2580
- /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3 mr-1" }),
2581
- "Diff with Previous"
2582
- ]
2583
- }
2584
- ) }),
2585
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Compare this request with the immediately preceding one" })
2586
- ] })
2587
- ] }),
2588
- requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2589
- RequestDiffContent,
2590
- {
2591
- rawBody: log.rawRequestBody,
2592
- displayedBody: displayedRequestBody,
2593
- emptyLabel: "No transformation applied — raw and sent request bodies are identical."
2594
- }
2595
- ) : /* @__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(
2596
- LazyJsonViewer,
2597
- {
2598
- data: requestExpansion.parsedData,
2599
- bulkDepth: requestExpansion.bulkDepth,
2600
- bulkRevision: requestExpansion.bulkRevision,
2601
- anatomyPaths,
2602
- expandToPath
2603
- }
2604
- ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words", children: displayedRequestBody }) })
2605
- ] }) }),
2577
+ shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 pt-1 pb-3", children: 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(
2578
+ LazyJsonViewer,
2579
+ {
2580
+ data: rawRequestExpansion.parsedData,
2581
+ bulkDepth: rawRequestExpansion.bulkDepth,
2582
+ bulkRevision: rawRequestExpansion.bulkRevision
2583
+ }
2584
+ ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words", children: log.rawRequestBody }) }) }),
2585
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 pt-1 pb-3", children: requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2586
+ RequestDiffContent,
2587
+ {
2588
+ rawBody: log.rawRequestBody,
2589
+ displayedBody: displayedRequestBody,
2590
+ emptyLabel: "No transformation applied — raw and sent request bodies are identical."
2591
+ }
2592
+ ) : /* @__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(
2593
+ LazyJsonViewer,
2594
+ {
2595
+ data: requestExpansion.parsedData,
2596
+ bulkDepth: requestExpansion.bulkDepth,
2597
+ bulkRevision: requestExpansion.bulkRevision,
2598
+ anatomyPaths,
2599
+ expandToPath
2600
+ }
2601
+ ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words", children: displayedRequestBody }) }) }) }),
2606
2602
  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(
2607
2603
  LazyRequestAnatomy,
2608
2604
  {
@@ -2653,16 +2649,15 @@ const LogEntry = reactExports.memo(function({
2653
2649
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2654
2650
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
2655
2651
  ] }),
2656
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2657
- CopyButton,
2652
+ log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2653
+ LazyJsonViewerFromString,
2658
2654
  {
2659
2655
  text: log.responseText,
2660
- label: "Copy",
2661
- copied: responseCopy.copied,
2662
- onCopy: responseCopy.copy
2656
+ defaultExpandDepth: 0,
2657
+ bulkDepth: responseExpansion.bulkDepth,
2658
+ bulkRevision: responseExpansion.bulkRevision
2663
2659
  }
2664
- ) }),
2665
- 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" }),
2660
+ ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
2666
2661
  log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2667
2662
  LazyStreamingChunkSequence,
2668
2663
  {
@@ -3163,6 +3158,18 @@ function CrabLogo({ className }) {
3163
3158
  }
3164
3159
  );
3165
3160
  }
3161
+ const McpLogoPng = "data:image/png;base64,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";
3162
+ function McpLogo({ className }) {
3163
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3164
+ "img",
3165
+ {
3166
+ src: McpLogoPng,
3167
+ alt: "Model Context Protocol",
3168
+ "aria-hidden": "true",
3169
+ className: cn("inline-block size-8 object-contain invert", className)
3170
+ }
3171
+ );
3172
+ }
3166
3173
  function Select({
3167
3174
  ...props
3168
3175
  }) {
@@ -5196,6 +5203,25 @@ function computeTokenSummary(logs) {
5196
5203
  }
5197
5204
  return { totalIn, totalOut };
5198
5205
  }
5206
+ function formatTimeRange(logs) {
5207
+ const first = logs[0];
5208
+ const last = logs[logs.length - 1];
5209
+ if (first === void 0 || last === void 0) return null;
5210
+ const format = (iso) => new Date(iso).toLocaleTimeString([], {
5211
+ hour: "2-digit",
5212
+ minute: "2-digit",
5213
+ second: "2-digit"
5214
+ });
5215
+ return `${format(first.timestamp)} - ${format(last.timestamp)}`;
5216
+ }
5217
+ function getFirstUserAgent(logs) {
5218
+ for (const log of logs) {
5219
+ if (log.userAgent !== null && log.userAgent !== void 0 && log.userAgent !== "") {
5220
+ return log.userAgent;
5221
+ }
5222
+ }
5223
+ return null;
5224
+ }
5199
5225
  function CopyableCommand({ command }) {
5200
5226
  const [copied, setCopied] = reactExports.useState(false);
5201
5227
  const handleCopy = reactExports.useCallback(() => {
@@ -5247,6 +5273,82 @@ function CopyableCommand({ command }) {
5247
5273
  )
5248
5274
  ] });
5249
5275
  }
5276
+ function McpReadyBadge() {
5277
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
5278
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex h-7 items-center gap-2 rounded-md border border-cyan-400/30 bg-cyan-500/10 px-2.5 font-mono text-[11px] font-medium text-cyan-300 shadow-[0_0_16px_rgba(34,211,238,0.08)]", children: [
5279
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "size-1.5 rounded-full bg-emerald-300 shadow-[0_0_8px_rgba(110,231,183,0.8)]" }),
5280
+ "MCP Ready",
5281
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden text-cyan-200/70 sm:inline", children: "/api/mcp" })
5282
+ ] }) }),
5283
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { sideOffset: 8, className: "max-w-[320px] text-left leading-relaxed", children: "Coding agents can inspect logs, replay requests, test providers, and debug sessions through MCP at /api/mcp." })
5284
+ ] }) });
5285
+ }
5286
+ function SessionContextBar({
5287
+ sessionId,
5288
+ logs,
5289
+ totalIn,
5290
+ totalOut
5291
+ }) {
5292
+ const [copied, setCopied] = reactExports.useState(false);
5293
+ const timeRange = reactExports.useMemo(() => formatTimeRange(logs), [logs]);
5294
+ const userAgent = reactExports.useMemo(() => getFirstUserAgent(logs), [logs]);
5295
+ const handleCopyLink = reactExports.useCallback(() => {
5296
+ void window.navigator.clipboard.writeText(window.location.href).then(() => {
5297
+ setCopied(true);
5298
+ setTimeout(() => setCopied(false), 2e3);
5299
+ });
5300
+ }, []);
5301
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 flex items-center gap-3 border border-border rounded-md bg-muted/20 px-3 py-2 text-xs", children: [
5302
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5303
+ "a",
5304
+ {
5305
+ href: "/",
5306
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
5307
+ "aria-label": "Back to all sessions",
5308
+ title: "Back to all sessions",
5309
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { className: "size-3.5" })
5310
+ }
5311
+ ),
5312
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [
5313
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
5314
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-semibold text-purple-400/90 truncate", title: sessionId, children: truncateSessionId(sessionId) }),
5315
+ userAgent !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
5316
+ "span",
5317
+ {
5318
+ className: "font-mono text-muted-foreground truncate max-w-[220px]",
5319
+ title: userAgent,
5320
+ children: userAgent
5321
+ }
5322
+ )
5323
+ ] }),
5324
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-muted-foreground", children: [
5325
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
5326
+ logs.length,
5327
+ " request",
5328
+ logs.length !== 1 ? "s" : ""
5329
+ ] }),
5330
+ timeRange !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: timeRange }),
5331
+ (totalIn > 0 || totalOut > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
5332
+ formatTokens(totalIn),
5333
+ " in / ",
5334
+ formatTokens(totalOut),
5335
+ " out"
5336
+ ] })
5337
+ ] })
5338
+ ] }),
5339
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5340
+ "button",
5341
+ {
5342
+ type: "button",
5343
+ onClick: handleCopyLink,
5344
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
5345
+ "aria-label": copied ? "Copied session link" : "Copy session link",
5346
+ title: copied ? "Copied session link" : "Copy session link",
5347
+ children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
5348
+ }
5349
+ )
5350
+ ] });
5351
+ }
5250
5352
  function ProxyViewer({
5251
5353
  logs,
5252
5354
  sessions,
@@ -5261,7 +5363,8 @@ function ProxyViewer({
5261
5363
  onViewModeChange,
5262
5364
  strip,
5263
5365
  slowResponseThresholdSeconds,
5264
- hideSessionFilter = false
5366
+ hideSessionFilter = false,
5367
+ pinnedSessionId
5265
5368
  }) {
5266
5369
  const { totalIn, totalOut } = reactExports.useMemo(() => computeTokenSummary(logs), [logs]);
5267
5370
  const [exporting, setExporting] = reactExports.useState(false);
@@ -5283,6 +5386,14 @@ function ProxyViewer({
5283
5386
  clearTimeout(t2);
5284
5387
  };
5285
5388
  }, []);
5389
+ reactExports.useEffect(() => {
5390
+ if (pinnedSessionId === void 0) {
5391
+ document.title = "LLM Inspector";
5392
+ return;
5393
+ }
5394
+ const requestLabel = logs.length === 1 ? "1 req" : `${logs.length} req`;
5395
+ document.title = `${truncateSessionId(pinnedSessionId)} - ${requestLabel} - LLM Inspector`;
5396
+ }, [logs.length, pinnedSessionId]);
5286
5397
  const handleExport = reactExports.useCallback(async () => {
5287
5398
  setExporting(true);
5288
5399
  try {
@@ -5308,117 +5419,141 @@ function ProxyViewer({
5308
5419
  [comparisonPredecessors]
5309
5420
  );
5310
5421
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1400px] xl:max-w-[1600px] 2xl:max-w-[1800px] mx-auto px-6 pb-6", children: [
5311
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end pt-6 pb-8 relative", children: [
5312
- /* @__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: [
5313
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-end gap-1 group cursor-default", "aria-hidden": "true", children: [
5314
- /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" }),
5315
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-end gap-0.5", children: crabVariants.map((Crab, i) => {
5316
- const color = [
5317
- "text-amber-500",
5318
- "text-rose-500",
5319
- "text-sky-500",
5320
- "text-emerald-500",
5321
- "text-violet-500",
5322
- "text-orange-500",
5323
- "text-cyan-500",
5324
- "text-pink-500",
5325
- "text-lime-500",
5326
- "text-blue-500",
5327
- "text-yellow-500",
5328
- "text-fuchsia-500"
5329
- ][i];
5330
- const entranceClass = crabEntrancePhase === "hidden" ? "opacity-0 scale-0" : crabEntrancePhase === "playing" ? "animate-crab-piano-pop" : "";
5331
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
5332
- Crab,
5422
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sticky top-0 z-30 bg-background pt-6", children: [
5423
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[1fr_auto_1fr] items-start gap-3 pb-8", children: [
5424
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}),
5425
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "flex min-w-0 flex-col items-center gap-2 text-center", children: [
5426
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex max-w-[calc(100vw-7rem)] items-end gap-2 whitespace-nowrap", children: [
5427
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
5428
+ "span",
5333
5429
  {
5334
- className: `size-5 ${color} transition-all duration-300 ease-out group-hover:scale-125 group-hover:-translate-y-1 ${entranceClass}`,
5335
- style: {
5336
- transitionDelay: `${i * 50}ms`,
5337
- ...crabEntrancePhase === "playing" ? { animationDelay: `${i * 400}ms` } : {}
5338
- }
5339
- },
5340
- i
5341
- );
5342
- }) })
5430
+ className: "flex shrink-0 items-end gap-1 group cursor-default",
5431
+ "aria-hidden": "true",
5432
+ children: [
5433
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" }),
5434
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden items-end gap-0.5 sm:flex", children: crabVariants.map((Crab, i) => {
5435
+ const color = [
5436
+ "text-amber-500",
5437
+ "text-rose-500",
5438
+ "text-sky-500",
5439
+ "text-emerald-500",
5440
+ "text-violet-500",
5441
+ "text-orange-500",
5442
+ "text-cyan-500",
5443
+ "text-pink-500",
5444
+ "text-lime-500",
5445
+ "text-blue-500",
5446
+ "text-yellow-500",
5447
+ "text-fuchsia-500"
5448
+ ][i];
5449
+ const entranceClass = crabEntrancePhase === "hidden" ? "opacity-0 scale-0" : crabEntrancePhase === "playing" ? "animate-crab-piano-pop" : "";
5450
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
5451
+ Crab,
5452
+ {
5453
+ className: `size-5 ${color} transition-all duration-300 ease-out group-hover:scale-125 group-hover:-translate-y-1 ${entranceClass}`,
5454
+ style: {
5455
+ transitionDelay: `${i * 50}ms`,
5456
+ ...crabEntrancePhase === "playing" ? { animationDelay: `${i * 400}ms` } : {}
5457
+ }
5458
+ },
5459
+ i
5460
+ );
5461
+ }) })
5462
+ ]
5463
+ }
5464
+ ),
5465
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex min-w-0 items-baseline gap-2 pl-1", children: [
5466
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-lg font-bold", children: "LLM Inspector" }),
5467
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 font-mono text-xs font-semibold text-muted-foreground", children: [
5468
+ "v",
5469
+ packageJson.version
5470
+ ] })
5471
+ ] }),
5472
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4 shrink-0 text-muted-foreground/70", "aria-hidden": "true" }),
5473
+ /* @__PURE__ */ jsxRuntimeExports.jsx(McpLogo, { className: "size-10 shrink-0" })
5474
+ ] }),
5475
+ /* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {})
5343
5476
  ] }),
5344
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-baseline gap-2", children: [
5345
- "LLM Inspector",
5346
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-muted-foreground font-mono", children: [
5347
- "v",
5348
- packageJson.version
5349
- ] })
5350
- ] })
5351
- ] }),
5352
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}) })
5353
- ] }),
5354
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
5355
- !hideSessionFilter && /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedSession, onValueChange: onSessionChange, children: [
5356
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[350px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All sessions" }) }),
5357
- /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
5358
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All sessions" }),
5359
- sessions.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: s, children: truncateSessionId(s) }, s))
5360
- ] })
5477
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}) })
5361
5478
  ] }),
5362
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedModel, onValueChange: onModelChange, children: [
5363
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[250px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All models" }) }),
5364
- /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
5365
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All models" }),
5366
- models.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: m, children: m }, m))
5367
- ] })
5368
- ] }),
5369
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5370
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5479
+ pinnedSessionId !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
5480
+ SessionContextBar,
5481
+ {
5482
+ sessionId: pinnedSessionId,
5483
+ logs,
5484
+ totalIn,
5485
+ totalOut
5486
+ }
5487
+ ),
5488
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
5489
+ !hideSessionFilter && /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedSession, onValueChange: onSessionChange, children: [
5490
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[350px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All sessions" }) }),
5491
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
5492
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All sessions" }),
5493
+ sessions.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: s, children: truncateSessionId(s) }, s))
5494
+ ] })
5495
+ ] }),
5496
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedModel, onValueChange: onModelChange, children: [
5497
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[250px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All models" }) }),
5498
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
5499
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All models" }),
5500
+ models.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: m, children: m }, m))
5501
+ ] })
5502
+ ] }),
5503
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5504
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5505
+ "button",
5506
+ {
5507
+ type: "button",
5508
+ onClick: () => onViewModeChange("simple"),
5509
+ className: `h-8 px-3 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5510
+ children: "Simple"
5511
+ }
5512
+ ),
5513
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5514
+ "button",
5515
+ {
5516
+ type: "button",
5517
+ onClick: () => onViewModeChange("full"),
5518
+ className: `h-8 px-3 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5519
+ children: "Full"
5520
+ }
5521
+ )
5522
+ ] }),
5523
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
5524
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5525
+ logs.length,
5526
+ " request",
5527
+ logs.length !== 1 ? "s" : "",
5528
+ totalIn > 0 || totalOut > 0 ? ` · ${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out` : ""
5529
+ ] }),
5530
+ logs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
5371
5531
  "button",
5372
5532
  {
5373
5533
  type: "button",
5374
- onClick: () => onViewModeChange("simple"),
5375
- className: `h-8 px-3 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5376
- children: "Simple"
5534
+ onClick: () => {
5535
+ void handleExport();
5536
+ },
5537
+ disabled: exporting,
5538
+ 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",
5539
+ title: "Export all logs as JSON ZIP",
5540
+ children: exporting ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Exporting..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5541
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3.5" }),
5542
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Export" })
5543
+ ] })
5377
5544
  }
5378
5545
  ),
5379
5546
  /* @__PURE__ */ jsxRuntimeExports.jsx(
5380
5547
  "button",
5381
5548
  {
5382
5549
  type: "button",
5383
- onClick: () => onViewModeChange("full"),
5384
- className: `h-8 px-3 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5385
- children: "Full"
5550
+ onClick: onClearAll,
5551
+ className: "h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer rounded-md hover:bg-muted",
5552
+ title: "Clear all logs",
5553
+ children: "Clear"
5386
5554
  }
5387
5555
  )
5388
- ] }),
5389
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
5390
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5391
- logs.length,
5392
- " request",
5393
- logs.length !== 1 ? "s" : "",
5394
- totalIn > 0 || totalOut > 0 ? ` · ${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out` : ""
5395
- ] }),
5396
- logs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
5397
- "button",
5398
- {
5399
- type: "button",
5400
- onClick: () => {
5401
- void handleExport();
5402
- },
5403
- disabled: exporting,
5404
- 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",
5405
- title: "Export all logs as JSON ZIP",
5406
- children: exporting ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Exporting..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5407
- /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3.5" }),
5408
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Export" })
5409
- ] })
5410
- }
5411
- ),
5412
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5413
- "button",
5414
- {
5415
- type: "button",
5416
- onClick: onClearAll,
5417
- className: "h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer rounded-md hover:bg-muted",
5418
- title: "Clear all logs",
5419
- children: "Clear"
5420
- }
5421
- )
5556
+ ] })
5422
5557
  ] }),
5423
5558
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: logs.length === 0 ? selectedSession !== "__all__" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center text-muted-foreground py-16 space-y-4", children: [
5424
5559
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium", children: "Session not found" }),
@@ -5426,7 +5561,7 @@ function ProxyViewer({
5426
5561
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs", children: [
5427
5562
  "This session may have been cleared or never existed.",
5428
5563
  " ",
5429
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5564
+ hideSessionFilter ? /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "/", className: "underline hover:text-foreground transition-colors", children: "Back to all sessions" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5430
5565
  "button",
5431
5566
  {
5432
5567
  type: "button",
@@ -5499,6 +5634,11 @@ function filterLogs(logs, selectedSession, selectedModel) {
5499
5634
  });
5500
5635
  }
5501
5636
  const DEBOUNCE_MS = 50;
5637
+ function buildLogsStreamUrl(sessionId) {
5638
+ if (sessionId === void 0) return "/api/logs/stream";
5639
+ const params = new URLSearchParams({ sessionId });
5640
+ return `/api/logs/stream?${params.toString()}`;
5641
+ }
5502
5642
  function ProxyViewerContainer({
5503
5643
  initialSessionId
5504
5644
  } = {}) {
@@ -5550,7 +5690,7 @@ function ProxyViewerContainer({
5550
5690
  if (eventSourceRef.current) {
5551
5691
  eventSourceRef.current.close();
5552
5692
  }
5553
- const es = new EventSource("/api/logs/stream");
5693
+ const es = new EventSource(buildLogsStreamUrl(initialSessionId));
5554
5694
  eventSourceRef.current = es;
5555
5695
  es.onmessage = (event) => {
5556
5696
  try {
@@ -5591,7 +5731,7 @@ function ProxyViewerContainer({
5591
5731
  }
5592
5732
  reconnectTimeoutRef.current = setTimeout(connectSSE, 3e3);
5593
5733
  };
5594
- }, [scheduleUpdate]);
5734
+ }, [initialSessionId, scheduleUpdate]);
5595
5735
  reactExports.useEffect(() => {
5596
5736
  connectSSE();
5597
5737
  return () => {
@@ -5610,9 +5750,17 @@ function ProxyViewerContainer({
5610
5750
  };
5611
5751
  }, [connectSSE]);
5612
5752
  const handleClearAll = reactExports.useCallback(() => {
5753
+ if (initialSessionId !== void 0 && allLogs.length === 0) return;
5613
5754
  void (async () => {
5614
5755
  try {
5615
- const res = await fetch("/api/logs", { method: "DELETE" });
5756
+ const body = initialSessionId === void 0 ? void 0 : JSON.stringify({ ids: allLogs.map((log) => log.id) });
5757
+ const res = await fetch("/api/logs", {
5758
+ method: "DELETE",
5759
+ ...body === void 0 ? {} : {
5760
+ headers: { "Content-Type": "application/json" },
5761
+ body
5762
+ }
5763
+ });
5616
5764
  if (!res.ok) {
5617
5765
  setError("Failed to clear logs");
5618
5766
  return;
@@ -5624,7 +5772,7 @@ function ProxyViewerContainer({
5624
5772
  setError(err instanceof Error ? err.message : "Unknown error clearing logs");
5625
5773
  }
5626
5774
  })();
5627
- }, []);
5775
+ }, [allLogs, initialSessionId]);
5628
5776
  const handleClearGroup = reactExports.useCallback((ids) => {
5629
5777
  if (ids.length === 0) return;
5630
5778
  void (async () => {
@@ -5675,7 +5823,8 @@ function ProxyViewerContainer({
5675
5823
  onViewModeChange: setViewMode,
5676
5824
  strip,
5677
5825
  slowResponseThresholdSeconds,
5678
- hideSessionFilter: initialSessionId !== void 0
5826
+ hideSessionFilter: initialSessionId !== void 0,
5827
+ pinnedSessionId: initialSessionId
5679
5828
  }
5680
5829
  )
5681
5830
  ] });