@tonyclaw/llm-inspector 1.14.8 → 1.15.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 (30) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-CMuJQyt1.js +105 -0
  3. package/.output/public/assets/index-DciyfYBk.css +1 -0
  4. package/.output/public/assets/{main-CJ4MreBr.js → main-BLYgekFx.js} +1 -1
  5. package/.output/server/_libs/lucide-react.mjs +85 -111
  6. package/.output/server/_libs/radix-ui__react-id.mjs +1 -1
  7. package/.output/server/_ssr/{index-9uTJ4xYR.mjs → index-P66uoVEU.mjs} +677 -304
  8. package/.output/server/_ssr/index.mjs +2 -2
  9. package/.output/server/_ssr/{router-BKnjB_zi.mjs → router-DpLCKk51.mjs} +45 -14
  10. package/.output/server/{_tanstack-start-manifest_v-IsglLVKy.mjs → _tanstack-start-manifest_v-C9Wq6YdJ.mjs} +1 -1
  11. package/.output/server/index.mjs +22 -22
  12. package/package.json +1 -1
  13. package/src/components/ProxyViewer.tsx +99 -180
  14. package/src/components/proxy-viewer/ConversationGroup.tsx +70 -66
  15. package/src/components/proxy-viewer/ConversationHeader.tsx +15 -39
  16. package/src/components/proxy-viewer/LogEntry.tsx +68 -9
  17. package/src/components/proxy-viewer/LogEntryHeader.tsx +62 -75
  18. package/src/components/proxy-viewer/ThreadConnector.tsx +78 -65
  19. package/src/components/proxy-viewer/TurnGroup.tsx +83 -0
  20. package/src/components/ui/crab-variants.tsx +456 -0
  21. package/src/lib/stopReason.ts +7 -6
  22. package/src/proxy/formats/anthropic/handler.ts +2 -5
  23. package/src/proxy/formats/openai/handler.ts +33 -7
  24. package/src/proxy/formats/openai/schemas.ts +1 -0
  25. package/src/proxy/formats/openai/stream.ts +24 -0
  26. package/src/proxy/handler.ts +8 -2
  27. package/src/proxy/schemas.ts +6 -3
  28. package/styles/globals.css +38 -0
  29. package/.output/public/assets/index-CdnotuLh.js +0 -105
  30. package/.output/public/assets/index-vP91146S.css +0 -1
@@ -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, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-BKnjB_zi.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, O as OpenAIRequestSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, s as stripClaudeCodeBillingHeader } from "./router-DpLCKk51.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -8,13 +8,13 @@ import { J as JSZip } from "../_libs/jszip.mjs";
8
8
  import { c as cva } from "../_libs/class-variance-authority.mjs";
9
9
  import { d as diffLines, a as diffJson } from "../_libs/diff.mjs";
10
10
  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";
