@tonyclaw/llm-inspector 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/index-B0anmGQr.css +1 -0
- package/.output/public/assets/index-H_thmL2_.js +105 -0
- package/.output/public/assets/{main-BYCM7aJx.js → main-C3tLo75s.js} +3 -3
- package/.output/server/_libs/lucide-react.mjs +4 -4
- package/.output/server/_ssr/{index-DhChP_jV.mjs → index-C8VC13EA.mjs} +781 -163
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-PZjNwOcw.mjs → router-D5ccnemB.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-l1kWkG0h.mjs → _tanstack-start-manifest_v-DUbXa1lt.mjs} +1 -1
- package/.output/server/index.mjs +26 -26
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +126 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +388 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +8 -0
- package/src/components/proxy-viewer/LogEntry.tsx +14 -1
- package/src/components/proxy-viewer/LogEntryHeader.tsx +28 -0
- package/src/components/proxy-viewer/requestDiff.ts +277 -0
- package/.output/public/assets/index-DVgdkDgq.js +0 -105
- package/.output/public/assets/index-DZx2yk8v.css +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
|
|
2
|
-
import { C as CapturedLogSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, P as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, S as StreamingChunkSchema$1 } from "./router-
|
|
2
|
+
import { C as CapturedLogSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, P as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, S as StreamingChunkSchema$1 } from "./router-D5ccnemB.mjs";
|
|
3
3
|
import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
|
|
4
4
|
import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
|
|
5
5
|
import { J as JSZip } from "../_libs/jszip.mjs";
|
|
@@ -9,7 +9,7 @@ import { c as cva } from "../_libs/class-variance-authority.mjs";
|
|
|
9
9
|
import { d as diffLines, a as diffJson } from "../_libs/diff.mjs";
|
|
10
10
|
import { R as Root, T as Trigger$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
11
|
import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
|
|
12
|
-
import { D as Download, L as LayoutGrid, a as List, S as Settings, C as ChevronDown, b as Check, R as RotateCcw,
|
|
12
|
+
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, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, f as ChevronRight, g as Clock, M as MessageSquare, Z as Zap, h as LoaderCircle, W as Wrench, i as Globe, j as User, F as FileTerminal, k as Radio, l as Rows3, m as Columns2, E as ExternalLink, n as EyeOff, o as Eye, p as RotateCw, q as Pencil, T as Trash2, A as ArrowUp, r as ArrowDown, s as TriangleAlert, t as Minus, u as CircleCheckBig, v as CircleStop, w as CircleQuestionMark, x as Server, y as Gauge, z as Lock, B as Wifi, H as WifiOff, I as ChevronsUp, J as ChevronsDown, K as Brain, N as Terminal } from "../_libs/lucide-react.mjs";
|
|
13
13
|
import { M as Markdown } from "../_libs/react-markdown.mjs";
|
|
14
14
|
import { a as array, s as string, u as union, o as object, l as literal, n as number, b as boolean, r as record, _ as _enum } from "../_libs/zod.mjs";
|
|
15
15
|
import { R as Root2$1, L as List$1, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
|
|
@@ -275,7 +275,7 @@ async function exportLogsAsZip(logs) {
|
|
|
275
275
|
document.body.removeChild(anchor);
|
|
276
276
|
URL.revokeObjectURL(url);
|
|
277
277
|
}
|
|
278
|
-
const version = "1.
|
|
278
|
+
const version = "1.13.0";
|
|
279
279
|
const packageJson = {
|
|
280
280
|
version
|
|
281
281
|
};
|
|
@@ -1414,7 +1414,9 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1414
1414
|
expanded,
|
|
1415
1415
|
onToggle,
|
|
1416
1416
|
suppressApiFormatBadge = false,
|
|
1417
|
-
cacheTrend = null
|
|
1417
|
+
cacheTrend = null,
|
|
1418
|
+
isSelected = false,
|
|
1419
|
+
onToggleSelect
|
|
1418
1420
|
}) {
|
|
1419
1421
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
1420
1422
|
const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
|
|
@@ -1438,6 +1440,23 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1438
1440
|
}
|
|
1439
1441
|
},
|
|
1440
1442
|
children: [
|
|
1443
|
+
onToggleSelect !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1444
|
+
"button",
|
|
1445
|
+
{
|
|
1446
|
+
type: "button",
|
|
1447
|
+
onClick: (e) => {
|
|
1448
|
+
e.stopPropagation();
|
|
1449
|
+
onToggleSelect(log.id);
|
|
1450
|
+
},
|
|
1451
|
+
"aria-label": isSelected ? "Deselect for comparison" : "Select for comparison",
|
|
1452
|
+
"aria-pressed": isSelected,
|
|
1453
|
+
className: cn(
|
|
1454
|
+
"shrink-0 size-4 rounded-sm border flex items-center justify-center transition-colors cursor-pointer",
|
|
1455
|
+
isSelected ? "bg-amber-400 border-amber-400 text-amber-950" : "border-muted-foreground/40 hover:border-amber-400 hover:bg-amber-400/10"
|
|
1456
|
+
),
|
|
1457
|
+
children: isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3", strokeWidth: 3 })
|
|
1458
|
+
}
|
|
1459
|
+
),
|
|
1441
1460
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0", children: [
|
|
1442
1461
|
"#",
|
|
1443
1462
|
log.id
|
|
@@ -2385,7 +2404,9 @@ const LogEntry = reactExports.memo(function({
|
|
|
2385
2404
|
viewMode = "simple",
|
|
2386
2405
|
suppressApiFormatBadge = false,
|
|
2387
2406
|
strip,
|
|
2388
|
-
cacheTrend = null
|
|
2407
|
+
cacheTrend = null,
|
|
2408
|
+
isSelected = false,
|
|
2409
|
+
onToggleSelect
|
|
2389
2410
|
}) {
|
|
2390
2411
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2391
2412
|
const [requestCopied, setRequestCopied] = reactExports.useState(false);
|
|
@@ -2435,164 +2456,175 @@ const LogEntry = reactExports.memo(function({
|
|
|
2435
2456
|
});
|
|
2436
2457
|
}
|
|
2437
2458
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
2438
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
}
|
|
2449
|
-
),
|
|
2450
|
-
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
|
|
2451
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
|
|
2452
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }),
|
|
2453
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }),
|
|
2454
|
-
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }),
|
|
2455
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
|
|
2456
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
|
|
2457
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
|
|
2458
|
-
] }),
|
|
2459
|
-
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2460
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2461
|
-
CopyButton,
|
|
2459
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2460
|
+
"div",
|
|
2461
|
+
{
|
|
2462
|
+
className: cn(
|
|
2463
|
+
"border border-border rounded-lg mb-3 overflow-hidden",
|
|
2464
|
+
isSelected && "border-l-2 border-l-amber-400"
|
|
2465
|
+
),
|
|
2466
|
+
children: [
|
|
2467
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2468
|
+
LogEntryHeader,
|
|
2462
2469
|
{
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2470
|
+
log,
|
|
2471
|
+
parsedRequest,
|
|
2472
|
+
expanded,
|
|
2473
|
+
onToggle: () => setExpanded(!expanded),
|
|
2474
|
+
suppressApiFormatBadge,
|
|
2475
|
+
cacheTrend,
|
|
2476
|
+
isSelected,
|
|
2477
|
+
onToggleSelect
|
|
2467
2478
|
}
|
|
2468
|
-
)
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
viewMode,
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2479
|
+
),
|
|
2480
|
+
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
|
|
2481
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
|
|
2482
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }),
|
|
2483
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }),
|
|
2484
|
+
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }),
|
|
2485
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
|
|
2486
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
|
|
2487
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
|
|
2488
|
+
] }),
|
|
2489
|
+
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2490
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2491
|
+
CopyButton,
|
|
2492
|
+
{
|
|
2493
|
+
text: log.rawRequestBody,
|
|
2494
|
+
label: "Copy Raw Request",
|
|
2495
|
+
copied: rawRequestCopied,
|
|
2496
|
+
onCopy: handleCopyRawRequest
|
|
2485
2497
|
}
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
{
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2498
|
+
) }),
|
|
2499
|
+
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" })
|
|
2500
|
+
] }) }),
|
|
2501
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2502
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
|
|
2503
|
+
shouldShowRequestDiffButton(
|
|
2504
|
+
log.apiFormat,
|
|
2505
|
+
viewMode,
|
|
2506
|
+
strip,
|
|
2507
|
+
log.rawRequestBody !== null
|
|
2508
|
+
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2509
|
+
DiffToggleButton,
|
|
2510
|
+
{
|
|
2511
|
+
active: requestDiff,
|
|
2512
|
+
onClick: (e) => {
|
|
2513
|
+
e.stopPropagation();
|
|
2514
|
+
setRequestDiff(!requestDiff);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
),
|
|
2518
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2519
|
+
Button,
|
|
2520
|
+
{
|
|
2521
|
+
variant: "outline",
|
|
2522
|
+
size: "sm",
|
|
2523
|
+
className: "h-7 text-xs",
|
|
2524
|
+
onClick: (e) => {
|
|
2525
|
+
e.stopPropagation();
|
|
2526
|
+
setReplayOpen(true);
|
|
2527
|
+
},
|
|
2528
|
+
children: [
|
|
2529
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3 mr-1" }),
|
|
2530
|
+
"Replay"
|
|
2531
|
+
]
|
|
2532
|
+
}
|
|
2533
|
+
),
|
|
2534
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2535
|
+
CopyButton,
|
|
2536
|
+
{
|
|
2537
|
+
text: displayedRequestBody,
|
|
2538
|
+
label: "Copy Request",
|
|
2539
|
+
copied: requestCopied,
|
|
2540
|
+
onCopy: handleCopyRequest
|
|
2541
|
+
}
|
|
2542
|
+
)
|
|
2543
|
+
] }),
|
|
2544
|
+
requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2545
|
+
DiffView,
|
|
2546
|
+
{
|
|
2547
|
+
result: requestDiffResult,
|
|
2548
|
+
emptyLabel: "No transformation applied — raw and sent request bodies are identical."
|
|
2549
|
+
}
|
|
2550
|
+
) : 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" })
|
|
2551
|
+
] }) }),
|
|
2552
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2553
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
|
|
2554
|
+
viewMode,
|
|
2555
|
+
log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
|
|
2556
|
+
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2557
|
+
DiffToggleButton,
|
|
2558
|
+
{
|
|
2559
|
+
active: headersDiff,
|
|
2560
|
+
onClick: (e) => {
|
|
2561
|
+
e.stopPropagation();
|
|
2562
|
+
setHeadersDiff(!headersDiff);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
) }),
|
|
2566
|
+
headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2567
|
+
DiffView,
|
|
2568
|
+
{
|
|
2569
|
+
result: headersDiffResult,
|
|
2570
|
+
emptyLabel: "No transformation applied — raw and processed headers are identical."
|
|
2571
|
+
}
|
|
2572
|
+
) : 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: [
|
|
2573
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2574
|
+
key,
|
|
2575
|
+
":"
|
|
2576
|
+
] }),
|
|
2577
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2578
|
+
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
|
|
2579
|
+
] }) }),
|
|
2580
|
+
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: [
|
|
2581
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2582
|
+
key,
|
|
2583
|
+
":"
|
|
2584
|
+
] }),
|
|
2585
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2586
|
+
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
|
|
2587
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
|
|
2588
|
+
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: [
|
|
2589
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
|
|
2590
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
|
|
2591
|
+
] }),
|
|
2592
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2593
|
+
CopyButton,
|
|
2594
|
+
{
|
|
2595
|
+
text: log.responseText,
|
|
2596
|
+
label: "Copy Response",
|
|
2597
|
+
copied: responseCopied,
|
|
2598
|
+
onCopy: handleCopyResponse
|
|
2599
|
+
}
|
|
2600
|
+
) }),
|
|
2601
|
+
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" }),
|
|
2602
|
+
log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2603
|
+
StreamingChunkSequence,
|
|
2604
|
+
{
|
|
2605
|
+
logId: log.id,
|
|
2606
|
+
truncated: log.streamingChunksPath !== null
|
|
2607
|
+
}
|
|
2608
|
+
)
|
|
2609
|
+
] }) }),
|
|
2610
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2611
|
+
ResponseView,
|
|
2506
2612
|
{
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
{
|
|
2517
|
-
result: requestDiffResult,
|
|
2518
|
-
emptyLabel: "No transformation applied — raw and sent request bodies are identical."
|
|
2519
|
-
}
|
|
2520
|
-
) : 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" })
|
|
2521
|
-
] }) }),
|
|
2522
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2523
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
|
|
2524
|
-
viewMode,
|
|
2525
|
-
log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
|
|
2526
|
-
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2527
|
-
DiffToggleButton,
|
|
2528
|
-
{
|
|
2529
|
-
active: headersDiff,
|
|
2530
|
-
onClick: (e) => {
|
|
2531
|
-
e.stopPropagation();
|
|
2532
|
-
setHeadersDiff(!headersDiff);
|
|
2613
|
+
responseText: log.responseText,
|
|
2614
|
+
responseStatus: log.responseStatus,
|
|
2615
|
+
streaming: log.streaming,
|
|
2616
|
+
inputTokens: log.inputTokens,
|
|
2617
|
+
outputTokens: log.outputTokens,
|
|
2618
|
+
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
2619
|
+
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
2620
|
+
apiFormat: log.apiFormat,
|
|
2621
|
+
error: log.error
|
|
2533
2622
|
}
|
|
2534
|
-
}
|
|
2535
|
-
) })
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
result: headersDiffResult,
|
|
2540
|
-
emptyLabel: "No transformation applied — raw and processed headers are identical."
|
|
2541
|
-
}
|
|
2542
|
-
) : 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: [
|
|
2543
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2544
|
-
key,
|
|
2545
|
-
":"
|
|
2546
|
-
] }),
|
|
2547
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2548
|
-
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
|
|
2549
|
-
] }) }),
|
|
2550
|
-
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: [
|
|
2551
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2552
|
-
key,
|
|
2553
|
-
":"
|
|
2554
|
-
] }),
|
|
2555
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2556
|
-
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
|
|
2557
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
|
|
2558
|
-
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: [
|
|
2559
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
|
|
2560
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
|
|
2561
|
-
] }),
|
|
2562
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2563
|
-
CopyButton,
|
|
2564
|
-
{
|
|
2565
|
-
text: log.responseText,
|
|
2566
|
-
label: "Copy Response",
|
|
2567
|
-
copied: responseCopied,
|
|
2568
|
-
onCopy: handleCopyResponse
|
|
2569
|
-
}
|
|
2570
|
-
) }),
|
|
2571
|
-
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" }),
|
|
2572
|
-
log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2573
|
-
StreamingChunkSequence,
|
|
2574
|
-
{
|
|
2575
|
-
logId: log.id,
|
|
2576
|
-
truncated: log.streamingChunksPath !== null
|
|
2577
|
-
}
|
|
2578
|
-
)
|
|
2579
|
-
] }) }),
|
|
2580
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2581
|
-
ResponseView,
|
|
2582
|
-
{
|
|
2583
|
-
responseText: log.responseText,
|
|
2584
|
-
responseStatus: log.responseStatus,
|
|
2585
|
-
streaming: log.streaming,
|
|
2586
|
-
inputTokens: log.inputTokens,
|
|
2587
|
-
outputTokens: log.outputTokens,
|
|
2588
|
-
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
2589
|
-
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
2590
|
-
apiFormat: log.apiFormat,
|
|
2591
|
-
error: log.error
|
|
2592
|
-
}
|
|
2593
|
-
) }) })
|
|
2594
|
-
] }) })
|
|
2595
|
-
] }),
|
|
2623
|
+
) }) })
|
|
2624
|
+
] }) })
|
|
2625
|
+
]
|
|
2626
|
+
}
|
|
2627
|
+
),
|
|
2596
2628
|
/* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
|
|
2597
2629
|
] });
|
|
2598
2630
|
});
|
|
@@ -2609,7 +2641,9 @@ const ConversationGroup = reactExports.memo(function({
|
|
|
2609
2641
|
group,
|
|
2610
2642
|
viewMode = "simple",
|
|
2611
2643
|
strip,
|
|
2612
|
-
cacheTrends
|
|
2644
|
+
cacheTrends,
|
|
2645
|
+
selectedSet,
|
|
2646
|
+
onToggleSelect
|
|
2613
2647
|
}) {
|
|
2614
2648
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2615
2649
|
const stats = computeStats(group.logs);
|
|
@@ -2640,7 +2674,9 @@ const ConversationGroup = reactExports.memo(function({
|
|
|
2640
2674
|
viewMode,
|
|
2641
2675
|
suppressApiFormatBadge: !mixed,
|
|
2642
2676
|
strip,
|
|
2643
|
-
cacheTrend: cacheTrends?.get(log.id) ?? null
|
|
2677
|
+
cacheTrend: cacheTrends?.get(log.id) ?? null,
|
|
2678
|
+
isSelected: selectedSet.has(log.id),
|
|
2679
|
+
onToggleSelect
|
|
2644
2680
|
},
|
|
2645
2681
|
log.id
|
|
2646
2682
|
)) })
|
|
@@ -3920,6 +3956,480 @@ function compareField(previous, current) {
|
|
|
3920
3956
|
if (current < previous) return { direction: "down", delta: previous - current };
|
|
3921
3957
|
return null;
|
|
3922
3958
|
}
|
|
3959
|
+
const ROOT_PATH = "";
|
|
3960
|
+
function formatPath(segments) {
|
|
3961
|
+
if (segments.length === 0) return ROOT_PATH;
|
|
3962
|
+
let out = "";
|
|
3963
|
+
for (let i = 0; i < segments.length; i++) {
|
|
3964
|
+
const seg = segments[i];
|
|
3965
|
+
if (seg === void 0) continue;
|
|
3966
|
+
if (typeof seg === "number") {
|
|
3967
|
+
out += `[${seg}]`;
|
|
3968
|
+
} else if (i === 0) {
|
|
3969
|
+
out += seg;
|
|
3970
|
+
} else {
|
|
3971
|
+
out += `.${seg}`;
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
return out;
|
|
3975
|
+
}
|
|
3976
|
+
function isPlainObject(value) {
|
|
3977
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3978
|
+
}
|
|
3979
|
+
function normalizeRequest(raw) {
|
|
3980
|
+
if (typeof raw === "string") {
|
|
3981
|
+
try {
|
|
3982
|
+
return toNode(JSON.parse(raw));
|
|
3983
|
+
} catch {
|
|
3984
|
+
return { kind: "primitive", value: raw };
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
return toNode(raw);
|
|
3988
|
+
}
|
|
3989
|
+
function toNode(value) {
|
|
3990
|
+
if (value === null) return { kind: "primitive", value: null };
|
|
3991
|
+
if (typeof value === "string") return { kind: "primitive", value };
|
|
3992
|
+
if (typeof value === "number") return { kind: "primitive", value };
|
|
3993
|
+
if (typeof value === "boolean") return { kind: "primitive", value };
|
|
3994
|
+
if (Array.isArray(value)) {
|
|
3995
|
+
return { kind: "array", value: value.map((v) => toNode(v)) };
|
|
3996
|
+
}
|
|
3997
|
+
if (isPlainObject(value)) {
|
|
3998
|
+
const out = {};
|
|
3999
|
+
for (const k of Object.keys(value).sort()) {
|
|
4000
|
+
out[k] = toNode(value[k]);
|
|
4001
|
+
}
|
|
4002
|
+
return { kind: "object", value: out };
|
|
4003
|
+
}
|
|
4004
|
+
return { kind: "primitive", value: null };
|
|
4005
|
+
}
|
|
4006
|
+
function diffTrees(left, right) {
|
|
4007
|
+
const ops = [];
|
|
4008
|
+
walk([], left, right, ops);
|
|
4009
|
+
return ops;
|
|
4010
|
+
}
|
|
4011
|
+
function walk(segments, left, right, out) {
|
|
4012
|
+
const path = formatPath(segments);
|
|
4013
|
+
if (nodeEqual(left, right)) {
|
|
4014
|
+
out.push({ kind: "equal", path, value: left });
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
if (left.kind !== right.kind) {
|
|
4018
|
+
out.push({ kind: "changed", path, left, right });
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
if (left.kind === "primitive" && right.kind === "primitive") {
|
|
4022
|
+
out.push({ kind: "changed", path, left, right });
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
4026
|
+
const leftKeys = Object.keys(left.value);
|
|
4027
|
+
const rightKeys = Object.keys(right.value);
|
|
4028
|
+
const rightKeySet = new Set(rightKeys);
|
|
4029
|
+
for (const k of leftKeys) {
|
|
4030
|
+
const lChild = left.value[k];
|
|
4031
|
+
if (lChild === void 0) continue;
|
|
4032
|
+
if (!rightKeySet.has(k)) {
|
|
4033
|
+
out.push({
|
|
4034
|
+
kind: "removed",
|
|
4035
|
+
path: formatPath([...segments, k]),
|
|
4036
|
+
value: lChild
|
|
4037
|
+
});
|
|
4038
|
+
} else {
|
|
4039
|
+
const rChild = right.value[k];
|
|
4040
|
+
if (rChild === void 0) continue;
|
|
4041
|
+
walk([...segments, k], lChild, rChild, out);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
for (const k of rightKeys) {
|
|
4045
|
+
if (leftKeys.includes(k)) continue;
|
|
4046
|
+
const rChild = right.value[k];
|
|
4047
|
+
if (rChild === void 0) continue;
|
|
4048
|
+
out.push({
|
|
4049
|
+
kind: "added",
|
|
4050
|
+
path: formatPath([...segments, k]),
|
|
4051
|
+
value: rChild
|
|
4052
|
+
});
|
|
4053
|
+
}
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
4057
|
+
const minLen = Math.min(left.value.length, right.value.length);
|
|
4058
|
+
for (let i = 0; i < minLen; i++) {
|
|
4059
|
+
const lChild = left.value[i];
|
|
4060
|
+
const rChild = right.value[i];
|
|
4061
|
+
if (lChild === void 0 || rChild === void 0) continue;
|
|
4062
|
+
walk([...segments, i], lChild, rChild, out);
|
|
4063
|
+
}
|
|
4064
|
+
for (let i = minLen; i < right.value.length; i++) {
|
|
4065
|
+
const rChild = right.value[i];
|
|
4066
|
+
if (rChild === void 0) continue;
|
|
4067
|
+
out.push({
|
|
4068
|
+
kind: "added",
|
|
4069
|
+
path: formatPath([...segments, i]),
|
|
4070
|
+
value: rChild
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
for (let i = minLen; i < left.value.length; i++) {
|
|
4074
|
+
const lChild = left.value[i];
|
|
4075
|
+
if (lChild === void 0) continue;
|
|
4076
|
+
out.push({
|
|
4077
|
+
kind: "removed",
|
|
4078
|
+
path: formatPath([...segments, i]),
|
|
4079
|
+
value: lChild
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
function nodeEqual(a, b) {
|
|
4085
|
+
if (a.kind !== b.kind) return false;
|
|
4086
|
+
if (a.kind === "primitive" && b.kind === "primitive") {
|
|
4087
|
+
return a.value === b.value;
|
|
4088
|
+
}
|
|
4089
|
+
if (a.kind === "array" && b.kind === "array") {
|
|
4090
|
+
if (a.value.length !== b.value.length) return false;
|
|
4091
|
+
for (let i = 0; i < a.value.length; i++) {
|
|
4092
|
+
const ai = a.value[i];
|
|
4093
|
+
const bi = b.value[i];
|
|
4094
|
+
if (ai === void 0 || bi === void 0) return false;
|
|
4095
|
+
if (!nodeEqual(ai, bi)) return false;
|
|
4096
|
+
}
|
|
4097
|
+
return true;
|
|
4098
|
+
}
|
|
4099
|
+
if (a.kind === "object" && b.kind === "object") {
|
|
4100
|
+
const aKeys = Object.keys(a.value);
|
|
4101
|
+
const bKeys = Object.keys(b.value);
|
|
4102
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4103
|
+
for (const k of aKeys) {
|
|
4104
|
+
const av = a.value[k];
|
|
4105
|
+
const bv = b.value[k];
|
|
4106
|
+
if (av === void 0 || bv === void 0) return false;
|
|
4107
|
+
if (!nodeEqual(av, bv)) return false;
|
|
4108
|
+
}
|
|
4109
|
+
return true;
|
|
4110
|
+
}
|
|
4111
|
+
return false;
|
|
4112
|
+
}
|
|
4113
|
+
function previewNode(node, maxLen = 80) {
|
|
4114
|
+
let s;
|
|
4115
|
+
switch (node.kind) {
|
|
4116
|
+
case "primitive":
|
|
4117
|
+
s = node.value === null ? "null" : JSON.stringify(node.value);
|
|
4118
|
+
break;
|
|
4119
|
+
case "array":
|
|
4120
|
+
s = `[… ${node.value.length} items]`;
|
|
4121
|
+
break;
|
|
4122
|
+
case "object":
|
|
4123
|
+
s = `{… ${Object.keys(node.value).length} keys}`;
|
|
4124
|
+
break;
|
|
4125
|
+
}
|
|
4126
|
+
if (s.length > maxLen) s = `${s.slice(0, maxLen - 1)}…`;
|
|
4127
|
+
return s;
|
|
4128
|
+
}
|
|
4129
|
+
function nodeToJsonString(node, indent = 2) {
|
|
4130
|
+
return JSON.stringify(nodeToJsonValue(node), null, indent);
|
|
4131
|
+
}
|
|
4132
|
+
function nodeToJsonValue(node) {
|
|
4133
|
+
switch (node.kind) {
|
|
4134
|
+
case "primitive":
|
|
4135
|
+
return node.value;
|
|
4136
|
+
case "array":
|
|
4137
|
+
return node.value.map(nodeToJsonValue);
|
|
4138
|
+
case "object": {
|
|
4139
|
+
const out = {};
|
|
4140
|
+
for (const [k, v] of Object.entries(node.value)) {
|
|
4141
|
+
out[k] = nodeToJsonValue(v);
|
|
4142
|
+
}
|
|
4143
|
+
return out;
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
function parentPath(path) {
|
|
4148
|
+
if (path === "") return "";
|
|
4149
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
4150
|
+
const ch = path[i];
|
|
4151
|
+
if (ch === "." || ch === "[") {
|
|
4152
|
+
return path.substring(0, i);
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
return "";
|
|
4156
|
+
}
|
|
4157
|
+
function isDeepEqual(op) {
|
|
4158
|
+
return op.kind === "equal" && (op.value.kind === "object" || op.value.kind === "array");
|
|
4159
|
+
}
|
|
4160
|
+
function groupContiguousEquals(ops) {
|
|
4161
|
+
const out = [];
|
|
4162
|
+
let i = 0;
|
|
4163
|
+
while (i < ops.length) {
|
|
4164
|
+
const op = ops[i];
|
|
4165
|
+
if (op !== void 0 && isDeepEqual(op)) {
|
|
4166
|
+
const startParent = parentPath(op.path);
|
|
4167
|
+
let j = i + 1;
|
|
4168
|
+
while (j < ops.length) {
|
|
4169
|
+
const next = ops[j];
|
|
4170
|
+
if (next === void 0) break;
|
|
4171
|
+
if (!isDeepEqual(next)) break;
|
|
4172
|
+
if (parentPath(next.path) !== startParent) break;
|
|
4173
|
+
j++;
|
|
4174
|
+
}
|
|
4175
|
+
if (j - i > 1) {
|
|
4176
|
+
const equalOps = [];
|
|
4177
|
+
for (let k = i; k < j; k++) {
|
|
4178
|
+
const eop = ops[k];
|
|
4179
|
+
if (eop !== void 0 && eop.kind === "equal") {
|
|
4180
|
+
equalOps.push(eop);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
out.push({ kind: "equal-run", ops: equalOps });
|
|
4184
|
+
i = j;
|
|
4185
|
+
continue;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
if (op !== void 0) {
|
|
4189
|
+
out.push({ kind: "single", op });
|
|
4190
|
+
}
|
|
4191
|
+
i++;
|
|
4192
|
+
}
|
|
4193
|
+
return out;
|
|
4194
|
+
}
|
|
4195
|
+
function EqualRunRow({
|
|
4196
|
+
ops,
|
|
4197
|
+
expanded,
|
|
4198
|
+
onToggle
|
|
4199
|
+
}) {
|
|
4200
|
+
const first = ops[0];
|
|
4201
|
+
const last = ops[ops.length - 1];
|
|
4202
|
+
if (first === void 0 || last === void 0) {
|
|
4203
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-3 text-muted-foreground/40 text-xs", children: "—" });
|
|
4204
|
+
}
|
|
4205
|
+
const firstPath = first.path;
|
|
4206
|
+
const lastPath = last.path;
|
|
4207
|
+
const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
|
|
4208
|
+
const summary = first.value.kind === "array" ? `${ops.length} equal arrays` : first.value.kind === "object" ? `${ops.length} equal objects` : "equal";
|
|
4209
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3", children: [
|
|
4210
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4211
|
+
"button",
|
|
4212
|
+
{
|
|
4213
|
+
type: "button",
|
|
4214
|
+
onClick: onToggle,
|
|
4215
|
+
className: "w-full text-left flex items-center gap-2 px-2 py-1 text-xs text-muted-foreground hover:bg-muted/40 rounded cursor-pointer",
|
|
4216
|
+
children: [
|
|
4217
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4218
|
+
ChevronRight,
|
|
4219
|
+
{
|
|
4220
|
+
className: cn("size-3 transition-transform shrink-0", expanded && "rotate-90")
|
|
4221
|
+
}
|
|
4222
|
+
),
|
|
4223
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1", children: label }),
|
|
4224
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 shrink-0", children: [
|
|
4225
|
+
"(",
|
|
4226
|
+
summary,
|
|
4227
|
+
")"
|
|
4228
|
+
] })
|
|
4229
|
+
]
|
|
4230
|
+
}
|
|
4231
|
+
),
|
|
4232
|
+
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-5 mt-1 mb-2 space-y-2", children: ops.map((op) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border/50 rounded p-2 bg-muted/20", children: [
|
|
4233
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
|
|
4234
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 2 })
|
|
4235
|
+
] }, op.path)) })
|
|
4236
|
+
] });
|
|
4237
|
+
}
|
|
4238
|
+
function AddOrRemoveRow({
|
|
4239
|
+
op,
|
|
4240
|
+
kind
|
|
4241
|
+
}) {
|
|
4242
|
+
const accent = kind === "added" ? "border-l-2 border-l-emerald-400/70 bg-emerald-500/5" : "border-l-2 border-l-rose-400/70 bg-rose-500/5";
|
|
4243
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("col-span-3 px-2 py-1 rounded text-xs", accent), children: [
|
|
4244
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
|
|
4245
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono break-all", children: kind === "added" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-300/90", children: [
|
|
4246
|
+
"+ ",
|
|
4247
|
+
previewNode(op.value, 400)
|
|
4248
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-rose-300/90 line-through", children: [
|
|
4249
|
+
"- ",
|
|
4250
|
+
previewNode(op.value, 400)
|
|
4251
|
+
] }) })
|
|
4252
|
+
] });
|
|
4253
|
+
}
|
|
4254
|
+
function ChangedRow({ op }) {
|
|
4255
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5", children: [
|
|
4256
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
|
|
4257
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
4258
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-rose-300/90 break-all line-through", children: previewNode(op.left, 400) }),
|
|
4259
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-300/90 break-all", children: previewNode(op.right, 400) })
|
|
4260
|
+
] })
|
|
4261
|
+
] });
|
|
4262
|
+
}
|
|
4263
|
+
function SideSummary({ log, side }) {
|
|
4264
|
+
const conversationId = getConversationId(log);
|
|
4265
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 space-y-1 text-xs", children: [
|
|
4266
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4267
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4268
|
+
Badge,
|
|
4269
|
+
{
|
|
4270
|
+
variant: "outline",
|
|
4271
|
+
className: cn(
|
|
4272
|
+
"text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
|
|
4273
|
+
side === "left" ? "border-rose-500/40 text-rose-400" : "border-emerald-500/40 text-emerald-400"
|
|
4274
|
+
),
|
|
4275
|
+
children: side === "left" ? "← Left" : "Right →"
|
|
4276
|
+
}
|
|
4277
|
+
),
|
|
4278
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-blue-400/80", children: [
|
|
4279
|
+
"#",
|
|
4280
|
+
log.id
|
|
4281
|
+
] }),
|
|
4282
|
+
log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground truncate", children: log.model })
|
|
4283
|
+
] }),
|
|
4284
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-muted-foreground font-mono", children: [
|
|
4285
|
+
log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400", children: [
|
|
4286
|
+
"Cache +",
|
|
4287
|
+
formatTokens(log.cacheCreationInputTokens)
|
|
4288
|
+
] }),
|
|
4289
|
+
log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-purple-400", children: [
|
|
4290
|
+
"Cache ~",
|
|
4291
|
+
formatTokens(log.cacheReadInputTokens)
|
|
4292
|
+
] }),
|
|
4293
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: log.timestamp, children: log.timestamp })
|
|
4294
|
+
] }),
|
|
4295
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-muted-foreground/70 font-mono truncate", title: conversationId, children: [
|
|
4296
|
+
"session: ",
|
|
4297
|
+
conversationId
|
|
4298
|
+
] })
|
|
4299
|
+
] });
|
|
4300
|
+
}
|
|
4301
|
+
function CompareDrawer({ left, right, onClose }) {
|
|
4302
|
+
const ops = reactExports.useMemo(() => {
|
|
4303
|
+
const l = normalizeRequest(parseRequest(left.rawRequestBody) ?? left.rawRequestBody);
|
|
4304
|
+
const r = normalizeRequest(parseRequest(right.rawRequestBody) ?? right.rawRequestBody);
|
|
4305
|
+
return diffTrees(l, r);
|
|
4306
|
+
}, [left.rawRequestBody, right.rawRequestBody]);
|
|
4307
|
+
const grouped = reactExports.useMemo(() => groupContiguousEquals(ops), [ops]);
|
|
4308
|
+
const [expandedRuns, setExpandedRuns] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
4309
|
+
const toggleRun = (idx) => {
|
|
4310
|
+
setExpandedRuns((prev) => {
|
|
4311
|
+
const next = new Set(prev);
|
|
4312
|
+
if (next.has(idx)) next.delete(idx);
|
|
4313
|
+
else next.add(idx);
|
|
4314
|
+
return next;
|
|
4315
|
+
});
|
|
4316
|
+
};
|
|
4317
|
+
reactExports.useEffect(() => {
|
|
4318
|
+
const onKey = (e) => {
|
|
4319
|
+
if (e.key === "Escape") onClose();
|
|
4320
|
+
};
|
|
4321
|
+
document.addEventListener("keydown", onKey);
|
|
4322
|
+
const prevOverflow = document.body.style.overflow;
|
|
4323
|
+
document.body.style.overflow = "hidden";
|
|
4324
|
+
return () => {
|
|
4325
|
+
document.removeEventListener("keydown", onKey);
|
|
4326
|
+
document.body.style.overflow = prevOverflow;
|
|
4327
|
+
};
|
|
4328
|
+
}, [onClose]);
|
|
4329
|
+
const sameSession = getConversationId(left) === getConversationId(right);
|
|
4330
|
+
const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
|
|
4331
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4332
|
+
"div",
|
|
4333
|
+
{
|
|
4334
|
+
className: "fixed inset-0 z-50 flex justify-end",
|
|
4335
|
+
role: "dialog",
|
|
4336
|
+
"aria-modal": "true",
|
|
4337
|
+
"aria-label": "Compare two log requests",
|
|
4338
|
+
children: [
|
|
4339
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4340
|
+
"button",
|
|
4341
|
+
{
|
|
4342
|
+
type: "button",
|
|
4343
|
+
onClick: onClose,
|
|
4344
|
+
"aria-label": "Close compare drawer",
|
|
4345
|
+
className: "absolute inset-0 bg-black/40 cursor-default",
|
|
4346
|
+
tabIndex: -1
|
|
4347
|
+
}
|
|
4348
|
+
),
|
|
4349
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4350
|
+
"div",
|
|
4351
|
+
{
|
|
4352
|
+
className: cn(
|
|
4353
|
+
"relative bg-background border-l border-border shadow-xl",
|
|
4354
|
+
"w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"
|
|
4355
|
+
),
|
|
4356
|
+
onClick: (e) => e.stopPropagation(),
|
|
4357
|
+
onKeyDown: (e) => e.stopPropagation(),
|
|
4358
|
+
children: [
|
|
4359
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-4 px-4 py-3 border-b border-border", children: [
|
|
4360
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-4 min-w-0", children: [
|
|
4361
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: left, side: "left" }),
|
|
4362
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: right, side: "right" })
|
|
4363
|
+
] }),
|
|
4364
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4365
|
+
"button",
|
|
4366
|
+
{
|
|
4367
|
+
type: "button",
|
|
4368
|
+
onClick: onClose,
|
|
4369
|
+
"aria-label": "Close",
|
|
4370
|
+
className: "shrink-0 p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",
|
|
4371
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
|
|
4372
|
+
}
|
|
4373
|
+
)
|
|
4374
|
+
] }),
|
|
4375
|
+
!sameSession && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border", children: "Heads up: the two selected logs are from different sessions." }),
|
|
4376
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: allEqual ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-12 text-center text-muted-foreground text-sm", children: "The two Request payloads are identical." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs", children: [
|
|
4377
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground", children: [
|
|
4378
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Path" }),
|
|
4379
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4380
|
+
"Left (Log #",
|
|
4381
|
+
left.id,
|
|
4382
|
+
")"
|
|
4383
|
+
] }),
|
|
4384
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4385
|
+
"Right (Log #",
|
|
4386
|
+
right.id,
|
|
4387
|
+
")"
|
|
4388
|
+
] })
|
|
4389
|
+
] }),
|
|
4390
|
+
grouped.map((g, i) => {
|
|
4391
|
+
if (g.kind === "equal-run") {
|
|
4392
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4393
|
+
EqualRunRow,
|
|
4394
|
+
{
|
|
4395
|
+
ops: g.ops,
|
|
4396
|
+
expanded: expandedRuns.has(i),
|
|
4397
|
+
onToggle: () => toggleRun(i)
|
|
4398
|
+
},
|
|
4399
|
+
`r${i}`
|
|
4400
|
+
);
|
|
4401
|
+
}
|
|
4402
|
+
const op = g.op;
|
|
4403
|
+
if (op.kind === "equal") {
|
|
4404
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4405
|
+
"div",
|
|
4406
|
+
{
|
|
4407
|
+
className: "col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",
|
|
4408
|
+
children: [
|
|
4409
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs truncate", title: op.path, children: op.path }),
|
|
4410
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) }),
|
|
4411
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) })
|
|
4412
|
+
]
|
|
4413
|
+
},
|
|
4414
|
+
`e${i}`
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
if (op.kind === "added") {
|
|
4418
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(AddOrRemoveRow, { op, kind: "added" }, `a${i}`);
|
|
4419
|
+
}
|
|
4420
|
+
if (op.kind === "removed") {
|
|
4421
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(AddOrRemoveRow, { op, kind: "removed" }, `r${i}`);
|
|
4422
|
+
}
|
|
4423
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(ChangedRow, { op }, `c${i}`);
|
|
4424
|
+
})
|
|
4425
|
+
] }) })
|
|
4426
|
+
]
|
|
4427
|
+
}
|
|
4428
|
+
)
|
|
4429
|
+
]
|
|
4430
|
+
}
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
3923
4433
|
function truncateSessionId(id) {
|
|
3924
4434
|
if (id.length <= 30) return id;
|
|
3925
4435
|
return id.slice(0, 12) + "…" + id.slice(-12);
|
|
@@ -4000,6 +4510,9 @@ function ProxyViewer({
|
|
|
4000
4510
|
const { totalIn, totalOut } = computeTokenSummary(logs);
|
|
4001
4511
|
const [groupedView, setGroupedView] = reactExports.useState(true);
|
|
4002
4512
|
const [exporting, setExporting] = reactExports.useState(false);
|
|
4513
|
+
const [selectedLogIds, setSelectedLogIds] = reactExports.useState([]);
|
|
4514
|
+
const [compareOpen, setCompareOpen] = reactExports.useState(false);
|
|
4515
|
+
const [comparePair, setComparePair] = reactExports.useState(null);
|
|
4003
4516
|
const handleExport = reactExports.useCallback(async () => {
|
|
4004
4517
|
setExporting(true);
|
|
4005
4518
|
try {
|
|
@@ -4009,6 +4522,73 @@ function ProxyViewer({
|
|
|
4009
4522
|
}
|
|
4010
4523
|
}, [logs]);
|
|
4011
4524
|
const parentRef = reactExports.useRef(null);
|
|
4525
|
+
const handleToggleSelect = reactExports.useCallback((logId) => {
|
|
4526
|
+
setSelectedLogIds((prev) => {
|
|
4527
|
+
if (prev.includes(logId)) {
|
|
4528
|
+
return prev.filter((id) => id !== logId);
|
|
4529
|
+
}
|
|
4530
|
+
if (prev.length < 2) {
|
|
4531
|
+
return [...prev, logId];
|
|
4532
|
+
}
|
|
4533
|
+
const newer = prev[1];
|
|
4534
|
+
if (newer === void 0) return prev;
|
|
4535
|
+
return [newer, logId];
|
|
4536
|
+
});
|
|
4537
|
+
}, []);
|
|
4538
|
+
reactExports.useEffect(() => {
|
|
4539
|
+
setSelectedLogIds([]);
|
|
4540
|
+
setCompareOpen(false);
|
|
4541
|
+
}, [selectedSession, selectedModel]);
|
|
4542
|
+
const selectedSet = reactExports.useMemo(() => new Set(selectedLogIds), [selectedLogIds]);
|
|
4543
|
+
const openCompare = reactExports.useCallback(() => {
|
|
4544
|
+
if (selectedLogIds.length !== 2) return;
|
|
4545
|
+
const [idA, idB] = selectedLogIds;
|
|
4546
|
+
if (idA === void 0 || idB === void 0) return;
|
|
4547
|
+
const logA = logs.find((l) => l.id === idA);
|
|
4548
|
+
const logB = logs.find((l) => l.id === idB);
|
|
4549
|
+
if (logA === void 0 || logB === void 0) return;
|
|
4550
|
+
setComparePair([logA, logB]);
|
|
4551
|
+
setCompareOpen(true);
|
|
4552
|
+
}, [selectedLogIds, logs]);
|
|
4553
|
+
const closeCompare = reactExports.useCallback(() => {
|
|
4554
|
+
setCompareOpen(false);
|
|
4555
|
+
}, []);
|
|
4556
|
+
const clearSelection = reactExports.useCallback(() => {
|
|
4557
|
+
setSelectedLogIds([]);
|
|
4558
|
+
}, []);
|
|
4559
|
+
const selectedSummary = reactExports.useMemo(() => {
|
|
4560
|
+
if (selectedLogIds.length !== 2) return null;
|
|
4561
|
+
const [idA, idB] = selectedLogIds;
|
|
4562
|
+
if (idA === void 0 || idB === void 0) return null;
|
|
4563
|
+
const logA = logs.find((l) => l.id === idA);
|
|
4564
|
+
const logB = logs.find((l) => l.id === idB);
|
|
4565
|
+
if (logA === void 0 || logB === void 0) return null;
|
|
4566
|
+
const sameSession = getConversationId(logA) === getConversationId(logB);
|
|
4567
|
+
let elapsed = "";
|
|
4568
|
+
if (logA.timestamp !== null && logB.timestamp !== null) {
|
|
4569
|
+
const a = Date.parse(logA.timestamp);
|
|
4570
|
+
const b = Date.parse(logB.timestamp);
|
|
4571
|
+
if (!Number.isNaN(a) && !Number.isNaN(b)) {
|
|
4572
|
+
const ms = Math.abs(b - a);
|
|
4573
|
+
elapsed = formatElapsed2(ms);
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
return {
|
|
4577
|
+
logA,
|
|
4578
|
+
logB,
|
|
4579
|
+
sameSession,
|
|
4580
|
+
elapsed
|
|
4581
|
+
};
|
|
4582
|
+
}, [selectedLogIds, logs]);
|
|
4583
|
+
function formatElapsed2(ms) {
|
|
4584
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
4585
|
+
const sec = Math.floor(ms / 1e3);
|
|
4586
|
+
if (sec < 60) return `${sec}s`;
|
|
4587
|
+
const min = Math.floor(sec / 60);
|
|
4588
|
+
if (min < 60) return `${min}m`;
|
|
4589
|
+
const hr = Math.floor(min / 60);
|
|
4590
|
+
return `${hr}h${min % 60}m`;
|
|
4591
|
+
}
|
|
4012
4592
|
const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
|
|
4013
4593
|
const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
|
|
4014
4594
|
const renderGroups = logs.length > 0 && groupedView && !(groups.length === 1 && groups[0]?.logs.length === logs.length);
|
|
@@ -4157,7 +4737,9 @@ function ProxyViewer({
|
|
|
4157
4737
|
group,
|
|
4158
4738
|
viewMode,
|
|
4159
4739
|
strip,
|
|
4160
|
-
cacheTrends
|
|
4740
|
+
cacheTrends,
|
|
4741
|
+
selectedSet,
|
|
4742
|
+
onToggleSelect: handleToggleSelect
|
|
4161
4743
|
}
|
|
4162
4744
|
)
|
|
4163
4745
|
},
|
|
@@ -4184,7 +4766,9 @@ function ProxyViewer({
|
|
|
4184
4766
|
log,
|
|
4185
4767
|
viewMode,
|
|
4186
4768
|
strip,
|
|
4187
|
-
cacheTrend: cacheTrends.get(log.id) ?? null
|
|
4769
|
+
cacheTrend: cacheTrends.get(log.id) ?? null,
|
|
4770
|
+
isSelected: selectedSet.has(log.id),
|
|
4771
|
+
onToggleSelect: handleToggleSelect
|
|
4188
4772
|
}
|
|
4189
4773
|
)
|
|
4190
4774
|
},
|
|
@@ -4193,7 +4777,41 @@ function ProxyViewer({
|
|
|
4193
4777
|
}
|
|
4194
4778
|
})
|
|
4195
4779
|
}
|
|
4196
|
-
) }) })
|
|
4780
|
+
) }) }),
|
|
4781
|
+
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: [
|
|
4782
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-4 text-amber-400 shrink-0" }),
|
|
4783
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-mono", children: [
|
|
4784
|
+
"#",
|
|
4785
|
+
selectedSummary.logA.id,
|
|
4786
|
+
" ↔ #",
|
|
4787
|
+
selectedSummary.logB.id,
|
|
4788
|
+
" · ",
|
|
4789
|
+
selectedSummary.sameSession ? "same session" : "different sessions",
|
|
4790
|
+
selectedSummary.elapsed !== "" && ` · ${selectedSummary.elapsed} apart`
|
|
4791
|
+
] }),
|
|
4792
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4793
|
+
"button",
|
|
4794
|
+
{
|
|
4795
|
+
type: "button",
|
|
4796
|
+
onClick: clearSelection,
|
|
4797
|
+
className: "text-muted-foreground hover:text-foreground transition-colors cursor-pointer inline-flex items-center gap-1",
|
|
4798
|
+
children: [
|
|
4799
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-3" }),
|
|
4800
|
+
"Clear"
|
|
4801
|
+
]
|
|
4802
|
+
}
|
|
4803
|
+
),
|
|
4804
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4805
|
+
"button",
|
|
4806
|
+
{
|
|
4807
|
+
type: "button",
|
|
4808
|
+
onClick: openCompare,
|
|
4809
|
+
className: "bg-amber-400 text-amber-950 hover:bg-amber-300 transition-colors px-3 py-1 rounded font-medium cursor-pointer",
|
|
4810
|
+
children: "Compare 2 logs"
|
|
4811
|
+
}
|
|
4812
|
+
)
|
|
4813
|
+
] }),
|
|
4814
|
+
compareOpen && comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
|
|
4197
4815
|
] });
|
|
4198
4816
|
}
|
|
4199
4817
|
object({
|