@tonyclaw/llm-inspector 1.14.6 → 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 (31) 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-Dp5657Eq.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/_libs/radix-ui__react-tooltip.mjs +1 -1
  8. package/.output/server/_ssr/{index-D2yS8VvO.mjs → index-9uTJ4xYR.mjs} +813 -595
  9. package/.output/server/_ssr/index.mjs +2 -2
  10. package/.output/server/_ssr/{router-DCfjmmJu.mjs → router-BKnjB_zi.mjs} +2 -2
  11. package/.output/server/{_tanstack-start-manifest_v-DupqJc5d.mjs → _tanstack-start-manifest_v-IsglLVKy.mjs} +1 -1
  12. package/.output/server/index.mjs +26 -26
  13. package/package.json +1 -1
  14. package/src/components/ProxyViewer.tsx +114 -146
  15. package/src/components/providers/ImportWizardDialog.tsx +6 -0
  16. package/src/components/providers/ProviderCard.tsx +79 -26
  17. package/src/components/providers/ProviderForm.tsx +37 -22
  18. package/src/components/providers/ProvidersPanel.tsx +118 -58
  19. package/src/components/providers/SettingsDialog.tsx +25 -15
  20. package/src/components/proxy-viewer/ConversationGroup.tsx +50 -10
  21. package/src/components/proxy-viewer/ConversationHeader.tsx +48 -2
  22. package/src/components/proxy-viewer/LogEntry.tsx +116 -45
  23. package/src/components/proxy-viewer/LogEntryHeader.tsx +89 -71
  24. package/src/components/proxy-viewer/ReplayDialog.tsx +16 -6
  25. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +24 -16
  26. package/src/components/proxy-viewer/ThreadConnector.tsx +104 -0
  27. package/src/components/proxy-viewer/index.ts +2 -1
  28. package/src/components/ui/confirm-dialog.tsx +51 -0
  29. package/src/lib/stopReason.ts +57 -0
  30. package/.output/public/assets/index-BFNoWwFI.css +0 -1
  31. package/.output/public/assets/index-LH-YtFEM.js +0 -105
@@ -1,21 +1,21 @@
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-DCfjmmJu.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";
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";
17
18
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
18
- 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";
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";
21
21
  import { R as Root$3, V as Viewport$1, C as Corner, S as ScrollAreaScrollbar, a as ScrollAreaThumb } from "../_libs/radix-ui__react-scroll-area.mjs";
@@ -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.6";
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(
@@ -3015,6 +3178,10 @@ function ImportWizardDialog({
3015
3178
  return;
3016
3179
  }
3017
3180
  const result = parsed.data;
3181
+ setProviders(
3182
+ (prev) => prev.map((p, i) => selected.has(i) ? { ...p, alreadyExists: true } : p)
3183
+ );
3184
+ setSelected(/* @__PURE__ */ new Set());
3018
3185
  if (result.errors !== void 0 && result.errors.length > 0 && result.message !== void 0) {
3019
3186
  setImportResult(result.message);
3020
3187
  } else {
@@ -3109,6 +3276,35 @@ function ImportWizardDialog({
3109
3276
  ] })
3110
3277
  ] }) });
3111
3278
  }
3279
+ function ConfirmDialog({
3280
+ open,
3281
+ onOpenChange,
3282
+ title,
3283
+ description,
3284
+ confirmLabel = "Confirm",
3285
+ variant = "default",
3286
+ onConfirm
3287
+ }) {
3288
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
3289
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: title }) }),
3290
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: description }),
3291
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
3292
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }),
3293
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3294
+ Button,
3295
+ {
3296
+ variant,
3297
+ size: "sm",
3298
+ onClick: () => {
3299
+ onConfirm();
3300
+ onOpenChange(false);
3301
+ },
3302
+ children: confirmLabel
3303
+ }
3304
+ )
3305
+ ] })
3306
+ ] }) });
3307
+ }
3112
3308
  function maskApiKey(apiKey) {
3113
3309
  if (apiKey.length <= 8) return "••••••••";
3114
3310
  return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
@@ -3175,20 +3371,26 @@ function TestStatus({ result }) {
3175
3371
  }
3176
3372
  if (result.cacheCreationInputTokens !== void 0 && result.cacheCreationInputTokens > 0) {
3177
3373
  tokenParts.push(
3178
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
3179
- "+",
3180
- result.cacheCreationInputTokens,
3181
- " cache"
3182
- ] }, "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")
3183
3382
  );
