@tonyclaw/llm-inspector 1.14.7 → 1.14.9

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 (34) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-Dv-dj1xH.js +105 -0
  3. package/.output/public/assets/index-bqeypwJB.css +1 -0
  4. package/.output/public/assets/{main-BV7uNIIz.js → main-C8OUJKbz.js} +1 -1
  5. package/.output/server/_libs/lucide-react.mjs +87 -79
  6. package/.output/server/_libs/radix-ui__react-id.mjs +1 -1
  7. package/.output/server/_ssr/{index-BvHLASu8.mjs → index-_9xcAkkw.mjs} +861 -608
  8. package/.output/server/_ssr/index.mjs +2 -2
  9. package/.output/server/_ssr/{router-lUOA8pi6.mjs → router-CmanwZJc.mjs} +45 -14
  10. package/.output/server/{_tanstack-start-manifest_v-XNH7fVPN.mjs → _tanstack-start-manifest_v-BVIiyDeJ.mjs} +1 -1
  11. package/.output/server/index.mjs +23 -23
  12. package/package.json +1 -1
  13. package/src/components/ProxyViewer.tsx +137 -146
  14. package/src/components/providers/ProviderCard.tsx +79 -26
  15. package/src/components/providers/ProviderForm.tsx +37 -22
  16. package/src/components/providers/ProvidersPanel.tsx +79 -47
  17. package/src/components/providers/SettingsDialog.tsx +25 -15
  18. package/src/components/proxy-viewer/ConversationGroup.tsx +74 -11
  19. package/src/components/proxy-viewer/ConversationHeader.tsx +63 -2
  20. package/src/components/proxy-viewer/LogEntry.tsx +184 -54
  21. package/src/components/proxy-viewer/LogEntryHeader.tsx +148 -143
  22. package/src/components/proxy-viewer/ReplayDialog.tsx +16 -6
  23. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +24 -16
  24. package/src/components/proxy-viewer/ThreadConnector.tsx +93 -0
  25. package/src/components/proxy-viewer/index.ts +2 -1
  26. package/src/lib/stopReason.ts +57 -0
  27. package/src/proxy/formats/anthropic/handler.ts +2 -5
  28. package/src/proxy/formats/openai/handler.ts +33 -7
  29. package/src/proxy/formats/openai/schemas.ts +1 -0
  30. package/src/proxy/formats/openai/stream.ts +24 -0
  31. package/src/proxy/handler.ts +8 -2
  32. package/src/proxy/schemas.ts +6 -3
  33. package/.output/public/assets/index-Cmi8TfeU.js +0 -105
  34. package/.output/public/assets/index-DXUNTCVh.css +0 -1
@@ -1,20 +1,20 @@
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-lUOA8pi6.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, O as OpenAIRequestSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema } from "./router-CmanwZJc.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
- import { J as JSZip } from "../_libs/jszip.mjs";
6
5
  import { c as clsx } from "../_libs/clsx.mjs";
7
6
  import { t as twMerge } from "../_libs/tailwind-merge.mjs";
7
+ 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
- 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";
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";
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";
12
12
  import "../_libs/modelcontextprotocol__server.mjs";
13
- import { D as Download, L as LayoutGrid, a as List, G as GitCompareArrows, X, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, U as Upload, c as Scan, P as Plus, d as Copy, e as CircleAlert, f as ChevronUp, g as ChevronRight, h as Clock, M as MessageSquare, Z as Zap, i as LoaderCircle, W as Wrench, j as Globe, k as User, F as FileTerminal, l as Radio, m as Rows3, n as Columns2, o as Minus, p as Pencil, E as Equal, q as EyeOff, r as Eye, s as ExternalLink, t as RotateCw, T as Trash2, A as ArrowUp, u as ArrowDown, v as TriangleAlert, w as CircleCheckBig, x as CircleStop, y as CircleQuestionMark, z as Server, B as Gauge, H as Lock, I as Wifi, J as WifiOff, K as ChevronsUp, N as ChevronsDown, O as Brain, Q as Terminal } from "../_libs/lucide-react.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 User, k as Clock, M as MessageSquare, Z as Zap, W as Wrench, l as Globe, 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";
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 { R as Root2$1, L as List$1, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
17
- 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";
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";
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";
@@ -237,6 +237,63 @@ function useStripConfig() {
237
237
  setStrip
238
238
  };
239
239
  }
