@tonyclaw/llm-inspector 1.14.7 → 1.14.8

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 (28) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-CdnotuLh.js +105 -0
  3. package/.output/public/assets/index-vP91146S.css +1 -0
  4. package/.output/public/assets/{main-BV7uNIIz.js → main-CJ4MreBr.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-9uTJ4xYR.mjs} +744 -581
  8. package/.output/server/_ssr/index.mjs +2 -2
  9. package/.output/server/_ssr/{router-lUOA8pi6.mjs → router-BKnjB_zi.mjs} +2 -2
  10. package/.output/server/{_tanstack-start-manifest_v-XNH7fVPN.mjs → _tanstack-start-manifest_v-IsglLVKy.mjs} +1 -1
  11. package/.output/server/index.mjs +28 -28
  12. package/package.json +1 -1
  13. package/src/components/ProxyViewer.tsx +114 -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 +50 -10
  19. package/src/components/proxy-viewer/ConversationHeader.tsx +48 -2
  20. package/src/components/proxy-viewer/LogEntry.tsx +116 -45
  21. package/src/components/proxy-viewer/LogEntryHeader.tsx +89 -71
  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 +104 -0
  25. package/src/components/proxy-viewer/index.ts +2 -1
  26. package/src/lib/stopReason.ts +57 -0
  27. package/.output/public/assets/index-Cmi8TfeU.js +0 -105
  28. 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, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-BKnjB_zi.mjs";
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 Clock, M as MessageSquare, Z as Zap, W as Wrench, k as Globe, l as User, F as FileTerminal, m as Radio, n as Rows3, o as Columns2, p as Minus, q as Pencil, E as Equal, r as EyeOff, s as Eye, t as ExternalLink, u as RotateCw, T as Trash2, A as ArrowUp, v as ArrowDown, w as TriangleAlert, x as CircleCheckBig, y as CircleStop, z as CircleQuestionMark, B as Server, H as Gauge, I as Lock, J as Wifi, K as WifiOff, N as ChevronsUp, O as ChevronsDown, Q as Brain, V as Terminal } from "../_libs/lucide-react.mjs";
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.8";
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,10 @@ function ConversationHeader({
349
416
  apiFormat,
350
417
  expanded,
351
418
  onToggle,
352
- hideApiFormat = false
419
+ hideApiFormat = false,
420
+ isLoading = false,
421
+ viewMode,
422
+ onToggleViewMode
353
423
  }) {
354
424
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
355
425
  "div",
@@ -370,7 +440,23 @@ function ConversationHeader({
370
440
  }
371
441
  },
372
442
  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" }),
443
+ expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" }),
444
+ expanded && onToggleViewMode !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
445
+ "button",
446
+ {
447
+ type: "button",
448
+ onClick: (e) => {
449
+ e.stopPropagation();
450
+ onToggleViewMode();
451
+ },
452
+ className: cn(
453
+ "px-1.5 py-0.5 rounded text-[10px] font-mono transition-colors shrink-0 cursor-pointer",
454
+ viewMode === "thread" ? "bg-amber-500/15 text-amber-400 border border-amber-500/30" : "bg-muted text-muted-foreground border border-border hover:text-foreground"
455
+ ),
456
+ title: viewMode === "thread" ? "Thread view — click for flat view" : "Flat view — click for thread view",
457
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "size-3" })
458
+ }
459
+ ),
374
460
  /* @__PURE__ */ jsxRuntimeExports.jsx(
375
461
  "span",
376
462
  {
@@ -509,48 +595,6 @@ function Button({
509
595
  }
510
596
  );
511
597
  }
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
598
  function classifyValue(value) {
555
599
  if (value === null) return "null";
556
600
  if (Array.isArray(value)) return "array";
@@ -953,7 +997,7 @@ function TabsTrigger({
953
997
  ...props
954
998
  }) {
955
999
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
956
- Trigger$2,
1000
+ Trigger$3,
957
1001
  {
958
1002
  "data-slot": "tabs-trigger",
959
1003
  className: cn(
@@ -1415,9 +1459,7 @@ const LogEntryHeader = reactExports.memo(function({
1415
1459
  expanded,
1416
1460
  onToggle,
1417
1461
  suppressApiFormatBadge = false,
1418
- cacheTrend = null,
1419
- isSelected = false,
1420
- onToggleSelect
1462
+ cacheTrend = null
1421
1463
  }) {
1422
1464
  const statusCategory = getStatusCategory(log.responseStatus);
1423
1465
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
@@ -1441,31 +1483,14 @@ const LogEntryHeader = reactExports.memo(function({
1441
1483
  }
1442
1484
  },
1443
1485
  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
1486
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0", children: [
1462
1487
  "#",
1463
1488
  log.id
1464
1489
  ] }),
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
- ] }),
1490
+ log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1491
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4" }) }) }),
1492
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.model })
1493
+ ] }) }),
1469
1494
  !suppressApiFormatBadge && /* @__PURE__ */ jsxRuntimeExports.jsx(
1470
1495
  Badge,
1471
1496
  {
@@ -1524,28 +1549,40 @@ const LogEntryHeader = reactExports.memo(function({
1524
1549
  )
1525
1550
  ] })
1526
1551
  ] }),
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)
1539
- ] })
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
- ] }),
1552
+ log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1553
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1554
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
1555
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
1556
+ "Cache +",
1557
+ formatTokens(log.cacheCreationInputTokens)
1558
+ ] })
1559
+ ] }) }),
1560
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
1561
+ ] }) }),
1562
+ log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1563
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1564
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
1565
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
1566
+ "Cache ~",
1567
+ formatTokens(log.cacheReadInputTokens)
1568
+ ] })
1569
+ ] }) }),
1570
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
1571
+ ] }) }),
1572
+ messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1573
+ /* @__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: [
1574
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
1575
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: messageCount })
1576
+ ] }) }),
1577
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of messages in the conversation" })
1578
+ ] }) }),
1579
+ toolCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1580
+ /* @__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: [
1581
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1582
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: toolCount })
1583
+ ] }) }),
1584
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of tools defined in the request" })
1585
+ ] }) }),
1549
1586
  log.origin !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1550
1587
  "span",
1551
1588
  {
@@ -1568,21 +1605,20 @@ const LogEntryHeader = reactExports.memo(function({
1568
1605
  ]
1569
1606
  }
1570
1607
  ),
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" }),
1608
+ (log.clientPid !== null || log.clientProjectFolder !== null) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1609
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-purple-400/80 text-xs shrink-0", children: [
1610
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileTerminal, { className: "size-3" }),
1611
+ log.clientProjectFolder !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: log.clientProjectFolder }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
1612
+ "PID ",
1613
+ log.clientPid
1614
+ ] })
1615
+ ] }) }),
1616
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.clientCwd !== null ? `PID: ${log.clientPid ?? "?"} CWD: ${log.clientCwd}` : `Process ID: ${log.clientPid ?? "?"}` })
1617
+ ] }) }),
1618
+ log.streaming && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1619
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Radio, { className: "size-3 text-muted-foreground/60 shrink-0" }) }),
1620
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request used SSE streaming" })
1621
+ ] }) }),
1586
1622
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
1587
1623
  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
1624
  ]
@@ -1597,12 +1633,12 @@ function Dialog({
1597
1633
  function DialogTrigger({
1598
1634
  ...props
1599
1635
  }) {
1600
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$1, { "data-slot": "dialog-trigger", ...props });
1636
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$2, { "data-slot": "dialog-trigger", ...props });
1601
1637
  }
1602
1638
  function DialogPortal({
1603
1639
  ...props
1604
1640
  }) {
1605
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { "data-slot": "dialog-portal", ...props });
1641
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
1606
1642
  }
1607
1643
  function DialogOverlay({
1608
1644
  className,
@@ -2167,15 +2203,18 @@ function ReplayDialog({ log, open, onOpenChange }) {
2167
2203
  /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, { value: "modified", className: "space-y-4", children: [
2168
2204
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2169
2205
  /* @__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
- )
2206
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2207
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2208
+ "textarea",
2209
+ {
2210
+ 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",
2211
+ value: modifiedBody,
2212
+ onChange: (e) => setModifiedBody(e.target.value),
2213
+ spellCheck: false
2214
+ }
2215
+ ) }),
2216
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Edit the request body before re-sending to the provider" })
2217
+ ] }) })
2179
2218
  ] }),
2180
2219
  error !== null && error !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-md", children: error }),
2181
2220
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -2359,22 +2398,25 @@ function StreamingChunkSequence({
2359
2398
  ] });
2360
2399
  }
2361
2400
  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
- ),
2401
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2402
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2403
+ "button",
2404
+ {
2405
+ type: "button",
2406
+ className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
2407
+ onClick: () => setContainerExpanded((v) => !v),
2408
+ children: [
2409
+ containerExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3" }),
2410
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Raw SSE Events" }),
2411
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "text-[9px] px-1 py-0 h-4 font-mono ml-1", children: [
2412
+ logId,
2413
+ truncated === true ? "+" : ""
2414
+ ] })
2415
+ ]
2416
+ }
2417
+ ) }),
2418
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Server-Sent Events streaming chunks from the provider" })
2419
+ ] }) }),
2378
2420
  containerExpanded === true ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-border bg-muted/20 overflow-auto max-h-64", children: renderBody() }) : null
2379
2421
  ] });
2380
2422
  }
@@ -2414,23 +2456,25 @@ function DiffToggleButton({
2414
2456
  active,
2415
2457
  onClick
2416
2458
  }) {
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
- );
2459
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2460
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2461
+ "button",
2462
+ {
2463
+ type: "button",
2464
+ onClick,
2465
+ "aria-pressed": active,
2466
+ className: cn(
2467
+ "flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
2468
+ active ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground hover:bg-muted"
2469
+ ),
2470
+ children: [
2471
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3" }),
2472
+ active ? "Showing diff" : "Diff with Raw"
2473
+ ]
2474
+ }
2475
+ ) }),
2476
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: active ? "Hide diff view" : "Compare proxy output against the original raw version" })
2477
+ ] }) });
2434
2478
  }
2435
2479
  const LogEntry = reactExports.memo(function({
2436
2480
  log,
@@ -2438,8 +2482,7 @@ const LogEntry = reactExports.memo(function({
2438
2482
  suppressApiFormatBadge = false,
2439
2483
  strip,
2440
2484
  cacheTrend = null,
2441
- isSelected = false,
2442
- onToggleSelect
2485
+ onCompareWithPrevious
2443
2486
  }) {
2444
2487
  const [expanded, setExpanded] = reactExports.useState(false);
2445
2488
  const [requestCopied, setRequestCopied] = reactExports.useState(false);
@@ -2489,178 +2532,268 @@ const LogEntry = reactExports.memo(function({
2489
2532
  });
2490
2533
  }
2491
2534
  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,
2535
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border rounded-lg mb-3 overflow-hidden", children: [
2536
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2537
+ LogEntryHeader,
2538
+ {
2539
+ log,
2540
+ parsedRequest,
2541
+ expanded,
2542
+ onToggle: () => setExpanded(!expanded),
2543
+ suppressApiFormatBadge,
2544
+ cacheTrend
2545
+ }
2546
+ ),
2547
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
2548
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
2549
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2550
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }) }),
2551
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "HTTP headers received from the upstream provider" })
2552
+ ] }) }),
2553
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2554
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }) }),
2555
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request and response headers sent and received" })
2556
+ ] }) }),
2557
+ shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2558
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }) }),
2559
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Exact HTTP request sent to the upstream provider" })
2560
+ ] }) }),
2561
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
2562
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2563
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }) }),
2564
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Exact HTTP response from the upstream provider" })
2565
+ ] }) }),
2566
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
2567
+ ] }),
2568
+ shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2569
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2570
+ CopyButton,
2502
2571
  {
2503
- log,
2504
- parsedRequest,
2505
- expanded,
2506
- onToggle: () => setExpanded(!expanded),
2507
- suppressApiFormatBadge,
2508
- cacheTrend,
2509
- isSelected,
2510
- onToggleSelect
2572
+ text: log.rawRequestBody,
2573
+ label: "Copy Raw Request",
2574
+ copied: rawRequestCopied,
2575
+ onCopy: handleCopyRawRequest
2511
2576
  }
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."
2577
+ ) }),
2578
+ 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" })
2579
+ ] }) }),
2580
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2581
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2582
+ shouldShowRequestDiffButton(
2583
+ log.apiFormat,
2584
+ viewMode,
2585
+ strip,
2586
+ log.rawRequestBody !== null
2587
+ ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2588
+ DiffToggleButton,
2589
+ {
2590
+ active: requestDiff,
2591
+ onClick: (e) => {
2592
+ e.stopPropagation();
2593
+ setRequestDiff(!requestDiff);
2582
2594
  }
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,
2595
+ }
2596
+ ),
2597
+ onCompareWithPrevious !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2598
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2599
+ Button,
2591
2600
  {
2592
- active: headersDiff,
2601
+ variant: "outline",
2602
+ size: "sm",
2603
+ className: "h-7 text-xs",
2593
2604
  onClick: (e) => {
2594
2605
  e.stopPropagation();
2595
- setHeadersDiff(!headersDiff);
2596
- }
2606
+ onCompareWithPrevious();
2607
+ },
2608
+ children: [
2609
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3 mr-1" }),
2610
+ "Diff with Previous"
2611
+ ]
2597
2612
  }
2598
2613
  ) }),
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" })
2614
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Compare this request with the immediately preceding one" })
2612
2615
  ] }) }),
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,
2616
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2617
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2618
+ Button,
2627
2619
  {
2628
- text: log.responseText,
2629
- label: "Copy Response",
2630
- copied: responseCopied,
2631
- onCopy: handleCopyResponse
2620
+ variant: "outline",
2621
+ size: "sm",
2622
+ className: "h-7 text-xs",
2623
+ onClick: (e) => {
2624
+ e.stopPropagation();
2625
+ setReplayOpen(true);
2626
+ },
2627
+ children: [
2628
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3 mr-1" }),
2629
+ "Replay"
2630
+ ]
2632
2631
  }
2633
2632
  ) }),
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
- }
2633
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Re-send this request to the provider" })
2634
+ ] }) }),
2635
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2636
+ CopyButton,
2637
+ {
2638
+ text: displayedRequestBody,
2639
+ label: "Copy Request",
2640
+ copied: requestCopied,
2641
+ onCopy: handleCopyRequest
2642
+ }
2643
+ )
2644
+ ] }),
2645
+ requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2646
+ DiffView,
2647
+ {
2648
+ result: requestDiffResult,
2649
+ emptyLabel: "No transformation applied — raw and sent request bodies are identical."
2650
+ }
2651
+ ) : 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" })
2652
+ ] }) }),
2653
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2654
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
2655
+ viewMode,
2656
+ log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
2657
+ ) && /* @__PURE__ */ jsxRuntimeExports.jsx(
2658
+ DiffToggleButton,
2659
+ {
2660
+ active: headersDiff,
2661
+ onClick: (e) => {
2662
+ e.stopPropagation();
2663
+ setHeadersDiff(!headersDiff);
2664
+ }
2665
+ }
2666
+ ) }),
2667
+ headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2668
+ DiffView,
2669
+ {
2670
+ result: headersDiffResult,
2671
+ emptyLabel: "No transformation applied — raw and processed headers are identical."
2672
+ }
2673
+ ) : 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: [
2674
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2675
+ key,
2676
+ ":"
2677
+ ] }),
2678
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2679
+ ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
2680
+ ] }) }),
2681
+ 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: [
2682
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2683
+ key,
2684
+ ":"
2685
+ ] }),
2686
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2687
+ ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
2688
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
2689
+ 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: [
2690
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2691
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
2692
+ ] }),
2693
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2694
+ CopyButton,
2695
+ {
2696
+ text: log.responseText,
2697
+ label: "Copy Response",
2698
+ copied: responseCopied,
2699
+ onCopy: handleCopyResponse
2700
+ }
2701
+ ) }),
2702
+ 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" }),
2703
+ log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
2704
+ StreamingChunkSequence,
2705
+ {
2706
+ logId: log.id,
2707
+ truncated: log.streamingChunksPath !== null
2708
+ }
2709
+ )
2710
+ ] }) }),
2711
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2712
+ ResponseView,
2713
+ {
2714
+ responseText: log.responseText,
2715
+ responseStatus: log.responseStatus,
2716
+ streaming: log.streaming,
2717
+ inputTokens: log.inputTokens,
2718
+ outputTokens: log.outputTokens,
2719
+ cacheCreationInputTokens: log.cacheCreationInputTokens,
2720
+ cacheReadInputTokens: log.cacheReadInputTokens,
2721
+ apiFormat: log.apiFormat,
2722
+ error: log.error
2723
+ }
2724
+ ) }) })
2725
+ ] }) })
2726
+ ] }),
2727
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
2728
+ ] });
2729
+ });
2730
+ function ThreadConnector({
2731
+ stopReason,
2732
+ isPending,
2733
+ isFirst,
2734
+ isLast
2735
+ }) {
2736
+ const isBoundary = stopReason === "end_turn" || stopReason === "stop";
2737
+ const isToolUse = stopReason === "tool_use";
2738
+ cn(
2739
+ "w-0.5 bg-muted-foreground/30",
2740
+ isPending && "border-dashed bg-transparent border-l-2 border-muted-foreground/20"
2741
+ );
2742
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch h-full w-6 shrink-0 relative", children: [
2743
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute left-1/2 -translate-x-1/2 w-0.5 top-0 flex flex-col items-center", children: [
2744
+ !isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx(
2745
+ "div",
2746
+ {
2747
+ className: cn("w-0.5 grow", "bg-muted-foreground/30", "h-4"),
2748
+ style: { minHeight: "0.5rem" }
2749
+ }
2750
+ ),
2751
+ isFirst && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-4" })
2752
+ ] }),
2753
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-1/2 -translate-x-1/2 top-4 flex items-center justify-center z-10", children: isBoundary ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2754
+ "div",
2755
+ {
2756
+ className: cn(
2757
+ "size-2.5 rounded-full border-2",
2758
+ "bg-background border-amber-400",
2759
+ "shadow-[0_0_6px_rgba(251,191,36,0.4)]"
2760
+ ),
2761
+ title: stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"
2762
+ }
2763
+ ) : isToolUse ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2764
+ "div",
2765
+ {
2766
+ className: "size-2 rounded-full bg-muted-foreground/25",
2767
+ title: "Tool Use — turn continues"
2768
+ }
2769
+ ) : isPending ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2770
+ "div",
2771
+ {
2772
+ className: "size-2.5 rounded-full border-2 border-dashed border-muted-foreground/30 animate-pulse",
2773
+ title: "Response pending"
2774
+ }
2775
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "size-1.5 rounded-full bg-muted-foreground/30" }) }),
2776
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2777
+ "div",
2778
+ {
2779
+ className: "absolute left-1/2 -translate-x-1/2 w-0.5 top-4 flex flex-col items-center",
2780
+ style: { bottom: 0 },
2781
+ children: [
2782
+ !isBoundary && /* @__PURE__ */ jsxRuntimeExports.jsx(
2783
+ "div",
2784
+ {
2785
+ className: cn(
2786
+ "w-0.5 flex-1",
2787
+ isPending ? "border-dashed bg-transparent border-l-2 border-muted-foreground/20" : "bg-muted-foreground/30"
2641
2788
  )
2642
- ] }) }),
2643
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2644
- ResponseView,
2645
- {
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
2655
- }
2656
- ) }) })
2657
- ] }) })
2789
+ }
2790
+ ),
2791
+ isBoundary && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 h-4 bg-muted-foreground/10" })
2658
2792
  ]
