@tonyclaw/llm-inspector 1.11.8 → 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-GI0Ezr88.js → main-C3tLo75s.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +30 -18
- package/.output/server/_ssr/{index-RTuHuCHN.mjs → index-C8VC13EA.mjs} +852 -170
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DeW8M_70.mjs → router-D5ccnemB.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-DkqH07iU.mjs → _tanstack-start-manifest_v-DUbXa1lt.mjs} +1 -1
- package/.output/server/index.mjs +30 -30
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +140 -4
- package/src/components/proxy-viewer/CompareDrawer.tsx +388 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +16 -0
- package/src/components/proxy-viewer/LogEntry.tsx +22 -1
- package/src/components/proxy-viewer/LogEntryHeader.tsx +60 -0
- package/src/components/proxy-viewer/cacheTrend.ts +50 -0
- package/src/components/proxy-viewer/requestDiff.ts +277 -0
- package/.output/public/assets/index-DiYqfnSp.css +0 -1
- package/.output/public/assets/index-KwQ0pWoC.js +0 -105
|
@@ -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
|
};
|
|
@@ -1395,12 +1395,28 @@ const STATUS_BADGE_CLASSES = {
|
|
|
1395
1395
|
server_error: "",
|
|
1396
1396
|
pending: "bg-muted text-muted-foreground border-border"
|
|
1397
1397
|
};
|
|
1398
|
+
function CacheTrendIndicator({ trend }) {
|
|
1399
|
+
if (trend === null) return null;
|
|
1400
|
+
const isUp = trend.direction === "up";
|
|
1401
|
+
const Icon2 = isUp ? ArrowUp : ArrowDown;
|
|
1402
|
+
const sign = isUp ? "+" : "-";
|
|
1403
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-0.5 text-muted-foreground tabular-nums", children: [
|
|
1404
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { className: isUp ? "size-3 text-emerald-400" : "size-3 text-rose-400" }),
|
|
1405
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
|
|
1406
|
+
sign,
|
|
1407
|
+
formatTokens(trend.delta)
|
|
1408
|
+
] })
|
|
1409
|
+
] });
|
|
1410
|
+
}
|
|
1398
1411
|
const LogEntryHeader = reactExports.memo(function({
|
|
1399
1412
|
log,
|
|
1400
1413
|
parsedRequest,
|
|
1401
1414
|
expanded,
|
|
1402
1415
|
onToggle,
|
|
1403
|
-
suppressApiFormatBadge = false
|
|
1416
|
+
suppressApiFormatBadge = false,
|
|
1417
|
+
cacheTrend = null,
|
|
1418
|
+
isSelected = false,
|
|
1419
|
+
onToggleSelect
|
|
1404
1420
|
}) {
|
|
1405
1421
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
1406
1422
|
const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
|
|
@@ -1424,6 +1440,23 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1424
1440
|
}
|
|
1425
1441
|
},
|
|
1426
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
|
+
),
|
|
1427
1460
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0", children: [
|
|
1428
1461
|
"#",
|
|
1429
1462
|
log.id
|
|
@@ -1490,14 +1523,20 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1490
1523
|
)
|
|
1491
1524
|
] })
|
|
1492
1525
|
] }),
|
|
1493
|
-
log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1526
|
+
log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
|
|
1527
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
|
|
1528
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
|
|
1529
|
+
"Cache +",
|
|
1530
|
+
formatTokens(log.cacheCreationInputTokens)
|
|
1531
|
+
] })
|
|
1532
|
+
] }),
|
|
1533
|
+
log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
|
|
1534
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
|
|
1535
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
|
|
1536
|
+
"Cache ~",
|
|
1537
|
+
formatTokens(log.cacheReadInputTokens)
|
|
1538
|
+
] })
|
|
1539
|
+
] }),
|
|
1501
1540
|
messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
|
|
1502
1541
|
/* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
|
|
1503
1542
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: messageCount })
|
|
@@ -2364,7 +2403,10 @@ const LogEntry = reactExports.memo(function({
|
|
|
2364
2403
|
log,
|
|
2365
2404
|
viewMode = "simple",
|
|
2366
2405
|
suppressApiFormatBadge = false,
|
|
2367
|
-
strip
|
|
2406
|
+
strip,
|
|
2407
|
+
cacheTrend = null,
|
|
2408
|
+
isSelected = false,
|
|
2409
|
+
onToggleSelect
|
|
2368
2410
|
}) {
|
|
2369
2411
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2370
2412
|
const [requestCopied, setRequestCopied] = reactExports.useState(false);
|
|
@@ -2414,163 +2456,175 @@ const LogEntry = reactExports.memo(function({
|
|
|
2414
2456
|
});
|
|
2415
2457
|
}
|
|
2416
2458
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
2417
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
),
|
|
2428
|
-
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
|
|
2429
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
|
|
2430
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }),
|
|
2431
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }),
|
|
2432
|
-
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }),
|
|
2433
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
|
|
2434
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
|
|
2435
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
|
|
2436
|
-
] }),
|
|
2437
|
-
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2438
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2439
|
-
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,
|
|
2440
2469
|
{
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2470
|
+
log,
|
|
2471
|
+
parsedRequest,
|
|
2472
|
+
expanded,
|
|
2473
|
+
onToggle: () => setExpanded(!expanded),
|
|
2474
|
+
suppressApiFormatBadge,
|
|
2475
|
+
cacheTrend,
|
|
2476
|
+
isSelected,
|
|
2477
|
+
onToggleSelect
|
|
2445
2478
|
}
|
|
2446
|
-
)
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
viewMode,
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
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
|
|
2463
2497
|
}
|
|
2464
|
-
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
{
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
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,
|
|
2484
2612
|
{
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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
|
|
2489
2622
|
}
|
|
2490
|
-
)
|
|
2491
|
-
] })
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
result: requestDiffResult,
|
|
2496
|
-
emptyLabel: "No transformation applied — raw and sent request bodies are identical."
|
|
2497
|
-
}
|
|
2498
|
-
) : 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" })
|
|
2499
|
-
] }) }),
|
|
2500
|
-
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2501
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
|
|
2502
|
-
viewMode,
|
|
2503
|
-
log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
|
|
2504
|
-
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2505
|
-
DiffToggleButton,
|
|
2506
|
-
{
|
|
2507
|
-
active: headersDiff,
|
|
2508
|
-
onClick: (e) => {
|
|
2509
|
-
e.stopPropagation();
|
|
2510
|
-
setHeadersDiff(!headersDiff);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
) }),
|
|
2514
|
-
headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2515
|
-
DiffView,
|
|
2516
|
-
{
|
|
2517
|
-
result: headersDiffResult,
|
|
2518
|
-
emptyLabel: "No transformation applied — raw and processed headers are identical."
|
|
2519
|
-
}
|
|
2520
|
-
) : 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: [
|
|
2521
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2522
|
-
key,
|
|
2523
|
-
":"
|
|
2524
|
-
] }),
|
|
2525
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2526
|
-
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
|
|
2527
|
-
] }) }),
|
|
2528
|
-
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: [
|
|
2529
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2530
|
-
key,
|
|
2531
|
-
":"
|
|
2532
|
-
] }),
|
|
2533
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2534
|
-
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
|
|
2535
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
|
|
2536
|
-
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: [
|
|
2537
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
|
|
2538
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
|
|
2539
|
-
] }),
|
|
2540
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2541
|
-
CopyButton,
|
|
2542
|
-
{
|
|
2543
|
-
text: log.responseText,
|
|
2544
|
-
label: "Copy Response",
|
|
2545
|
-
copied: responseCopied,
|
|
2546
|
-
onCopy: handleCopyResponse
|
|
2547
|
-
}
|
|
2548
|
-
) }),
|
|
2549
|
-
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" }),
|
|
2550
|
-
log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2551
|
-
StreamingChunkSequence,
|
|
2552
|
-
{
|
|
2553
|
-
logId: log.id,
|
|
2554
|
-
truncated: log.streamingChunksPath !== null
|
|
2555
|
-
}
|
|
2556
|
-
)
|
|
2557
|
-
] }) }),
|
|
2558
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2559
|
-
ResponseView,
|
|
2560
|
-
{
|
|
2561
|
-
responseText: log.responseText,
|
|
2562
|
-
responseStatus: log.responseStatus,
|
|
2563
|
-
streaming: log.streaming,
|
|
2564
|
-
inputTokens: log.inputTokens,
|
|
2565
|
-
outputTokens: log.outputTokens,
|
|
2566
|
-
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
2567
|
-
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
2568
|
-
apiFormat: log.apiFormat,
|
|
2569
|
-
error: log.error
|
|
2570
|
-
}
|
|
2571
|
-
) }) })
|
|
2572
|
-
] }) })
|
|
2573
|
-
] }),
|
|
2623
|
+
) }) })
|
|
2624
|
+
] }) })
|
|
2625
|
+
]
|
|
2626
|
+
}
|
|
2627
|
+
),
|
|
2574
2628
|
/* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
|
|
2575
2629
|
] });
|
|
2576
2630
|
});
|
|
@@ -2586,7 +2640,10 @@ function computeStats(logs) {
|
|
|
2586
2640
|
const ConversationGroup = reactExports.memo(function({
|
|
2587
2641
|
group,
|
|
2588
2642
|
viewMode = "simple",
|
|
2589
|
-
strip
|
|
2643
|
+
strip,
|
|
2644
|
+
cacheTrends,
|
|
2645
|
+
selectedSet,
|
|
2646
|
+
onToggleSelect
|
|
2590
2647
|
}) {
|
|
2591
2648
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2592
2649
|
const stats = computeStats(group.logs);
|
|
@@ -2616,7 +2673,10 @@ const ConversationGroup = reactExports.memo(function({
|
|
|
2616
2673
|
log,
|
|
2617
2674
|
viewMode,
|
|
2618
2675
|
suppressApiFormatBadge: !mixed,
|
|
2619
|
-
strip
|
|
2676
|
+
strip,
|
|
2677
|
+
cacheTrend: cacheTrends?.get(log.id) ?? null,
|
|
2678
|
+
isSelected: selectedSet.has(log.id),
|
|
2679
|
+
onToggleSelect
|
|
2620
2680
|
},
|
|
2621
2681
|
log.id
|
|
2622
2682
|
)) })
|
|
@@ -3873,6 +3933,503 @@ function ProxySettingsTab() {
|
|
|
3873
3933
|
] })
|
|
3874
3934
|
] });
|
|
3875
3935
|
}
|
|
3936
|
+
function computeCacheTrends(groups) {
|
|
3937
|
+
const result = /* @__PURE__ */ new Map();
|
|
3938
|
+
for (const group of groups) {
|
|
3939
|
+
const logs = group.logs;
|
|
3940
|
+
for (let i = 1; i < logs.length; i++) {
|
|
3941
|
+
const prev = logs[i - 1];
|
|
3942
|
+
const curr = logs[i];
|
|
3943
|
+
if (prev === void 0 || curr === void 0) continue;
|
|
3944
|
+
result.set(curr.id, {
|
|
3945
|
+
creation: compareField(prev.cacheCreationInputTokens, curr.cacheCreationInputTokens),
|
|
3946
|
+
read: compareField(prev.cacheReadInputTokens, curr.cacheReadInputTokens)
|
|
3947
|
+
});
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
return result;
|
|
3951
|
+
}
|
|
3952
|
+
function compareField(previous, current) {
|
|
3953
|
+
if (current === null) return null;
|
|
3954
|
+
if (previous === null) return null;
|
|
3955
|
+
if (current > previous) return { direction: "up", delta: current - previous };
|
|
3956
|
+
if (current < previous) return { direction: "down", delta: previous - current };
|
|
3957
|
+
return null;
|
|
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
|
+
}
|
|
3876
4433
|
function truncateSessionId(id) {
|
|
3877
4434
|
if (id.length <= 30) return id;
|
|
3878
4435
|
return id.slice(0, 12) + "…" + id.slice(-12);
|
|
@@ -3953,6 +4510,9 @@ function ProxyViewer({
|
|
|
3953
4510
|
const { totalIn, totalOut } = computeTokenSummary(logs);
|
|
3954
4511
|
const [groupedView, setGroupedView] = reactExports.useState(true);
|
|
3955
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);
|
|
3956
4516
|
const handleExport = reactExports.useCallback(async () => {
|
|
3957
4517
|
setExporting(true);
|
|
3958
4518
|
try {
|
|
@@ -3962,7 +4522,75 @@ function ProxyViewer({
|
|
|
3962
4522
|
}
|
|
3963
4523
|
}, [logs]);
|
|
3964
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
|
+
}
|
|
3965
4592
|
const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
|
|
4593
|
+
const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
|
|
3966
4594
|
const renderGroups = logs.length > 0 && groupedView && !(groups.length === 1 && groups[0]?.logs.length === logs.length);
|
|
3967
4595
|
const rowVirtualizer = useVirtualizer({
|
|
3968
4596
|
count: renderGroups ? groups.length : logs.length,
|
|
@@ -4103,7 +4731,17 @@ function ProxyViewer({
|
|
|
4103
4731
|
width: "100%",
|
|
4104
4732
|
transform: `translateY(${virtualRow.start}px)`
|
|
4105
4733
|
},
|
|
4106
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4734
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4735
|
+
ConversationGroup,
|
|
4736
|
+
{
|
|
4737
|
+
group,
|
|
4738
|
+
viewMode,
|
|
4739
|
+
strip,
|
|
4740
|
+
cacheTrends,
|
|
4741
|
+
selectedSet,
|
|
4742
|
+
onToggleSelect: handleToggleSelect
|
|
4743
|
+
}
|
|
4744
|
+
)
|
|
4107
4745
|
},
|
|
4108
4746
|
group.id
|
|
4109
4747
|
);
|
|
@@ -4122,14 +4760,58 @@ function ProxyViewer({
|
|
|
4122
4760
|
width: "100%",
|
|
4123
4761
|
transform: `translateY(${virtualRow.start}px)`
|
|
4124
4762
|
},
|
|
4125
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4763
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4764
|
+
LogEntry,
|
|
4765
|
+
{
|
|
4766
|
+
log,
|
|
4767
|
+
viewMode,
|
|
4768
|
+
strip,
|
|
4769
|
+
cacheTrend: cacheTrends.get(log.id) ?? null,
|
|
4770
|
+
isSelected: selectedSet.has(log.id),
|
|
4771
|
+
onToggleSelect: handleToggleSelect
|
|
4772
|
+
}
|
|
4773
|
+
)
|
|
4126
4774
|
},
|
|
4127
4775
|
log.id
|
|
4128
4776
|
);
|
|
4129
4777
|
}
|
|
4130
4778
|
})
|
|
4131
4779
|
}
|
|
4132
|
-
) }) })
|
|
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 })
|
|
4133
4815
|
] });
|
|
4134
4816
|
}
|
|
4135
4817
|
object({
|