240
+ function cn(...inputs) {
241
+ return twMerge(clsx(inputs));
242
+ }
243
+ function formatTokens(count) {
244
+ if (count >= 1048576) return (count / 1048576).toFixed(1).replace(/\.0$/, "") + "M";
245
+ if (count >= 1024) return (count / 1024).toFixed(1).replace(/\.0$/, "") + "K";
246
+ return count.toString();
247
+ }
248
+ function getStatusCategory(status) {
249
+ if (status === null) return "pending";
250
+ if (status >= 200 && status < 300) return "success";
251
+ if (status >= 400 && status < 500) return "client_error";
252
+ if (status >= 500) return "server_error";
253
+ return "pending";
254
+ }
255
+ function TooltipProvider({
256
+ delayDuration = 0,
257
+ ...props
258
+ }) {
259
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
260
+ Provider,
261
+ {
262
+ "data-slot": "tooltip-provider",
263
+ delayDuration,
264
+ ...props
265
+ }
266
+ );
267
+ }
268
+ function Tooltip({ ...props }) {
269
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Root3, { "data-slot": "tooltip", ...props });
270
+ }
271
+ function TooltipTrigger({ ...props }) {
272
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger, { "data-slot": "tooltip-trigger", ...props });
273
+ }
274
+ function TooltipContent({
275
+ className,
276
+ sideOffset = 0,
277
+ children,
278
+ ...props
279
+ }) {
280
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
281
+ Content2,
282
+ {
283
+ "data-slot": "tooltip-content",
284
+ sideOffset,
285
+ className: cn(
286
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
287
+ className
288
+ ),
289
+ ...props,
290
+ children: [
291
+ children,
292
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Arrow2, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
293
+ ]
294
+ }
295
+ ) });
296
+ }
240
297
  async function fetchStreamingChunks(logId) {
241
298
  try {
242
299
  const response = await fetch(`/api/logs/${logId}/chunks`);
@@ -276,24 +333,34 @@ async function exportLogsAsZip(logs) {
276
333
  document.body.removeChild(anchor);
277
334
  URL.revokeObjectURL(url);
278
335
  }
279
- const version = "1.14.7";
336
+ const version = "1.14.9";
280
337
  const packageJson = {
281
338
  version
282
339
  };
283
- function cn(...inputs) {
284
- return twMerge(clsx(inputs));
285
- }
286
- function formatTokens(count) {
287
- if (count >= 1048576) return (count / 1048576).toFixed(1).replace(/\.0$/, "") + "M";
288
- if (count >= 1024) return (count / 1024).toFixed(1).replace(/\.0$/, "") + "K";
289
- return count.toString();
340
+ function isRecord(value) {
341
+ return typeof value === "object" && value !== null && !Array.isArray(value);
290
342
  }
291
- function getStatusCategory(status) {
292
- if (status === null) return "pending";
293
- if (status >= 200 && status < 300) return "success";
294
- if (status >= 400 && status < 500) return "client_error";
295
- if (status >= 500) return "server_error";
296
- return "pending";
343
+ function extractStopReason(log) {
344
+ if (log.responseText === null) return null;
345
+ try {
346
+ let json = JSON.parse(log.responseText);
347
+ if (typeof json === "string") {
348
+ json = JSON.parse(json);
349
+ }
350
+ if (!isRecord(json)) return null;
351
+ if (log.apiFormat === "anthropic" && typeof json.stop_reason === "string") {
352
+ if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
353
+ return json.stop_reason;
354
+ }
355
+ return null;
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") {
358
+ return "stop";
359
+ }
360
+ return null;
361
+ } catch {
362
+ return null;
363
+ }
297
364
  }
298
365
  const badgeVariants = cva(
299
366
  "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",
@@ -349,7 +416,11 @@ function ConversationHeader({
349
416
  apiFormat,
350
417
  expanded,
351
418
  onToggle,
352
- hideApiFormat = false
419
+ hideApiFormat = false,
420
+ isLoading = false,
421
+ viewMode,
422
+ onToggleViewMode,
423
+ userAgent
353
424
  }) {
354
425
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
355
426
  "div",
@@ -370,7 +441,23 @@ function ConversationHeader({
370
441
  }
371
442
  },
372
443
  children: [
373
- expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" }),
444
+ 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" }),
445
+ expanded && onToggleViewMode !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
446
+ "button",
447
+ {
448
+ type: "button",
449
+ onClick: (e) => {
450
+ e.stopPropagation();
451
+ onToggleViewMode();
452
+ },
453
+ className: cn(
454
+ "px-1.5 py-0.5 rounded text-[10px] font-mono transition-colors shrink-0 cursor-pointer",
455
+ 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"
456
+ ),
457
+ title: viewMode === "thread" ? "Thread view — click for flat view" : "Flat view — click for thread view",
458
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "size-3" })
459
+ }
460
+ ),
374
461
  /* @__PURE__ */ jsxRuntimeExports.jsx(
375
462
  "span",
376
463
  {
@@ -379,6 +466,17 @@ function ConversationHeader({
379
466
  children: conversationId.length > 24 ? conversationId.slice(0, 12) + "…" + conversationId.slice(-12) : conversationId
380
467
  }
381
468
  ),
469
+ userAgent !== null && userAgent !== void 0 && userAgent !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
470
+ "span",
471
+ {
472
+ className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
473
+ title: userAgent,
474
+ children: [
475
+ /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "size-3" }),
476
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[120px]", children: userAgent })
477
+ ]
478
+ }
479
+ ),
382
480
  !hideApiFormat && /* @__PURE__ */ jsxRuntimeExports.jsx(
383
481
  Badge,
384
482
  {
@@ -509,48 +607,6 @@ function Button({
509
607
  }
510
608
  );
511
609
  }
512
- function TooltipProvider({
513
- delayDuration = 0,
514
- ...props
515
- }) {
516
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
517
- Provider,
518
- {
519
- "data-slot": "tooltip-provider",
520
- delayDuration,
521
- ...props
522
- }
523
- );
524
- }
525
- function Tooltip({ ...props }) {
526
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root3, { "data-slot": "tooltip", ...props });
527
- }
528
- function TooltipTrigger({ ...props }) {
529
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$3, { "data-slot": "tooltip-trigger", ...props });
530
- }
531
- function TooltipContent({
532
- className,
533
- sideOffset = 0,
534
- children,
535
- ...props
536
- }) {
537
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
538
- Content2$1,
539
- {
540
- "data-slot": "tooltip-content",
541
- sideOffset,
542
- className: cn(
543
- "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
544
- className
545
- ),
546
- ...props,
547
- children: [
548
- children,
549
- /* @__PURE__ */ jsxRuntimeExports.jsx(Arrow2, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
550
- ]
551
- }
552
- ) });
553
- }
554
610
  function classifyValue(value) {
555
611
  if (value === null) return "null";
556
612
  if (Array.isArray(value)) return "array";
@@ -953,7 +1009,7 @@ function TabsTrigger({
953
1009
  ...props
954
1010
  }) {
955
1011
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
956
- Trigger$2,
1012
+ Trigger$3,
957
1013
  {
958
1014
  "data-slot": "tabs-trigger",
959
1015
  className: cn(
@@ -1411,18 +1467,15 @@ function CacheTrendIndicator({ trend }) {
1411
1467
  }
1412
1468
  const LogEntryHeader = reactExports.memo(function({
1413
1469
  log,
1414
- parsedRequest,
1470
+ messageCount = null,
1471
+ toolCount = null,
1415
1472
  expanded,
1416
1473
  onToggle,
1417
- suppressApiFormatBadge = false,
1418
- cacheTrend = null,
1419
- isSelected = false,
1420
- onToggleSelect
1474
+ responseToolNames = null,
1475
+ cacheTrend = null
1421
1476
  }) {
1422
1477
  const statusCategory = getStatusCategory(log.responseStatus);
1423
1478
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
1424
- const messageCount = parsedRequest !== null ? parsedRequest.messages.length : null;
1425
- const toolCount = parsedRequest !== null && parsedRequest.tools !== void 0 && parsedRequest.tools.length > 0 ? parsedRequest.tools.length : null;
1426
1479
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1427
1480
  "div",
1428
1481
  {
@@ -1441,45 +1494,22 @@ const LogEntryHeader = reactExports.memo(function({
1441
1494
  }
1442
1495
  },
1443
1496
  children: [
1444
- onToggleSelect !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
1445
- "button",
1446
- {
1447
- type: "button",
1448
- onClick: (e) => {
1449
- e.stopPropagation();
1450
- onToggleSelect(log.id);
1451
- },
1452
- "aria-label": isSelected ? "Deselect for comparison" : "Select for comparison",
1453
- "aria-pressed": isSelected,
1454
- className: cn(
1455
- "shrink-0 size-4 rounded-sm border flex items-center justify-center transition-colors cursor-pointer",
1456
- isSelected ? "bg-amber-400 border-amber-400 text-amber-950" : "border-muted-foreground/40 hover:border-amber-400 hover:bg-amber-400/10"
1457
- ),
1458
- children: isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3", strokeWidth: 3 })
1459
- }
1460
- ),
1461
1497
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0", children: [
1462
1498
  "#",
1463
1499
  log.id
1464
1500
  ] }),
1465
- log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1466
- /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4 shrink-0" }),
1467
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "secondary", className: "text-[10px] px-1.5 py-0 h-5 font-mono", children: log.model })
1468
- ] }),
1469
- !suppressApiFormatBadge && /* @__PURE__ */ jsxRuntimeExports.jsx(
1501
+ log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1502
+ /* @__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" }) }) }),
1503
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.model })
1504
+ ] }) }),
1505
+ statusCategory !== "success" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: statusCategory === "server_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1470
1506
  Badge,
1471
1507
  {
1472
- variant: "outline",
1473
- className: cn(
1474
- "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
1475
- log.apiFormat === "openai" && "border-blue-500/40 text-blue-400",
1476
- log.apiFormat === "anthropic" && "border-orange-500/40 text-orange-400",
1477
- log.apiFormat === "unknown" && "border-muted text-muted-foreground"
1478
- ),
1479
- children: log.apiFormat === "anthropic" ? "Anthropic" : log.apiFormat === "openai" ? "OpenAI" : "Unknown"
1508
+ variant: "destructive",
1509
+ className: "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
1510
+ children: log.responseStatus
1480
1511
  }
1481
- ),
1482
- 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(
1512
+ ) : statusCategory === "pending" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1483
1513
  Badge,
1484
1514
  {
1485
1515
  variant: "outline",
@@ -1499,7 +1529,7 @@ const LogEntryHeader = reactExports.memo(function({
1499
1529
  ),
1500
1530
  children: log.responseStatus
1501
1531
  }
1502
- ),
1532
+ ) }),
1503
1533
  log.elapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1504
1534
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1505
1535
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(log.elapsedMs) })
@@ -1524,28 +1554,50 @@ const LogEntryHeader = reactExports.memo(function({
1524
1554
  )
1525
1555
  ] })
1526
1556
  ] }),
1527
- log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1528
- /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
1529
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
1530
- "Cache +",
1531
- formatTokens(log.cacheCreationInputTokens)
1532
- ] })
1533
- ] }),
1534
- log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1535
- /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
1536
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
1537
- "Cache ~",
1538
- formatTokens(log.cacheReadInputTokens)
1557
+ log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1558
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1559
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
1560
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
1561
+ "Cache +",
1562
+ formatTokens(log.cacheCreationInputTokens)
1563
+ ] })
1564
+ ] }) }),
1565
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
1566
+ ] }) }),
1567
+ log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1568
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1569
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
1570
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
1571
+ "Cache ~",
1572
+ formatTokens(log.cacheReadInputTokens)
1573
+ ] })
1574
+ ] }) }),
1575
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
1576
+ ] }) }),
1577
+ messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1578
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1579
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
1580
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: messageCount })
1581
+ ] }) }),
1582
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of messages in the conversation" })
1583
+ ] }) }),
1584
+ toolCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1585
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1586
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1587
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: toolCount })
1588
+ ] }) }),
1589
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of tools defined in the request" })
1590
+ ] }) }),
1591
+ responseToolNames !== null && responseToolNames.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1592
+ /* @__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: [
1593
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1594
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[160px]", children: responseToolNames.join(", ") })
1595
+ ] }) }),
1596
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(TooltipContent, { children: [
1597
+ "Tools called by model: ",
1598
+ responseToolNames.join(", ")
1539
1599
  ] })
1540
- ] }),
1541
- messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1542
- /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
1543
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: messageCount })
1544
- ] }),
1545
- toolCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1546
- /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1547
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: toolCount })
1548
- ] }),
1600
+ ] }) }),
1549
1601
  log.origin !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1550
1602
  "span",
1551
1603
  {
@@ -1557,32 +1609,20 @@ const LogEntryHeader = reactExports.memo(function({
1557
1609
  ]
1558
1610
  }
1559
1611
  ),
1560
- log.userAgent !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1561
- "span",
1562
- {
1563
- className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
1564
- title: `User-Agent: ${log.userAgent}`,
1565
- children: [
1566
- /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "size-3" }),
1567
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[150px]", title: log.userAgent, children: log.userAgent })
1568
- ]
1569
- }
1570
- ),
1571
- (log.clientPid !== null || log.clientProjectFolder !== null) && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1572
- "span",
1573
- {
1574
- className: "flex items-center gap-1 text-purple-400/80 text-xs shrink-0",
1575
- title: log.clientCwd !== null ? `PID: ${log.clientPid ?? "?"} | CWD: ${log.clientCwd}` : `PID: ${log.clientPid ?? "?"}`,
1576
- children: [
1577
- /* @__PURE__ */ jsxRuntimeExports.jsx(FileTerminal, { className: "size-3" }),
1578
- 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: [
1579
- "PID ",
1580
- log.clientPid
1581
- ] })
1582
- ]
1583
- }
1584
- ),
1585
- log.streaming && /* @__PURE__ */ jsxRuntimeExports.jsx(Radio, { className: "size-3 text-muted-foreground/60 shrink-0" }),
1612
+ (log.clientPid !== null || log.clientProjectFolder !== null) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1613
+ /* @__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: [
1614
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileTerminal, { className: "size-3" }),
1615
+ 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: [
1616
+ "PID ",
1617
+ log.clientPid
1618
+ ] })
1619
+ ] }) }),
1620
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.clientCwd !== null ? `PID: ${log.clientPid ?? "?"} CWD: ${log.clientCwd}` : `Process ID: ${log.clientPid ?? "?"}` })
1621
+ ] }) }),
1622
+ log.streaming && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1623
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Radio, { className: "size-3 text-muted-foreground/60 shrink-0" }) }),
1624
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request used SSE streaming" })
1625
+ ] }) }),
1586
1626
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
1587
1627
  expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" })
1588
1628
  ]
@@ -1597,12 +1637,12 @@ function Dialog({
1597
1637
  function DialogTrigger({
1598
1638
  ...props
1599
1639
  }) {
1600
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$1, { "data-slot": "dialog-trigger", ...props });
1640
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$2, { "data-slot": "dialog-trigger", ...props });
1601
1641
  }
1602
1642
  function DialogPortal({
1603
1643
  ...props
1604
1644
  }) {
1605
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { "data-slot": "dialog-portal", ...props });
1645
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
1606
1646
  }
1607
1647
  function DialogOverlay({
1608
1648
  className,
@@ -2167,15 +2207,18 @@ function ReplayDialog({ log, open, onOpenChange }) {
2167
2207
  /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, { value: "modified", className: "space-y-4", children: [
2168
2208
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2169
2209
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-sm font-medium mb-2 block", children: "Request Body (JSON)" }),
2170
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2171
- "textarea",
2172
- {
2173
- className: "w-full h-64 p-3 font-mono text-xs bg-muted rounded-md border border-input resize-none focus:outline-none focus:ring-2 focus:ring-ring",
2174
- value: modifiedBody,
2175
- onChange: (e) => setModifiedBody(e.target.value),
2176
- spellCheck: false
2177
- }
2178
- )
2210
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2211
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2212
+ "textarea",
2213
+ {
2214
+ className: "w-full h-64 p-3 font-mono text-xs bg-muted rounded-md border border-input resize-none focus:outline-none focus:ring-2 focus:ring-ring",
2215
+ value: modifiedBody,
2216
+ onChange: (e) => setModifiedBody(e.target.value),
2217
+ spellCheck: false
2218
+ }
2219
+ ) }),
2220
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Edit the request body before re-sending to the provider" })
2221
+ ] }) })
2179
2222
  ] }),
2180
2223
  error !== null && error !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-md", children: error }),
2181
2224
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -2359,22 +2402,25 @@ function StreamingChunkSequence({
2359
2402
  ] });
2360
2403
  }
2361
2404
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
2362
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2363
- "button",
2364
- {
2365
- type: "button",
2366
- className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
2367
- onClick: () => setContainerExpanded((v) => !v),
2368
- children: [
2369
- containerExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3" }),
2370
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Raw SSE Events" }),
2371
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "text-[9px] px-1 py-0 h-4 font-mono ml-1", children: [
2372
- logId,
2373
- truncated === true ? "+" : ""
2374
- ] })
2375
- ]
2376
- }
2377
- ),
2405
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2406
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2407
+ "button",
2408
+ {
2409
+ type: "button",
2410
+ className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
2411
+ onClick: () => setContainerExpanded((v) => !v),
2412
+ children: [
2413
+ containerExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3" }),
2414
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Raw SSE Events" }),
2415
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "text-[9px] px-1 py-0 h-4 font-mono ml-1", children: [
2416
+ logId,
2417
+ truncated === true ? "+" : ""
2418
+ ] })
2419
+ ]
2420
+ }
2421
+ ) }),
2422
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Server-Sent Events streaming chunks from the provider" })
2423
+ ] }) }),
2378
2424
  containerExpanded === true ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-border bg-muted/20 overflow-auto max-h-64", children: renderBody() }) : null
2379
2425
  ] });
2380
2426
  }
@@ -2414,32 +2460,32 @@ function DiffToggleButton({
2414
2460
  active,
2415
2461
  onClick
2416
2462
  }) {
2417
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2418
- "button",
2419
- {
2420
- type: "button",
2421
- onClick,
2422
- "aria-pressed": active,
2423
- className: cn(
2424
- "flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
2425
- active ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground hover:bg-muted"
2426
- ),
2427
- title: active ? "Hide diff with raw" : "Diff this view against the raw version",
2428
- children: [
2429
- /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3" }),
2430
- active ? "Showing diff" : "Diff with Raw"
2431
- ]
2432
- }
2433
- );
2463
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2464
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2465
+ "button",
2466
+ {
2467
+ type: "button",
2468
+ onClick,
2469
+ "aria-pressed": active,
2470
+ className: cn(
2471
+ "flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
2472
+ active ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground hover:bg-muted"
2473
+ ),
2474
+ children: [
2475
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3" }),
2476
+ active ? "Showing diff" : "Diff with Raw"
2477
+ ]
2478
+ }
2479
+ ) }),
2480
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: active ? "Hide diff view" : "Compare proxy output against the original raw version" })
2481
+ ] }) });
2434
2482
  }
2435
2483
  const LogEntry = reactExports.memo(function({
2436
2484
  log,
2437
2485
  viewMode = "simple",
2438
- suppressApiFormatBadge = false,
2439
2486
  strip,
2440
2487
  cacheTrend = null,
2441
- isSelected = false,
2442
- onToggleSelect
2488
+ onCompareWithPrevious
2443
2489
  }) {
2444
2490
  const [expanded, setExpanded] = reactExports.useState(false);
2445
2491
  const [requestCopied, setRequestCopied] = reactExports.useState(false);
@@ -2448,7 +2494,60 @@ const LogEntry = reactExports.memo(function({
2448
2494
  const [replayOpen, setReplayOpen] = reactExports.useState(false);
2449
2495
  const [headersDiff, setHeadersDiff] = reactExports.useState(false);
2450
2496
  const [requestDiff, setRequestDiff] = reactExports.useState(false);
2451
- const parsedRequest = reactExports.useMemo(() => parseRequest(log.rawRequestBody), [log.rawRequestBody]);
2497
+ const messageCount = reactExports.useMemo(() => {
2498
+ if (log.rawRequestBody === null) return null;
2499
+ if (log.apiFormat === "anthropic") {
2500
+ const parsed = parseRequest(log.rawRequestBody);
2501
+ if (parsed !== null) return parsed.messages.length;
2502
+ } else if (log.apiFormat === "openai") {
2503
+ try {
2504
+ const result = OpenAIRequestSchema.safeParse(JSON.parse(log.rawRequestBody));
2505
+ if (result.success) return result.data.messages.length;
2506
+ } catch {
2507
+ }
2508
+ }
2509
+ return null;
2510
+ }, [log.rawRequestBody, log.apiFormat]);
2511
+ const toolCount = reactExports.useMemo(() => {
2512
+ if (log.rawRequestBody === null) return null;
2513
+ if (log.apiFormat === "anthropic") {
2514
+ const parsed = parseRequest(log.rawRequestBody);
2515
+ if (parsed !== null && parsed.tools !== void 0 && parsed.tools.length > 0) {
2516
+ return parsed.tools.length;
2517
+ }
2518
+ } else if (log.apiFormat === "openai") {
2519
+ try {
2520
+ const result = OpenAIRequestSchema.safeParse(JSON.parse(log.rawRequestBody));
2521
+ if (result.success && result.data.tools !== void 0 && result.data.tools.length > 0) {
2522
+ return result.data.tools.length;
2523
+ }
2524
+ } catch {
2525
+ }
2526
+ }
2527
+ return null;
2528
+ }, [log.rawRequestBody, log.apiFormat]);
2529
+ const responseToolNames = reactExports.useMemo(() => {
2530
+ if (log.responseText === null) return null;
2531
+ if (log.apiFormat === "openai") {
2532
+ const parsed = parseOpenAIResponse(log.responseText);
2533
+ if (parsed !== null) {
2534
+ const toolCalls = parsed.choices[0]?.message?.tool_calls;
2535
+ if (toolCalls !== void 0 && toolCalls !== null && toolCalls.length > 0) {
2536
+ return toolCalls.map((tc) => tc.function?.name ?? "?").filter((n) => n !== "");
2537
+ }
2538
+ }
2539
+ } else if (log.apiFormat === "anthropic") {
2540
+ try {
2541
+ const result = InspectorResponseSchema.safeParse(JSON.parse(log.responseText));
2542
+ if (result.success) {
2543
+ const names = result.data.content.filter((c) => c.type === "tool_use").map((c) => c.name);
2544
+ if (names.length > 0) return names;
2545
+ }
2546
+ } catch {
2547
+ }
2548
+ }
2549
+ return null;
2550
+ }, [log.responseText, log.apiFormat]);
2452
2551
  const strippedRequestBody = reactExports.useMemo(() => {
2453
2552
  if (!strip || log.apiFormat !== "anthropic" || log.rawRequestBody === null) {
2454
2553
  return null;
@@ -2489,178 +2588,258 @@ const LogEntry = reactExports.memo(function({
2489
2588
  });
2490
2589
  }
2491
2590
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2492
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2493
- "div",
2494
- {
2495
- className: cn(
2496
- "border border-border rounded-lg mb-3 overflow-hidden",
2497
- isSelected && "border-l-2 border-l-amber-400"
2498
- ),
2499
- children: [
2500
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2501
- LogEntryHeader,
2591
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border rounded-lg mb-3 overflow-hidden", children: [
2592
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2593
+ LogEntryHeader,
2594
+ {
2595
+ log,
2596
+ messageCount,
2597
+ toolCount,
2598
+ expanded,
2599
+ onToggle: () => setExpanded(!expanded),
2600
+ responseToolNames,
2601
+ cacheTrend
2602
+ }
2603
+ ),
2604
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
2605
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
2606
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2607
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }) }),
2608
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "HTTP headers received from the upstream provider" })
2609
+ ] }) }),
2610
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2611
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }) }),
2612
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request and response headers sent and received" })
2613
+ ] }) }),
2614
+ shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2615
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }) }),
2616
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Exact HTTP request sent to the upstream provider" })
2617
+ ] }) }),
2618
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
2619
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2620
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }) }),
2621
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Exact HTTP response from the upstream provider" })
2622
+ ] }) }),
2623
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
2624
+ ] }),
2625
+ shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2626
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2627
+ CopyButton,
2502
2628
  {
2503
- log,
2504
- parsedRequest,
2505
- expanded,
2506
- onToggle: () => setExpanded(!expanded),
2507
- suppressApiFormatBadge,
2508
- cacheTrend,
2509
- isSelected,
2510
- onToggleSelect
2629
+ text: log.rawRequestBody,
2630
+ label: "Copy Raw Request",
2631
+ copied: rawRequestCopied,
2632
+ onCopy: handleCopyRawRequest
2511
2633
  }
2512
- ),
2513
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
2514
- /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
2515
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }),
2516
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }),
2517
- shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }),
2518
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
2519
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
2520
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
2521
- ] }),
2522
- shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2523
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2524
- CopyButton,
2525
- {
2526
- text: log.rawRequestBody,
2527
- label: "Copy Raw Request",
2528
- copied: rawRequestCopied,
2529
- onCopy: handleCopyRawRequest
2530
- }
2531
- ) }),
2532
- log.rawRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.rawRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2533
- ] }) }),
2534
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2535
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2536
- shouldShowRequestDiffButton(
2537
- log.apiFormat,
2538
- viewMode,
2539
- strip,
2540
- log.rawRequestBody !== null
2541
- ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2542
- DiffToggleButton,
2543
- {
2544
- active: requestDiff,
2545
- onClick: (e) => {
2546
- e.stopPropagation();
2547
- setRequestDiff(!requestDiff);
2548
- }
2549
- }
2550
- ),
2551
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2552
- Button,
2553
- {
2554
- variant: "outline",
2555
- size: "sm",
2556
- className: "h-7 text-xs",
2557
- onClick: (e) => {
2558
- e.stopPropagation();
2559
- setReplayOpen(true);
2560
- },
2561
- children: [
2562
- /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3 mr-1" }),
2563
- "Replay"
2564
- ]
2565
- }
2566
- ),
2567
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2568
- CopyButton,
2569
- {
2570
- text: displayedRequestBody,
2571
- label: "Copy Request",
2572
- copied: requestCopied,
2573
- onCopy: handleCopyRequest
2574
- }
2575
- )
2576
- ] }),
2577
- requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2578
- DiffView,
2579
- {
2580
- result: requestDiffResult,
2581
- emptyLabel: "No transformation applied — raw and sent request bodies are identical."
2634
+ ) }),
2635
+ log.rawRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.rawRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2636
+ ] }) }),
2637
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2638
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2639
+ shouldShowRequestDiffButton(
2640
+ log.apiFormat,
2641
+ viewMode,
2642
+ strip,
2643
+ log.rawRequestBody !== null
2644
+ ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2645
+ DiffToggleButton,
2646
+ {
2647
+ active: requestDiff,
2648
+ onClick: (e) => {
2649
+ e.stopPropagation();
2650
+ setRequestDiff(!requestDiff);
2582
2651
  }
2583
- ) : displayedRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: displayedRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2584
- ] }) }),
2585
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2586
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
2587
- viewMode,
2588
- log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
2589
- ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2590
- DiffToggleButton,
2652
+ }
2653
+ ),
2654
+ onCompareWithPrevious !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2655
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2656
+ Button,
2591
2657
  {
2592
- active: headersDiff,
2658
+ variant: "outline",
2659
+ size: "sm",
2660
+ className: "h-7 text-xs",
2593
2661
  onClick: (e) => {
2594
2662
  e.stopPropagation();
2595
- setHeadersDiff(!headersDiff);
2596
- }
2663
+ onCompareWithPrevious();
2664
+ },
2665
+ children: [
2666
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3 mr-1" }),
2667
+ "Diff with Previous"
2668
+ ]
2597
2669
  }
2598
2670
  ) }),
2599
- headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2600
- DiffView,
2601
- {
2602
- result: headersDiffResult,
2603
- emptyLabel: "No transformation applied — raw and processed headers are identical."
2604
- }
2605
- ) : log.headers && Object.keys(log.headers).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.headers).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2606
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2607
- key,
2608
- ":"
2609
- ] }),
2610
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2611
- ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
2671
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Compare this request with the immediately preceding one" })
2612
2672
  ] }) }),
2613
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2614
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2615
- key,
2616
- ":"
2617
- ] }),
2618
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2619
- ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
2620
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
2621
- log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
2622
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2623
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
2624
- ] }),
2625
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2626
- CopyButton,
2673
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2674
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2675
+ Button,
2627
2676
  {
2628
- text: log.responseText,
2629
- label: "Copy Response",
2630
- copied: responseCopied,
2631
- onCopy: handleCopyResponse
2677
+ variant: "outline",
2678
+ size: "sm",
2679
+ className: "h-7 text-xs",
2680
+ onClick: (e) => {
2681
+ e.stopPropagation();
2682
+ setReplayOpen(true);
2683
+ },
2684
+ children: [
2685
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3 mr-1" }),
2686
+ "Replay"
2687
+ ]
2632
2688
  }
2633
2689
  ) }),
2634
- log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.responseText, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
2635
- log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
2636
- StreamingChunkSequence,
2637
- {
2638
- logId: log.id,
2639
- truncated: log.streamingChunksPath !== null
2640
- }
2641
- )
2690
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Re-send this request to the provider" })
2642
2691
  ] }) }),
2643
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2644
- ResponseView,
2692
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2693
+ CopyButton,
2645
2694
  {
2646
- responseText: log.responseText,
2647
- responseStatus: log.responseStatus,
2648
- streaming: log.streaming,
2649
- inputTokens: log.inputTokens,
2650
- outputTokens: log.outputTokens,
2651
- cacheCreationInputTokens: log.cacheCreationInputTokens,
2652
- cacheReadInputTokens: log.cacheReadInputTokens,
2653
- apiFormat: log.apiFormat,
2654
- error: log.error
2695
+ text: displayedRequestBody,
2696
+ label: "Copy Request",
2697
+ copied: requestCopied,
2698
+ onCopy: handleCopyRequest
2655
2699
  }
2656
- ) }) })
2657
- ] }) })
2658
- ]
2659
- }
2660
- ),
2700
+ )
2701
+ ] }),
2702
+ requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2703
+ DiffView,
2704
+ {
2705
+ result: requestDiffResult,
2706
+ emptyLabel: "No transformation applied — raw and sent request bodies are identical."
2707
+ }
2708
+ ) : displayedRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: displayedRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2709
+ ] }) }),
2710
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2711
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
2712
+ viewMode,
2713
+ log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
2714
+ ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2715
+ DiffToggleButton,
2716
+ {
2717
+ active: headersDiff,
2718
+ onClick: (e) => {
2719
+ e.stopPropagation();
2720
+ setHeadersDiff(!headersDiff);
2721
+ }
2722
+ }
2723
+ ) }),
2724
+ headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2725
+ DiffView,
2726
+ {
2727
+ result: headersDiffResult,
2728
+ emptyLabel: "No transformation applied — raw and processed headers are identical."
2729
+ }
2730
+ ) : log.headers && Object.keys(log.headers).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.headers).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2731
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2732
+ key,
2733
+ ":"
2734
+ ] }),
2735
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2736
+ ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
2737
+ ] }) }),
2738
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2739
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2740
+ key,
2741
+ ":"
2742
+ ] }),
2743
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2744
+ ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
2745
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
2746
+ log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
2747
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2748
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
2749
+ ] }),
2750
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2751
+ CopyButton,
2752
+ {
2753
+ text: log.responseText,
2754
+ label: "Copy Response",
2755
+ copied: responseCopied,
2756
+ onCopy: handleCopyResponse
2757
+ }
2758
+ ) }),
2759
+ log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.responseText, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
2760
+ log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
2761
+ StreamingChunkSequence,
2762
+ {
2763
+ logId: log.id,
2764
+ truncated: log.streamingChunksPath !== null
2765
+ }
2766
+ )
2767
+ ] }) }),
2768
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2769
+ ResponseView,
2770
+ {
2771
+ responseText: log.responseText,
2772
+ responseStatus: log.responseStatus,
2773
+ streaming: log.streaming,
2774
+ inputTokens: log.inputTokens,
2775
+ outputTokens: log.outputTokens,
2776
+ cacheCreationInputTokens: log.cacheCreationInputTokens,
2777
+ cacheReadInputTokens: log.cacheReadInputTokens,
2778
+ apiFormat: log.apiFormat,
2779
+ error: log.error
2780
+ }
2781
+ ) }) })
2782
+ ] }) })
2783
+ ] }),
2661
2784
  /* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
2662
2785
  ] });
2663
2786
  });
2787
+ function ThreadConnector({
2788
+ stopReason,
2789
+ isPending,
2790
+ isFirst,
2791
+ isLast: _isLast,
2792
+ isTurnStart
2793
+ }) {
2794
+ const isBoundary = stopReason === "end_turn" || stopReason === "stop";
2795
+ const isToolUse = stopReason === "tool_use";
2796
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center w-6 shrink-0", children: [
2797
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center h-4", children: !isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 bg-muted-foreground/30" }) }),
2798
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-0.5", children: isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2799
+ "div",
2800
+ {
2801
+ className: cn(
2802
+ "size-2.5 rounded-full border-2",
2803
+ "bg-background border-amber-400",
2804
+ "shadow-[0_0_6px_rgba(251,191,36,0.4)]"
2805
+ ),
2806
+ title: stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"
2807
+ }
2808
+ ) : isToolUse ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2809
+ "div",
2810
+ {
2811
+ className: cn(
2812
+ "size-2 rounded-full",
2813
+ isTurnStart ? "bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.5)]" : "bg-muted-foreground/25"
2814
+ ),
2815
+ title: isTurnStart ? "Tool Use — start of turn" : "Tool Use — turn continues"
2816
+ }
2817
+ ) : isPending ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2818
+ "div",
2819
+ {
2820
+ className: "size-2.5 rounded-full border-2 border-dashed border-muted-foreground/30 animate-pulse",
2821
+ title: "Response pending"
2822
+ }
2823
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2824
+ "div",
2825
+ {
2826
+ className: cn(
2827
+ "size-1.5 rounded-full",
2828
+ isTurnStart ? "bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.5)]" : "bg-muted-foreground/30"
2829
+ )
2830
+ }
2831
+ ) }),
2832
+ /* @__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(
2833
+ "div",
2834
+ {
2835
+ className: cn(
2836
+ "w-0.5 h-full",
2837
+ isPending ? "border-dashed bg-transparent border-l-2 border-muted-foreground/20" : "bg-muted-foreground/30"
2838
+ )
2839
+ }
2840
+ ) })
2841
+ ] });
2842
+ }
2664
2843
  function computeStats(logs) {
2665
2844
  let totalInput = 0;
2666
2845
  let totalOutput = 0;
@@ -2675,14 +2854,31 @@ const ConversationGroup = reactExports.memo(function({
2675
2854
  viewMode = "simple",
2676
2855
  strip,
2677
2856
  cacheTrends,
2678
- selectedSet,
2679
- onToggleSelect
2857
+ onCompareWithPrevious,
2858
+ defaultGroupViewMode = "thread"
2680
2859
  }) {
2681
2860
  const [expanded, setExpanded] = reactExports.useState(false);
2861
+ const [groupViewMode, setGroupViewMode] = reactExports.useState(defaultGroupViewMode);
2862
+ reactExports.useEffect(() => {
2863
+ setGroupViewMode(defaultGroupViewMode);
2864
+ }, [defaultGroupViewMode]);
2682
2865
  const stats = computeStats(group.logs);
2683
2866
  const startTime = group.logs[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2684
2867
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2685
2868
  const mixed = hasMixedApiFormat(group.logs);
2869
+ const isLoading = group.logs.some((log) => log.responseStatus === null);
2870
+ const stopReasons = reactExports.useMemo(() => group.logs.map((log) => extractStopReason(log)), [group.logs]);
2871
+ const turnIndices = reactExports.useMemo(() => {
2872
+ const indices = [];
2873
+ let turn = 0;
2874
+ for (let i = 0; i < stopReasons.length; i++) {
2875
+ if (i > 0 && (stopReasons[i - 1] === "end_turn" || stopReasons[i - 1] === "stop")) {
2876
+ turn++;
2877
+ }
2878
+ indices.push(turn);
2879
+ }
2880
+ return indices;
2881
+ }, [stopReasons]);
2686
2882
  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;
2687
2883
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
2688
2884
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -2697,22 +2893,58 @@ const ConversationGroup = reactExports.memo(function({
2697
2893
  apiFormat: getGroupApiFormat(group.logs),
2698
2894
  expanded,
2699
2895
  onToggle: () => setExpanded(!expanded),
2700
- hideApiFormat: mixed
2896
+ hideApiFormat: mixed,
2897
+ isLoading,
2898
+ userAgent: group.logs[0]?.userAgent ?? null,
2899
+ viewMode: groupViewMode,
2900
+ onToggleViewMode: () => setGroupViewMode((prev) => prev === "thread" ? "flat" : "thread")
2701
2901
  }
2702
2902
  ),
2703
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 border-l-2 border-muted ml-3", children: group.logs.map((log) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2903
+ 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(
2704
2904
  LogEntry,
2705
2905
  {
2706
2906
  log,
2707
2907
  viewMode,
2708
- suppressApiFormatBadge: !mixed,
2709
2908
  strip,
2710
2909
  cacheTrend: cacheTrends?.get(log.id) ?? null,
2711
- isSelected: selectedSet.has(log.id),
2712
- onToggleSelect
2910
+ onCompareWithPrevious: () => onCompareWithPrevious(log)
2713
2911
  },
2714
2912
  log.id
2715
- )) })
2913
+ )) }),
2914
+ expanded && groupViewMode === "thread" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-3", children: group.logs.map((log, idx) => {
2915
+ const isTurnStart = idx === 0 || stopReasons[idx - 1] === "end_turn" || stopReasons[idx - 1] === "stop";
2916
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
2917
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2918
+ ThreadConnector,
2919
+ {
2920
+ stopReason: stopReasons[idx] ?? null,
2921
+ isPending: log.responseStatus === null,
2922
+ isFirst: idx === 0,
2923
+ isLast: idx === group.logs.length - 1,
2924
+ isTurnStart
2925
+ }
2926
+ ),
2927
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2928
+ "div",
2929
+ {
2930
+ className: cn(
2931
+ "flex-1 min-w-0 mb-2 rounded-lg",
2932
+ (turnIndices[idx] ?? 0) % 2 === 0 ? "bg-muted/10" : "bg-muted/25"
2933
+ ),
2934
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2935
+ LogEntry,
2936
+ {
2937
+ log,
2938
+ viewMode,
2939
+ strip,
2940
+ cacheTrend: cacheTrends?.get(log.id) ?? null,
2941
+ onCompareWithPrevious: () => onCompareWithPrevious(log)
2942
+ }
2943
+ )
2944
+ }
2945
+ )
2946
+ ] }, log.id);
2947
+ }) })
2716
2948
  ] });
2717
2949
  });
2718
2950
  function CrabLogo({ className }) {
@@ -2760,7 +2992,7 @@ function SelectTrigger({
2760
2992
  ...props
2761
2993
  }) {
2762
2994
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2763
- Trigger,
2995
+ Trigger$1,
2764
2996
  {
2765
2997
  "data-slot": "select-trigger",
2766
2998
  "data-size": size,
@@ -2783,8 +3015,8 @@ function SelectContent({
2783
3015
  align = "center",
2784
3016
  ...props
2785
3017
  }) {
2786
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2787
- Content2,
3018
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3019
+ Content2$1,
2788
3020
  {
2789
3021
  "data-slot": "select-content",
2790
3022
  className: cn(
@@ -3208,20 +3440,26 @@ function TestStatus({ result }) {
3208
3440
  }
3209
3441
  if (result.cacheCreationInputTokens !== void 0 && result.cacheCreationInputTokens > 0) {
3210
3442
  tokenParts.push(
3211
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
3212
- "+",
3213
- result.cacheCreationInputTokens,
3214
- " cache"
3215
- ] }, "cache-create")
3443
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3444
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
3445
+ "+",
3446
+ result.cacheCreationInputTokens,
3447
+ " cache"
3448
+ ] }) }),
3449
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
3450
+ ] }) }, "cache-create")
3216
3451
  );
3217
3452
  }
3218
3453
  if (result.cacheReadInputTokens !== void 0 && result.cacheReadInputTokens > 0) {
3219
3454
  tokenParts.push(
3220
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
3221
- "~",
3222
- result.cacheReadInputTokens,
3223
- " cached"
3224
- ] }, "cache-read")
3455
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3456
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
3457
+ "~",
3458
+ result.cacheReadInputTokens,
3459
+ " cached"
3460
+ ] }) }),
3461
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
3462
+ ] }) }, "cache-read")
3225
3463
  );
3226
3464
  }
3227
3465
  const displayTokens = [];
@@ -3229,15 +3467,18 @@ function TestStatus({ result }) {
3229
3467
  if (i > 0) displayTokens.push(", ");
3230
3468
  displayTokens.push(tokenParts[i]);
3231
3469
  }
3232
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600 shrink-0", children: [
3233
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3" }),
3234
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connected" }),
3235
- tokenParts.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
3236
- "(",
3237
- displayTokens,
3238
- ")"
3239
- ] })
3240
- ] });
3470
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3471
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600 shrink-0", children: [
3472
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3" }),
3473
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connected" }),
3474
+ tokenParts.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
3475
+ "(",
3476
+ displayTokens,
3477
+ ")"
3478
+ ] })
3479
+ ] }) }),
3480
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Connection test passed" })
3481
+ ] }) });
3241
3482
  }
3242
3483
  const error = result.error;
3243
3484
  const errorMessage = error?.message ?? "Connection failed";
@@ -3247,17 +3488,22 @@ function TestStatus({ result }) {
3247
3488
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 shrink-0", children: [
3248
3489
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-red-600 max-w-[200px]", children: [
3249
3490
  getErrorIcon(errorType),
3250
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage }),
3251
- errorDetails !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
3252
- "button",
3253
- {
3254
- type: "button",
3255
- onClick: () => setShowDetails(!showDetails),
3256
- className: "shrink-0 text-muted-foreground hover:text-foreground transition-colors",
3257
- title: showDetails ? "Hide details" : "Show details",
3258
- children: showDetails ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-3" })
3259
- }
3260
- )
3491
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3492
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage }) }),
3493
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Connection test failed" })
3494
+ ] }) }),
3495
+ errorDetails !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3496
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3497
+ "button",
3498
+ {
3499
+ type: "button",
3500
+ onClick: () => setShowDetails(!showDetails),
3501
+ className: "shrink-0 text-muted-foreground hover:text-foreground transition-colors",
3502
+ children: showDetails ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-3" })
3503
+ }
3504
+ ) }),
3505
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: showDetails ? "Hide error details" : "Show detailed error information" })
3506
+ ] }) })
3261
3507
  ] }),
3262
3508
  showDetails && errorDetails !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-muted-foreground bg-muted/50 rounded p-2 max-w-[300px]", children: [
3263
3509
  errorHint !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1", children: errorHint }),
@@ -3328,8 +3574,14 @@ function ProviderCard({
3328
3574
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
3329
3575
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
3330
3576
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: provider.name }),
3331
- provider.source === "company" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 shrink-0", children: "公司" }),
3332
- provider.source === "personal" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 shrink-0", children: "个人" })
3577
+ provider.source === "company" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3578
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 shrink-0", children: "公司" }) }),
3579
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Company-provided API key" })
3580
+ ] }) }),
3581
+ provider.source === "personal" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3582
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 shrink-0", children: "个人" }) }),
3583
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Your personal API key" })
3584
+ ] }) })
3333
3585
  ] }),
3334
3586
  docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3335
3587
  "a",
@@ -3808,24 +4060,30 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
3808
4060
  ] }),
3809
4061
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
3810
4062
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 border-b border-border", children: [
3811
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3812
- "button",
3813
- {
3814
- type: "button",
3815
- onClick: () => setActiveTab("anthropic"),
3816
- className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "anthropic" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
3817
- children: "Anthropic Format"
3818
- }
3819
- ),
3820
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3821
- "button",
3822
- {
3823
- type: "button",
3824
- onClick: () => setActiveTab("openai"),
3825
- className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "openai" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
3826
- children: "OpenAI Format"
3827
- }
3828
- )
4063
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4064
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4065
+ "button",
4066
+ {
4067
+ type: "button",
4068
+ onClick: () => setActiveTab("anthropic"),
4069
+ className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "anthropic" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
4070
+ children: "Anthropic Format"
4071
+ }
4072
+ ) }),
4073
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Anthropic Messages API protocol" })
4074
+ ] }) }),
4075
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4076
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4077
+ "button",
4078
+ {
4079
+ type: "button",
4080
+ onClick: () => setActiveTab("openai"),
4081
+ className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "openai" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
4082
+ children: "OpenAI Format"
4083
+ }
4084
+ ) }),
4085
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "OpenAI Chat Completions API protocol" })
4086
+ ] }) })
3829
4087
  ] }),
3830
4088
  errors.format !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.format })
3831
4089
  ] }),
@@ -4222,32 +4480,38 @@ function ProvidersPanel({
4222
4480
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between sticky top-0 z-10 bg-background pb-2", children: [
4223
4481
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-medium", children: "Providers" }),
4224
4482
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
4225
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
4226
- Button,
4227
- {
4228
- variant: "outline",
4229
- size: "sm",
4230
- onClick: () => handleExport(),
4231
- className: "gap-1 hover:bg-muted",
4232
- children: [
4233
- /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
4234
- "Export"
4235
- ]
4236
- }
4237
- ),
4238
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
4239
- Button,
4240
- {
4241
- variant: "outline",
4242
- size: "sm",
4243
- onClick: handleImportClick,
4244
- className: "gap-1 hover:bg-muted",
4245
- children: [
4246
- /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
4247
- "Import"
4248
- ]
4249
- }
4250
- ),
4483
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4484
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4485
+ Button,
4486
+ {
4487
+ variant: "outline",
4488
+ size: "sm",
4489
+ onClick: () => handleExport(),
4490
+ className: "gap-1 hover:bg-muted",
4491
+ children: [
4492
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
4493
+ "Export"
4494
+ ]
4495
+ }
4496
+ ) }),
4497
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Download providers as JSON for backup or sharing" })
4498
+ ] }) }),
4499
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4500
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4501
+ Button,
4502
+ {
4503
+ variant: "outline",
4504
+ size: "sm",
4505
+ onClick: handleImportClick,
4506
+ className: "gap-1 hover:bg-muted",
4507
+ children: [
4508
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
4509
+ "Import"
4510
+ ]
4511
+ }
4512
+ ) }),
4513
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Import providers from an exported JSON file" })
4514
+ ] }) }),
4251
4515
  /* @__PURE__ */ jsxRuntimeExports.jsx(
4252
4516
  "input",
4253
4517
  {
@@ -4283,21 +4547,23 @@ function ProvidersPanel({
4283
4547
  configPath !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground bg-muted/30 rounded-md px-3 py-2", children: [
4284
4548
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: "Config:" }),
4285
4549
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate", title: configPath, children: configPath }),
4286
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4287
- "button",
4288
- {
4289
- type: "button",
4290
- onClick: () => {
4291
- void window.navigator.clipboard.writeText(configPath).then(() => {
4292
- setConfigPathCopied(true);
4293
- setTimeout(() => setConfigPathCopied(false), 2e3);
4294
- });
4295
- },
4296
- className: "shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors",
4297
- title: "Copy path",
4298
- children: configPathCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
4299
- }
4300
- )
4550
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4551
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4552
+ "button",
4553
+ {
4554
+ type: "button",
4555
+ onClick: () => {
4556
+ void window.navigator.clipboard.writeText(configPath).then(() => {
4557
+ setConfigPathCopied(true);
4558
+ setTimeout(() => setConfigPathCopied(false), 2e3);
4559
+ });
4560
+ },
4561
+ className: "shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors",
4562
+ children: configPathCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
4563
+ }
4564
+ ) }),
4565
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Copy config file path to clipboard" })
4566
+ ] }) })
4301
4567
  ] }),