3184
3383
  }
3185
3384
  if (result.cacheReadInputTokens !== void 0 && result.cacheReadInputTokens > 0) {
3186
3385
  tokenParts.push(
3187
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
3188
- "~",
3189
- result.cacheReadInputTokens,
3190
- " cached"
3191
- ] }, "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")
3192
3394
  );
3193
3395
  }
3194
3396
  const displayTokens = [];
@@ -3196,15 +3398,18 @@ function TestStatus({ result }) {
3196
3398
  if (i > 0) displayTokens.push(", ");
3197
3399
  displayTokens.push(tokenParts[i]);
3198
3400
  }
3199
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600 shrink-0", children: [
3200
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3" }),
3201
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connected" }),
3202
- tokenParts.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
3203
- "(",
3204
- displayTokens,
3205
- ")"
3206
- ] })
3207
- ] });
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
+ ] }) });
3208
3413
  }
3209
3414
  const error = result.error;
3210
3415
  const errorMessage = error?.message ?? "Connection failed";
@@ -3214,17 +3419,22 @@ function TestStatus({ result }) {
3214
3419
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 shrink-0", children: [
3215
3420
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-red-600 max-w-[200px]", children: [
3216
3421
  getErrorIcon(errorType),
3217
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage }),
3218
- errorDetails !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
3219
- "button",
3220
- {
3221
- type: "button",
3222
- onClick: () => setShowDetails(!showDetails),
3223
- className: "shrink-0 text-muted-foreground hover:text-foreground transition-colors",
3224
- title: showDetails ? "Hide details" : "Show details",
3225
- children: showDetails ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-3" })
3226
- }
3227
- )
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
+ ] }) })
3228
3438
  ] }),
3229
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: [
3230
3440
  errorHint !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1", children: errorHint }),
@@ -3295,8 +3505,14 @@ function ProviderCard({
3295
3505
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
3296
3506
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
3297
3507
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: provider.name }),
3298
- 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: "公司" }),
3299
- 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
+ ] }) })
3300
3516
  ] }),
3301
3517
  docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3302
3518
  "a",
@@ -3775,24 +3991,30 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
3775
3991
  ] }),
3776
3992
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
3777
3993
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 border-b border-border", children: [
3778
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3779
- "button",
3780
- {
3781
- type: "button",
3782
- onClick: () => setActiveTab("anthropic"),
3783
- 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"}`,
3784
- children: "Anthropic Format"
3785
- }
3786
- ),
3787
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3788
- "button",
3789
- {
3790
- type: "button",
3791
- onClick: () => setActiveTab("openai"),
3792
- 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"}`,
3793
- children: "OpenAI Format"
3794
- }
3795
- )
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
+ ] }) })
3796
4018
  ] }),
3797
4019
  errors.format !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.format })
3798
4020
  ] }),
