@tonyclaw/llm-inspector 1.12.0 → 1.14.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-B5q3Llgm.css +1 -0
- package/.output/public/assets/index-C6tbslcs.js +105 -0
- package/.output/public/assets/{main-BYCM7aJx.js → main-C1k6vRnH.js} +3 -3
- package/.output/server/_libs/cfworker__json-schema.mjs +1 -0
- package/.output/server/_libs/lucide-react.mjs +64 -58
- package/.output/server/_libs/modelcontextprotocol__server.mjs +9738 -0
- package/.output/server/_libs/zod.mjs +79 -16
- package/.output/server/_ssr/{index-DhChP_jV.mjs → index-AxruZp16.mjs} +1201 -165
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-PZjNwOcw.mjs → router-DtleGqN8.mjs} +650 -29
- package/.output/server/_tanstack-start-manifest_v-B1WAHWIa.mjs +4 -0
- package/.output/server/index.mjs +24 -24
- package/README.md +98 -0
- package/package.json +3 -1
- package/src/components/ProxyViewer.tsx +126 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +880 -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/formats/openai/ResponseView.tsx +74 -4
- package/src/components/proxy-viewer/requestDiff.ts +277 -0
- package/src/lib/serverPort.ts +41 -0
- package/src/mcp/loopback.ts +76 -0
- package/src/mcp/previewExtractor.ts +166 -0
- package/src/mcp/server.ts +320 -0
- package/src/mcp/toolHandlers.ts +259 -0
- package/src/proxy/formats/openai/schemas.ts +19 -0
- package/src/proxy/handler.ts +23 -2
- package/src/proxy/openaiOrphanToolStrip.ts +148 -0
- package/src/proxy/schemas.ts +1 -0
- package/src/routes/api/mcp.ts +25 -0
- package/.output/public/assets/index-DVgdkDgq.js +0 -105
- package/.output/public/assets/index-DZx2yk8v.css +0 -1
- package/.output/server/_tanstack-start-manifest_v-l1kWkG0h.mjs +0 -4
|
@@ -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-DtleGqN8.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,9 +9,10 @@ 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
|
|
12
|
+
import "../_libs/modelcontextprotocol__server.mjs";
|
|
13
|
+
import { D as Download, L as LayoutGrid, a as List, G as GitCompareArrows, X, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, U as Upload, 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, n as Minus, o as Pencil, E as Equal, p as ExternalLink, q as EyeOff, r as Eye, s as RotateCw, T as Trash2, A as ArrowUp, t as ArrowDown, u as TriangleAlert, v as CircleCheckBig, w as CircleStop, x as CircleQuestionMark, y as Server, z as Gauge, B as Lock, H as Wifi, I as WifiOff, J as ChevronsUp, K as ChevronsDown, N as Brain, O as Terminal } from "../_libs/lucide-react.mjs";
|
|
13
14
|
import { M as Markdown } from "../_libs/react-markdown.mjs";
|
|
14
|
-
import { a as array,
|
|
15
|
+
import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean, r as record, _ as _enum } from "../_libs/zod.mjs";
|
|
15
16
|
import { R as Root2$1, L as List$1, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
|
|
16
17
|
import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
|
|
17
18
|
import { P as Provider, R as Root3, T as Trigger$3, a as Portal$2, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
|
|
@@ -275,7 +276,7 @@ async function exportLogsAsZip(logs) {
|
|
|
275
276
|
document.body.removeChild(anchor);
|
|
276
277
|
URL.revokeObjectURL(url);
|
|
277
278
|
}
|
|
278
|
-
const version = "1.
|
|
279
|
+
const version = "1.14.0";
|
|
279
280
|
const packageJson = {
|
|
280
281
|
version
|
|
281
282
|
};
|
|
@@ -1414,7 +1415,9 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1414
1415
|
expanded,
|
|
1415
1416
|
onToggle,
|
|
1416
1417
|
suppressApiFormatBadge = false,
|
|
1417
|
-
cacheTrend = null
|
|
1418
|
+
cacheTrend = null,
|
|
1419
|
+
isSelected = false,
|
|
1420
|
+
onToggleSelect
|
|
1418
1421
|
}) {
|
|
1419
1422
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
1420
1423
|
const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
|
|
@@ -1438,6 +1441,23 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1438
1441
|
}
|
|
1439
1442
|
},
|
|
1440
1443
|
children: [
|
|
1444
|
+
onToggleSelect !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1445
|
+
"button",
|
|
1446
|
+
{
|
|
1447
|
+
type: "button",
|
|
1448
|
+
onClick: (e) => {
|
|
1449
|
+
e.stopPropagation();
|
|
1450
|
+
onToggleSelect(log.id);
|
|
1451
|
+
},
|
|
1452
|
+
"aria-label": isSelected ? "Deselect for comparison" : "Select for comparison",
|
|
1453
|
+
"aria-pressed": isSelected,
|
|
1454
|
+
className: cn(
|
|
1455
|
+
"shrink-0 size-4 rounded-sm border flex items-center justify-center transition-colors cursor-pointer",
|
|
1456
|
+
isSelected ? "bg-amber-400 border-amber-400 text-amber-950" : "border-muted-foreground/40 hover:border-amber-400 hover:bg-amber-400/10"
|
|
1457
|
+
),
|
|
1458
|
+
children: isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3", strokeWidth: 3 })
|
|
1459
|
+
}
|
|
1460
|
+
),
|
|
1441
1461
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0", children: [
|
|
1442
1462
|
"#",
|
|
1443
1463
|
log.id
|
|
@@ -1869,9 +1889,37 @@ function StructuredResponseViewAnthropic({
|
|
|
1869
1889
|
] })
|
|
1870
1890
|
] });
|
|
1871
1891
|
}
|
|
1892
|
+
function parseToolArguments(raw) {
|
|
1893
|
+
if (raw === void 0 || raw === "") return {};
|
|
1894
|
+
try {
|
|
1895
|
+
return JSON.parse(raw);
|
|
1896
|
+
} catch {
|
|
1897
|
+
return null;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function OpenAIToolCallBlock({ call }) {
|
|
1901
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
1902
|
+
const name = call.function.name ?? "(unnamed tool)";
|
|
1903
|
+
const parsed = parseToolArguments(call.function.arguments);
|
|
1904
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-l-2 border-blue-500/40 my-1", children: [
|
|
1905
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-blue-500/5 transition-colors rounded-r-sm group", children: [
|
|
1906
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5 text-blue-400 shrink-0" }),
|
|
1907
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "text-[10px] font-mono px-1.5 py-0 h-4", children: name }),
|
|
1908
|
+
call.id !== void 0 && call.id !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-muted-foreground/60 truncate", children: call.id }),
|
|
1909
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
|
|
1910
|
+
open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" })
|
|
1911
|
+
] }),
|
|
1912
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: parsed === null ? (
|
|
1913
|
+
// JSON.parse failed — show the raw string so the user can
|
|
1914
|
+
// still see what the model tried to call.
|
|
1915
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words text-rose-300/90", children: call.function.arguments })
|
|
1916
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(parsed), defaultExpandDepth: 2 }) }) }) })
|
|
1917
|
+
] }) });
|
|
1918
|
+
}
|
|
1872
1919
|
function OpenAIResponseView({ response }) {
|
|
1873
1920
|
const choice = response.choices[0];
|
|
1874
1921
|
const message = choice?.message;
|
|
1922
|
+
const toolCalls = message?.tool_calls ?? [];
|
|
1875
1923
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
|
|
1876
1924
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
1877
1925
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "secondary", className: "text-[10px] px-1.5 py-0 h-5 font-mono", children: response.model }),
|
|
@@ -1908,6 +1956,10 @@ function OpenAIResponseView({ response }) {
|
|
|
1908
1956
|
remainingText.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: remainingText }) })
|
|
1909
1957
|
] });
|
|
1910
1958
|
})(),
|
|
1959
|
+
toolCalls.map((call, i) => (
|
|
1960
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: tool_calls is the positionally stable list from the response
|
|
1961
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(OpenAIToolCallBlock, { call }, call.id ?? `tc-${i}`)
|
|
1962
|
+
)),
|
|
1911
1963
|
message?.function_call !== null && message?.function_call !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-blue-500/30 rounded-md p-3 bg-blue-500/5", children: [
|
|
1912
1964
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-blue-400 font-mono mb-1", children: "function_call" }),
|
|
1913
1965
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono text-xs", children: [
|
|
@@ -1919,7 +1971,7 @@ function OpenAIResponseView({ response }) {
|
|
|
1919
1971
|
] })
|
|
1920
1972
|
] })
|
|
1921
1973
|
] }),
|
|
1922
|
-
(message?.content === null || message?.content === void 0 || message.content.length === 0) && (message?.reasoning_content === null || message?.reasoning_content === void 0 || message.reasoning_content.length === 0) && (message?.function_call === null || message?.function_call === void 0) && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
|
|
1974
|
+
(message?.content === null || message?.content === void 0 || message.content.length === 0) && (message?.reasoning_content === null || message?.reasoning_content === void 0 || message.reasoning_content.length === 0) && (message?.function_call === null || message?.function_call === void 0) && toolCalls.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
|
|
1923
1975
|
] })
|
|
1924
1976
|
] });
|
|
1925
1977
|
}
|
|
@@ -2385,7 +2437,9 @@ const LogEntry = reactExports.memo(function({
|
|
|
2385
2437
|
viewMode = "simple",
|
|
2386
2438
|
suppressApiFormatBadge = false,
|
|
2387
2439
|
strip,
|
|
2388
|
-
cacheTrend = null
|
|
2440
|
+
cacheTrend = null,
|
|
2441
|
+
isSelected = false,
|
|
2442
|
+
onToggleSelect
|
|
2389
2443
|
}) {
|
|
2390
2444
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2391
2445
|
const [requestCopied, setRequestCopied] = reactExports.useState(false);
|
|
@@ -2435,164 +2489,175 @@ const LogEntry = reactExports.memo(function({
|
|
|
2435
2489
|
});
|
|
2436
2490
|
}
|
|
2437
2491
|
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,
|
|
2492
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2493
|
+
"div",
|
|
2494
|
+
{
|
|
2495
|
+
className: cn(
|
|
2496
|
+
"border border-border rounded-lg mb-3 overflow-hidden",
|
|
2497
|
+
isSelected && "border-l-2 border-l-amber-400"
|
|
2498
|
+
),
|
|
2499
|
+
children: [
|
|
2500
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2501
|
+
LogEntryHeader,
|
|
2462
2502
|
{
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2503
|
+
log,
|
|
2504
|
+
parsedRequest,
|
|
2505
|
+
expanded,
|
|
2506
|
+
onToggle: () => setExpanded(!expanded),
|
|
2507
|
+
suppressApiFormatBadge,
|
|
2508
|
+
cacheTrend,
|
|
2509
|
+
isSelected,
|
|
2510
|
+
onToggleSelect
|
|
2467
2511
|
}
|
|
2468
|
-
)
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
viewMode,
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2512
|
+
),
|
|
2513
|
+
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
|
|
2514
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
|
|
2515
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }),
|
|
2516
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }),
|
|
2517
|
+
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }),
|
|
2518
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
|
|
2519
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }),
|
|
2520
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
|
|
2521
|
+
] }),
|
|
2522
|
+
shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2523
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2524
|
+
CopyButton,
|
|
2525
|
+
{
|
|
2526
|
+
text: log.rawRequestBody,
|
|
2527
|
+
label: "Copy Raw Request",
|
|
2528
|
+
copied: rawRequestCopied,
|
|
2529
|
+
onCopy: handleCopyRawRequest
|
|
2485
2530
|
}
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
{
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2531
|
+
) }),
|
|
2532
|
+
log.rawRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.rawRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
|
|
2533
|
+
] }) }),
|
|
2534
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2535
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
|
|
2536
|
+
shouldShowRequestDiffButton(
|
|
2537
|
+
log.apiFormat,
|
|
2538
|
+
viewMode,
|
|
2539
|
+
strip,
|
|
2540
|
+
log.rawRequestBody !== null
|
|
2541
|
+
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2542
|
+
DiffToggleButton,
|
|
2543
|
+
{
|
|
2544
|
+
active: requestDiff,
|
|
2545
|
+
onClick: (e) => {
|
|
2546
|
+
e.stopPropagation();
|
|
2547
|
+
setRequestDiff(!requestDiff);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
),
|
|
2551
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2552
|
+
Button,
|
|
2553
|
+
{
|
|
2554
|
+
variant: "outline",
|
|
2555
|
+
size: "sm",
|
|
2556
|
+
className: "h-7 text-xs",
|
|
2557
|
+
onClick: (e) => {
|
|
2558
|
+
e.stopPropagation();
|
|
2559
|
+
setReplayOpen(true);
|
|
2560
|
+
},
|
|
2561
|
+
children: [
|
|
2562
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "size-3 mr-1" }),
|
|
2563
|
+
"Replay"
|
|
2564
|
+
]
|
|
2565
|
+
}
|
|
2566
|
+
),
|
|
2567
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2568
|
+
CopyButton,
|
|
2569
|
+
{
|
|
2570
|
+
text: displayedRequestBody,
|
|
2571
|
+
label: "Copy Request",
|
|
2572
|
+
copied: requestCopied,
|
|
2573
|
+
onCopy: handleCopyRequest
|
|
2574
|
+
}
|
|
2575
|
+
)
|
|
2576
|
+
] }),
|
|
2577
|
+
requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2578
|
+
DiffView,
|
|
2579
|
+
{
|
|
2580
|
+
result: requestDiffResult,
|
|
2581
|
+
emptyLabel: "No transformation applied — raw and sent request bodies are identical."
|
|
2582
|
+
}
|
|
2583
|
+
) : displayedRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: displayedRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
|
|
2584
|
+
] }) }),
|
|
2585
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
|
|
2586
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
|
|
2587
|
+
viewMode,
|
|
2588
|
+
log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
|
|
2589
|
+
) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2590
|
+
DiffToggleButton,
|
|
2591
|
+
{
|
|
2592
|
+
active: headersDiff,
|
|
2593
|
+
onClick: (e) => {
|
|
2594
|
+
e.stopPropagation();
|
|
2595
|
+
setHeadersDiff(!headersDiff);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
) }),
|
|
2599
|
+
headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2600
|
+
DiffView,
|
|
2601
|
+
{
|
|
2602
|
+
result: headersDiffResult,
|
|
2603
|
+
emptyLabel: "No transformation applied — raw and processed headers are identical."
|
|
2604
|
+
}
|
|
2605
|
+
) : log.headers && Object.keys(log.headers).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.headers).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
2606
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2607
|
+
key,
|
|
2608
|
+
":"
|
|
2609
|
+
] }),
|
|
2610
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2611
|
+
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
|
|
2612
|
+
] }) }),
|
|
2613
|
+
viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
2614
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
|
|
2615
|
+
key,
|
|
2616
|
+
":"
|
|
2617
|
+
] }),
|
|
2618
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
|
|
2619
|
+
] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
|
|
2620
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
|
|
2621
|
+
log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
|
|
2622
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
|
|
2623
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
|
|
2624
|
+
] }),
|
|
2625
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2626
|
+
CopyButton,
|
|
2627
|
+
{
|
|
2628
|
+
text: log.responseText,
|
|
2629
|
+
label: "Copy Response",
|
|
2630
|
+
copied: responseCopied,
|
|
2631
|
+
onCopy: handleCopyResponse
|
|
2632
|
+
}
|
|
2633
|
+
) }),
|
|
2634
|
+
log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.responseText, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
|
|
2635
|
+
log.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2636
|
+
StreamingChunkSequence,
|
|
2637
|
+
{
|
|
2638
|
+
logId: log.id,
|
|
2639
|
+
truncated: log.streamingChunksPath !== null
|
|
2640
|
+
}
|
|
2641
|
+
)
|
|
2642
|
+
] }) }),
|
|
2643
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2644
|
+
ResponseView,
|
|
2506
2645
|
{
|
|
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);
|
|
2646
|
+
responseText: log.responseText,
|
|
2647
|
+
responseStatus: log.responseStatus,
|
|
2648
|
+
streaming: log.streaming,
|
|
2649
|
+
inputTokens: log.inputTokens,
|
|
2650
|
+
outputTokens: log.outputTokens,
|
|
2651
|
+
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
2652
|
+
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
2653
|
+
apiFormat: log.apiFormat,
|
|
2654
|
+
error: log.error
|
|
2533
2655
|
}
|
|
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
|
-
] }),
|
|
2656
|
+
) }) })
|
|
2657
|
+
] }) })
|
|
2658
|
+
]
|
|
2659
|
+
}
|
|
2660
|
+
),
|
|
2596
2661
|
/* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
|
|
2597
2662
|
] });
|
|
2598
2663
|
});
|
|
@@ -2609,7 +2674,9 @@ const ConversationGroup = reactExports.memo(function({
|
|
|
2609
2674
|
group,
|
|
2610
2675
|
viewMode = "simple",
|
|
2611
2676
|
strip,
|
|
2612
|
-
cacheTrends
|
|
2677
|
+
cacheTrends,
|
|
2678
|
+
selectedSet,
|
|
2679
|
+
onToggleSelect
|
|
2613
2680
|
}) {
|
|
2614
2681
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2615
2682
|
const stats = computeStats(group.logs);
|
|
@@ -2640,7 +2707,9 @@ const ConversationGroup = reactExports.memo(function({
|
|
|
2640
2707
|
viewMode,
|
|
2641
2708
|
suppressApiFormatBadge: !mixed,
|
|
2642
2709
|
strip,
|
|
2643
|
-
cacheTrend: cacheTrends?.get(log.id) ?? null
|
|
2710
|
+
cacheTrend: cacheTrends?.get(log.id) ?? null,
|
|
2711
|
+
isSelected: selectedSet.has(log.id),
|
|
2712
|
+
onToggleSelect
|
|
2644
2713
|
},
|
|
2645
2714
|
log.id
|
|
2646
2715
|
)) })
|
|
@@ -3920,6 +3989,865 @@ function compareField(previous, current) {
|
|
|
3920
3989
|
if (current < previous) return { direction: "down", delta: previous - current };
|
|
3921
3990
|
return null;
|
|
3922
3991
|
}
|
|
3992
|
+
const ROOT_PATH = "";
|
|
3993
|
+
function formatPath(segments) {
|
|
3994
|
+
if (segments.length === 0) return ROOT_PATH;
|
|
3995
|
+
let out = "";
|
|
3996
|
+
for (let i = 0; i < segments.length; i++) {
|
|
3997
|
+
const seg = segments[i];
|
|
3998
|
+
if (seg === void 0) continue;
|
|
3999
|
+
if (typeof seg === "number") {
|
|
4000
|
+
out += `[${seg}]`;
|
|
4001
|
+
} else if (i === 0) {
|
|
4002
|
+
out += seg;
|
|
4003
|
+
} else {
|
|
4004
|
+
out += `.${seg}`;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
return out;
|
|
4008
|
+
}
|
|
4009
|
+
function isPlainObject(value) {
|
|
4010
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4011
|
+
}
|
|
4012
|
+
function normalizeRequest(raw) {
|
|
4013
|
+
if (typeof raw === "string") {
|
|
4014
|
+
try {
|
|
4015
|
+
return toNode(JSON.parse(raw));
|
|
4016
|
+
} catch {
|
|
4017
|
+
return { kind: "primitive", value: raw };
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
return toNode(raw);
|
|
4021
|
+
}
|
|
4022
|
+
function toNode(value) {
|
|
4023
|
+
if (value === null) return { kind: "primitive", value: null };
|
|
4024
|
+
if (typeof value === "string") return { kind: "primitive", value };
|
|
4025
|
+
if (typeof value === "number") return { kind: "primitive", value };
|
|
4026
|
+
if (typeof value === "boolean") return { kind: "primitive", value };
|
|
4027
|
+
if (Array.isArray(value)) {
|
|
4028
|
+
return { kind: "array", value: value.map((v) => toNode(v)) };
|
|
4029
|
+
}
|
|
4030
|
+
if (isPlainObject(value)) {
|
|
4031
|
+
const out = {};
|
|
4032
|
+
for (const k of Object.keys(value).sort()) {
|
|
4033
|
+
out[k] = toNode(value[k]);
|
|
4034
|
+
}
|
|
4035
|
+
return { kind: "object", value: out };
|
|
4036
|
+
}
|
|
4037
|
+
return { kind: "primitive", value: null };
|
|
4038
|
+
}
|
|
4039
|
+
function diffTrees(left, right) {
|
|
4040
|
+
const ops = [];
|
|
4041
|
+
walk([], left, right, ops);
|
|
4042
|
+
return ops;
|
|
4043
|
+
}
|
|
4044
|
+
function walk(segments, left, right, out) {
|
|
4045
|
+
const path = formatPath(segments);
|
|
4046
|
+
if (nodeEqual(left, right)) {
|
|
4047
|
+
out.push({ kind: "equal", path, value: left });
|
|
4048
|
+
return;
|
|
4049
|
+
}
|
|
4050
|
+
if (left.kind !== right.kind) {
|
|
4051
|
+
out.push({ kind: "changed", path, left, right });
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
4054
|
+
if (left.kind === "primitive" && right.kind === "primitive") {
|
|
4055
|
+
out.push({ kind: "changed", path, left, right });
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
4059
|
+
const leftKeys = Object.keys(left.value);
|
|
4060
|
+
const rightKeys = Object.keys(right.value);
|
|
4061
|
+
const rightKeySet = new Set(rightKeys);
|
|
4062
|
+
for (const k of leftKeys) {
|
|
4063
|
+
const lChild = left.value[k];
|
|
4064
|
+
if (lChild === void 0) continue;
|
|
4065
|
+
if (!rightKeySet.has(k)) {
|
|
4066
|
+
out.push({
|
|
4067
|
+
kind: "removed",
|
|
4068
|
+
path: formatPath([...segments, k]),
|
|
4069
|
+
value: lChild
|
|
4070
|
+
});
|
|
4071
|
+
} else {
|
|
4072
|
+
const rChild = right.value[k];
|
|
4073
|
+
if (rChild === void 0) continue;
|
|
4074
|
+
walk([...segments, k], lChild, rChild, out);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
for (const k of rightKeys) {
|
|
4078
|
+
if (leftKeys.includes(k)) continue;
|
|
4079
|
+
const rChild = right.value[k];
|
|
4080
|
+
if (rChild === void 0) continue;
|
|
4081
|
+
out.push({
|
|
4082
|
+
kind: "added",
|
|
4083
|
+
path: formatPath([...segments, k]),
|
|
4084
|
+
value: rChild
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
4089
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
4090
|
+
const minLen = Math.min(left.value.length, right.value.length);
|
|
4091
|
+
for (let i = 0; i < minLen; i++) {
|
|
4092
|
+
const lChild = left.value[i];
|
|
4093
|
+
const rChild = right.value[i];
|
|
4094
|
+
if (lChild === void 0 || rChild === void 0) continue;
|
|
4095
|
+
walk([...segments, i], lChild, rChild, out);
|
|
4096
|
+
}
|
|
4097
|
+
for (let i = minLen; i < right.value.length; i++) {
|
|
4098
|
+
const rChild = right.value[i];
|
|
4099
|
+
if (rChild === void 0) continue;
|
|
4100
|
+
out.push({
|
|
4101
|
+
kind: "added",
|
|
4102
|
+
path: formatPath([...segments, i]),
|
|
4103
|
+
value: rChild
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
for (let i = minLen; i < left.value.length; i++) {
|
|
4107
|
+
const lChild = left.value[i];
|
|
4108
|
+
if (lChild === void 0) continue;
|
|
4109
|
+
out.push({
|
|
4110
|
+
kind: "removed",
|
|
4111
|
+
path: formatPath([...segments, i]),
|
|
4112
|
+
value: lChild
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
function nodeEqual(a, b) {
|
|
4118
|
+
if (a.kind !== b.kind) return false;
|
|
4119
|
+
if (a.kind === "primitive" && b.kind === "primitive") {
|
|
4120
|
+
return a.value === b.value;
|
|
4121
|
+
}
|
|
4122
|
+
if (a.kind === "array" && b.kind === "array") {
|
|
4123
|
+
if (a.value.length !== b.value.length) return false;
|
|
4124
|
+
for (let i = 0; i < a.value.length; i++) {
|
|
4125
|
+
const ai = a.value[i];
|
|
4126
|
+
const bi = b.value[i];
|
|
4127
|
+
if (ai === void 0 || bi === void 0) return false;
|
|
4128
|
+
if (!nodeEqual(ai, bi)) return false;
|
|
4129
|
+
}
|
|
4130
|
+
return true;
|
|
4131
|
+
}
|
|
4132
|
+
if (a.kind === "object" && b.kind === "object") {
|
|
4133
|
+
const aKeys = Object.keys(a.value);
|
|
4134
|
+
const bKeys = Object.keys(b.value);
|
|
4135
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4136
|
+
for (const k of aKeys) {
|
|
4137
|
+
const av = a.value[k];
|
|
4138
|
+
const bv = b.value[k];
|
|
4139
|
+
if (av === void 0 || bv === void 0) return false;
|
|
4140
|
+
if (!nodeEqual(av, bv)) return false;
|
|
4141
|
+
}
|
|
4142
|
+
return true;
|
|
4143
|
+
}
|
|
4144
|
+
return false;
|
|
4145
|
+
}
|
|
4146
|
+
function previewNode(node, maxLen = 80) {
|
|
4147
|
+
let s;
|
|
4148
|
+
switch (node.kind) {
|
|
4149
|
+
case "primitive":
|
|
4150
|
+
s = node.value === null ? "null" : JSON.stringify(node.value);
|
|
4151
|
+
break;
|
|
4152
|
+
case "array":
|
|
4153
|
+
s = `[… ${node.value.length} items]`;
|
|
4154
|
+
break;
|
|
4155
|
+
case "object":
|
|
4156
|
+
s = `{… ${Object.keys(node.value).length} keys}`;
|
|
4157
|
+
break;
|
|
4158
|
+
}
|
|
4159
|
+
if (s.length > maxLen) s = `${s.slice(0, maxLen - 1)}…`;
|
|
4160
|
+
return s;
|
|
4161
|
+
}
|
|
4162
|
+
function nodeToJsonString(node, indent = 2) {
|
|
4163
|
+
return JSON.stringify(nodeToJsonValue(node), null, indent);
|
|
4164
|
+
}
|
|
4165
|
+
function nodeToJsonValue(node) {
|
|
4166
|
+
switch (node.kind) {
|
|
4167
|
+
case "primitive":
|
|
4168
|
+
return node.value;
|
|
4169
|
+
case "array":
|
|
4170
|
+
return node.value.map(nodeToJsonValue);
|
|
4171
|
+
case "object": {
|
|
4172
|
+
const out = {};
|
|
4173
|
+
for (const [k, v] of Object.entries(node.value)) {
|
|
4174
|
+
out[k] = nodeToJsonValue(v);
|
|
4175
|
+
}
|
|
4176
|
+
return out;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
function parentPath(path) {
|
|
4181
|
+
if (path === "") return "";
|
|
4182
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
4183
|
+
const ch = path[i];
|
|
4184
|
+
if (ch === "." || ch === "[") {
|
|
4185
|
+
return path.substring(0, i);
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
return "";
|
|
4189
|
+
}
|
|
4190
|
+
function isDeepEqual(op) {
|
|
4191
|
+
return op.kind === "equal" && (op.value.kind === "object" || op.value.kind === "array");
|
|
4192
|
+
}
|
|
4193
|
+
function groupContiguousEquals(ops) {
|
|
4194
|
+
const out = [];
|
|
4195
|
+
let i = 0;
|
|
4196
|
+
while (i < ops.length) {
|
|
4197
|
+
const op = ops[i];
|
|
4198
|
+
if (op !== void 0 && isDeepEqual(op)) {
|
|
4199
|
+
const startParent = parentPath(op.path);
|
|
4200
|
+
let j = i + 1;
|
|
4201
|
+
while (j < ops.length) {
|
|
4202
|
+
const next = ops[j];
|
|
4203
|
+
if (next === void 0) break;
|
|
4204
|
+
if (!isDeepEqual(next)) break;
|
|
4205
|
+
if (parentPath(next.path) !== startParent) break;
|
|
4206
|
+
j++;
|
|
4207
|
+
}
|
|
4208
|
+
if (j - i > 1) {
|
|
4209
|
+
const equalOps = [];
|
|
4210
|
+
for (let k = i; k < j; k++) {
|
|
4211
|
+
const eop = ops[k];
|
|
4212
|
+
if (eop !== void 0 && eop.kind === "equal") {
|
|
4213
|
+
equalOps.push(eop);
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
out.push({ kind: "equal-run", ops: equalOps });
|
|
4217
|
+
i = j;
|
|
4218
|
+
continue;
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
if (op !== void 0) {
|
|
4222
|
+
out.push({ kind: "single", op });
|
|
4223
|
+
}
|
|
4224
|
+
i++;
|
|
4225
|
+
}
|
|
4226
|
+
return out;
|
|
4227
|
+
}
|
|
4228
|
+
const KIND_VISUAL = {
|
|
4229
|
+
added: {
|
|
4230
|
+
icon: Plus,
|
|
4231
|
+
accent: "text-emerald-600 dark:text-emerald-400",
|
|
4232
|
+
bg: "bg-emerald-500/5 hover:bg-emerald-500/10",
|
|
4233
|
+
border: "border-l-emerald-500",
|
|
4234
|
+
label: "ADDED"
|
|
4235
|
+
},
|
|
4236
|
+
removed: {
|
|
4237
|
+
icon: Minus,
|
|
4238
|
+
accent: "text-rose-600 dark:text-rose-400",
|
|
4239
|
+
bg: "bg-rose-500/5 hover:bg-rose-500/10",
|
|
4240
|
+
border: "border-l-rose-500",
|
|
4241
|
+
label: "REMOVED"
|
|
4242
|
+
},
|
|
4243
|
+
changed: {
|
|
4244
|
+
icon: Pencil,
|
|
4245
|
+
accent: "text-amber-600 dark:text-amber-400",
|
|
4246
|
+
bg: "bg-amber-500/5 hover:bg-amber-500/10",
|
|
4247
|
+
border: "border-l-amber-500",
|
|
4248
|
+
label: "CHANGED"
|
|
4249
|
+
},
|
|
4250
|
+
equal: {
|
|
4251
|
+
icon: Equal,
|
|
4252
|
+
accent: "text-muted-foreground/70",
|
|
4253
|
+
bg: "bg-muted/20 hover:bg-muted/30",
|
|
4254
|
+
border: "border-l-muted-foreground/20",
|
|
4255
|
+
label: "EQUAL"
|
|
4256
|
+
}
|
|
4257
|
+
};
|
|
4258
|
+
function EqualRunRow({
|
|
4259
|
+
ops,
|
|
4260
|
+
expanded,
|
|
4261
|
+
onToggle
|
|
4262
|
+
}) {
|
|
4263
|
+
const first = ops[0];
|
|
4264
|
+
const last = ops[ops.length - 1];
|
|
4265
|
+
if (first === void 0 || last === void 0) {
|
|
4266
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground/40 text-xs", children: "—" });
|
|
4267
|
+
}
|
|
4268
|
+
const firstPath = first.path;
|
|
4269
|
+
const lastPath = last.path;
|
|
4270
|
+
const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
|
|
4271
|
+
const summary = first.value.kind === "array" ? `${ops.length} equal arrays` : first.value.kind === "object" ? `${ops.length} equal objects` : "equal";
|
|
4272
|
+
const v = KIND_VISUAL.equal;
|
|
4273
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("border-l-4 rounded-sm", v.border, v.bg), children: [
|
|
4274
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4275
|
+
"button",
|
|
4276
|
+
{
|
|
4277
|
+
type: "button",
|
|
4278
|
+
onClick: onToggle,
|
|
4279
|
+
className: "w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",
|
|
4280
|
+
children: [
|
|
4281
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4282
|
+
ChevronRight,
|
|
4283
|
+
{
|
|
4284
|
+
className: cn("size-3 transition-transform shrink-0", expanded && "rotate-90")
|
|
4285
|
+
}
|
|
4286
|
+
),
|
|
4287
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(v.icon, { className: cn("size-3 shrink-0", v.accent) }),
|
|
4288
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1", title: `${firstPath} … ${lastPath}`, children: label }),
|
|
4289
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-[10px] uppercase tracking-wider shrink-0", v.accent), children: v.label }),
|
|
4290
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 shrink-0", children: [
|
|
4291
|
+
"(",
|
|
4292
|
+
summary,
|
|
4293
|
+
")"
|
|
4294
|
+
] })
|
|
4295
|
+
]
|
|
4296
|
+
}
|
|
4297
|
+
),
|
|
4298
|
+
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-5 mt-1 mb-2 space-y-2 pr-2", children: ops.map((op) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border/50 rounded p-2 bg-muted/20", children: [
|
|
4299
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
|
|
4300
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 2 })
|
|
4301
|
+
] }, op.path)) })
|
|
4302
|
+
] });
|
|
4303
|
+
}
|
|
4304
|
+
function UnifiedOpRow({
|
|
4305
|
+
op,
|
|
4306
|
+
idx,
|
|
4307
|
+
copiedPath,
|
|
4308
|
+
onCopyPath,
|
|
4309
|
+
expanded,
|
|
4310
|
+
onToggle
|
|
4311
|
+
}) {
|
|
4312
|
+
const v = KIND_VISUAL[op.kind];
|
|
4313
|
+
const Icon2 = v.icon;
|
|
4314
|
+
const isExpandable2 = op.kind === "added" || op.kind === "removed" ? op.value.kind === "object" || op.value.kind === "array" : op.kind === "changed" ? op.left.kind === "object" || op.left.kind === "array" || op.right.kind === "object" || op.right.kind === "array" : false;
|
|
4315
|
+
const preview = op.kind === "changed" ? [
|
|
4316
|
+
{
|
|
4317
|
+
text: previewNode(op.left, 400),
|
|
4318
|
+
tone: "text-rose-700 dark:text-rose-300 line-through"
|
|
4319
|
+
},
|
|
4320
|
+
{ text: previewNode(op.right, 400), tone: "text-emerald-700 dark:text-emerald-300" }
|
|
4321
|
+
] : op.kind === "removed" ? [
|
|
4322
|
+
{
|
|
4323
|
+
text: previewNode(op.value, 400),
|
|
4324
|
+
tone: "text-rose-700 dark:text-rose-300 line-through"
|
|
4325
|
+
}
|
|
4326
|
+
] : op.kind === "added" ? [{ text: previewNode(op.value, 400), tone: "text-emerald-700 dark:text-emerald-300" }] : [{ text: previewNode(op.value, 400), tone: "text-muted-foreground" }];
|
|
4327
|
+
const justCopied = copiedPath === op.path && op.path !== "";
|
|
4328
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4329
|
+
"div",
|
|
4330
|
+
{
|
|
4331
|
+
"data-diff-idx": idx,
|
|
4332
|
+
"data-diff-kind": op.kind,
|
|
4333
|
+
className: cn("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors", v.border, v.bg),
|
|
4334
|
+
children: [
|
|
4335
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4336
|
+
"button",
|
|
4337
|
+
{
|
|
4338
|
+
type: "button",
|
|
4339
|
+
onClick: onToggle,
|
|
4340
|
+
disabled: !isExpandable2,
|
|
4341
|
+
className: cn(
|
|
4342
|
+
"w-full flex items-center gap-2 text-xs text-left rounded-sm",
|
|
4343
|
+
isExpandable2 ? "cursor-pointer" : "cursor-default"
|
|
4344
|
+
),
|
|
4345
|
+
"aria-expanded": isExpandable2 ? expanded : void 0,
|
|
4346
|
+
"aria-label": isExpandable2 ? expanded ? `Collapse ${op.path || "root"}` : `Expand ${op.path || "root"}` : void 0,
|
|
4347
|
+
children: [
|
|
4348
|
+
isExpandable2 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4349
|
+
ChevronRight,
|
|
4350
|
+
{
|
|
4351
|
+
className: cn(
|
|
4352
|
+
"size-3 shrink-0 transition-transform",
|
|
4353
|
+
v.accent,
|
|
4354
|
+
expanded && "rotate-90"
|
|
4355
|
+
)
|
|
4356
|
+
}
|
|
4357
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "size-3 shrink-0", "aria-hidden": "true" }),
|
|
4358
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { className: cn("size-3.5 shrink-0", v.accent), strokeWidth: 2.5 }),
|
|
4359
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1 min-w-0", title: op.path || "(root)", children: op.path === "" ? "(root)" : op.path }),
|
|
4360
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4361
|
+
"span",
|
|
4362
|
+
{
|
|
4363
|
+
className: cn(
|
|
4364
|
+
"text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",
|
|
4365
|
+
v.accent,
|
|
4366
|
+
op.kind === "equal" ? "bg-muted/40" : "bg-background/60"
|
|
4367
|
+
),
|
|
4368
|
+
children: v.label
|
|
4369
|
+
}
|
|
4370
|
+
),
|
|
4371
|
+
op.path !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4372
|
+
"span",
|
|
4373
|
+
{
|
|
4374
|
+
role: "button",
|
|
4375
|
+
tabIndex: 0,
|
|
4376
|
+
onClick: (e) => {
|
|
4377
|
+
e.stopPropagation();
|
|
4378
|
+
onCopyPath(op.path);
|
|
4379
|
+
},
|
|
4380
|
+
onKeyDown: (e) => {
|
|
4381
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
4382
|
+
e.stopPropagation();
|
|
4383
|
+
e.preventDefault();
|
|
4384
|
+
onCopyPath(op.path);
|
|
4385
|
+
}
|
|
4386
|
+
},
|
|
4387
|
+
className: cn(
|
|
4388
|
+
"shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
|
|
4389
|
+
justCopied ? "text-emerald-500" : "text-muted-foreground/50 hover:text-foreground hover:bg-muted"
|
|
4390
|
+
),
|
|
4391
|
+
"aria-label": justCopied ? "Copied" : "Copy path",
|
|
4392
|
+
title: justCopied ? "Copied!" : "Copy path",
|
|
4393
|
+
children: justCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
|
|
4394
|
+
}
|
|
4395
|
+
)
|
|
4396
|
+
]
|
|
4397
|
+
}
|
|
4398
|
+
),
|
|
4399
|
+
preview.map((p, i) => (
|
|
4400
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: preview list is rebuilt on every render and is positional
|
|
4401
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("font-mono text-xs mt-1 break-all pl-5", p.tone), children: p.text }, i)
|
|
4402
|
+
)),
|
|
4403
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4404
|
+
"div",
|
|
4405
|
+
{
|
|
4406
|
+
className: "overflow-hidden transition-all duration-200",
|
|
4407
|
+
style: { maxHeight: expanded && isExpandable2 ? "2000px" : "0" },
|
|
4408
|
+
"aria-hidden": !expanded,
|
|
4409
|
+
children: expanded && isExpandable2 && op.kind !== "equal" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedSubtree, { op }) : null
|
|
4410
|
+
}
|
|
4411
|
+
)
|
|
4412
|
+
]
|
|
4413
|
+
}
|
|
4414
|
+
);
|
|
4415
|
+
}
|
|
4416
|
+
function ExpandedSubtree({ op }) {
|
|
4417
|
+
if (op.kind === "added" || op.kind === "removed") {
|
|
4418
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 2 }) });
|
|
4419
|
+
}
|
|
4420
|
+
const leftIsStructured = op.left.kind === "object" || op.left.kind === "array";
|
|
4421
|
+
const rightIsStructured = op.right.kind === "object" || op.right.kind === "array";
|
|
4422
|
+
if (!leftIsStructured && !rightIsStructured) {
|
|
4423
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 text-xs text-muted-foreground/70 italic", children: "Primitive values are shown inline above." });
|
|
4424
|
+
}
|
|
4425
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2", children: [
|
|
4426
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-rose-500/30 rounded p-2 bg-rose-500/5", children: [
|
|
4427
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-rose-500 mb-1", children: "Old" }),
|
|
4428
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.left), defaultExpandDepth: 2 })
|
|
4429
|
+
] }),
|
|
4430
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-emerald-500/30 rounded p-2 bg-emerald-500/5", children: [
|
|
4431
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-emerald-500 mb-1", children: "New" }),
|
|
4432
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.right), defaultExpandDepth: 2 })
|
|
4433
|
+
] })
|
|
4434
|
+
] });
|
|
4435
|
+
}
|
|
4436
|
+
function SummaryChips({
|
|
4437
|
+
counts,
|
|
4438
|
+
onJumpTo
|
|
4439
|
+
}) {
|
|
4440
|
+
const total = counts.added + counts.removed + counts.changed;
|
|
4441
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap", children: [
|
|
4442
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-medium", children: [
|
|
4443
|
+
total,
|
|
4444
|
+
" ",
|
|
4445
|
+
total === 1 ? "change" : "changes"
|
|
4446
|
+
] }),
|
|
4447
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4448
|
+
"button",
|
|
4449
|
+
{
|
|
4450
|
+
type: "button",
|
|
4451
|
+
onClick: () => onJumpTo("removed"),
|
|
4452
|
+
disabled: counts.removed === 0,
|
|
4453
|
+
className: cn(
|
|
4454
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
4455
|
+
counts.removed > 0 ? "border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
|
|
4456
|
+
),
|
|
4457
|
+
title: counts.removed > 0 ? "Jump to first removed" : "No removals",
|
|
4458
|
+
children: [
|
|
4459
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
|
|
4460
|
+
counts.removed,
|
|
4461
|
+
" removed"
|
|
4462
|
+
]
|
|
4463
|
+
}
|
|
4464
|
+
),
|
|
4465
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4466
|
+
"button",
|
|
4467
|
+
{
|
|
4468
|
+
type: "button",
|
|
4469
|
+
onClick: () => onJumpTo("added"),
|
|
4470
|
+
disabled: counts.added === 0,
|
|
4471
|
+
className: cn(
|
|
4472
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
4473
|
+
counts.added > 0 ? "border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
|
|
4474
|
+
),
|
|
4475
|
+
title: counts.added > 0 ? "Jump to first added" : "No additions",
|
|
4476
|
+
children: [
|
|
4477
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-3" }),
|
|
4478
|
+
counts.added,
|
|
4479
|
+
" added"
|
|
4480
|
+
]
|
|
4481
|
+
}
|
|
4482
|
+
),
|
|
4483
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4484
|
+
"button",
|
|
4485
|
+
{
|
|
4486
|
+
type: "button",
|
|
4487
|
+
onClick: () => onJumpTo("changed"),
|
|
4488
|
+
disabled: counts.changed === 0,
|
|
4489
|
+
className: cn(
|
|
4490
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
4491
|
+
counts.changed > 0 ? "border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
|
|
4492
|
+
),
|
|
4493
|
+
title: counts.changed > 0 ? "Jump to first changed" : "No changes",
|
|
4494
|
+
children: [
|
|
4495
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "size-3" }),
|
|
4496
|
+
counts.changed,
|
|
4497
|
+
" changed"
|
|
4498
|
+
]
|
|
4499
|
+
}
|
|
4500
|
+
)
|
|
4501
|
+
] });
|
|
4502
|
+
}
|
|
4503
|
+
function ModeToggle({
|
|
4504
|
+
mode,
|
|
4505
|
+
onChange
|
|
4506
|
+
}) {
|
|
4507
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "inline-flex rounded-md border border-border overflow-hidden", children: [
|
|
4508
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4509
|
+
"button",
|
|
4510
|
+
{
|
|
4511
|
+
type: "button",
|
|
4512
|
+
onClick: () => onChange("unified"),
|
|
4513
|
+
"aria-pressed": mode === "unified",
|
|
4514
|
+
className: cn(
|
|
4515
|
+
"flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",
|
|
4516
|
+
mode === "unified" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
|
|
4517
|
+
),
|
|
4518
|
+
title: "Unified view (single column, emphasized diffs)",
|
|
4519
|
+
children: [
|
|
4520
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Rows3, { className: "size-3" }),
|
|
4521
|
+
"Unified"
|
|
4522
|
+
]
|
|
4523
|
+
}
|
|
4524
|
+
),
|
|
4525
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4526
|
+
"button",
|
|
4527
|
+
{
|
|
4528
|
+
type: "button",
|
|
4529
|
+
onClick: () => onChange("split"),
|
|
4530
|
+
"aria-pressed": mode === "split",
|
|
4531
|
+
className: cn(
|
|
4532
|
+
"flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",
|
|
4533
|
+
mode === "split" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
|
|
4534
|
+
),
|
|
4535
|
+
title: "Split view (path | left | right)",
|
|
4536
|
+
children: [
|
|
4537
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Columns2, { className: "size-3" }),
|
|
4538
|
+
"Split"
|
|
4539
|
+
]
|
|
4540
|
+
}
|
|
4541
|
+
)
|
|
4542
|
+
] });
|
|
4543
|
+
}
|
|
4544
|
+
function SideSummary({ log, side }) {
|
|
4545
|
+
const conversationId = getConversationId(log);
|
|
4546
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 space-y-1 text-xs", children: [
|
|
4547
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4548
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4549
|
+
Badge,
|
|
4550
|
+
{
|
|
4551
|
+
variant: "outline",
|
|
4552
|
+
className: cn(
|
|
4553
|
+
"text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
|
|
4554
|
+
side === "left" ? "border-rose-500/40 text-rose-400" : "border-emerald-500/40 text-emerald-400"
|
|
4555
|
+
),
|
|
4556
|
+
children: side === "left" ? "← Left" : "Right →"
|
|
4557
|
+
}
|
|
4558
|
+
),
|
|
4559
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-blue-400/80", children: [
|
|
4560
|
+
"#",
|
|
4561
|
+
log.id
|
|
4562
|
+
] }),
|
|
4563
|
+
log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground truncate", children: log.model })
|
|
4564
|
+
] }),
|
|
4565
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-muted-foreground font-mono", children: [
|
|
4566
|
+
log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400", children: [
|
|
4567
|
+
"Cache +",
|
|
4568
|
+
formatTokens(log.cacheCreationInputTokens)
|
|
4569
|
+
] }),
|
|
4570
|
+
log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-purple-400", children: [
|
|
4571
|
+
"Cache ~",
|
|
4572
|
+
formatTokens(log.cacheReadInputTokens)
|
|
4573
|
+
] }),
|
|
4574
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: log.timestamp, children: log.timestamp })
|
|
4575
|
+
] }),
|
|
4576
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-muted-foreground/70 font-mono truncate", title: conversationId, children: [
|
|
4577
|
+
"session: ",
|
|
4578
|
+
conversationId
|
|
4579
|
+
] })
|
|
4580
|
+
] });
|
|
4581
|
+
}
|
|
4582
|
+
function CompareDrawer({ left, right, onClose }) {
|
|
4583
|
+
const ops = reactExports.useMemo(() => {
|
|
4584
|
+
const l = normalizeRequest(parseRequest(left.rawRequestBody) ?? left.rawRequestBody);
|
|
4585
|
+
const r = normalizeRequest(parseRequest(right.rawRequestBody) ?? right.rawRequestBody);
|
|
4586
|
+
return diffTrees(l, r);
|
|
4587
|
+
}, [left.rawRequestBody, right.rawRequestBody]);
|
|
4588
|
+
const grouped = reactExports.useMemo(() => groupContiguousEquals(ops), [ops]);
|
|
4589
|
+
const counts = reactExports.useMemo(() => {
|
|
4590
|
+
let added = 0;
|
|
4591
|
+
let removed = 0;
|
|
4592
|
+
let changed = 0;
|
|
4593
|
+
for (const g of grouped) {
|
|
4594
|
+
if (g.kind !== "single") continue;
|
|
4595
|
+
switch (g.op.kind) {
|
|
4596
|
+
case "added":
|
|
4597
|
+
added++;
|
|
4598
|
+
break;
|
|
4599
|
+
case "removed":
|
|
4600
|
+
removed++;
|
|
4601
|
+
break;
|
|
4602
|
+
case "changed":
|
|
4603
|
+
changed++;
|
|
4604
|
+
break;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
return { added, removed, changed };
|
|
4608
|
+
}, [grouped]);
|
|
4609
|
+
const [expandedRuns, setExpandedRuns] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
4610
|
+
const toggleRun = (idx) => {
|
|
4611
|
+
setExpandedRuns((prev) => {
|
|
4612
|
+
const next = new Set(prev);
|
|
4613
|
+
if (next.has(idx)) next.delete(idx);
|
|
4614
|
+
else next.add(idx);
|
|
4615
|
+
return next;
|
|
4616
|
+
});
|
|
4617
|
+
};
|
|
4618
|
+
const [expandedRows, setExpandedRows] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
4619
|
+
const toggleRow = (idx) => {
|
|
4620
|
+
setExpandedRows((prev) => {
|
|
4621
|
+
const next = new Set(prev);
|
|
4622
|
+
if (next.has(idx)) next.delete(idx);
|
|
4623
|
+
else next.add(idx);
|
|
4624
|
+
return next;
|
|
4625
|
+
});
|
|
4626
|
+
};
|
|
4627
|
+
reactExports.useEffect(() => {
|
|
4628
|
+
setExpandedRows(/* @__PURE__ */ new Set());
|
|
4629
|
+
}, [left.id, right.id]);
|
|
4630
|
+
const [mode, setMode] = reactExports.useState("unified");
|
|
4631
|
+
const bodyRef = reactExports.useRef(null);
|
|
4632
|
+
const [copiedPath, setCopiedPath] = reactExports.useState(null);
|
|
4633
|
+
const copyResetTimer = reactExports.useRef(null);
|
|
4634
|
+
const onCopyPath = (path) => {
|
|
4635
|
+
void window.navigator.clipboard.writeText(path).then(() => {
|
|
4636
|
+
setCopiedPath(path);
|
|
4637
|
+
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
4638
|
+
copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
|
|
4639
|
+
});
|
|
4640
|
+
};
|
|
4641
|
+
reactExports.useEffect(() => {
|
|
4642
|
+
return () => {
|
|
4643
|
+
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
4644
|
+
};
|
|
4645
|
+
}, []);
|
|
4646
|
+
const jumpToKind = (kind) => {
|
|
4647
|
+
const idx = grouped.findIndex((g) => g.kind === "single" && g.op.kind === kind);
|
|
4648
|
+
if (idx === -1) return;
|
|
4649
|
+
const root = bodyRef.current;
|
|
4650
|
+
if (root === null) return;
|
|
4651
|
+
const el = root.querySelector(`[data-diff-idx="${idx}"]`);
|
|
4652
|
+
if (el !== null) {
|
|
4653
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
4654
|
+
}
|
|
4655
|
+
};
|
|
4656
|
+
reactExports.useEffect(() => {
|
|
4657
|
+
const onKey = (e) => {
|
|
4658
|
+
if (e.key === "Escape") onClose();
|
|
4659
|
+
};
|
|
4660
|
+
document.addEventListener("keydown", onKey);
|
|
4661
|
+
const prevOverflow = document.body.style.overflow;
|
|
4662
|
+
document.body.style.overflow = "hidden";
|
|
4663
|
+
return () => {
|
|
4664
|
+
document.removeEventListener("keydown", onKey);
|
|
4665
|
+
document.body.style.overflow = prevOverflow;
|
|
4666
|
+
};
|
|
4667
|
+
}, [onClose]);
|
|
4668
|
+
const sameSession = getConversationId(left) === getConversationId(right);
|
|
4669
|
+
const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
|
|
4670
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4671
|
+
"div",
|
|
4672
|
+
{
|
|
4673
|
+
className: "fixed inset-0 z-50 flex justify-end",
|
|
4674
|
+
role: "dialog",
|
|
4675
|
+
"aria-modal": "true",
|
|
4676
|
+
"aria-label": "Compare two log requests",
|
|
4677
|
+
children: [
|
|
4678
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4679
|
+
"button",
|
|
4680
|
+
{
|
|
4681
|
+
type: "button",
|
|
4682
|
+
onClick: onClose,
|
|
4683
|
+
"aria-label": "Close compare drawer",
|
|
4684
|
+
className: "absolute inset-0 bg-black/40 cursor-default",
|
|
4685
|
+
tabIndex: -1
|
|
4686
|
+
}
|
|
4687
|
+
),
|
|
4688
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4689
|
+
"div",
|
|
4690
|
+
{
|
|
4691
|
+
className: cn(
|
|
4692
|
+
"relative bg-background border-l border-border shadow-xl",
|
|
4693
|
+
"w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"
|
|
4694
|
+
),
|
|
4695
|
+
onClick: (e) => e.stopPropagation(),
|
|
4696
|
+
onKeyDown: (e) => e.stopPropagation(),
|
|
4697
|
+
children: [
|
|
4698
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-4 px-4 py-3 border-b border-border", children: [
|
|
4699
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-4 min-w-0", children: [
|
|
4700
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: left, side: "left" }),
|
|
4701
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: right, side: "right" })
|
|
4702
|
+
] }),
|
|
4703
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4704
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ModeToggle, { mode, onChange: setMode }),
|
|
4705
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4706
|
+
"button",
|
|
4707
|
+
{
|
|
4708
|
+
type: "button",
|
|
4709
|
+
onClick: onClose,
|
|
4710
|
+
"aria-label": "Close",
|
|
4711
|
+
className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",
|
|
4712
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
|
|
4713
|
+
}
|
|
4714
|
+
)
|
|
4715
|
+
] })
|
|
4716
|
+
] }),
|
|
4717
|
+
!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." }),
|
|
4718
|
+
allEqual ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm", children: "The two Request payloads are identical." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
4719
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChips, { counts, onJumpTo: jumpToKind }),
|
|
4720
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: bodyRef, className: "flex-1 min-h-0 overflow-y-auto", children: mode === "unified" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 space-y-0.5", children: grouped.map((g, i) => {
|
|
4721
|
+
if (g.kind === "equal-run") {
|
|
4722
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4723
|
+
EqualRunRow,
|
|
4724
|
+
{
|
|
4725
|
+
ops: g.ops,
|
|
4726
|
+
expanded: expandedRuns.has(i),
|
|
4727
|
+
onToggle: () => toggleRun(i)
|
|
4728
|
+
},
|
|
4729
|
+
`r${i}`
|
|
4730
|
+
);
|
|
4731
|
+
}
|
|
4732
|
+
const op = g.op;
|
|
4733
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4734
|
+
UnifiedOpRow,
|
|
4735
|
+
{
|
|
4736
|
+
op,
|
|
4737
|
+
idx: i,
|
|
4738
|
+
copiedPath,
|
|
4739
|
+
onCopyPath,
|
|
4740
|
+
expanded: expandedRows.has(i),
|
|
4741
|
+
onToggle: () => toggleRow(i)
|
|
4742
|
+
},
|
|
4743
|
+
`o${i}`
|
|
4744
|
+
);
|
|
4745
|
+
}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(SplitBody, { grouped, left, right }) })
|
|
4746
|
+
] })
|
|
4747
|
+
]
|
|
4748
|
+
}
|
|
4749
|
+
)
|
|
4750
|
+
]
|
|
4751
|
+
}
|
|
4752
|
+
);
|
|
4753
|
+
}
|
|
4754
|
+
function SplitBody({
|
|
4755
|
+
grouped,
|
|
4756
|
+
left,
|
|
4757
|
+
right
|
|
4758
|
+
}) {
|
|
4759
|
+
return /* @__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: [
|
|
4760
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground", children: [
|
|
4761
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Path" }),
|
|
4762
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4763
|
+
"Left (Log #",
|
|
4764
|
+
left.id,
|
|
4765
|
+
")"
|
|
4766
|
+
] }),
|
|
4767
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4768
|
+
"Right (Log #",
|
|
4769
|
+
right.id,
|
|
4770
|
+
")"
|
|
4771
|
+
] })
|
|
4772
|
+
] }),
|
|
4773
|
+
grouped.map((g, i) => {
|
|
4774
|
+
if (g.kind === "equal-run") {
|
|
4775
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4776
|
+
"div",
|
|
4777
|
+
{
|
|
4778
|
+
className: "col-span-3 px-2 py-1 text-xs text-muted-foreground/60",
|
|
4779
|
+
children: [
|
|
4780
|
+
g.ops.length,
|
|
4781
|
+
" equal siblings collapsed — switch to Unified to expand"
|
|
4782
|
+
]
|
|
4783
|
+
},
|
|
4784
|
+
i
|
|
4785
|
+
);
|
|
4786
|
+
}
|
|
4787
|
+
const op = g.op;
|
|
4788
|
+
if (op.kind === "equal") {
|
|
4789
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4790
|
+
"div",
|
|
4791
|
+
{
|
|
4792
|
+
className: "col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",
|
|
4793
|
+
children: [
|
|
4794
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs truncate", title: op.path, children: op.path }),
|
|
4795
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) }),
|
|
4796
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) })
|
|
4797
|
+
]
|
|
4798
|
+
},
|
|
4799
|
+
i
|
|
4800
|
+
);
|
|
4801
|
+
}
|
|
4802
|
+
if (op.kind === "added") {
|
|
4803
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4804
|
+
"div",
|
|
4805
|
+
{
|
|
4806
|
+
className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",
|
|
4807
|
+
children: [
|
|
4808
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
|
|
4809
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-emerald-300/90", children: [
|
|
4810
|
+
"+ ",
|
|
4811
|
+
previewNode(op.value, 400)
|
|
4812
|
+
] })
|
|
4813
|
+
]
|
|
4814
|
+
},
|
|
4815
|
+
i
|
|
4816
|
+
);
|
|
4817
|
+
}
|
|
4818
|
+
if (op.kind === "removed") {
|
|
4819
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4820
|
+
"div",
|
|
4821
|
+
{
|
|
4822
|
+
className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",
|
|
4823
|
+
children: [
|
|
4824
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
|
|
4825
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-rose-300/90 line-through", children: [
|
|
4826
|
+
"− ",
|
|
4827
|
+
previewNode(op.value, 400)
|
|
4828
|
+
] })
|
|
4829
|
+
]
|
|
4830
|
+
},
|
|
4831
|
+
i
|
|
4832
|
+
);
|
|
4833
|
+
}
|
|
4834
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4835
|
+
"div",
|
|
4836
|
+
{
|
|
4837
|
+
className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",
|
|
4838
|
+
children: [
|
|
4839
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
|
|
4840
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
4841
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-rose-300/90 break-all line-through", children: previewNode(op.left, 400) }),
|
|
4842
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-300/90 break-all", children: previewNode(op.right, 400) })
|
|
4843
|
+
] })
|
|
4844
|
+
]
|
|
4845
|
+
},
|
|
4846
|
+
i
|
|
4847
|
+
);
|
|
4848
|
+
})
|
|
4849
|
+
] });
|
|
4850
|
+
}
|
|
3923
4851
|
function truncateSessionId(id) {
|
|
3924
4852
|
if (id.length <= 30) return id;
|
|
3925
4853
|
return id.slice(0, 12) + "…" + id.slice(-12);
|
|
@@ -4000,6 +4928,9 @@ function ProxyViewer({
|
|
|
4000
4928
|
const { totalIn, totalOut } = computeTokenSummary(logs);
|
|
4001
4929
|
const [groupedView, setGroupedView] = reactExports.useState(true);
|
|
4002
4930
|
const [exporting, setExporting] = reactExports.useState(false);
|
|
4931
|
+
const [selectedLogIds, setSelectedLogIds] = reactExports.useState([]);
|
|
4932
|
+
const [compareOpen, setCompareOpen] = reactExports.useState(false);
|
|
4933
|
+
const [comparePair, setComparePair] = reactExports.useState(null);
|
|
4003
4934
|
const handleExport = reactExports.useCallback(async () => {
|
|
4004
4935
|
setExporting(true);
|
|
4005
4936
|
try {
|
|
@@ -4009,6 +4940,73 @@ function ProxyViewer({
|
|
|
4009
4940
|
}
|
|
4010
4941
|
}, [logs]);
|
|
4011
4942
|
const parentRef = reactExports.useRef(null);
|
|
4943
|
+
const handleToggleSelect = reactExports.useCallback((logId) => {
|
|
4944
|
+
setSelectedLogIds((prev) => {
|
|
4945
|
+
if (prev.includes(logId)) {
|
|
4946
|
+
return prev.filter((id) => id !== logId);
|
|
4947
|
+
}
|
|
4948
|
+
if (prev.length < 2) {
|
|
4949
|
+
return [...prev, logId];
|
|
4950
|
+
}
|
|
4951
|
+
const newer = prev[1];
|
|
4952
|
+
if (newer === void 0) return prev;
|
|
4953
|
+
return [newer, logId];
|
|
4954
|
+
});
|
|
4955
|
+
}, []);
|
|
4956
|
+
reactExports.useEffect(() => {
|
|
4957
|
+
setSelectedLogIds([]);
|
|
4958
|
+
setCompareOpen(false);
|
|
4959
|
+
}, [selectedSession, selectedModel]);
|
|
4960
|
+
const selectedSet = reactExports.useMemo(() => new Set(selectedLogIds), [selectedLogIds]);
|
|
4961
|
+
const openCompare = reactExports.useCallback(() => {
|
|
4962
|
+
if (selectedLogIds.length !== 2) return;
|
|
4963
|
+
const [idA, idB] = selectedLogIds;
|
|
4964
|
+
if (idA === void 0 || idB === void 0) return;
|
|
4965
|
+
const logA = logs.find((l) => l.id === idA);
|
|
4966
|
+
const logB = logs.find((l) => l.id === idB);
|
|
4967
|
+
if (logA === void 0 || logB === void 0) return;
|
|
4968
|
+
setComparePair([logA, logB]);
|
|
4969
|
+
setCompareOpen(true);
|
|
4970
|
+
}, [selectedLogIds, logs]);
|
|
4971
|
+
const closeCompare = reactExports.useCallback(() => {
|
|
4972
|
+
setCompareOpen(false);
|
|
4973
|
+
}, []);
|
|
4974
|
+
const clearSelection = reactExports.useCallback(() => {
|
|
4975
|
+
setSelectedLogIds([]);
|
|
4976
|
+
}, []);
|
|
4977
|
+
const selectedSummary = reactExports.useMemo(() => {
|
|
4978
|
+
if (selectedLogIds.length !== 2) return null;
|
|
4979
|
+
const [idA, idB] = selectedLogIds;
|
|
4980
|
+
if (idA === void 0 || idB === void 0) return null;
|
|
4981
|
+
const logA = logs.find((l) => l.id === idA);
|
|
4982
|
+
const logB = logs.find((l) => l.id === idB);
|
|
4983
|
+
if (logA === void 0 || logB === void 0) return null;
|
|
4984
|
+
const sameSession = getConversationId(logA) === getConversationId(logB);
|
|
4985
|
+
let elapsed = "";
|
|
4986
|
+
if (logA.timestamp !== null && logB.timestamp !== null) {
|
|
4987
|
+
const a = Date.parse(logA.timestamp);
|
|
4988
|
+
const b = Date.parse(logB.timestamp);
|
|
4989
|
+
if (!Number.isNaN(a) && !Number.isNaN(b)) {
|
|
4990
|
+
const ms = Math.abs(b - a);
|
|
4991
|
+
elapsed = formatElapsed2(ms);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
return {
|
|
4995
|
+
logA,
|
|
4996
|
+
logB,
|
|
4997
|
+
sameSession,
|
|
4998
|
+
elapsed
|
|
4999
|
+
};
|
|
5000
|
+
}, [selectedLogIds, logs]);
|
|
5001
|
+
function formatElapsed2(ms) {
|
|
5002
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
5003
|
+
const sec = Math.floor(ms / 1e3);
|
|
5004
|
+
if (sec < 60) return `${sec}s`;
|
|
5005
|
+
const min = Math.floor(sec / 60);
|
|
5006
|
+
if (min < 60) return `${min}m`;
|
|
5007
|
+
const hr = Math.floor(min / 60);
|
|
5008
|
+
return `${hr}h${min % 60}m`;
|
|
5009
|
+
}
|
|
4012
5010
|
const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
|
|
4013
5011
|
const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
|
|
4014
5012
|
const renderGroups = logs.length > 0 && groupedView && !(groups.length === 1 && groups[0]?.logs.length === logs.length);
|
|
@@ -4157,7 +5155,9 @@ function ProxyViewer({
|
|
|
4157
5155
|
group,
|
|
4158
5156
|
viewMode,
|
|
4159
5157
|
strip,
|
|
4160
|
-
cacheTrends
|
|
5158
|
+
cacheTrends,
|
|
5159
|
+
selectedSet,
|
|
5160
|
+
onToggleSelect: handleToggleSelect
|
|
4161
5161
|
}
|
|
4162
5162
|
)
|
|
4163
5163
|
},
|
|
@@ -4184,7 +5184,9 @@ function ProxyViewer({
|
|
|
4184
5184
|
log,
|
|
4185
5185
|
viewMode,
|
|
4186
5186
|
strip,
|
|
4187
|
-
cacheTrend: cacheTrends.get(log.id) ?? null
|
|
5187
|
+
cacheTrend: cacheTrends.get(log.id) ?? null,
|
|
5188
|
+
isSelected: selectedSet.has(log.id),
|
|
5189
|
+
onToggleSelect: handleToggleSelect
|
|
4188
5190
|
}
|
|
4189
5191
|
)
|
|
4190
5192
|
},
|
|
@@ -4193,7 +5195,41 @@ function ProxyViewer({
|
|
|
4193
5195
|
}
|
|
4194
5196
|
})
|
|
4195
5197
|
}
|
|
4196
|
-
) }) })
|
|
5198
|
+
) }) }),
|
|
5199
|
+
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: [
|
|
5200
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-4 text-amber-400 shrink-0" }),
|
|
5201
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-mono", children: [
|
|
5202
|
+
"#",
|
|
5203
|
+
selectedSummary.logA.id,
|
|
5204
|
+
" ↔ #",
|
|
5205
|
+
selectedSummary.logB.id,
|
|
5206
|
+
" · ",
|
|
5207
|
+
selectedSummary.sameSession ? "same session" : "different sessions",
|
|
5208
|
+
selectedSummary.elapsed !== "" && ` · ${selectedSummary.elapsed} apart`
|
|
5209
|
+
] }),
|
|
5210
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
5211
|
+
"button",
|
|
5212
|
+
{
|
|
5213
|
+
type: "button",
|
|
5214
|
+
onClick: clearSelection,
|
|
5215
|
+
className: "text-muted-foreground hover:text-foreground transition-colors cursor-pointer inline-flex items-center gap-1",
|
|
5216
|
+
children: [
|
|
5217
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-3" }),
|
|
5218
|
+
"Clear"
|
|
5219
|
+
]
|
|
5220
|
+
}
|
|
5221
|
+
),
|
|
5222
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
5223
|
+
"button",
|
|
5224
|
+
{
|
|
5225
|
+
type: "button",
|
|
5226
|
+
onClick: openCompare,
|
|
5227
|
+
className: "bg-amber-400 text-amber-950 hover:bg-amber-300 transition-colors px-3 py-1 rounded font-medium cursor-pointer",
|
|
5228
|
+
children: "Compare 2 logs"
|
|
5229
|
+
}
|
|
5230
|
+
)
|
|
5231
|
+
] }),
|
|
5232
|
+
compareOpen && comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
|
|
4197
5233
|
] });
|
|
4198
5234
|
}
|
|
4199
5235
|
object({
|