11
- import { R as Root2, T as Trigger$1, I as Icon, V as Value, P as Portal$1, C as Content2$1, 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";
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
12
  import "../_libs/modelcontextprotocol__server.mjs";
13
- import { D as Download, L as LayoutGrid, a as List, G as GitBranch, S as Settings, C as ChevronDown, b as Check, c as GitCompareArrows, R as RotateCcw, X, U as Upload, d as Scan, P as Plus, e as Copy, f as CircleAlert, g as ChevronUp, h as LoaderCircle, i as ChevronRight, j as Clock, M as MessageSquare, Z as Zap, W as Wrench, k as Globe, l as User, F as FileTerminal, m as Radio, n as Rows3, o as Columns2, p as Minus, q as Pencil, E as Equal, r as EyeOff, s as Eye, t as ExternalLink, u as RotateCw, T as Trash2, A as ArrowUp, v as ArrowDown, w as TriangleAlert, x as CircleCheckBig, y as CircleStop, z as CircleQuestionMark, B as Server, H as Gauge, I as Lock, J as Wifi, K as WifiOff, N as ChevronsUp, O as ChevronsDown, Q as Brain, V as Terminal } from "../_libs/lucide-react.mjs";
13
+ import { D as Download, S as Settings, C as ChevronDown, a as Check, X, 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, 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, T as Trash2, 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
14
  import { M as Markdown } from "../_libs/react-markdown.mjs";
15
15
  import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
16
- import { P as Provider, R as Root3, T as Trigger, a as Portal, C as Content2, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
17
- import { R as Root2$1, L as List$1, T as Trigger$3, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
16
+ 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";
17
+ import { R as Root2$1, L as List, T as Trigger$3, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
18
18
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
19
19
  import { R as Root$1 } from "../_libs/radix-ui__react-separator.mjs";
20
20
  import { R as Root$2, C as CollapsibleTrigger$1, a as CollapsibleContent$1 } from "../_libs/radix-ui__react-collapsible.mjs";
@@ -269,7 +269,7 @@ function Tooltip({ ...props }) {
269
269
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Root3, { "data-slot": "tooltip", ...props });
270
270
  }
271
271
  function TooltipTrigger({ ...props }) {
272
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger, { "data-slot": "tooltip-trigger", ...props });
272
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$1, { "data-slot": "tooltip-trigger", ...props });
273
273
  }
274
274
  function TooltipContent({
275
275
  className,
@@ -277,8 +277,8 @@ function TooltipContent({
277
277
  children,
278
278
  ...props
279
279
  }) {
280
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
281
- Content2,
280
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
281
+ Content2$1,
282
282
  {
283
283
  "data-slot": "tooltip-content",
284
284
  sideOffset,
@@ -333,7 +333,7 @@ async function exportLogsAsZip(logs) {
333
333
  document.body.removeChild(anchor);
334
334
  URL.revokeObjectURL(url);
335
335
  }
336
- const version = "1.14.8";
336
+ const version = "1.15.0";
337
337
  const packageJson = {
338
338
  version
339
339
  };
@@ -348,13 +348,13 @@ function extractStopReason(log) {
348
348
  json = JSON.parse(json);
349
349
  }
350
350
  if (!isRecord(json)) return null;
351
- if (log.apiFormat === "anthropic" && typeof json.stop_reason === "string") {
351
+ if (typeof json.stop_reason === "string") {
352
352
  if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
353
353
  return json.stop_reason;
354
354
  }
355
355
  return null;
356
356
  }
357
- if (log.apiFormat === "openai" && 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") {
357
+ 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") {
358
358
  return "stop";
359
359
  }
360
360
  return null;
@@ -362,6 +362,9 @@ function extractStopReason(log) {
362
362
  return null;
363
363
  }
364
364
  }
365
+ function isTurnBoundary(stopReason) {
366
+ return stopReason === "end_turn" || stopReason === "stop";
367
+ }
365
368
  const badgeVariants = cva(
366
369
  "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
367
370
  {
@@ -418,8 +421,7 @@ function ConversationHeader({
418
421
  onToggle,
419
422
  hideApiFormat = false,
420
423
  isLoading = false,
421
- viewMode,
422
- onToggleViewMode
424
+ userAgent
423
425
  }) {
424
426
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
425
427
  "div",
@@ -441,22 +443,6 @@ function ConversationHeader({
441
443
  },
442
444
  children: [
443
445
  expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" }),
444
- expanded && onToggleViewMode !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
445
- "button",
446
- {
447
- type: "button",
448
- onClick: (e) => {
449
- e.stopPropagation();
450
- onToggleViewMode();
451
- },
452
- className: cn(
453
- "px-1.5 py-0.5 rounded text-[10px] font-mono transition-colors shrink-0 cursor-pointer",
454
- viewMode === "thread" ? "bg-amber-500/15 text-amber-400 border border-amber-500/30" : "bg-muted text-muted-foreground border border-border hover:text-foreground"
455
- ),
456
- title: viewMode === "thread" ? "Thread view — click for flat view" : "Flat view — click for thread view",
457
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "size-3" })
458
- }
459
- ),
460
446
  /* @__PURE__ */ jsxRuntimeExports.jsx(
461
447
  "span",
462
448
  {
@@ -465,6 +451,17 @@ function ConversationHeader({
465
451
  children: conversationId.length > 24 ? conversationId.slice(0, 12) + "…" + conversationId.slice(-12) : conversationId
466
452
  }
467
453
  ),
454
+ userAgent !== null && userAgent !== void 0 && userAgent !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
455
+ "span",
456
+ {
457
+ className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
458
+ title: userAgent,
459
+ children: [
460
+ /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "size-3" }),
461
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[120px]", children: userAgent })
462
+ ]
463
+ }
464
+ ),
468
465
  !hideApiFormat && /* @__PURE__ */ jsxRuntimeExports.jsx(
469
466
  Badge,
470
467
  {
@@ -983,7 +980,7 @@ function TabsList({
983
980
  ...props
984
981
  }) {
985
982
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
986
- List$1,
983
+ List,
987
984
  {
988
985
  "data-slot": "tabs-list",
989
986
  "data-variant": variant,
@@ -1455,16 +1452,15 @@ function CacheTrendIndicator({ trend }) {
1455
1452
  }
1456
1453
  const LogEntryHeader = reactExports.memo(function({
1457
1454
  log,
1458
- parsedRequest,
1455
+ messageCount = null,
1456
+ toolCount = null,
1459
1457
  expanded,
1460
1458
  onToggle,
1461
- suppressApiFormatBadge = false,
1459
+ responseToolNames = null,
1462
1460
  cacheTrend = null
1463
1461
  }) {
1464
1462
  const statusCategory = getStatusCategory(log.responseStatus);
1465
1463
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
1466
- const messageCount = parsedRequest !== null ? parsedRequest.messages.length : null;
1467
- const toolCount = parsedRequest !== null && parsedRequest.tools !== void 0 && parsedRequest.tools.length > 0 ? parsedRequest.tools.length : null;
1468
1464
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1469
1465
  "div",
1470
1466
  {
@@ -1491,20 +1487,14 @@ const LogEntryHeader = reactExports.memo(function({
1491
1487
  /* @__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" }) }) }),
1492
1488
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.model })
1493
1489
  ] }) }),
1494
- !suppressApiFormatBadge && /* @__PURE__ */ jsxRuntimeExports.jsx(
1490
+ statusCategory !== "success" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: statusCategory === "server_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1495
1491
  Badge,
1496
1492
  {
1497
- variant: "outline",
1498
- className: cn(
1499
- "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
1500
- log.apiFormat === "openai" && "border-blue-500/40 text-blue-400",
1501
- log.apiFormat === "anthropic" && "border-orange-500/40 text-orange-400",
1502
- log.apiFormat === "unknown" && "border-muted text-muted-foreground"
1503
- ),
1504
- children: log.apiFormat === "anthropic" ? "Anthropic" : log.apiFormat === "openai" ? "OpenAI" : "Unknown"
1493
+ variant: "destructive",
1494
+ className: "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
1495
+ children: log.responseStatus
1505
1496
  }
1506
- ),
1507
- statusCategory === "server_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "destructive", className: "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums", children: log.responseStatus }) : statusCategory === "pending" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1497
+ ) : statusCategory === "pending" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1508
1498
  Badge,
1509
1499
  {
1510
1500
  variant: "outline",
@@ -1524,8 +1514,8 @@ const LogEntryHeader = reactExports.memo(function({
1524
1514
  ),
1525
1515
  children: log.responseStatus
1526
1516
  }
1527
- ),
1528
- log.elapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1517
+ ) }),
1518
+ log.elapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden xl:flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1529
1519
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1530
1520
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(log.elapsedMs) })
1531
1521
  ] }),
@@ -1583,10 +1573,20 @@ const LogEntryHeader = reactExports.memo(function({
1583
1573
  ] }) }),
1584
1574
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of tools defined in the request" })
1585
1575
  ] }) }),
1576
+ responseToolNames !== null && responseToolNames.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1577
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-amber-400/80 text-xs shrink-0", children: [
1578
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1579
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[160px]", children: responseToolNames.join(", ") })
1580
+ ] }) }),
1581
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(TooltipContent, { children: [
1582
+ "Tools called by model: ",
1583
+ responseToolNames.join(", ")
1584
+ ] })
1585
+ ] }) }),
1586
1586
  log.origin !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1587
1587
  "span",
1588
1588
  {
1589
- className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
1589
+ className: "hidden xl:flex items-center gap-1 text-muted-foreground text-xs shrink-0",
1590
1590
  title: `Origin: ${log.origin}`,
1591
1591
  children: [
1592
1592
  /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { className: "size-3" }),
@@ -1594,19 +1594,8 @@ const LogEntryHeader = reactExports.memo(function({
1594
1594
  ]
1595
1595
  }
1596
1596
  ),
1597
- log.userAgent !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1598
- "span",
1599
- {
1600
- className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
1601
- title: `User-Agent: ${log.userAgent}`,
1602
- children: [
1603
- /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "size-3" }),
1604
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[150px]", title: log.userAgent, children: log.userAgent })
1605
- ]
1606
- }
1607
- ),
1608
1597
  (log.clientPid !== null || log.clientProjectFolder !== null) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1609
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-purple-400/80 text-xs shrink-0", children: [
1598
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden xl:flex items-center gap-1 text-purple-400/80 text-xs shrink-0", children: [
1610
1599
  /* @__PURE__ */ jsxRuntimeExports.jsx(FileTerminal, { className: "size-3" }),
1611
1600
  log.clientProjectFolder !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: log.clientProjectFolder }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
1612
1601
  "PID ",
