@tonyclaw/llm-inspector 1.11.7 → 1.12.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-D8CKc4zg.js → index-DVgdkDgq.js} +31 -31
- package/.output/public/assets/index-DZx2yk8v.css +1 -0
- package/.output/public/assets/{main-7LuJWU4Q.js → main-BYCM7aJx.js} +3 -3
- package/.output/server/_libs/lucide-react.mjs +26 -14
- package/.output/server/_ssr/{index-i-uTB3Y3.mjs → index-DhChP_jV.mjs} +82 -18
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-CYFPDggE.mjs → router-PZjNwOcw.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-jl5h5rzb.mjs → _tanstack-start-manifest_v-l1kWkG0h.mjs} +1 -1
- package/.output/server/index.mjs +25 -25
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +14 -2
- package/src/components/proxy-viewer/ConversationGroup.tsx +8 -0
- package/src/components/proxy-viewer/LogEntry.tsx +8 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +32 -0
- package/src/components/proxy-viewer/cacheTrend.ts +50 -0
- package/.output/public/assets/index-DiYqfnSp.css +0 -1
|
@@ -75,6 +75,16 @@ const createLucideIcon = (iconName, iconNode) => {
|
|
|
75
75
|
Component.displayName = toPascalCase(iconName);
|
|
76
76
|
return Component;
|
|
77
77
|
};
|
|
78
|
+
const __iconNode$L = [
|
|
79
|
+
["path", { d: "M12 5v14", key: "s699le" }],
|
|
80
|
+
["path", { d: "m19 12-7 7-7-7", key: "1idqje" }]
|
|
81
|
+
];
|
|
82
|
+
const ArrowDown = createLucideIcon("arrow-down", __iconNode$L);
|
|
83
|
+
const __iconNode$K = [
|
|
84
|
+
["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }],
|
|
85
|
+
["path", { d: "M12 19V5", key: "x0mq9r" }]
|
|
86
|
+
];
|
|
87
|
+
const ArrowUp = createLucideIcon("arrow-up", __iconNode$K);
|
|
78
88
|
const __iconNode$J = [
|
|
79
89
|
["path", { d: "M12 18V5", key: "adv99a" }],
|
|
80
90
|
["path", { d: "M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4", key: "1e3is1" }],
|
|
@@ -388,18 +398,20 @@ const __iconNode = [
|
|
|
388
398
|
];
|
|
389
399
|
const Zap = createLucideIcon("zap", __iconNode);
|
|
390
400
|
export {
|
|
391
|
-
|
|
392
|
-
|
|
401
|
+
ArrowUp as A,
|
|
402
|
+
Wifi as B,
|
|
393
403
|
ChevronDown as C,
|
|
394
404
|
Download as D,
|
|
395
405
|
ExternalLink as E,
|
|
396
406
|
FileTerminal as F,
|
|
397
407
|
Globe as G,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
408
|
+
WifiOff as H,
|
|
409
|
+
ChevronsUp as I,
|
|
410
|
+
ChevronsDown as J,
|
|
411
|
+
Brain as K,
|
|
401
412
|
LayoutGrid as L,
|
|
402
413
|
MessageSquare as M,
|
|
414
|
+
Terminal as N,
|
|
403
415
|
Plus as P,
|
|
404
416
|
RotateCcw as R,
|
|
405
417
|
Settings as S,
|
|
@@ -425,13 +437,13 @@ export {
|
|
|
425
437
|
Eye as o,
|
|
426
438
|
RotateCw as p,
|
|
427
439
|
Pencil as q,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
440
|
+
ArrowDown as r,
|
|
441
|
+
TriangleAlert as s,
|
|
442
|
+
Minus as t,
|
|
443
|
+
CircleCheckBig as u,
|
|
444
|
+
CircleStop as v,
|
|
445
|
+
CircleQuestionMark as w,
|
|
446
|
+
Server as x,
|
|
447
|
+
Gauge as y,
|
|
448
|
+
Lock as z
|
|
437
449
|
};
|
|
@@ -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-PZjNwOcw.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, X, 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, G as Globe, i as User, F as FileTerminal, j as Radio, k as GitCompareArrows, l as Rows3, m as Columns2, E as ExternalLink, n as EyeOff, o as Eye, p as RotateCw, q as Pencil, T as Trash2, r as
|
|
12
|
+
import { D as Download, L as LayoutGrid, a as List, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, X, 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, G as Globe, i as User, F as FileTerminal, j as Radio, k as GitCompareArrows, 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.12.0";
|
|
279
279
|
const packageJson = {
|
|
280
280
|
version
|
|
281
281
|
};
|
|
@@ -1395,12 +1395,26 @@ 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
|
|
1404
1418
|
}) {
|
|
1405
1419
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
1406
1420
|
const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
|
|
@@ -1490,14 +1504,20 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
1490
1504
|
)
|
|
1491
1505
|
] })
|
|
1492
1506
|
] }),
|
|
1493
|
-
log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1507
|
+
log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
|
|
1508
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
|
|
1509
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
|
|
1510
|
+
"Cache +",
|
|
1511
|
+
formatTokens(log.cacheCreationInputTokens)
|
|
1512
|
+
] })
|
|
1513
|
+
] }),
|
|
1514
|
+
log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
|
|
1515
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
|
|
1516
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
|
|
1517
|
+
"Cache ~",
|
|
1518
|
+
formatTokens(log.cacheReadInputTokens)
|
|
1519
|
+
] })
|
|
1520
|
+
] }),
|
|
1501
1521
|
messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
|
|
1502
1522
|
/* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
|
|
1503
1523
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: messageCount })
|
|
@@ -2364,7 +2384,8 @@ const LogEntry = reactExports.memo(function({
|
|
|
2364
2384
|
log,
|
|
2365
2385
|
viewMode = "simple",
|
|
2366
2386
|
suppressApiFormatBadge = false,
|
|
2367
|
-
strip
|
|
2387
|
+
strip,
|
|
2388
|
+
cacheTrend = null
|
|
2368
2389
|
}) {
|
|
2369
2390
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2370
2391
|
const [requestCopied, setRequestCopied] = reactExports.useState(false);
|
|
@@ -2422,7 +2443,8 @@ const LogEntry = reactExports.memo(function({
|
|
|
2422
2443
|
parsedRequest,
|
|
2423
2444
|
expanded,
|
|
2424
2445
|
onToggle: () => setExpanded(!expanded),
|
|
2425
|
-
suppressApiFormatBadge
|
|
2446
|
+
suppressApiFormatBadge,
|
|
2447
|
+
cacheTrend
|
|
2426
2448
|
}
|
|
2427
2449
|
),
|
|
2428
2450
|
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
|
|
@@ -2586,7 +2608,8 @@ function computeStats(logs) {
|
|
|
2586
2608
|
const ConversationGroup = reactExports.memo(function({
|
|
2587
2609
|
group,
|
|
2588
2610
|
viewMode = "simple",
|
|
2589
|
-
strip
|
|
2611
|
+
strip,
|
|
2612
|
+
cacheTrends
|
|
2590
2613
|
}) {
|
|
2591
2614
|
const [expanded, setExpanded] = reactExports.useState(false);
|
|
2592
2615
|
const stats = computeStats(group.logs);
|
|
@@ -2616,7 +2639,8 @@ const ConversationGroup = reactExports.memo(function({
|
|
|
2616
2639
|
log,
|
|
2617
2640
|
viewMode,
|
|
2618
2641
|
suppressApiFormatBadge: !mixed,
|
|
2619
|
-
strip
|
|
2642
|
+
strip,
|
|
2643
|
+
cacheTrend: cacheTrends?.get(log.id) ?? null
|
|
2620
2644
|
},
|
|
2621
2645
|
log.id
|
|
2622
2646
|
)) })
|
|
@@ -3873,6 +3897,29 @@ function ProxySettingsTab() {
|
|
|
3873
3897
|
] })
|
|
3874
3898
|
] });
|
|
3875
3899
|
}
|
|
3900
|
+
function computeCacheTrends(groups) {
|
|
3901
|
+
const result = /* @__PURE__ */ new Map();
|
|
3902
|
+
for (const group of groups) {
|
|
3903
|
+
const logs = group.logs;
|
|
3904
|
+
for (let i = 1; i < logs.length; i++) {
|
|
3905
|
+
const prev = logs[i - 1];
|
|
3906
|
+
const curr = logs[i];
|
|
3907
|
+
if (prev === void 0 || curr === void 0) continue;
|
|
3908
|
+
result.set(curr.id, {
|
|
3909
|
+
creation: compareField(prev.cacheCreationInputTokens, curr.cacheCreationInputTokens),
|
|
3910
|
+
read: compareField(prev.cacheReadInputTokens, curr.cacheReadInputTokens)
|
|
3911
|
+
});
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
return result;
|
|
3915
|
+
}
|
|
3916
|
+
function compareField(previous, current) {
|
|
3917
|
+
if (current === null) return null;
|
|
3918
|
+
if (previous === null) return null;
|
|
3919
|
+
if (current > previous) return { direction: "up", delta: current - previous };
|
|
3920
|
+
if (current < previous) return { direction: "down", delta: previous - current };
|
|
3921
|
+
return null;
|
|
3922
|
+
}
|
|
3876
3923
|
function truncateSessionId(id) {
|
|
3877
3924
|
if (id.length <= 30) return id;
|
|
3878
3925
|
return id.slice(0, 12) + "…" + id.slice(-12);
|
|
@@ -3963,6 +4010,7 @@ function ProxyViewer({
|
|
|
3963
4010
|
}, [logs]);
|
|
3964
4011
|
const parentRef = reactExports.useRef(null);
|
|
3965
4012
|
const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
|
|
4013
|
+
const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
|
|
3966
4014
|
const renderGroups = logs.length > 0 && groupedView && !(groups.length === 1 && groups[0]?.logs.length === logs.length);
|
|
3967
4015
|
const rowVirtualizer = useVirtualizer({
|
|
3968
4016
|
count: renderGroups ? groups.length : logs.length,
|
|
@@ -4103,7 +4151,15 @@ function ProxyViewer({
|
|
|
4103
4151
|
width: "100%",
|
|
4104
4152
|
transform: `translateY(${virtualRow.start}px)`
|
|
4105
4153
|
},
|
|
4106
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4154
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4155
|
+
ConversationGroup,
|
|
4156
|
+
{
|
|
4157
|
+
group,
|
|
4158
|
+
viewMode,
|
|
4159
|
+
strip,
|
|
4160
|
+
cacheTrends
|
|
4161
|
+
}
|
|
4162
|
+
)
|
|
4107
4163
|
},
|
|
4108
4164
|
group.id
|
|
4109
4165
|
);
|
|
@@ -4122,7 +4178,15 @@ function ProxyViewer({
|
|
|
4122
4178
|
width: "100%",
|
|
4123
4179
|
transform: `translateY(${virtualRow.start}px)`
|
|
4124
4180
|
},
|
|
4125
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4181
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4182
|
+
LogEntry,
|
|
4183
|
+
{
|
|
4184
|
+
log,
|
|
4185
|
+
viewMode,
|
|
4186
|
+
strip,
|
|
4187
|
+
cacheTrend: cacheTrends.get(log.id) ?? null
|
|
4188
|
+
}
|
|
4189
|
+
)
|
|
4126
4190
|
},
|
|
4127
4191
|
log.id
|
|
4128
4192
|
);
|
|
@@ -198,7 +198,7 @@ function getResponse() {
|
|
|
198
198
|
return event.res;
|
|
199
199
|
}
|
|
200
200
|
async function getStartManifest(matchedRoutes) {
|
|
201
|
-
const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-
|
|
201
|
+
const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-l1kWkG0h.mjs");
|
|
202
202
|
const startManifest = tsrStartManifest();
|
|
203
203
|
const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
|
|
204
204
|
rootRoute.assets = rootRoute.assets || [];
|
|
@@ -767,7 +767,7 @@ let entriesPromise;
|
|
|
767
767
|
let baseManifestPromise;
|
|
768
768
|
let cachedFinalManifestPromise;
|
|
769
769
|
async function loadEntries() {
|
|
770
|
-
const routerEntry = await import("./router-
|
|
770
|
+
const routerEntry = await import("./router-PZjNwOcw.mjs").then((n) => n.r);
|
|
771
771
|
const startEntry = await import("./start-HYkvq4Ni.mjs");
|
|
772
772
|
return { startEntry, routerEntry };
|
|
773
773
|
}
|
|
@@ -44,7 +44,7 @@ import "../_libs/debounce-fn.mjs";
|
|
|
44
44
|
import "../_libs/mimic-function.mjs";
|
|
45
45
|
import "../_libs/semver.mjs";
|
|
46
46
|
import "../_libs/uint8array-extras.mjs";
|
|
47
|
-
const appCss = "/assets/index-
|
|
47
|
+
const appCss = "/assets/index-DZx2yk8v.css";
|
|
48
48
|
const Route$h = createRootRoute({
|
|
49
49
|
head: () => ({
|
|
50
50
|
meta: [
|
|
@@ -68,7 +68,7 @@ function RootDocument({ children }) {
|
|
|
68
68
|
] })
|
|
69
69
|
] });
|
|
70
70
|
}
|
|
71
|
-
const $$splitComponentImporter = () => import("./index-
|
|
71
|
+
const $$splitComponentImporter = () => import("./index-DhChP_jV.mjs");
|
|
72
72
|
const Route$g = createFileRoute("/")({
|
|
73
73
|
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
74
74
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-
|
|
1
|
+
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-BYCM7aJx.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DVgdkDgq.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts" } }, "clientEntry": "/assets/main-BYCM7aJx.js" });
|
|
2
2
|
export {
|
|
3
3
|
tsrStartManifest
|
|
4
4
|
};
|
package/.output/server/index.mjs
CHANGED
|
@@ -35,54 +35,54 @@ const headers = ((m) => function headersRouteRule(event) {
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
const assets = {
|
|
38
|
+
"/assets/alibaba-TTwafVwX.svg": {
|
|
39
|
+
"type": "image/svg+xml",
|
|
40
|
+
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
41
|
+
"mtime": "2026-06-09T08:02:30.828Z",
|
|
42
|
+
"size": 5915,
|
|
43
|
+
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
44
|
+
},
|
|
38
45
|
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
39
46
|
"type": "image/jpeg",
|
|
40
47
|
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
41
|
-
"mtime": "2026-06-
|
|
48
|
+
"mtime": "2026-06-09T08:02:30.828Z",
|
|
42
49
|
"size": 6918,
|
|
43
50
|
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
44
51
|
},
|
|
45
|
-
"/assets/
|
|
46
|
-
"type": "
|
|
47
|
-
"etag": '"
|
|
48
|
-
"mtime": "2026-06-
|
|
49
|
-
"size":
|
|
50
|
-
"path": "../public/assets/
|
|
52
|
+
"/assets/index-DZx2yk8v.css": {
|
|
53
|
+
"type": "text/css; charset=utf-8",
|
|
54
|
+
"etag": '"1177b-93l31JbQrAbdmLoUOxZGbqTQwcc"',
|
|
55
|
+
"mtime": "2026-06-09T08:02:30.830Z",
|
|
56
|
+
"size": 71547,
|
|
57
|
+
"path": "../public/assets/index-DZx2yk8v.css"
|
|
51
58
|
},
|
|
52
59
|
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
53
60
|
"type": "image/svg+xml",
|
|
54
61
|
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
55
|
-
"mtime": "2026-06-
|
|
62
|
+
"mtime": "2026-06-09T08:02:30.828Z",
|
|
56
63
|
"size": 11256,
|
|
57
64
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
58
65
|
},
|
|
59
|
-
"/assets/
|
|
60
|
-
"type": "text/css; charset=utf-8",
|
|
61
|
-
"etag": '"11755-g+paI7gxmWkWiKXnB74OdQXg0jE"',
|
|
62
|
-
"mtime": "2026-06-08T10:48:17.641Z",
|
|
63
|
-
"size": 71509,
|
|
64
|
-
"path": "../public/assets/index-DiYqfnSp.css"
|
|
65
|
-
},
|
|
66
|
-
"/assets/main-7LuJWU4Q.js": {
|
|
66
|
+
"/assets/main-BYCM7aJx.js": {
|
|
67
67
|
"type": "text/javascript; charset=utf-8",
|
|
68
|
-
"etag": '"50599-
|
|
69
|
-
"mtime": "2026-06-
|
|
68
|
+
"etag": '"50599-LT3fOJsfcl49n+Hp+rSe62uyiuk"',
|
|
69
|
+
"mtime": "2026-06-09T08:02:30.830Z",
|
|
70
70
|
"size": 329113,
|
|
71
|
-
"path": "../public/assets/main-
|
|
71
|
+
"path": "../public/assets/main-BYCM7aJx.js"
|
|
72
72
|
},
|
|
73
73
|
"/assets/qwen-CONDcHqt.png": {
|
|
74
74
|
"type": "image/png",
|
|
75
75
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
76
|
-
"mtime": "2026-06-
|
|
76
|
+
"mtime": "2026-06-09T08:02:30.830Z",
|
|
77
77
|
"size": 357059,
|
|
78
78
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
79
79
|
},
|
|
80
|
-
"/assets/index-
|
|
80
|
+
"/assets/index-DVgdkDgq.js": {
|
|
81
81
|
"type": "text/javascript; charset=utf-8",
|
|
82
|
-
"etag": '"
|
|
83
|
-
"mtime": "2026-06-
|
|
84
|
-
"size":
|
|
85
|
-
"path": "../public/assets/index-
|
|
82
|
+
"etag": '"8a87f-jt4qwgWrMm8cZtArzPx7qX8p12k"',
|
|
83
|
+
"mtime": "2026-06-09T08:02:30.830Z",
|
|
84
|
+
"size": 567423,
|
|
85
|
+
"path": "../public/assets/index-DVgdkDgq.js"
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
function readAsset(id) {
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { CrabLogo } from "./ui/crab-logo";
|
|
14
14
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
|
15
15
|
import { SettingsDialog } from "./providers/SettingsDialog";
|
|
16
|
+
import { computeCacheTrends } from "./proxy-viewer/cacheTrend";
|
|
16
17
|
|
|
17
18
|
function truncateSessionId(id: string): string {
|
|
18
19
|
if (id.length <= 30) return id;
|
|
@@ -124,6 +125,7 @@ export function ProxyViewer({
|
|
|
124
125
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
125
126
|
|
|
126
127
|
const groups = useMemo(() => groupLogsByConversation(logs), [logs]);
|
|
128
|
+
const cacheTrends = useMemo(() => computeCacheTrends(groups), [groups]);
|
|
127
129
|
|
|
128
130
|
// Determine what items to render (groups or individual logs)
|
|
129
131
|
const renderGroups =
|
|
@@ -303,7 +305,12 @@ export function ProxyViewer({
|
|
|
303
305
|
transform: `translateY(${virtualRow.start}px)`,
|
|
304
306
|
}}
|
|
305
307
|
>
|
|
306
|
-
<ConversationGroup
|
|
308
|
+
<ConversationGroup
|
|
309
|
+
group={group}
|
|
310
|
+
viewMode={viewMode}
|
|
311
|
+
strip={strip}
|
|
312
|
+
cacheTrends={cacheTrends}
|
|
313
|
+
/>
|
|
307
314
|
</div>
|
|
308
315
|
);
|
|
309
316
|
} else {
|
|
@@ -322,7 +329,12 @@ export function ProxyViewer({
|
|
|
322
329
|
transform: `translateY(${virtualRow.start}px)`,
|
|
323
330
|
}}
|
|
324
331
|
>
|
|
325
|
-
<LogEntry
|
|
332
|
+
<LogEntry
|
|
333
|
+
log={log}
|
|
334
|
+
viewMode={viewMode}
|
|
335
|
+
strip={strip}
|
|
336
|
+
cacheTrend={cacheTrends.get(log.id) ?? null}
|
|
337
|
+
/>
|
|
326
338
|
</div>
|
|
327
339
|
);
|
|
328
340
|
}
|
|
@@ -9,12 +9,18 @@ import {
|
|
|
9
9
|
type ConversationGroupData,
|
|
10
10
|
} from "./ConversationHeader";
|
|
11
11
|
import { LogEntry } from "./LogEntry";
|
|
12
|
+
import type { CacheTrendEntry } from "./cacheTrend";
|
|
12
13
|
|
|
13
14
|
export type ConversationGroupProps = {
|
|
14
15
|
group: ConversationGroupData;
|
|
15
16
|
viewMode?: "simple" | "full";
|
|
16
17
|
/** Live strip-Claude-Code-billing-header flag from the viewer container. */
|
|
17
18
|
strip: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Pre-computed per-log cache token trend map (keyed by `log.id`) shared
|
|
21
|
+
* across the whole viewer. Each `LogEntry` looks up its own entry.
|
|
22
|
+
*/
|
|
23
|
+
cacheTrends?: Map<number, CacheTrendEntry>;
|
|
18
24
|
};
|
|
19
25
|
|
|
20
26
|
function computeStats(logs: CapturedLog[]): {
|
|
@@ -34,6 +40,7 @@ export const ConversationGroup = memo(function ({
|
|
|
34
40
|
group,
|
|
35
41
|
viewMode = "simple",
|
|
36
42
|
strip,
|
|
43
|
+
cacheTrends,
|
|
37
44
|
}: ConversationGroupProps): JSX.Element {
|
|
38
45
|
const [expanded, setExpanded] = useState(false);
|
|
39
46
|
|
|
@@ -73,6 +80,7 @@ export const ConversationGroup = memo(function ({
|
|
|
73
80
|
viewMode={viewMode}
|
|
74
81
|
suppressApiFormatBadge={!mixed}
|
|
75
82
|
strip={strip}
|
|
83
|
+
cacheTrend={cacheTrends?.get(log.id) ?? null}
|
|
76
84
|
/>
|
|
77
85
|
))}
|
|
78
86
|
</div>
|
|
@@ -12,6 +12,7 @@ import { LogEntryHeader } from "./LogEntryHeader";
|
|
|
12
12
|
import { ReplayDialog } from "./ReplayDialog";
|
|
13
13
|
import { ResponseView } from "./ResponseView";
|
|
14
14
|
import { StreamingChunkSequence } from "./StreamingChunkSequence";
|
|
15
|
+
import type { CacheTrendEntry } from "./cacheTrend";
|
|
15
16
|
|
|
16
17
|
export type LogEntryProps = {
|
|
17
18
|
log: CapturedLog;
|
|
@@ -25,6 +26,11 @@ export type LogEntryProps = {
|
|
|
25
26
|
* cost).
|
|
26
27
|
*/
|
|
27
28
|
strip: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Per-log cache token trend, looked up in the viewer-level trend map.
|
|
31
|
+
* `null` (or absent) means the header should render with no arrows.
|
|
32
|
+
*/
|
|
33
|
+
cacheTrend?: CacheTrendEntry | null;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
/**
|
|
@@ -131,6 +137,7 @@ export const LogEntry = memo(function ({
|
|
|
131
137
|
viewMode = "simple",
|
|
132
138
|
suppressApiFormatBadge = false,
|
|
133
139
|
strip,
|
|
140
|
+
cacheTrend = null,
|
|
134
141
|
}: LogEntryProps): JSX.Element {
|
|
135
142
|
const [expanded, setExpanded] = useState<boolean>(false);
|
|
136
143
|
const [requestCopied, setRequestCopied] = useState<boolean>(false);
|
|
@@ -193,6 +200,7 @@ export const LogEntry = memo(function ({
|
|
|
193
200
|
expanded={expanded}
|
|
194
201
|
onToggle={() => setExpanded(!expanded)}
|
|
195
202
|
suppressApiFormatBadge={suppressApiFormatBadge}
|
|
203
|
+
cacheTrend={cacheTrend}
|
|
196
204
|
/>
|
|
197
205
|
|
|
198
206
|
{expanded && (
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArrowDown,
|
|
3
|
+
ArrowUp,
|
|
2
4
|
ChevronDown,
|
|
3
5
|
ChevronRight,
|
|
4
6
|
Clock,
|
|
@@ -17,6 +19,7 @@ import { cn, formatTokens, getStatusCategory, type StatusCategory } from "../../
|
|
|
17
19
|
import type { CapturedLog, InspectorRequest } from "../../proxy/schemas";
|
|
18
20
|
import { Badge } from "../ui/badge";
|
|
19
21
|
import { ProviderLogo, detectProvider } from "../providers/ProviderLogo";
|
|
22
|
+
import type { CacheTrend } from "./cacheTrend";
|
|
20
23
|
|
|
21
24
|
function formatElapsed(ms: number): string {
|
|
22
25
|
if (ms < 1000) return `${ms}ms`;
|
|
@@ -30,6 +33,26 @@ const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
|
|
|
30
33
|
pending: "bg-muted text-muted-foreground border-border",
|
|
31
34
|
};
|
|
32
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Inline trend indicator: small arrow (green up / red down) plus the absolute
|
|
38
|
+
* delta in compact form. Returns `null` when there is no trend to display.
|
|
39
|
+
*/
|
|
40
|
+
function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Element | null {
|
|
41
|
+
if (trend === null) return null;
|
|
42
|
+
const isUp = trend.direction === "up";
|
|
43
|
+
const Icon = isUp ? ArrowUp : ArrowDown;
|
|
44
|
+
const sign = isUp ? "+" : "-";
|
|
45
|
+
return (
|
|
46
|
+
<span className="flex items-center gap-0.5 text-muted-foreground tabular-nums">
|
|
47
|
+
<Icon className={isUp ? "size-3 text-emerald-400" : "size-3 text-rose-400"} />
|
|
48
|
+
<span className="font-mono">
|
|
49
|
+
{sign}
|
|
50
|
+
{formatTokens(trend.delta)}
|
|
51
|
+
</span>
|
|
52
|
+
</span>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
export type LogEntryHeaderProps = {
|
|
34
57
|
log: CapturedLog;
|
|
35
58
|
parsedRequest: InspectorRequest | null;
|
|
@@ -37,6 +60,12 @@ export type LogEntryHeaderProps = {
|
|
|
37
60
|
onToggle: () => void;
|
|
38
61
|
/** Suppress the API format badge when log is displayed within a group */
|
|
39
62
|
suppressApiFormatBadge?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Per-log cache token trend (creation + read) relative to the previous log
|
|
65
|
+
* in the same conversation group. When `undefined` or a field is `null`,
|
|
66
|
+
* the corresponding cache span renders as it did before — no arrow.
|
|
67
|
+
*/
|
|
68
|
+
cacheTrend?: { creation: CacheTrend | null; read: CacheTrend | null } | null;
|
|
40
69
|
};
|
|
41
70
|
|
|
42
71
|
export const LogEntryHeader = memo(function ({
|
|
@@ -45,6 +74,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
45
74
|
expanded,
|
|
46
75
|
onToggle,
|
|
47
76
|
suppressApiFormatBadge = false,
|
|
77
|
+
cacheTrend = null,
|
|
48
78
|
}: LogEntryHeaderProps): JSX.Element {
|
|
49
79
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
50
80
|
|
|
@@ -163,6 +193,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
163
193
|
{/* Cache tokens */}
|
|
164
194
|
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
165
195
|
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
196
|
+
<CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
|
|
166
197
|
<span className="font-mono tabular-nums text-emerald-400">
|
|
167
198
|
Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
168
199
|
</span>
|
|
@@ -170,6 +201,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
170
201
|
)}
|
|
171
202
|
{log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
|
|
172
203
|
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
204
|
+
<CacheTrendIndicator trend={cacheTrend?.read ?? null} />
|
|
173
205
|
<span className="font-mono tabular-nums text-purple-400">
|
|
174
206
|
Cache ~{formatTokens(log.cacheReadInputTokens)}
|
|
175
207
|
</span>
|