4302
4568
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2", children: [
4303
4569
  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "size-4 shrink-0" }),
@@ -4310,16 +4576,18 @@ function ProvidersPanel({
4310
4576
  "Add Your First Provider"
4311
4577
  ] })
4312
4578
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
4313
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 border-b border-border", children: ["all", "personal", "company"].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
4314
- "button",
4315
- {
4316
- type: "button",
4317
- onClick: () => setSourceFilter(tab),
4318
- className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${sourceFilter === tab ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
4319
- children: tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"
4320
- },
4321
- tab
4322
- )) }),
4579
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 border-b border-border", children: ["all", "personal", "company"].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4580
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4581
+ "button",
4582
+ {
4583
+ type: "button",
4584
+ onClick: () => setSourceFilter(tab),
4585
+ className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${sourceFilter === tab ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
4586
+ children: tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"
4587
+ }
4588
+ ) }),
4589
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: tab === "all" ? "Show all providers" : tab === "personal" ? "Providers you configured yourself" : "Providers set by your organization" })
4590
+ ] }) }, tab)) }),
4323
4591
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: listScrollRef, className: "space-y-3", children: filteredProviders.map((provider) => /* @__PURE__ */ jsxRuntimeExports.jsx(
4324
4592
  ProviderCard,
4325
4593
  {
@@ -4490,22 +4758,25 @@ function ProxySettingsTab() {
4490
4758
  " and overrides the env var for subsequent requests."
4491
4759
  ] })
4492
4760
  ] }),