@@ -2479,7 +2468,6 @@ function DiffToggleButton({
2479
2468
  const LogEntry = reactExports.memo(function({
2480
2469
  log,
2481
2470
  viewMode = "simple",
2482
- suppressApiFormatBadge = false,
2483
2471
  strip,
2484
2472
  cacheTrend = null,
2485
2473
  onCompareWithPrevious
@@ -2491,7 +2479,60 @@ const LogEntry = reactExports.memo(function({
2491
2479
  const [replayOpen, setReplayOpen] = reactExports.useState(false);
2492
2480
  const [headersDiff, setHeadersDiff] = reactExports.useState(false);
2493
2481
  const [requestDiff, setRequestDiff] = reactExports.useState(false);
2494
- const parsedRequest = reactExports.useMemo(() => parseRequest(log.rawRequestBody), [log.rawRequestBody]);
2482
+ const messageCount = reactExports.useMemo(() => {
2483
+ if (log.rawRequestBody === null) return null;
2484
+ if (log.apiFormat === "anthropic") {
2485
+ const parsed = parseRequest(log.rawRequestBody);
2486
+ if (parsed !== null) return parsed.messages.length;
2487
+ } else if (log.apiFormat === "openai") {
2488
+ try {
2489
+ const result = OpenAIRequestSchema.safeParse(JSON.parse(log.rawRequestBody));
2490
+ if (result.success) return result.data.messages.length;
2491
+ } catch {
2492
+ }
2493
+ }
2494
+ return null;
2495
+ }, [log.rawRequestBody, log.apiFormat]);
2496
+ const toolCount = reactExports.useMemo(() => {
2497
+ if (log.rawRequestBody === null) return null;
2498
+ if (log.apiFormat === "anthropic") {
2499
+ const parsed = parseRequest(log.rawRequestBody);
2500
+ if (parsed !== null && parsed.tools !== void 0 && parsed.tools.length > 0) {
2501
+ return parsed.tools.length;
2502
+ }
2503
+ } else if (log.apiFormat === "openai") {
2504
+ try {
2505
+ const result = OpenAIRequestSchema.safeParse(JSON.parse(log.rawRequestBody));
2506
+ if (result.success && result.data.tools !== void 0 && result.data.tools.length > 0) {
2507
+ return result.data.tools.length;
2508
+ }
2509
+ } catch {
2510
+ }
2511
+ }
2512
+ return null;
2513
+ }, [log.rawRequestBody, log.apiFormat]);
2514
+ const responseToolNames = reactExports.useMemo(() => {
2515
+ if (log.responseText === null) return null;
2516
+ if (log.apiFormat === "openai") {
2517
+ const parsed = parseOpenAIResponse(log.responseText);
2518
+ if (parsed !== null) {
2519
+ const toolCalls = parsed.choices[0]?.message?.tool_calls;
2520
+ if (toolCalls !== void 0 && toolCalls !== null && toolCalls.length > 0) {
2521
+ return toolCalls.map((tc) => tc.function?.name ?? "?").filter((n) => n !== "");
2522
+ }
2523
+ }
2524
+ } else if (log.apiFormat === "anthropic") {
2525
+ try {
2526
+ const result = InspectorResponseSchema.safeParse(JSON.parse(log.responseText));
2527
+ if (result.success) {
2528
+ const names = result.data.content.filter((c) => c.type === "tool_use").map((c) => c.name);
2529
+ if (names.length > 0) return names;
2530
+ }
2531
+ } catch {
2532
+ }
2533
+ }
2534
+ return null;
2535
+ }, [log.responseText, log.apiFormat]);
2495
2536
  const strippedRequestBody = reactExports.useMemo(() => {
2496
2537
  if (!strip || log.apiFormat !== "anthropic" || log.rawRequestBody === null) {
2497
2538
  return null;
@@ -2537,10 +2578,11 @@ const LogEntry = reactExports.memo(function({
2537
2578
  LogEntryHeader,
2538
2579
  {
2539
2580
  log,
2540
- parsedRequest,
2581
+ messageCount,
2582
+ toolCount,
2541
2583
  expanded,
2542
2584
  onToggle: () => setExpanded(!expanded),
2543
- suppressApiFormatBadge,
2585
+ responseToolNames,
2544
2586
  cacheTrend
2545
2587
  }
2546
2588
  ),
@@ -2727,72 +2769,470 @@ const LogEntry = reactExports.memo(function({
2727
2769
  /* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
2728
2770
  ] });
2729
2771
  });
2772
+ function SvgShell({
2773
+ className,
2774
+ style,
2775
+ d,
2776
+ eyeStalks,
2777
+ eyes,
2778
+ legs,
2779
+ extras
2780
+ }) {
2781
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2782
+ "svg",
2783
+ {
2784
+ viewBox: "0 0 24 24",
2785
+ fill: "none",
2786
+ stroke: "currentColor",
2787
+ strokeWidth: "1.5",
2788
+ strokeLinecap: "round",
2789
+ strokeLinejoin: "round",
2790
+ "aria-hidden": "true",
2791
+ style,
2792
+ className: cn("inline-block size-5", className),
2793
+ children: [
2794
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d }),
2795
+ eyeStalks,
2796
+ eyes,
2797
+ legs,
2798
+ extras
2799
+ ]
2800
+ }
2801
+ );
2802
+ }
2803
+ const StdLegs = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2804
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6.5", y1: "16", x2: "4.5", y2: "19.5" }),
2805
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9", y1: "17.5", x2: "8", y2: "20.5" }),
2806
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "15", y1: "17.5", x2: "16", y2: "20.5" }),
2807
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "17.5", y1: "16", x2: "19.5", y2: "19.5" })
2808
+ ] });
2809
+ const ShortLegs = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2810
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "7", y1: "16", x2: "5.5", y2: "18.5" }),
2811
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9.5", y1: "17", x2: "8.5", y2: "19.5" }),
2812
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14.5", y1: "17", x2: "15.5", y2: "19.5" }),
2813
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "17", y1: "16", x2: "18.5", y2: "18.5" })
2814
+ ] });
2815
+ function Crab1({ className }) {
2816
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2817
+ SvgShell,
2818
+ {
2819
+ className,
2820
+ d: "M5 13 C5 9 8 7 12 7 C16 7 19 9 19 13 C19 16 16 18 12 18 C8 18 5 16 5 13 Z",
2821
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2822
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "7", x2: "9.5", y2: "5" }),
2823
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "7", x2: "14.5", y2: "5" })
2824
+ ] }),
2825
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2826
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9.5", cy: "4.5", r: "0.9", fill: "currentColor", stroke: "none" }),
2827
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "14.5", cy: "4.5", r: "0.9", fill: "currentColor", stroke: "none" })
2828
+ ] }),
2829
+ legs: StdLegs,
2830
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2831
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9.5 1.5 10 2 12.5 C2.5 14 4 13.5 5 12.5" }),
2832
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9.5 22.5 10 22 12.5 C21.5 14 20 13.5 19 12.5" })
2833
+ ] })
2834
+ }
2835
+ );
2836
+ }
2837
+ function Crab2({ className }) {
2838
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2839
+ SvgShell,
2840
+ {
2841
+ className,
2842
+ d: "M4 13 C4 7 7.5 5.5 12 5.5 C16.5 5.5 20 7 20 13 C20 17.5 16.5 19 12 19 C7.5 19 4 17.5 4 13 Z",
2843
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2844
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9.5", y1: "6.5", x2: "9.5", y2: "4.5" }),
2845
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14.5", y1: "6.5", x2: "14.5", y2: "4.5" })
2846
+ ] }),
2847
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2848
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9.5", cy: "4", r: "1.1", fill: "currentColor", stroke: "none" }),
2849
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "14.5", cy: "4", r: "1.1", fill: "currentColor", stroke: "none" })
2850
+ ] }),
2851
+ legs: ShortLegs,
2852
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2853
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C4 9.5 2.5 10 3 12 C3.5 13.5 4.5 13 5 12" }),
2854
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20 9.5 21.5 10 21 12 C20.5 13.5 19.5 13 19 12" })
2855
+ ] })
2856
+ }
2857
+ );
2858
+ }
2859
+ function Crab3({ className }) {
2860
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2861
+ SvgShell,
2862
+ {
2863
+ className,
2864
+ d: "M3 13.5 C3 8 7 6 12 6 C17 6 21 8 21 13.5 C21 17 17 19 12 19 C7 19 3 17 3 13.5 Z",
2865
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2866
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "7", y1: "7", x2: "6.5", y2: "4.5" }),
2867
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "17", y1: "7", x2: "17.5", y2: "4.5" })
2868
+ ] }),
2869
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2870
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6.5", cy: "4", r: "0.8", fill: "currentColor", stroke: "none" }),
2871
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "17.5", cy: "4", r: "0.8", fill: "currentColor", stroke: "none" })
2872
+ ] }),
2873
+ legs: StdLegs,
2874
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2875
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 12 C2.5 10.5 1 11 1.5 13 C2 14.5 3.5 14 4.5 13" }),
2876
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M20 12 C21.5 10.5 23 11 22.5 13 C22 14.5 20.5 14 19.5 13" })
2877
+ ] })
2878
+ }
2879
+ );
2880
+ }
2881
+ function Crab4({ className }) {
2882
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2883
+ SvgShell,
2884
+ {
2885
+ className,
2886
+ d: "M6 14 C6 8 9 5.5 12 5.5 C15 5.5 18 8 18 14 C18 18 15 20 12 20 C9 20 6 18 6 14 Z",
2887
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2888
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "6.5", x2: "10", y2: "3.5" }),
2889
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "6.5", x2: "14", y2: "3.5" })
2890
+ ] }),
2891
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2892
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "10", cy: "3", r: "0.7", fill: "currentColor", stroke: "none" }),
2893
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "14", cy: "3", r: "0.7", fill: "currentColor", stroke: "none" })
2894
+ ] }),
2895
+ legs: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2896
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "7", y1: "17", x2: "5", y2: "20.5" }),
2897
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9.5", y1: "18.5", x2: "8.5", y2: "21.5" }),
2898
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14.5", y1: "18.5", x2: "15.5", y2: "21.5" }),
2899
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "17", y1: "17", x2: "19", y2: "20.5" })
2900
+ ] }),
2901
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2902
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6.5 11 C5 8.5 3 9 3.5 11.5 C4 13.5 5 13 6 12" }),
2903
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M17.5 11 C19 8.5 21 9 20.5 11.5 C20 13.5 19 13 18 12" })
2904
+ ] })
2905
+ }
2906
+ );
2907
+ }
2908
+ function Crab5({ className }) {
2909
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2910
+ SvgShell,
2911
+ {
2912
+ className,
2913
+ d: "M5 13 C5 9 8 7 12 7 L13 4 L14 7 C16 7 19 9 19 13 C19 16 16 18 13 18 L12 21 L11 18 C8 18 5 16 5 13 Z",
2914
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2915
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "7", x2: "9.5", y2: "5" }),
2916
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "7", x2: "14.5", y2: "5" })
2917
+ ] }),
2918
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2919
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9.5", cy: "4.5", r: "0.8", fill: "currentColor", stroke: "none" }),
2920
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "14.5", cy: "4.5", r: "0.8", fill: "currentColor", stroke: "none" })
2921
+ ] }),
2922
+ legs: StdLegs,
2923
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2924
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9.5 1.5 10 2 12.5 C2.5 14 4 13.5 5 12.5" }),
2925
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9.5 22.5 10 22 12.5 C21.5 14 20 13.5 19 12.5" })
2926
+ ] })
2927
+ }
2928
+ );
2929
+ }
2930
+ function Crab6({ className }) {
2931
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2932
+ SvgShell,
2933
+ {
2934
+ className,
2935
+ d: "M5 13 C5 9 8 7 12 7 C16 7 19 9 19 13 C19 16 16 18 12 18 C8 18 5 16 5 13 Z",
2936
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2937
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "7", x2: "9.5", y2: "5" }),
2938
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "7", x2: "14.5", y2: "5" })
2939
+ ] }),
2940
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2941
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9.5", cy: "4.5", r: "0.9", fill: "currentColor", stroke: "none" }),
2942
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "13.5", y1: "4", x2: "15.5", y2: "5" })
2943
+ ] }),
2944
+ legs: StdLegs,
2945
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2946
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9.5 1.5 10 2 12.5 C2.5 14 4 13.5 5 12.5" }),
2947
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9.5 22.5 10 22 12.5 C21.5 14 20 13.5 19 12.5" })
2948
+ ] })
2949
+ }
2950
+ );
2951
+ }
2952
+ function Crab7({ className }) {
2953
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2954
+ SvgShell,
2955
+ {
2956
+ className,
2957
+ d: "M5.5 13.5 C5.5 10 8.5 8 12 8 C15.5 8 18.5 10 18.5 13.5 C18.5 16 15.5 17.5 12 17.5 C8.5 17.5 5.5 16 5.5 13.5 Z",
2958
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2959
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "8", x2: "10", y2: "6" }),
2960
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "8", x2: "14", y2: "6" })
2961
+ ] }),
2962
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2963
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9", y1: "6", x2: "11", y2: "6.5" }),
2964
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "13", y1: "6", x2: "15", y2: "6.5" })
2965
+ ] }),
2966
+ legs: ShortLegs,
2967
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2968
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 11.5 C4.5 10.5 3 11 3.5 12.5 C4 13.5 5 13 5.5 12.5" }),
2969
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M18 11.5 C19.5 10.5 21 11 20.5 12.5 C20 13.5 19 13 18.5 12.5" })
2970
+ ] })
2971
+ }
2972
+ );
2973
+ }
2974
+ function Crab8({ className }) {
2975
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2976
+ SvgShell,
2977
+ {
2978
+ className,
2979
+ d: "M5 13 C5 9 8 7 12 7 C16 7 19 9 19 13 C19 16 16 18 12 18 C8 18 5 16 5 13 Z",
2980
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2981
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "7", x2: "9.5", y2: "5" }),
2982
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "7", x2: "14.5", y2: "5" })
2983
+ ] }),
2984
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2985
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9.5", cy: "4.5", r: "0.7", fill: "currentColor", stroke: "none" }),
2986
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "14.5", cy: "4.5", r: "0.7", fill: "currentColor", stroke: "none" })
2987
+ ] }),
2988
+ legs: StdLegs,
2989
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2990
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9.5 1.5 10 2 12.5 C2.5 14 4 13.5 5 12.5" }),
2991
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9.5 22.5 10 22 12.5 C21.5 14 20 13.5 19 12.5" }),
2992
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "8.5", y1: "4", x2: "10.5", y2: "4.5" }),
2993
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "15.5", y1: "4", x2: "13.5", y2: "4.5" }),
2994
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9 15.5 C10 14.5 14 14.5 15 15.5" })
2995
+ ] })
2996
+ }
2997
+ );
2998
+ }
2999
+ function Crab9({ className }) {
3000
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3001
+ SvgShell,
3002
+ {
3003
+ className,
3004
+ d: "M5 13 C5 9 8 7 12 7 C16 7 19 9 19 13 C19 16 16 18 12 18 C8 18 5 16 5 13 Z",
3005
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3006
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "7", x2: "9.5", y2: "5" }),
3007
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "7", x2: "14.5", y2: "5" })
3008
+ ] }),
3009
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3010
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9.5", cy: "4.5", r: "1.2", fill: "none", stroke: "currentColor" }),
3011
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "14.5", cy: "4.5", r: "1.2", fill: "none", stroke: "currentColor" })
3012
+ ] }),
3013
+ legs: StdLegs,
3014
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3015
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9.5 1.5 10 2 12.5 C2.5 14 4 13.5 5 12.5" }),
3016
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9.5 22.5 10 22 12.5 C21.5 14 20 13.5 19 12.5" }),
3017
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "12", cy: "15", rx: "2", ry: "1.5", fill: "currentColor", stroke: "none" })
3018
+ ] })
3019
+ }
3020
+ );
3021
+ }
3022
+ function Crab10({ className }) {
3023
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3024
+ SvgShell,
3025
+ {
3026
+ className,
3027
+ d: "M4.5 13 C4.5 8 7.5 6 12 6 C16.5 6 19.5 8 19.5 13 C19.5 17 16.5 18.5 12 18.5 C7.5 18.5 4.5 17 4.5 13 Z",
3028
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3029
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "9.5", y1: "7", x2: "9", y2: "5" }),
3030
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14.5", y1: "7", x2: "15", y2: "5" })
3031
+ ] }),
3032
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3033
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9", cy: "4.5", r: "1", fill: "currentColor", stroke: "none" }),
3034
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "15", cy: "4.5", r: "1", fill: "currentColor", stroke: "none" })
3035
+ ] }),
3036
+ legs: StdLegs,
3037
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3038
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9 2 9.5 2.5 12 C3 14 4 13 5 12" }),
3039
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9 22 9.5 21.5 12 C21 14 20 13 19 12" }),
3040
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9 14.5 C10 16 14 16 15 14.5" }),
3041
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "7", cy: "13", r: "1", fill: "currentColor", stroke: "none", opacity: "0.3" }),
3042
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "17", cy: "13", r: "1", fill: "currentColor", stroke: "none", opacity: "0.3" })
3043
+ ] })
3044
+ }
3045
+ );
3046
+ }
3047
+ function Crab11({ className }) {
3048
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3049
+ SvgShell,
3050
+ {
3051
+ className,
3052
+ d: "M5 13 C5 9 8 7 12 7 C16 7 19 9 19 13 C19 16 16 18 12 18 C8 18 5 16 5 13 Z",
3053
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3054
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "7", x2: "9.5", y2: "5.5" }),
3055
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "7", x2: "14.5", y2: "5.5" })
3056
+ ] }),
3057
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3058
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "7.5", y1: "5.5", x2: "16.5", y2: "5.5" }),
3059
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "5", width: "4", height: "2", rx: "0.5", fill: "currentColor", stroke: "none" }),
3060
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "13", y: "5", width: "4", height: "2", rx: "0.5", fill: "currentColor", stroke: "none" })
3061
+ ] }),
3062
+ legs: StdLegs,
3063
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3064
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 11 C3.5 9.5 1.5 10 2 12.5 C2.5 14 4 13.5 5 12.5" }),
3065
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 11 C20.5 9.5 22.5 10 22 12.5 C21.5 14 20 13.5 19 12.5" }),
3066
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9.5 15 C10.5 16 13.5 16 14.5 15" })
3067
+ ] })
3068
+ }
3069
+ );
3070
+ }
3071
+ function Crab12({ className }) {
3072
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3073
+ SvgShell,
3074
+ {
3075
+ className,
3076
+ d: "M7 13.5 C7 10.5 9.5 9 12 9 C14.5 9 17 10.5 17 13.5 C17 16 14.5 17.5 12 17.5 C9.5 17.5 7 16 7 13.5 Z",
3077
+ eyeStalks: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3078
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10.5", y1: "9.5", x2: "10.5", y2: "8" }),
3079
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "13.5", y1: "9.5", x2: "13.5", y2: "8" })
3080
+ ] }),
3081
+ eyes: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3082
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "10.5", cy: "7.5", r: "0.8", fill: "currentColor", stroke: "none" }),
3083
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "13.5", cy: "7.5", r: "0.8", fill: "currentColor", stroke: "none" })
3084
+ ] }),
3085
+ legs: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3086
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "8", y1: "16", x2: "6.5", y2: "18" }),
3087
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "10", y1: "17", x2: "9.5", y2: "19" }),
3088
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "17", x2: "14.5", y2: "19" }),
3089
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "16", y1: "16", x2: "17.5", y2: "18" })
3090
+ ] }),
3091
+ extras: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3092
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M7.5 12 C6.5 11 5.5 11.5 6 13 C6.5 14 7 13.5 7.5 13" }),
3093
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M16.5 12 C17.5 11 18.5 11.5 18 13 C17.5 14 17 13.5 16.5 13" })
3094
+ ] })
3095
+ }
3096
+ );
3097
+ }
3098
+ const crabVariants = [
3099
+ Crab1,
3100
+ Crab2,
3101
+ Crab3,
3102
+ Crab4,
3103
+ Crab5,
3104
+ Crab6,
3105
+ Crab7,
3106
+ Crab8,
3107
+ Crab9,
3108
+ Crab10,
3109
+ Crab11,
3110
+ Crab12
3111
+ ];
3112
+ function getCrabVariant(index) {
3113
+ return crabVariants[Math.abs(index) % crabVariants.length] ?? Crab1;
3114
+ }
2730
3115
  function ThreadConnector({
2731
3116
  stopReason,
2732
3117
  isPending,
2733
3118
  isFirst,
2734
- isLast
3119
+ isTurnStart,
3120
+ crabIndex = 0,
3121
+ collapsible = false,
3122
+ collapsed = false,
3123
+ onToggle
2735
3124
  }) {
2736
3125
  const isBoundary = stopReason === "end_turn" || stopReason === "stop";
2737
- const isToolUse = stopReason === "tool_use";
2738
- cn(
2739
- "w-0.5 bg-muted-foreground/30",
2740
- isPending && "border-dashed bg-transparent border-l-2 border-muted-foreground/20"
2741
- );
2742
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch h-full w-6 shrink-0 relative", children: [
2743
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute left-1/2 -translate-x-1/2 w-0.5 top-0 flex flex-col items-center", children: [
2744
- !isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx(
2745
- "div",
2746
- {
2747
- className: cn("w-0.5 grow", "bg-muted-foreground/30", "h-4"),
2748
- style: { minHeight: "0.5rem" }
2749
- }
2750
- ),
2751
- isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-4" })
2752
- ] }),
2753
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-1/2 -translate-x-1/2 top-4 flex items-center justify-center z-10", children: isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2754
- "div",
3126
+ const isRunning = isPending && !isBoundary;
3127
+ const Crab = reactExports.useMemo(() => getCrabVariant(crabIndex), [crabIndex]);
3128
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center w-6 shrink-0", children: [
3129
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center h-2.5", children: !isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 bg-muted-foreground/30" }) }),
3130
+ collapsible && /* @__PURE__ */ jsxRuntimeExports.jsx(
3131
+ "button",
2755
3132
  {
2756
- className: cn(
2757
- "size-2.5 rounded-full border-2",
2758
- "bg-background border-amber-400",
2759
- "shadow-[0_0_6px_rgba(251,191,36,0.4)]"
2760
- ),
2761
- title: stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"
3133
+ type: "button",
3134
+ onClick: onToggle,
3135
+ title: collapsed ? "Expand turn" : "Collapse turn",
3136
+ className: "cursor-pointer flex justify-center py-0.5",
3137
+ children: collapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground hover:text-foreground transition-colors" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground hover:text-foreground transition-colors" })
2762
3138
  }
2763
- ) : isToolUse ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2764
- "div",
3139
+ ),
3140
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-0.5", children: isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx(
3141
+ "span",
2765
3142
  {
2766
- className: "size-2 rounded-full bg-muted-foreground/25",
2767
- title: "Tool Use turn continues"
3143
+ title: stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)",
3144
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3145
+ Crab,
3146
+ {
3147
+ className: cn(
3148
+ "size-3.5 text-amber-400",
3149
+ "animate-crab-settle",
3150
+ "drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]"
3151
+ )
3152
+ }
3153
+ )
2768
3154
  }
2769
- ) : isPending ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2770
- "div",
3155
+ ) : isTurnStart ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: "Start of turn", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3156
+ Crab,
2771
3157
  {
2772
- className: "size-2.5 rounded-full border-2 border-dashed border-muted-foreground/30 animate-pulse",
2773
- title: "Response pending"
3158
+ className: cn(
3159
+ "size-3.5 text-emerald-400",
3160
+ "animate-crab-appear",
3161
+ "drop-shadow-[0_0_4px_rgba(52,211,153,0.5)]"
3162
+ )
2774
3163
  }
2775
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "size-1.5 rounded-full bg-muted-foreground/30" }) }),
2776
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
3164
+ ) }) : isRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: "Processing…", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Crab, { className: cn("size-3.5 text-amber-300/80", "animate-crab-crawl") }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Crab, { className: "size-3.5 text-muted-foreground/40" }) }),
3165
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex justify-center min-h-1", children: isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 bg-muted-foreground/10 h-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2777
3166
  "div",