2659
2793
  }
2660
- ),
2661
- /* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
2794
+ )
2662
2795
  ] });
2663
- });
2796
+ }
2664
2797
  function computeStats(logs) {
2665
2798
  let totalInput = 0;
2666
2799
  let totalOutput = 0;
@@ -2675,14 +2808,20 @@ const ConversationGroup = reactExports.memo(function({
2675
2808
  viewMode = "simple",
2676
2809
  strip,
2677
2810
  cacheTrends,
2678
- selectedSet,
2679
- onToggleSelect
2811
+ onCompareWithPrevious,
2812
+ defaultGroupViewMode = "thread"
2680
2813
  }) {
2681
2814
  const [expanded, setExpanded] = reactExports.useState(false);
2815
+ const [groupViewMode, setGroupViewMode] = reactExports.useState(defaultGroupViewMode);
2816
+ reactExports.useEffect(() => {
2817
+ setGroupViewMode(defaultGroupViewMode);
2818
+ }, [defaultGroupViewMode]);
2682
2819
  const stats = computeStats(group.logs);
2683
2820
  const startTime = group.logs[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2684
2821
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2685
2822
  const mixed = hasMixedApiFormat(group.logs);
2823
+ const isLoading = group.logs.some((log) => log.responseStatus === null);
2824
+ const stopReasons = reactExports.useMemo(() => group.logs.map((log) => extractStopReason(log)), [group.logs]);
2686
2825
  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
2826
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
2688
2827
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -2697,10 +2836,13 @@ const ConversationGroup = reactExports.memo(function({
2697
2836
  apiFormat: getGroupApiFormat(group.logs),
2698
2837
  expanded,
2699
2838
  onToggle: () => setExpanded(!expanded),
2700
- hideApiFormat: mixed
2839
+ hideApiFormat: mixed,
2840
+ isLoading,
2841
+ viewMode: groupViewMode,
2842
+ onToggleViewMode: () => setGroupViewMode((prev) => prev === "thread" ? "flat" : "thread")
2701
2843
  }
2702
2844
  ),
2703
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 border-l-2 border-muted ml-3", children: group.logs.map((log) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2845
+ expanded && groupViewMode === "flat" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 border-l-2 border-muted ml-3", children: group.logs.map((log) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2704
2846
  LogEntry,
2705
2847
  {
2706
2848
  log,
@@ -2708,11 +2850,32 @@ const ConversationGroup = reactExports.memo(function({
2708
2850
  suppressApiFormatBadge: !mixed,
2709
2851
  strip,
2710
2852
  cacheTrend: cacheTrends?.get(log.id) ?? null,
2711
- isSelected: selectedSet.has(log.id),
2712
- onToggleSelect
2853
+ onCompareWithPrevious: () => onCompareWithPrevious(log)
2713
2854
  },
2714
2855
  log.id
2715
- )) })
2856
+ )) }),
2857
+ expanded && groupViewMode === "thread" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-3", children: group.logs.map((log, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
2858
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2859
+ ThreadConnector,
2860
+ {
2861
+ stopReason: stopReasons[idx] ?? null,
2862
+ isPending: log.responseStatus === null,
2863
+ isFirst: idx === 0,
2864
+ isLast: idx === group.logs.length - 1
2865
+ }
2866
+ ),
2867
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2868
+ LogEntry,
2869
+ {
2870
+ log,
2871
+ viewMode,
2872
+ suppressApiFormatBadge: !mixed,
2873
+ strip,
2874
+ cacheTrend: cacheTrends?.get(log.id) ?? null,
2875
+ onCompareWithPrevious: () => onCompareWithPrevious(log)
2876
+ }
2877
+ ) })
2878
+ ] }, log.id)) })
2716
2879
  ] });
2717
2880
  });
2718
2881
  function CrabLogo({ className }) {
@@ -2760,7 +2923,7 @@ function SelectTrigger({
2760
2923
  ...props
2761
2924
  }) {
2762
2925
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2763
- Trigger,
2926
+ Trigger$1,
2764
2927
  {
2765
2928
  "data-slot": "select-trigger",
2766
2929
  "data-size": size,
@@ -2783,8 +2946,8 @@ function SelectContent({
2783
2946
  align = "center",
2784
2947
  ...props
2785
2948
  }) {
2786
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2787
- Content2,
2949
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2950
+ Content2$1,
2788
2951
  {
2789
2952
  "data-slot": "select-content",
2790
2953
  className: cn(
@@ -3208,20 +3371,26 @@ function TestStatus({ result }) {
3208
3371
  }
3209
3372
  if (result.cacheCreationInputTokens !== void 0 && result.cacheCreationInputTokens > 0) {
3210
3373
  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")
3374
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3375
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
3376
+ "+",
3377
+ result.cacheCreationInputTokens,
3378
+ " cache"
3379
+ ] }) }),
3380
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
3381
+ ] }) }, "cache-create")
3216
3382
  );
3217
3383
  }
3218
3384
  if (result.cacheReadInputTokens !== void 0 && result.cacheReadInputTokens > 0) {
3219
3385
  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")
3386
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3387
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
3388
+ "~",
3389
+ result.cacheReadInputTokens,
3390
+ " cached"
3391
+ ] }) }),
3392
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
3393
+ ] }) }, "cache-read")
3225
3394
  );
3226
3395
  }
3227
3396
  const displayTokens = [];
@@ -3229,15 +3398,18 @@ function TestStatus({ result }) {
3229
3398
  if (i > 0) displayTokens.push(", ");
3230
3399
  displayTokens.push(tokenParts[i]);
3231
3400
  }
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
- ] });
3401
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3402
+ /* @__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: [
3403
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3" }),
3404
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connected" }),
3405
+ tokenParts.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
3406
+ "(",
3407
+ displayTokens,
3408
+ ")"
3409
+ ] })
3410
+ ] }) }),
3411
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Connection test passed" })
3412
+ ] }) });
3241
3413
  }
3242
3414
  const error = result.error;
3243
3415
  const errorMessage = error?.message ?? "Connection failed";
@@ -3247,17 +3419,22 @@ function TestStatus({ result }) {
3247
3419
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 shrink-0", children: [
3248
3420
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-red-600 max-w-[200px]", children: [
3249
3421
  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
- )
3422
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3423
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage }) }),
3424
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Connection test failed" })
3425
+ ] }) }),
3426
+ errorDetails !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3427
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3428
+ "button",
3429
+ {
3430
+ type: "button",
3431
+ onClick: () => setShowDetails(!showDetails),
3432
+ className: "shrink-0 text-muted-foreground hover:text-foreground transition-colors",
3433
+ children: showDetails ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-3" })
3434
+ }
3435
+ ) }),
3436
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: showDetails ? "Hide error details" : "Show detailed error information" })
3437
+ ] }) })
3261
3438
  ] }),
3262
3439
  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
3440
  errorHint !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1", children: errorHint }),
@@ -3328,8 +3505,14 @@ function ProviderCard({
3328
3505
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
3329
3506
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
3330
3507
  /* @__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: "个人" })
3508
+ provider.source === "company" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3509
+ /* @__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: "公司" }) }),
3510
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Company-provided API key" })
3511
+ ] }) }),
3512
+ provider.source === "personal" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3513
+ /* @__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: "个人" }) }),
3514
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Your personal API key" })
3515
+ ] }) })
3333
3516
  ] }),
3334
3517
  docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3335
3518
  "a",
@@ -3808,24 +3991,30 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
3808
3991
  ] }),
3809
3992
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
3810
3993
  /* @__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
- )
3994
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3995
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3996
+ "button",
3997
+ {
3998
+ type: "button",
3999
+ onClick: () => setActiveTab("anthropic"),
4000
+ 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"}`,
4001
+ children: "Anthropic Format"
4002
+ }
4003
+ ) }),
4004
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Anthropic Messages API protocol" })
4005
+ ] }) }),
4006
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4007
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4008
+ "button",
4009
+ {
4010
+ type: "button",
4011
+ onClick: () => setActiveTab("openai"),
4012
+ 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"}`,
4013
+ children: "OpenAI Format"
4014
+ }
4015
+ ) }),
4016
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "OpenAI Chat Completions API protocol" })
4017
+ ] }) })
3829
4018
  ] }),
3830
4019
  errors.format !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.format })
3831
4020
  ] }),
@@ -4222,32 +4411,38 @@ function ProvidersPanel({
4222
4411
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between sticky top-0 z-10 bg-background pb-2", children: [
4223
4412
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-medium", children: "Providers" }),
4224
4413
  /* @__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
- ),
4414
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4415
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4416
+ Button,
4417
+ {
4418
+ variant: "outline",
4419
+ size: "sm",
4420
+ onClick: () => handleExport(),
4421
+ className: "gap-1 hover:bg-muted",
4422
+ children: [
4423
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
4424
+ "Export"
4425
+ ]
4426
+ }
4427
+ ) }),
4428
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Download providers as JSON for backup or sharing" })
4429
+ ] }) }),
4430
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4431
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4432
+ Button,
4433
+ {
4434
+ variant: "outline",
4435
+ size: "sm",
4436
+ onClick: handleImportClick,
4437
+ className: "gap-1 hover:bg-muted",
4438
+ children: [
4439
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
4440
+ "Import"
4441
+ ]
4442
+ }
4443
+ ) }),
4444
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Import providers from an exported JSON file" })
4445
+ ] }) }),
4251
4446
  /* @__PURE__ */ jsxRuntimeExports.jsx(
4252
4447
  "input",
4253
4448
  {
@@ -4283,21 +4478,23 @@ function ProvidersPanel({
4283
4478
  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
4479
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: "Config:" }),
4285
4480
  /* @__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
- )
4481
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4482
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4483
+ "button",
4484
+ {
4485
+ type: "button",
4486
+ onClick: () => {
4487
+ void window.navigator.clipboard.writeText(configPath).then(() => {
4488
+ setConfigPathCopied(true);
4489
+ setTimeout(() => setConfigPathCopied(false), 2e3);
4490
+ });
4491
+ },
4492
+ className: "shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors",
4493
+ children: configPathCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
4494
+ }
4495
+ ) }),
4496
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Copy config file path to clipboard" })
4497
+ ] }) })
4301
4498
  ] }),
4302
4499
  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
4500
  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "size-4 shrink-0" }),
@@ -4310,16 +4507,18 @@ function ProvidersPanel({
4310
4507
  "Add Your First Provider"
4311
4508
  ] })
4312
4509
  ] }) : /* @__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
- )) }),
4510
+ /* @__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: [
4511
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4512
+ "button",
4513
+ {
4514
+ type: "button",
4515
+ onClick: () => setSourceFilter(tab),
4516
+ 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"}`,
4517
+ children: tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"
4518
+ }
4519
+ ) }),
4520
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: tab === "all" ? "Show all providers" : tab === "personal" ? "Providers you configured yourself" : "Providers set by your organization" })
4521
+ ] }) }, tab)) }),
4323
4522
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: listScrollRef, className: "space-y-3", children: filteredProviders.map((provider) => /* @__PURE__ */ jsxRuntimeExports.jsx(
4324
4523
  ProviderCard,
4325
4524
  {
@@ -4490,22 +4689,25 @@ function ProxySettingsTab() {
4490
4689
  " and overrides the env var for subsequent requests."
4491
4690
  ] })
4492
4691
  ] }),
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
- ] }),
4692
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4693
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-3", children: [
4694
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4695
+ "input",
4696
+ {
4697
+ type: "checkbox",
4698
+ role: "switch",
4699
+ checked: strip,
4700
+ disabled: isLoading || pending,
4701
+ onChange: (e) => {
4702
+ void handleToggle(e.currentTarget.checked);
4703
+ },
4704
+ className: "size-4 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
4705
+ }
4706
+ ),
4707
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
4708
+ ] }) }),
4709
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Strip Claude Code billing header to improve cache hit rates" })
4710
+ ] }) }),
4509
4711
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
4510
4712
  "Failed to save: ",