4493
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-3", children: [
4494
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4495
- "input",
4496
- {
4497
- type: "checkbox",
4498
- role: "switch",
4499
- checked: strip,
4500
- disabled: isLoading || pending,
4501
- onChange: (e) => {
4502
- void handleToggle(e.currentTarget.checked);
4503
- },
4504
- className: "size-4 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
4505
- }
4506
- ),
4507
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
4508
- ] }),
4761
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4762
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-3", children: [
4763
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4764
+ "input",
4765
+ {
4766
+ type: "checkbox",
4767
+ role: "switch",
4768
+ checked: strip,
4769
+ disabled: isLoading || pending,
4770
+ onChange: (e) => {
4771
+ void handleToggle(e.currentTarget.checked);
4772
+ },
4773
+ className: "size-4 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
4774
+ }
4775
+ ),
4776
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
4777
+ ] }) }),
4778
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Strip Claude Code billing header to improve cache hit rates" })
4779
+ ] }) }),
4509
4780
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
4510
4781
  "Failed to save: ",
4511
4782
  error
@@ -5473,9 +5744,8 @@ function ProxyViewer({
5473
5744
  }) {
5474
5745
  const { totalIn, totalOut } = computeTokenSummary(logs);
5475
5746
  const [groupedView, setGroupedView] = reactExports.useState(true);
5747
+ const [groupViewMode, setGroupViewMode] = reactExports.useState("thread");
5476
5748
  const [exporting, setExporting] = reactExports.useState(false);
5477
- const [selectedLogIds, setSelectedLogIds] = reactExports.useState([]);
5478
- const [compareOpen, setCompareOpen] = reactExports.useState(false);
5479
5749
  const [comparePair, setComparePair] = reactExports.useState(null);
5480
5750
  const handleExport = reactExports.useCallback(async () => {
5481
5751
  setExporting(true);
@@ -5486,76 +5756,37 @@ function ProxyViewer({
5486
5756
  }
5487
5757
  }, [logs]);
5488
5758
  const parentRef = reactExports.useRef(null);
5489
- const handleToggleSelect = reactExports.useCallback((logId) => {
5490
- setSelectedLogIds((prev) => {
5491
- if (prev.includes(logId)) {
5492
- return prev.filter((id) => id !== logId);
5493
- }
5494
- if (prev.length < 2) {
5495
- return [...prev, logId];
5496
- }
5497
- const newer = prev[1];
5498
- if (newer === void 0) return prev;
5499
- return [newer, logId];
5500
- });
5501
- }, []);
5502
5759
  reactExports.useEffect(() => {
5503
- setSelectedLogIds([]);
5504
- setCompareOpen(false);
5760
+ setComparePair(null);
5505
5761
  }, [selectedSession, selectedModel]);
5506
- const selectedSet = reactExports.useMemo(() => new Set(selectedLogIds), [selectedLogIds]);
5507
- const openCompare = reactExports.useCallback(() => {
5508
- if (selectedLogIds.length !== 2) return;
5509
- const [idA, idB] = selectedLogIds;
5510
- if (idA === void 0 || idB === void 0) return;
5511
- const logA = logs.find((l) => l.id === idA);
5512
- const logB = logs.find((l) => l.id === idB);
5513
- if (logA === void 0 || logB === void 0) return;
5514
- setComparePair([logA, logB]);
5515
- setCompareOpen(true);
5516
- }, [selectedLogIds, logs]);
5517
5762
  const closeCompare = reactExports.useCallback(() => {
5518
- setCompareOpen(false);
5519
- }, []);
5520
- const clearSelection = reactExports.useCallback(() => {
5521
- setSelectedLogIds([]);
5763
+ setComparePair(null);
5522
5764
  }, []);
5523
- const selectedSummary = reactExports.useMemo(() => {
5524
- if (selectedLogIds.length !== 2) return null;
5525
- const [idA, idB] = selectedLogIds;
5526
- if (idA === void 0 || idB === void 0) return null;
5527
- const logA = logs.find((l) => l.id === idA);
5528
- const logB = logs.find((l) => l.id === idB);
5529
- if (logA === void 0 || logB === void 0) return null;
5530
- const sameSession = getConversationId(logA) === getConversationId(logB);
5531
- let elapsed = "";
5532
- if (logA.timestamp !== null && logB.timestamp !== null) {
5533
- const a = Date.parse(logA.timestamp);
5534
- const b = Date.parse(logB.timestamp);
5535
- if (!Number.isNaN(a) && !Number.isNaN(b)) {
5536
- const ms = Math.abs(b - a);
5537
- elapsed = formatElapsed2(ms);
5538
- }
5539
- }
5540
- return {
5541
- logA,
5542
- logB,
5543
- sameSession,
5544
- elapsed
5545
- };
5546
- }, [selectedLogIds, logs]);
5547
- function formatElapsed2(ms) {
5548
- if (ms < 1e3) return `${ms}ms`;
5549
- const sec = Math.floor(ms / 1e3);
5550
- if (sec < 60) return `${sec}s`;
5551
- const min = Math.floor(sec / 60);
5552
- if (min < 60) return `${min}m`;
5553
- const hr = Math.floor(min / 60);
5554
- return `${hr}h${min % 60}m`;
5555
- }
5765
+ const handleCompareWithPrevious = reactExports.useCallback(
5766
+ (log) => {
5767
+ const idx = logs.indexOf(log);
5768
+ if (idx <= 0) return;
5769
+ const predecessor = logs[idx - 1];
5770
+ if (predecessor === void 0) return;
5771
+ setComparePair([predecessor, log]);
5772
+ },
5773
+ [logs]
5774
+ );
5556
5775
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
5557
5776
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
5558
- const renderGroups = logs.length > 0 && groupedView && !(groups.length === 1 && groups[0]?.logs.length === logs.length);
5777
+ const stopReasons = reactExports.useMemo(() => logs.map((log) => extractStopReason(log)), [logs]);
5778
+ const turnIndices = reactExports.useMemo(() => {
5779
+ const indices = [];
5780
+ let turn = 0;
5781
+ for (let i = 0; i < stopReasons.length; i++) {
5782
+ if (i > 0 && (stopReasons[i - 1] === "end_turn" || stopReasons[i - 1] === "stop")) {
5783
+ turn++;
5784
+ }
5785
+ indices.push(turn);
5786
+ }
5787
+ return indices;
5788
+ }, [stopReasons]);
5789
+ const renderGroups = logs.length > 0 && groupedView && groups.length > 1;
5559
5790
  const rowVirtualizer = useVirtualizer({
5560
5791
  count: renderGroups ? groups.length : logs.length,
5561
5792
  getScrollElement: () => parentRef.current,
@@ -5575,26 +5806,29 @@ function ProxyViewer({
5575
5806
  ] })
5576
5807
  ] })