@@ -3900,6 +4122,8 @@ function ProvidersPanel({
3900
4122
  const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
3901
4123
  const [highlightedProviderId, setHighlightedProviderId] = reactExports.useState(null);
3902
4124
  const [showImportWizard, setShowImportWizard] = reactExports.useState(false);
4125
+ const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
4126
+ const [deletingProviderId, setDeletingProviderId] = reactExports.useState(null);
3903
4127
  const [sourceFilter, setSourceFilter] = reactExports.useState("all");
3904
4128
  const listScrollRef = reactExports.useRef(null);
3905
4129
  const highlightTimeoutRef = reactExports.useRef(null);
@@ -4088,7 +4312,12 @@ function ProvidersPanel({
4088
4312
  })();
4089
4313
  }
4090
4314
  function handleDeleteProvider(providerId) {
4091
- if (!window.confirm("Are you sure you want to delete this provider?")) return;
4315
+ setDeletingProviderId(providerId);
4316
+ setConfirmOpen(true);
4317
+ }
4318
+ function executeDelete() {
4319
+ const providerId = deletingProviderId;
4320
+ if (providerId === null) return;
4092
4321
  void (async () => {
4093
4322
  let res;
4094
4323
  try {
@@ -4182,32 +4411,38 @@ function ProvidersPanel({
4182
4411
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between sticky top-0 z-10 bg-background pb-2", children: [
4183
4412
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-medium", children: "Providers" }),
4184
4413
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
4185
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
4186
- Button,
4187
- {
4188
- variant: "outline",
4189
- size: "sm",
4190
- onClick: () => handleExport(),
4191
- className: "gap-1 hover:bg-muted",
4192
- children: [
4193
- /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
4194
- "Export"
4195
- ]
4196
- }
4197
- ),
4198
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
4199
- Button,
4200
- {
4201
- variant: "outline",
4202
- size: "sm",
4203
- onClick: handleImportClick,
4204
- className: "gap-1 hover:bg-muted",
4205
- children: [
4206
- /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
4207
- "Import"
4208
- ]
4209
- }
4210
- ),
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
+ ] }) }),
4211
4446
  /* @__PURE__ */ jsxRuntimeExports.jsx(
4212
4447
  "input",
4213
4448
  {
@@ -4218,19 +4453,22 @@ function ProvidersPanel({
4218
4453
  style: { display: "none" }
4219
4454
  }
4220
4455
  ),
4221
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
4222
- Button,
4223
- {
4224
- variant: "outline",
4225
- size: "sm",
4226
- onClick: () => setShowImportWizard(true),
4227
- className: "gap-1",
4228
- children: [
4229
- /* @__PURE__ */ jsxRuntimeExports.jsx(Scan, { className: "size-3" }),
4230
- "Scan"
4231
- ]
4232
- }
4233
- ),
4456
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4457
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4458
+ Button,
4459
+ {
4460
+ variant: "outline",
4461
+ size: "sm",
4462
+ onClick: () => setShowImportWizard(true),
4463
+ className: "gap-1",
4464
+ children: [
4465
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Scan, { className: "size-3" }),
4466
+ "Scan"
4467
+ ]
4468
+ }
4469
+ ) }),
4470
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Detect and import provider configs from Claude Code and OpenCode" })
4471
+ ] }) }),
4234
4472
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: () => setShowForm(true), size: "sm", className: "gap-1", children: [
4235
4473
  /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4" }),
4236
4474
  "Add Provider"
@@ -4240,21 +4478,23 @@ function ProvidersPanel({
4240
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: [
4241
4479
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: "Config:" }),
4242
4480
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate", title: configPath, children: configPath }),
4243
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4244
- "button",
4245
- {
4246
- type: "button",
4247
- onClick: () => {
4248
- void window.navigator.clipboard.writeText(configPath).then(() => {
4249
- setConfigPathCopied(true);
4250
- setTimeout(() => setConfigPathCopied(false), 2e3);
4251
- });
4252
- },
4253
- className: "shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors",
4254
- title: "Copy path",
4255
- children: configPathCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
4256
- }
4257
- )
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
+ ] }) })
4258
4498
  ] }),
4259
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: [
4260
4500
  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "size-4 shrink-0" }),
@@ -4267,16 +4507,18 @@ function ProvidersPanel({
4267
4507
  "Add Your First Provider"
4268
4508
  ] })
4269
4509
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
4270
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 border-b border-border", children: ["all", "personal", "company"].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
4271
- "button",
4272
- {
4273
- type: "button",
4274
- onClick: () => setSourceFilter(tab),
4275
- 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"}`,
4276
- children: tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"
4277
- },
4278
- tab
4279
- )) }),
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)) }),
4280
4522
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: listScrollRef, className: "space-y-3", children: filteredProviders.map((provider) => /* @__PURE__ */ jsxRuntimeExports.jsx(
4281
4523
  ProviderCard,
4282
4524
  {
@@ -4305,6 +4547,18 @@ function ProvidersPanel({
4305
4547
  }
4306
4548
  }
4307
4549
  }
4550
+ ),
4551
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4552
+ ConfirmDialog,
4553
+ {
4554
+ open: confirmOpen,
4555
+ onOpenChange: setConfirmOpen,
4556
+ title: "Delete Provider",
4557
+ description: "Are you sure you want to delete this provider? This action cannot be undone.",
4558
+ confirmLabel: "Delete",
4559
+ variant: "destructive",
4560
+ onConfirm: executeDelete
4561
+ }
4308
4562
  )
4309
4563
  ] });
4310
4564
  }
@@ -4435,22 +4689,25 @@ function ProxySettingsTab() {
4435
4689
  " and overrides the env var for subsequent requests."
4436
4690
  ] })
4437
4691
  ] }),