2778
3167
  {
2779
- className: "absolute left-1/2 -translate-x-1/2 w-0.5 top-4 flex flex-col items-center",
2780
- style: { bottom: 0 },
2781
- children: [
2782
- !isBoundary && /* @__PURE__ */ jsxRuntimeExports.jsx(
2783
- "div",
3168
+ className: cn(
3169
+ "w-0.5 h-full",
3170
+ isPending ? "border-dashed bg-transparent border-l-2 border-muted-foreground/20" : "bg-muted-foreground/30"
3171
+ )
3172
+ }
3173
+ ) })
3174
+ ] });
3175
+ }
3176
+ function TurnGroup({
3177
+ logs,
3178
+ viewMode,
3179
+ strip,
3180
+ cacheTrends,
3181
+ onCompareWithPrevious,
3182
+ turnIndex = 0
3183
+ }) {
3184
+ const stopReasons = reactExports.useMemo(() => logs.map((l) => extractStopReason(l)), [logs]);
3185
+ const lastIdx = logs.length - 1;
3186
+ const lastStop = stopReasons[lastIdx] ?? null;
3187
+ const isComplete = lastStop !== null ? isTurnBoundary(lastStop) : false;
3188
+ const isPending = logs[lastIdx]?.responseStatus === null;
3189
+ const collapsible = isComplete && !isPending;
3190
+ const [collapsed, setCollapsed] = reactExports.useState(false);
3191
+ const toggleCollapse = reactExports.useCallback(() => {
3192
+ if (collapsible) setCollapsed((prev) => !prev);
3193
+ }, [collapsible]);
3194
+ const visibleLogs = reactExports.useMemo(() => {
3195
+ if (!collapsed) return logs;
3196
+ const last = logs[lastIdx];
3197
+ return last !== void 0 ? [last] : [];
3198
+ }, [logs, collapsed, lastIdx]);
3199
+ const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
3200
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3201
+ "div",
3202
+ {
3203
+ className: cn("border rounded-lg", isPending ? "border-amber-500/10" : "border-transparent"),
3204
+ children: visibleLogs.map((log, visibleIdx) => {
3205
+ const originalIdx = collapsed ? lastIdx : visibleIdx;
3206
+ const reason = stopReasons[originalIdx] ?? null;
3207
+ const isBoundary = reason === "end_turn" || reason === "stop";
3208
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
3209
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3210
+ ThreadConnector,
2784
3211
  {
2785
- className: cn(
2786
- "w-0.5 flex-1",
2787
- isPending ? "border-dashed bg-transparent border-l-2 border-muted-foreground/20" : "bg-muted-foreground/30"
2788
- )
3212
+ stopReason: reason,
3213
+ isPending: log.responseStatus === null,
3214
+ isFirst: originalIdx === 0,
3215
+ isTurnStart: originalIdx === 0,
3216
+ crabIndex: log.id % 12,
3217
+ collapsible: collapsible && isBoundary && logs.length > 1,
3218
+ collapsed,
3219
+ onToggle: toggleCollapse
2789
3220
  }
2790
3221
  ),
2791
- isBoundary && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 h-4 bg-muted-foreground/10" })
2792
- ]
2793
- }
2794
- )
2795
- ] });
3222
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("flex-1 min-w-0 mb-2 rounded-lg", bgClass), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3223
+ LogEntry,
3224
+ {
3225
+ log,
3226
+ viewMode,
3227
+ strip,
3228
+ cacheTrend: cacheTrends?.get(log.id) ?? null,
3229
+ onCompareWithPrevious: () => onCompareWithPrevious(log)
3230
+ }
3231
+ ) })
3232
+ ] }, log.id);
3233
+ })
3234
+ }
3235
+ );
2796
3236
  }