4511
4713
  error
@@ -5473,9 +5675,8 @@ function ProxyViewer({
5473
5675
  }) {
5474
5676
  const { totalIn, totalOut } = computeTokenSummary(logs);
5475
5677
  const [groupedView, setGroupedView] = reactExports.useState(true);
5678
+ const [groupViewMode, setGroupViewMode] = reactExports.useState("thread");
5476
5679
  const [exporting, setExporting] = reactExports.useState(false);
5477
- const [selectedLogIds, setSelectedLogIds] = reactExports.useState([]);
5478
- const [compareOpen, setCompareOpen] = reactExports.useState(false);
5479
5680
  const [comparePair, setComparePair] = reactExports.useState(null);
5480
5681
  const handleExport = reactExports.useCallback(async () => {
5481
5682
  setExporting(true);
@@ -5486,76 +5687,26 @@ function ProxyViewer({
5486
5687
  }
5487
5688
  }, [logs]);
5488
5689
  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
5690
  reactExports.useEffect(() => {
5503
- setSelectedLogIds([]);
5504
- setCompareOpen(false);
5691
+ setComparePair(null);
5505
5692
  }, [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
5693
  const closeCompare = reactExports.useCallback(() => {
5518
- setCompareOpen(false);
5519
- }, []);
5520
- const clearSelection = reactExports.useCallback(() => {
5521
- setSelectedLogIds([]);
5694
+ setComparePair(null);
5522
5695
  }, []);
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
- }
5696
+ const handleCompareWithPrevious = reactExports.useCallback(
5697
+ (log) => {
5698
+ const idx = logs.indexOf(log);
5699
+ if (idx <= 0) return;
5700
+ const predecessor = logs[idx - 1];
5701
+ if (predecessor === void 0) return;
5702
+ setComparePair([predecessor, log]);
5703
+ },
5704
+ [logs]
5705
+ );
5556
5706
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
5557
5707
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
5558
- const renderGroups = logs.length > 0 && groupedView && !(groups.length === 1 && groups[0]?.logs.length === logs.length);
5708
+ const stopReasons = reactExports.useMemo(() => logs.map((log) => extractStopReason(log)), [logs]);
5709
+ const renderGroups = logs.length > 0 && groupedView && groups.length > 1;
5559
5710
  const rowVirtualizer = useVirtualizer({
5560
5711
  count: renderGroups ? groups.length : logs.length,
5561
5712
  getScrollElement: () => parentRef.current,
@@ -5575,26 +5726,29 @@ function ProxyViewer({
5575
5726
  ] })
5576
5727
  ] })
5577
5728
  ] }),
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
- ] }),
5729
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
5730
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5731
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5732
+ "button",
5733
+ {
5734
+ type: "button",
5735
+ onClick: () => onViewModeChange("simple"),
5736
+ className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5737
+ children: "Simple"
5738
+ }
5739
+ ),
5740
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5741
+ "button",
5742
+ {
5743
+ type: "button",
5744
+ onClick: () => onViewModeChange("full"),
5745
+ className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5746
+ children: "Full"
5747
+ }
5748
+ )
5749
+ ] }) }),
5750
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Simple shows parsed output; Full adds raw headers and tokens" })
5751
+ ] }) }),
5598
5752
  /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}),