4438
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-3", children: [
4439
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4440
- "input",
4441
- {
4442
- type: "checkbox",
4443
- role: "switch",
4444
- checked: strip,
4445
- disabled: isLoading || pending,
4446
- onChange: (e) => {
4447
- void handleToggle(e.currentTarget.checked);
4448
- },
4449
- className: "size-4 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
4450
- }
4451
- ),
4452
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
4453
- ] }),
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
+ ] }) }),
4454
4711
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
4455
4712
  "Failed to save: ",
4456
4713
  error
@@ -5418,9 +5675,8 @@ function ProxyViewer({
5418
5675
  }) {
5419
5676
  const { totalIn, totalOut } = computeTokenSummary(logs);
5420
5677
  const [groupedView, setGroupedView] = reactExports.useState(true);
5678
+ const [groupViewMode, setGroupViewMode] = reactExports.useState("thread");
5421
5679
  const [exporting, setExporting] = reactExports.useState(false);
5422
- const [selectedLogIds, setSelectedLogIds] = reactExports.useState([]);
5423
- const [compareOpen, setCompareOpen] = reactExports.useState(false);
5424
5680
  const [comparePair, setComparePair] = reactExports.useState(null);
5425
5681
  const handleExport = reactExports.useCallback(async () => {
5426
5682
  setExporting(true);
@@ -5431,76 +5687,26 @@ function ProxyViewer({
5431
5687
  }
5432
5688
  }, [logs]);
5433
5689
  const parentRef = reactExports.useRef(null);
5434
- const handleToggleSelect = reactExports.useCallback((logId) => {
5435
- setSelectedLogIds((prev) => {
5436
- if (prev.includes(logId)) {
5437
- return prev.filter((id) => id !== logId);
5438
- }
5439
- if (prev.length < 2) {
5440
- return [...prev, logId];
5441
- }
5442
- const newer = prev[1];
5443
- if (newer === void 0) return prev;
5444
- return [newer, logId];
5445
- });
5446
- }, []);
5447
5690
  reactExports.useEffect(() => {
5448
- setSelectedLogIds([]);
5449
- setCompareOpen(false);
5691
+ setComparePair(null);
5450
5692
  }, [selectedSession, selectedModel]);
5451
- const selectedSet = reactExports.useMemo(() => new Set(selectedLogIds), [selectedLogIds]);
5452
- const openCompare = reactExports.useCallback(() => {
5453
- if (selectedLogIds.length !== 2) return;
5454
- const [idA, idB] = selectedLogIds;
5455
- if (idA === void 0 || idB === void 0) return;
5456
- const logA = logs.find((l) => l.id === idA);
5457
- const logB = logs.find((l) => l.id === idB);
5458
- if (logA === void 0 || logB === void 0) return;
5459
- setComparePair([logA, logB]);
5460
- setCompareOpen(true);
5461
- }, [selectedLogIds, logs]);
5462
5693
  const closeCompare = reactExports.useCallback(() => {
5463
- setCompareOpen(false);
5694
+ setComparePair(null);
5464
5695
  }, []);
5465
- const clearSelection = reactExports.useCallback(() => {
5466
- setSelectedLogIds([]);
5467
- }, []);
5468
- const selectedSummary = reactExports.useMemo(() => {
5469
- if (selectedLogIds.length !== 2) return null;
5470
- const [idA, idB] = selectedLogIds;
5471
- if (idA === void 0 || idB === void 0) return null;
5472
- const logA = logs.find((l) => l.id === idA);
5473
- const logB = logs.find((l) => l.id === idB);
5474
- if (logA === void 0 || logB === void 0) return null;
5475
- const sameSession = getConversationId(logA) === getConversationId(logB);
5476
- let elapsed = "";
5477
- if (logA.timestamp !== null && logB.timestamp !== null) {
5478
- const a = Date.parse(logA.timestamp);
5479
- const b = Date.parse(logB.timestamp);
5480
- if (!Number.isNaN(a) && !Number.isNaN(b)) {
5481
- const ms = Math.abs(b - a);
5482
- elapsed = formatElapsed2(ms);
5483
- }
5484
- }
5485
- return {
5486
- logA,
5487
- logB,
5488
- sameSession,
5489
- elapsed
5490
- };
5491
- }, [selectedLogIds, logs]);
5492
- function formatElapsed2(ms) {
5493
- if (ms < 1e3) return `${ms}ms`;
5494
- const sec = Math.floor(ms / 1e3);
5495
- if (sec < 60) return `${sec}s`;
5496
- const min = Math.floor(sec / 60);
5497
- if (min < 60) return `${min}m`;
5498
- const hr = Math.floor(min / 60);
5499
- return `${hr}h${min % 60}m`;
5500
- }
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
+ );
5501
5706
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
5502
5707
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
5503
- 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;
5504
5710
  const rowVirtualizer = useVirtualizer({
5505
5711
  count: renderGroups ? groups.length : logs.length,
5506
5712
  getScrollElement: () => parentRef.current,
@@ -5520,26 +5726,29 @@ function ProxyViewer({
5520
5726
  ] })
5521
5727
  ] })