2797
3237
  function computeStats(logs) {
2798
3238
  let totalInput = 0;
@@ -2809,22 +3249,52 @@ const ConversationGroup = reactExports.memo(function({
2809
3249
  strip,
2810
3250
  cacheTrends,
2811
3251
  onCompareWithPrevious,
2812
- defaultGroupViewMode = "thread"
3252
+ standalone = false
2813
3253
  }) {
2814
- const [expanded, setExpanded] = reactExports.useState(false);
2815
- const [groupViewMode, setGroupViewMode] = reactExports.useState(defaultGroupViewMode);
2816
- reactExports.useEffect(() => {
2817
- setGroupViewMode(defaultGroupViewMode);
2818
- }, [defaultGroupViewMode]);
3254
+ const [expanded, setExpanded] = reactExports.useState(standalone);
2819
3255
  const stats = computeStats(group.logs);
2820
3256
  const startTime = group.logs[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2821
3257
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2822
3258
  const mixed = hasMixedApiFormat(group.logs);
2823
3259
  const isLoading = group.logs.some((log) => log.responseStatus === null);
2824
3260
  const stopReasons = reactExports.useMemo(() => group.logs.map((log) => extractStopReason(log)), [group.logs]);
3261
+ const turnIndices = reactExports.useMemo(() => {
3262
+ const indices = [];
3263
+ let turn = 0;
3264
+ for (let i = 0; i < stopReasons.length; i++) {
3265
+ if (i > 0 && (stopReasons[i - 1] === "end_turn" || stopReasons[i - 1] === "stop")) {
3266
+ turn++;
3267
+ }
3268
+ indices.push(turn);
3269
+ }
3270
+ return indices;
3271
+ }, [stopReasons]);
3272
+ const turnGroups = reactExports.useMemo(() => {
3273
+ const groups = [];
3274
+ let currentGroup = [];
3275
+ let currentTurn = 0;
3276
+ for (let i = 0; i < group.logs.length; i++) {
3277
+ const turnIdx = turnIndices[i] ?? 0;
3278
+ const log = group.logs[i];
3279
+ if (!log) continue;
3280
+ if (turnIdx !== currentTurn) {
3281
+ if (currentGroup.length > 0) {
3282
+ groups.push({ logs: currentGroup, turnIndex: currentTurn });
3283
+ }
3284
+ currentGroup = [log];
3285
+ currentTurn = turnIdx;
3286
+ } else {
3287
+ currentGroup.push(log);
3288
+ }
3289
+ }
3290
+ if (currentGroup.length > 0) {
3291
+ groups.push({ logs: currentGroup, turnIndex: currentTurn });
3292
+ }
3293
+ return groups;
3294
+ }, [group.logs, turnIndices]);
2825
3295
  const displayId = group.conversationId.startsWith("PID:") || group.conversationId.includes("|") ? group.conversationId : group.conversationId.length > 24 ? group.conversationId.slice(0, 12) + "…" + group.conversationId.slice(-12) : group.conversationId;
2826
3296
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
2827
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3297
+ !standalone && /* @__PURE__ */ jsxRuntimeExports.jsx(
2828
3298
  ConversationHeader,
2829
3299
  {
2830
3300
  conversationId: displayId,
@@ -2838,44 +3308,21 @@ const ConversationGroup = reactExports.memo(function({
2838
3308
  onToggle: () => setExpanded(!expanded),
2839
3309
  hideApiFormat: mixed,
2840
3310
  isLoading,
2841
- viewMode: groupViewMode,
2842
- onToggleViewMode: () => setGroupViewMode((prev) => prev === "thread" ? "flat" : "thread")
3311
+ userAgent: group.logs[0]?.userAgent ?? null
2843
3312
  }
2844
3313
  ),
2845
- expanded && groupViewMode === "flat" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 border-l-2 border-muted ml-3", children: group.logs.map((log) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2846
- LogEntry,
3314
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3315
+ TurnGroup,
2847
3316
  {
2848
- log,
3317
+ logs: tg.logs,
2849
3318
  viewMode,
2850
- suppressApiFormatBadge: !mixed,
2851
3319
  strip,
2852
- cacheTrend: cacheTrends?.get(log.id) ?? null,
2853
- onCompareWithPrevious: () => onCompareWithPrevious(log)
3320
+ cacheTrends,
3321
+ onCompareWithPrevious,
3322
+ turnIndex: tg.turnIndex
2854
3323
  },
2855
- log.id
2856
- )) }),
2857
- expanded && groupViewMode === "thread" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-3", children: group.logs.map((log, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
2858
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2859
- ThreadConnector,
2860
- {
2861
- stopReason: stopReasons[idx] ?? null,
2862
- isPending: log.responseStatus === null,
2863
- isFirst: idx === 0,
2864
- isLast: idx === group.logs.length - 1
2865
- }
2866
- ),
2867
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2868
- LogEntry,
2869
- {
2870
- log,
2871
- viewMode,
2872
- suppressApiFormatBadge: !mixed,
2873
- strip,
2874
- cacheTrend: cacheTrends?.get(log.id) ?? null,
2875
- onCompareWithPrevious: () => onCompareWithPrevious(log)
2876
- }
2877
- ) })
2878
- ] }, log.id)) })
3324
+ tg.turnIndex
3325
+ )) })
2879
3326
  ] });