5577
5808
  ] }),
5578
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5579
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5580
- "button",
5581
- {
5582
- type: "button",
5583
- onClick: () => onViewModeChange("simple"),
5584
- className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5585
- children: "Simple"
5586
- }
5587
- ),
5588
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5589
- "button",
5590
- {
5591
- type: "button",
5592
- onClick: () => onViewModeChange("full"),
5593
- className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5594
- children: "Full"
5595
- }
5596
- )
5597
- ] }),
5809
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
5810
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5811
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5812
+ "button",
5813
+ {
5814
+ type: "button",
5815
+ onClick: () => onViewModeChange("simple"),
5816
+ className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5817
+ children: "Simple"
5818
+ }
5819
+ ),
5820
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5821
+ "button",
5822
+ {
5823
+ type: "button",
5824
+ onClick: () => onViewModeChange("full"),
5825
+ className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5826
+ children: "Full"
5827
+ }
5828
+ )
5829
+ ] }) }),
5830
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Simple shows parsed output; Full adds raw headers and tokens" })
5831
+ ] }) }),
5598
5832
  /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}),
5599
5833
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5600
5834
  logs.length,
@@ -5665,6 +5899,28 @@ function ProxyViewer({
5665
5899
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5666
5900
  }
5667
5901
  )
5902
+ ] }),
5903
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5904
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5905
+ "button",
5906
+ {
5907
+ type: "button",
5908
+ onClick: () => setGroupViewMode("thread"),
5909
+ 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"}`,
5910
+ title: "Thread view (connected timeline)",
5911
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "size-4" })
5912
+ }
5913
+ ),
5914
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5915
+ "button",
5916
+ {
5917
+ type: "button",
5918
+ onClick: () => setGroupViewMode("flat"),
5919
+ className: `px-2 py-1.5 cursor-pointer transition-colors ${groupViewMode === "flat" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5920
+ title: "Flat view (card list)",
5921
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5922
+ }
5923
+ )
5668
5924
  ] })
5669
5925
  ] }),
5670
5926
  /* @__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: [
@@ -5702,8 +5958,8 @@ function ProxyViewer({
5702
5958
  viewMode,
5703
5959
  strip,
5704
5960
  cacheTrends,
5705
- selectedSet,
5706
- onToggleSelect: handleToggleSelect
5961
+ onCompareWithPrevious: handleCompareWithPrevious,
5962
+ defaultGroupViewMode: groupViewMode
5707
5963
  }
5708
5964
  )
5709
5965
  },
@@ -5712,6 +5968,7 @@ function ProxyViewer({
5712
5968
  } else {
5713
5969
  const log = logs[virtualRow.index];
5714
5970
  if (log === void 0) return null;
5971
+ const idx = virtualRow.index;
5715
5972
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
5716
5973
  "div",
5717
5974
  {
@@ -5724,15 +5981,44 @@ function ProxyViewer({
5724
5981
  width: "100%",
5725
5982
  transform: `translateY(${virtualRow.start}px)`
5726
5983
  },
5727
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5984
+ children: groupViewMode === "thread" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch ml-3", children: [
5985
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5986
+ ThreadConnector,
5987
+ {
5988
+ stopReason: stopReasons[idx] ?? null,
5989
+ isPending: log.responseStatus === null,
5990
+ isFirst: idx === 0,
5991
+ isLast: idx === logs.length - 1,
5992
+ isTurnStart: idx === 0 || stopReasons[idx - 1] === "end_turn" || stopReasons[idx - 1] === "stop"
5993
+ }
5994
+ ),
5995
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5996
+ "div",
5997
+ {
5998
+ className: cn(
5999
+ "flex-1 min-w-0 mb-2 rounded-lg",
6000
+ (turnIndices[idx] ?? 0) % 2 === 0 ? "bg-muted/10" : "bg-muted/25"
6001
+ ),
6002
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6003
+ LogEntry,
6004
+ {
6005
+ log,
6006
+ viewMode,
6007
+ strip,
6008
+ cacheTrend: cacheTrends.get(log.id) ?? null,
6009
+ onCompareWithPrevious: () => handleCompareWithPrevious(log)
6010
+ }
6011
+ )
6012
+ }
6013
+ )
6014
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5728
6015
  LogEntry,
5729
6016
  {
5730
6017
  log,
5731
6018
  viewMode,
5732
6019
  strip,
5733
6020
  cacheTrend: cacheTrends.get(log.id) ?? null,
5734
- isSelected: selectedSet.has(log.id),
5735
- onToggleSelect: handleToggleSelect
6021
+ onCompareWithPrevious: () => handleCompareWithPrevious(log)
5736
6022
  }
5737
6023
  )
5738
6024
  },
@@ -5742,40 +6028,7 @@ function ProxyViewer({
5742
6028
  })
5743
6029
  }
5744
6030
  ) }) }),
5745
- selectedSummary !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-40 flex items-center gap-3 bg-background border border-border rounded-lg shadow-lg px-4 py-2 text-xs", children: [
5746
- /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-4 text-amber-400 shrink-0" }),
5747
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-mono", children: [
5748
- "#",
5749
- selectedSummary.logA.id,
5750
- " ↔ #",
5751
- selectedSummary.logB.id,
5752
- " · ",
5753
- selectedSummary.sameSession ? "same session" : "different sessions",
5754
- selectedSummary.elapsed !== "" && ` · ${selectedSummary.elapsed} apart`
5755
- ] }),
5756
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
5757
- "button",
5758
- {
5759
- type: "button",
5760
- onClick: clearSelection,
5761
- className: "text-muted-foreground hover:text-foreground transition-colors cursor-pointer inline-flex items-center gap-1",
5762
- children: [
5763
- /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-3" }),
5764
- "Clear"
5765
- ]
5766
- }
5767
- ),
5768
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5769
- "button",
5770
- {
5771
- type: "button",
5772
- onClick: openCompare,
5773
- className: "bg-amber-400 text-amber-950 hover:bg-amber-300 transition-colors px-3 py-1 rounded font-medium cursor-pointer",
5774
- children: "Compare 2 logs"
5775
- }
5776
- )
5777
- ] }),
5778
- compareOpen && comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
6031
+ comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
5779
6032
  ] });
5780
6033
  }
5781
6034
  object({