5522
5728
  ] }),
5523
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
5524
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5525
- "button",
5526
- {
5527
- type: "button",
5528
- onClick: () => onViewModeChange("simple"),
5529
- className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "simple" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5530
- children: "Simple"
5531
- }
5532
- ),
5533
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5534
- "button",
5535
- {
5536
- type: "button",
5537
- onClick: () => onViewModeChange("full"),
5538
- className: `px-2 py-1 cursor-pointer transition-colors text-xs ${viewMode === "full" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`,
5539
- children: "Full"
5540
- }
5541
- )
5542
- ] }),
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
+ ] }) }),
5543
5752
  /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {}),
5544
5753
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5545
5754
  logs.length,
@@ -5610,6 +5819,28 @@ function ProxyViewer({
5610
5819
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" })
5611
5820
  }
5612
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
+ )
5613
5844
  ] })
5614
5845
  ] }),
5615
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: [
@@ -5647,8 +5878,8 @@ function ProxyViewer({
5647
5878
  viewMode,
5648
5879
  strip,
5649
5880
  cacheTrends,
5650
- selectedSet,
5651
- onToggleSelect: handleToggleSelect
5881
+ onCompareWithPrevious: handleCompareWithPrevious,
5882
+ defaultGroupViewMode: groupViewMode
5652
5883
  }
5653
5884
  )
5654
5885
  },
@@ -5657,6 +5888,7 @@ function ProxyViewer({
5657
5888
  } else {
5658
5889
  const log = logs[virtualRow.index];
5659
5890
  if (log === void 0) return null;
5891
+ const idx = virtualRow.index;
5660
5892
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
5661
5893
  "div",
5662
5894
  {
@@ -5669,15 +5901,34 @@ function ProxyViewer({
5669
5901
  width: "100%",
5670
5902
  transform: `translateY(${virtualRow.start}px)`
5671
5903
  },
5672
- 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(
5673
5925
  LogEntry,
5674
5926
  {
5675
5927
  log,
5676
5928
  viewMode,
5677
5929
  strip,
5678
5930
  cacheTrend: cacheTrends.get(log.id) ?? null,
5679
- isSelected: selectedSet.has(log.id),
5680
- onToggleSelect: handleToggleSelect
5931
+ onCompareWithPrevious: () => handleCompareWithPrevious(log)
5681
5932
  }
5682
5933
  )
5683
5934
  },
@@ -5687,40 +5938,7 @@ function ProxyViewer({
5687
5938
  })
5688
5939
  }
5689
5940
  ) }) }),
5690
- 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: [
5691
- /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-4 text-amber-400 shrink-0" }),
5692
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-mono", children: [
5693
- "#",
5694
- selectedSummary.logA.id,
5695
- " ↔ #",
5696
- selectedSummary.logB.id,
5697
- " · ",
5698
- selectedSummary.sameSession ? "same session" : "different sessions",
5699
- selectedSummary.elapsed !== "" && ` · ${selectedSummary.elapsed} apart`
5700
- ] }),
5701
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
5702
- "button",
5703
- {
5704
- type: "button",
5705
- onClick: clearSelection,
5706
- className: "text-muted-foreground hover:text-foreground transition-colors cursor-pointer inline-flex items-center gap-1",
5707
- children: [
5708
- /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-3" }),
5709
- "Clear"
5710
- ]
5711
- }
5712
- ),
5713
- /* @__PURE__ */ jsxRuntimeExports.jsx(
5714
- "button",
5715
- {
5716
- type: "button",
5717
- onClick: openCompare,
5718
- className: "bg-amber-400 text-amber-950 hover:bg-amber-300 transition-colors px-3 py-1 rounded font-medium cursor-pointer",
5719
- children: "Compare 2 logs"
5720
- }
5721
- )
5722
- ] }),
5723
- 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 })
5724
5942
  ] });
5725
5943
  }
5726
5944
  object({