2880
3327
  });
2881
3328
  function CrabLogo({ className }) {
@@ -2923,7 +3370,7 @@ function SelectTrigger({
2923
3370
  ...props
2924
3371
  }) {
2925
3372
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2926
- Trigger$1,
3373
+ Trigger,
2927
3374
  {
2928
3375
  "data-slot": "select-trigger",
2929
3376
  "data-size": size,
@@ -2946,8 +3393,8 @@ function SelectContent({
2946
3393
  align = "center",
2947
3394
  ...props
2948
3395
  }) {
2949
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2950
- Content2$1,
3396
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3397
+ Content2,
2951
3398
  {
2952
3399
  "data-slot": "select-content",
2953
3400
  className: cn(
@@ -5674,8 +6121,6 @@ function ProxyViewer({
5674
6121
  strip
5675
6122
  }) {
5676
6123
  const { totalIn, totalOut } = computeTokenSummary(logs);
5677
- const [groupedView, setGroupedView] = reactExports.useState(true);
5678
- const [groupViewMode, setGroupViewMode] = reactExports.useState("thread");
5679
6124
  const [exporting, setExporting] = reactExports.useState(false);
5680
6125
  const [comparePair, setComparePair] = reactExports.useState(null);
5681
6126
  const handleExport = reactExports.useCallback(async () => {
@@ -5705,19 +6150,40 @@ function ProxyViewer({
5705
6150
  );
5706
6151
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
5707
6152
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
5708
- const stopReasons = reactExports.useMemo(() => logs.map((log) => extractStopReason(log)), [logs]);
5709
- const renderGroups = logs.length > 0 && groupedView && groups.length > 1;
5710
6153
  const rowVirtualizer = useVirtualizer({
5711
- count: renderGroups ? groups.length : logs.length,
6154
+ count: groups.length,
5712
6155
  getScrollElement: () => parentRef.current,
5713
6156
  estimateSize: () => 150,
5714
6157
  measureElement: typeof window !== "undefined" ? (element) => element.getBoundingClientRect().height : void 0,
5715
6158
  overscan: 5
5716
6159
  });
5717
6160
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1200px] mx-auto flex flex-col h-screen", style: { maxHeight: "100vh" }, children: [
5718
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-4 px-6 pt-6", children: [
5719
- /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-lg font-bold flex-1 flex items-center gap-2", children: [
5720
- /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 self-center" }),
6161
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end px-6 pt-6 pb-3 relative", children: [
6162
+ /* @__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: [
6163
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-end gap-1 group cursor-default", "aria-hidden": "true", children: [
6164
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" }),
6165
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-end gap-0.5", children: crabVariants.map((Crab, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6166
+ Crab,
6167
+ {
6168
+ className: `size-5 ${[
6169
+ "text-amber-500",
6170
+ "text-rose-500",
6171
+ "text-sky-500",
6172
+ "text-emerald-500",
6173
+ "text-violet-500",
6174
+ "text-orange-500",
6175
+ "text-cyan-500",
6176
+ "text-pink-500",
6177
+ "text-lime-500",
6178
+ "text-blue-500",
6179
+ "text-yellow-500",
6180
+ "text-fuchsia-500"
6181
+ ][i]} transition-all duration-300 ease-out group-hover:scale-125 group-hover:-translate-y-1`,
6182
+ style: { transitionDelay: `${i * 50}ms` }
6183
+ },
6184
+ i
6185
+ )) })
6186
+ ] }),
5721
6187
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-baseline gap-2", children: [
5722
6188
  "LLM Inspector",
5723
6189
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-muted-foreground font-mono", children: [
@@ -5726,6 +6192,23 @@ function ProxyViewer({
5726
6192
  ] })
5727
6193
  ] })
5728
6194
  ] }),
6195
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}) })
6196
+ ] }),
6197
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-6 mb-4", children: [
6198
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedSession, onValueChange: onSessionChange, children: [
6199
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[350px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All sessions" }) }),
6200
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
6201
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All sessions" }),
6202
+ sessions.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: s, children: truncateSessionId(s) }, s))
6203
+ ] })
6204
+ ] }),
6205
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedModel, onValueChange: onModelChange, children: [
6206
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[250px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All models" }) }),
6207
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
6208
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All models" }),
6209
+ models.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: m, children: m }, m))
6210
+ ] })
6211
+ ] }),
5729
6212
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
5730
6213
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5731
6214
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -5749,7 +6232,7 @@ function ProxyViewer({
5749
6232
  ] }) }),
5750
6233
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Simple shows parsed output; Full adds raw headers and tokens" })
5751
6234
  ] }) }),
5752
- /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}),
6235
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
5753
6236
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5754
6237
  logs.length,
5755
6238
  " request",
@@ -5768,7 +6251,7 @@ function ProxyViewer({
5768
6251
  title: "Export all logs as JSON ZIP",
5769
6252
  children: exporting ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Exporting..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5770
6253
  /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
5771
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Export All" })
6254
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Export" })
5772
6255
  ] })
5773
6256
  }
5774
6257
  ),
@@ -5779,71 +6262,12 @@ function ProxyViewer({
5779
6262
  onClick: onClearAll,
5780
6263
  className: "text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
5781
6264
  title: "Clear all logs",
5782
- children: "Clear All"
6265
+ children: "Clear"
5783
6266
  }
5784
6267
  )
5785
6268
  ] }),
5786
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 px-6 mb-4", children: [
5787
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedSession, onValueChange: onSessionChange, children: [
5788
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "flex-1 max-w-[400px] text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All sessions" }) }),
5789
- /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
5790
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All sessions" }),
5791
- sessions.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: s, children: truncateSessionId(s) }, s))
5792
- ] })
5793
- ] }),
5794
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: selectedModel, onValueChange: onModelChange, children: [
5795
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "All models" }) }),
5796
- /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
5797
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "__all__", children: "All models" }),
5798
- models.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: m, children: m }, m))
5799
- ] })
5800
- ] }),
5801
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5802
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5803
- "button",
5804
- {
5805
- type: "button",
5806
- onClick: () => setGroupedView(true),
5807
- className: `px-2 py-1.5 cursor-pointer transition-colors ${groupedView ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5808
- title: "Grouped view",
5809
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutGrid, { className: "size-4" })
5810
- }
5811
- ),
5812
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5813
- "button",
5814
- {
5815
- type: "button",
5816
- onClick: () => setGroupedView(false),
5817
- className: `px-2 py-1.5 cursor-pointer transition-colors ${!groupedView ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5818
- title: "Flat view",
5819
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5820
- }
5821
- )
5822
- ] }),
5823
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5824
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5825
- "button",
5826
- {
5827
- type: "button",
5828
- onClick: () => setGroupViewMode("thread"),
5829
- className: `px-2 py-1.5 cursor-pointer transition-colors ${groupViewMode === "thread" ? "bg-amber-500/15 text-amber-400 border-r border-amber-500/30" : "text-muted-foreground hover:bg-muted/50"}`,
5830
- title: "Thread view (connected timeline)",
5831
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "size-4" })
5832
- }
5833
- ),
5834
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5835
- "button",
5836
- {
5837
- type: "button",
5838
- onClick: () => setGroupViewMode("flat"),
5839
- className: `px-2 py-1.5 cursor-pointer transition-colors ${groupViewMode === "flat" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5840
- title: "Flat view (card list)",
5841
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5842
- }
5843
- )
5844
- ] })
5845
- ] }),
5846
6269
  /* @__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: [
6270
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-12 text-muted-foreground/20 mx-auto" }),
5847
6271
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "No requests captured yet." }),
5848
6272
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs", children: "Route AI coding tools through the proxy:" }),
5849
6273
  /* @__PURE__ */ jsxRuntimeExports.jsx(CopyableCommand, { command: "LLM_BASE_URL=http://localhost:25947/proxy <your-tool>" })
@@ -5856,85 +6280,34 @@ function ProxyViewer({
5856
6280
  position: "relative"
5857
6281
  },
5858
6282
  children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
5859
- if (renderGroups) {
5860
- const group = groups[virtualRow.index];
5861
- if (group === void 0) return null;
5862
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
5863
- "div",
5864
- {
5865
- "data-index": virtualRow.index,
5866
- ref: rowVirtualizer.measureElement,
5867
- style: {
5868
- position: "absolute",
5869
- top: 0,
5870
- left: 0,
5871
- width: "100%",
5872
- transform: `translateY(${virtualRow.start}px)`
5873
- },
5874
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5875
- ConversationGroup,
5876
- {
5877
- group,
5878
- viewMode,
5879
- strip,
5880
- cacheTrends,
5881
- onCompareWithPrevious: handleCompareWithPrevious,
5882
- defaultGroupViewMode: groupViewMode
5883
- }
5884
- )
5885
- },
5886
- group.id
5887
- );
5888
- } else {
5889
- const log = logs[virtualRow.index];
5890
- if (log === void 0) return null;
5891
- const idx = virtualRow.index;
5892
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
5893
- "div",
5894
- {
5895
- "data-index": virtualRow.index,
5896
- ref: rowVirtualizer.measureElement,
5897
- style: {
5898
- position: "absolute",
5899
- top: 0,
5900
- left: 0,
5901
- width: "100%",
5902
- transform: `translateY(${virtualRow.start}px)`
5903
- },
5904
- children: groupViewMode === "thread" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch ml-3", children: [
5905
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5906
- ThreadConnector,
5907
- {
5908
- stopReason: stopReasons[idx] ?? null,
5909
- isPending: log.responseStatus === null,
5910
- isFirst: idx === 0,
5911
- isLast: idx === logs.length - 1
5912
- }
5913
- ),
5914
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5915
- LogEntry,
5916
- {
5917
- log,
5918
- viewMode,
5919
- strip,
5920
- cacheTrend: cacheTrends.get(log.id) ?? null,
5921
- onCompareWithPrevious: () => handleCompareWithPrevious(log)
5922
- }
5923
- ) })
5924
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5925
- LogEntry,
5926
- {
5927
- log,
5928
- viewMode,
5929
- strip,
5930
- cacheTrend: cacheTrends.get(log.id) ?? null,
5931
- onCompareWithPrevious: () => handleCompareWithPrevious(log)
5932
- }
5933
- )
6283
+ const group = groups[virtualRow.index];
6284
+ if (group === void 0) return null;
6285
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
6286
+ "div",
6287
+ {
6288
+ "data-index": virtualRow.index,
6289
+ ref: rowVirtualizer.measureElement,
6290
+ style: {
6291
+ position: "absolute",
6292
+ top: 0,
6293
+ left: 0,
6294
+ width: "100%",
6295
+ transform: `translateY(${virtualRow.start}px)`
5934
6296
  },
5935
- log.id
5936
- );
5937
- }
6297
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6298
+ ConversationGroup,
6299
+ {
6300
+ group,
6301
+ viewMode,
6302
+ strip,
6303
+ cacheTrends,
6304
+ onCompareWithPrevious: handleCompareWithPrevious,
6305
+ standalone: groups.length === 1
6306
+ }
6307
+ )
6308
+ },
6309
+ group.id
6310
+ );
5938
6311
  })
5939
6312
  }
5940
6313
  ) }) }),