5599
5753
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5600
5754
  logs.length,
@@ -5665,6 +5819,28 @@ function ProxyViewer({
5665
5819
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5666
5820
  }
5667
5821
  )
5822
+ ] }),
5823
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5824
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5825
+ "button",
5826
+ {
5827
+ type: "button",
5828
+ onClick: () => setGroupViewMode("thread"),
5829
+ className: `px-2 py-1.5 cursor-pointer transition-colors ${groupViewMode === "thread" ? "bg-amber-500/15 text-amber-400 border-r border-amber-500/30" : "text-muted-foreground hover:bg-muted/50"}`,
5830
+ title: "Thread view (connected timeline)",
5831
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "size-4" })
5832
+ }
5833
+ ),
5834
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5835
+ "button",
5836
+ {
5837
+ type: "button",
5838
+ onClick: () => setGroupViewMode("flat"),
5839
+ className: `px-2 py-1.5 cursor-pointer transition-colors ${groupViewMode === "flat" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5840
+ title: "Flat view (card list)",
5841
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5842
+ }
5843
+ )
5668
5844
  ] })
5669
5845
  ] }),
5670
5846
  /* @__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 +5878,8 @@ function ProxyViewer({
5702
5878
  viewMode,
5703
5879
  strip,
5704
5880
  cacheTrends,
5705
- selectedSet,
5706
- onToggleSelect: handleToggleSelect
5881
+ onCompareWithPrevious: handleCompareWithPrevious,
5882
+ defaultGroupViewMode: groupViewMode
5707
5883
  }
5708
5884
  )
5709
5885
  },
@@ -5712,6 +5888,7 @@ function ProxyViewer({
5712
5888
  } else {
5713
5889
  const log = logs[virtualRow.index];
5714
5890
  if (log === void 0) return null;
5891
+ const idx = virtualRow.index;
5715
5892
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
5716
5893
  "div",
5717
5894
  {
@@ -5724,15 +5901,34 @@ function ProxyViewer({
5724
5901
  width: "100%",
5725
5902
  transform: `translateY(${virtualRow.start}px)`
5726
5903
  },
5727
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5904
+ children: groupViewMode === "thread" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch ml-3", children: [
5905
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5906
+ ThreadConnector,
5907
+ {
5908
+ stopReason: stopReasons[idx] ?? null,
5909
+ isPending: log.responseStatus === null,
5910
+ isFirst: idx === 0,
5911
+ isLast: idx === logs.length - 1
5912
+ }
5913
+ ),
5914
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5915
+ LogEntry,
5916
+ {
5917
+ log,
5918
+ viewMode,
5919
+ strip,
5920
+ cacheTrend: cacheTrends.get(log.id) ?? null,
5921
+ onCompareWithPrevious: () => handleCompareWithPrevious(log)
5922
+ }
5923
+ ) })
5924
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5728
5925
  LogEntry,
5729
5926
  {
5730
5927
  log,
5731
5928
  viewMode,
5732
5929
  strip,
5733
5930
  cacheTrend: cacheTrends.get(log.id) ?? null,
5734
- isSelected: selectedSet.has(log.id),
5735
- onToggleSelect: handleToggleSelect
5931
+ onCompareWithPrevious: () => handleCompareWithPrevious(log)
5736
5932
  }
5737
5933
  )
5738
5934
  },
@@ -5742,40 +5938,7 @@ function ProxyViewer({
5742
5938
  })
5743
5939
  }
5744
5940
  ) }) }),
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 })
5941
+ comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
5779
5942
  ] });
5780
5943
  }
5781
5944
  object({