@tonyclaw/llm-inspector 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/index-B0anmGQr.css +1 -0
- package/.output/public/assets/index-H_thmL2_.js +105 -0
- package/.output/public/assets/{main-BYCM7aJx.js → main-C3tLo75s.js} +3 -3
- package/.output/server/_libs/lucide-react.mjs +4 -4
- package/.output/server/_ssr/{index-DhChP_jV.mjs → index-C8VC13EA.mjs} +781 -163
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-PZjNwOcw.mjs → router-D5ccnemB.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-l1kWkG0h.mjs → _tanstack-start-manifest_v-DUbXa1lt.mjs} +1 -1
- package/.output/server/index.mjs +26 -26
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +126 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +388 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +8 -0
- package/src/components/proxy-viewer/LogEntry.tsx +14 -1
- package/src/components/proxy-viewer/LogEntryHeader.tsx +28 -0
- package/src/components/proxy-viewer/requestDiff.ts +277 -0
- package/.output/public/assets/index-DVgdkDgq.js +0 -105
- package/.output/public/assets/index-DZx2yk8v.css +0 -1
|
@@ -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-DUbXa1lt.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-D5ccnemB.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-B0anmGQr.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-C8VC13EA.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-C3tLo75s.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-H_thmL2_.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-C3tLo75s.js" });
|
|
2
2
|
export {
|
|
3
3
|
tsrStartManifest
|
|
4
4
|
};
|
package/.output/server/index.mjs
CHANGED
|
@@ -38,51 +38,51 @@ const assets = {
|
|
|
38
38
|
"/assets/alibaba-TTwafVwX.svg": {
|
|
39
39
|
"type": "image/svg+xml",
|
|
40
40
|
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
41
|
-
"mtime": "2026-06-
|
|
41
|
+
"mtime": "2026-06-09T09:07:57.962Z",
|
|
42
42
|
"size": 5915,
|
|
43
43
|
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
44
44
|
},
|
|
45
|
-
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
46
|
-
"type": "image/jpeg",
|
|
47
|
-
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
48
|
-
"mtime": "2026-06-09T08:02:30.828Z",
|
|
49
|
-
"size": 6918,
|
|
50
|
-
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
51
|
-
},
|
|
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"
|
|
58
|
-
},
|
|
59
45
|
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
60
46
|
"type": "image/svg+xml",
|
|
61
47
|
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
62
|
-
"mtime": "2026-06-
|
|
48
|
+
"mtime": "2026-06-09T09:07:57.965Z",
|
|
63
49
|
"size": 11256,
|
|
64
50
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
65
51
|
},
|
|
66
|
-
"/assets/
|
|
52
|
+
"/assets/index-B0anmGQr.css": {
|
|
53
|
+
"type": "text/css; charset=utf-8",
|
|
54
|
+
"etag": '"12a87-6IlHwgxaeugM2Z9VI5/+fVbaA40"',
|
|
55
|
+
"mtime": "2026-06-09T09:07:57.965Z",
|
|
56
|
+
"size": 76423,
|
|
57
|
+
"path": "../public/assets/index-B0anmGQr.css"
|
|
58
|
+
},
|
|
59
|
+
"/assets/main-C3tLo75s.js": {
|
|
67
60
|
"type": "text/javascript; charset=utf-8",
|
|
68
|
-
"etag": '"50599-
|
|
69
|
-
"mtime": "2026-06-
|
|
61
|
+
"etag": '"50599-nkxkxzEGV0G+Y8N4Lyzxy/ivv4w"',
|
|
62
|
+
"mtime": "2026-06-09T09:07:57.965Z",
|
|
70
63
|
"size": 329113,
|
|
71
|
-
"path": "../public/assets/main-
|
|
64
|
+
"path": "../public/assets/main-C3tLo75s.js"
|
|
72
65
|
},
|
|
73
66
|
"/assets/qwen-CONDcHqt.png": {
|
|
74
67
|
"type": "image/png",
|
|
75
68
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
76
|
-
"mtime": "2026-06-
|
|
69
|
+
"mtime": "2026-06-09T09:07:57.965Z",
|
|
77
70
|
"size": 357059,
|
|
78
71
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
79
72
|
},
|
|
80
|
-
"/assets/
|
|
73
|
+
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
74
|
+
"type": "image/jpeg",
|
|
75
|
+
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
76
|
+
"mtime": "2026-06-09T09:07:57.965Z",
|
|
77
|
+
"size": 6918,
|
|
78
|
+
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
79
|
+
},
|
|
80
|
+
"/assets/index-H_thmL2_.js": {
|
|
81
81
|
"type": "text/javascript; charset=utf-8",
|
|
82
|
-
"etag": '"
|
|
83
|
-
"mtime": "2026-06-
|
|
84
|
-
"size":
|
|
85
|
-
"path": "../public/assets/index-
|
|
82
|
+
"etag": '"8daed-n1W4t2jQHmBQs8e35+QecepRw4w"',
|
|
83
|
+
"mtime": "2026-06-09T09:07:57.965Z",
|
|
84
|
+
"size": 580333,
|
|
85
|
+
"path": "../public/assets/index-H_thmL2_.js"
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
function readAsset(id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type JSX, useCallback, useMemo, useState, useRef } from "react";
|
|
1
|
+
import { type JSX, useCallback, useEffect, useMemo, useState, useRef } from "react";
|
|
2
2
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
3
|
-
import { Download, LayoutGrid, List } from "lucide-react";
|
|
3
|
+
import { Download, GitCompareArrows, LayoutGrid, List, X } from "lucide-react";
|
|
4
4
|
import type { CapturedLog } from "../proxy/schemas";
|
|
5
5
|
import { exportLogsAsZip } from "../lib/export-logs";
|
|
6
6
|
import packageJson from "../../package.json";
|
|
@@ -14,6 +14,8 @@ 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
16
|
import { computeCacheTrends } from "./proxy-viewer/cacheTrend";
|
|
17
|
+
import { CompareDrawer } from "./proxy-viewer/CompareDrawer";
|
|
18
|
+
import { getConversationId } from "./proxy-viewer/ConversationHeader";
|
|
17
19
|
|
|
18
20
|
function truncateSessionId(id: string): string {
|
|
19
21
|
if (id.length <= 30) return id;
|
|
@@ -113,6 +115,9 @@ export function ProxyViewer({
|
|
|
113
115
|
const { totalIn, totalOut } = computeTokenSummary(logs);
|
|
114
116
|
const [groupedView, setGroupedView] = useState(true);
|
|
115
117
|
const [exporting, setExporting] = useState(false);
|
|
118
|
+
const [selectedLogIds, setSelectedLogIds] = useState<number[]>([]);
|
|
119
|
+
const [compareOpen, setCompareOpen] = useState(false);
|
|
120
|
+
const [comparePair, setComparePair] = useState<[CapturedLog, CapturedLog] | null>(null);
|
|
116
121
|
|
|
117
122
|
const handleExport = useCallback(async () => {
|
|
118
123
|
setExporting(true);
|
|
@@ -124,6 +129,88 @@ export function ProxyViewer({
|
|
|
124
129
|
}, [logs]);
|
|
125
130
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
126
131
|
|
|
132
|
+
const handleToggleSelect = useCallback((logId: number) => {
|
|
133
|
+
setSelectedLogIds((prev) => {
|
|
134
|
+
if (prev.includes(logId)) {
|
|
135
|
+
return prev.filter((id) => id !== logId);
|
|
136
|
+
}
|
|
137
|
+
if (prev.length < 2) {
|
|
138
|
+
return [...prev, logId];
|
|
139
|
+
}
|
|
140
|
+
// FIFO eviction: drop the oldest, append the new id.
|
|
141
|
+
const newer = prev[1];
|
|
142
|
+
if (newer === undefined) return prev;
|
|
143
|
+
return [newer, logId];
|
|
144
|
+
});
|
|
145
|
+
}, []);
|
|
146
|
+
|
|
147
|
+
// Reset the selection (and close the compare drawer) whenever the user
|
|
148
|
+
// changes the session or model filter, since the selected logs may no
|
|
149
|
+
// longer be in the visible list.
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
setSelectedLogIds([]);
|
|
152
|
+
setCompareOpen(false);
|
|
153
|
+
}, [selectedSession, selectedModel]);
|
|
154
|
+
|
|
155
|
+
const selectedSet = useMemo(() => new Set(selectedLogIds), [selectedLogIds]);
|
|
156
|
+
|
|
157
|
+
const openCompare = useCallback(() => {
|
|
158
|
+
if (selectedLogIds.length !== 2) return;
|
|
159
|
+
const [idA, idB] = selectedLogIds;
|
|
160
|
+
if (idA === undefined || idB === undefined) return;
|
|
161
|
+
const logA = logs.find((l) => l.id === idA);
|
|
162
|
+
const logB = logs.find((l) => l.id === idB);
|
|
163
|
+
if (logA === undefined || logB === undefined) return;
|
|
164
|
+
setComparePair([logA, logB]);
|
|
165
|
+
setCompareOpen(true);
|
|
166
|
+
}, [selectedLogIds, logs]);
|
|
167
|
+
|
|
168
|
+
const closeCompare = useCallback(() => {
|
|
169
|
+
setCompareOpen(false);
|
|
170
|
+
// Keep `comparePair` so the selection survives across the drawer being
|
|
171
|
+
// closed and re-opened; it is replaced the next time the user opens
|
|
172
|
+
// the drawer with a different pair.
|
|
173
|
+
}, []);
|
|
174
|
+
|
|
175
|
+
const clearSelection = useCallback(() => {
|
|
176
|
+
setSelectedLogIds([]);
|
|
177
|
+
}, []);
|
|
178
|
+
|
|
179
|
+
const selectedSummary = useMemo(() => {
|
|
180
|
+
if (selectedLogIds.length !== 2) return null;
|
|
181
|
+
const [idA, idB] = selectedLogIds;
|
|
182
|
+
if (idA === undefined || idB === undefined) return null;
|
|
183
|
+
const logA = logs.find((l) => l.id === idA);
|
|
184
|
+
const logB = logs.find((l) => l.id === idB);
|
|
185
|
+
if (logA === undefined || logB === undefined) return null;
|
|
186
|
+
const sameSession = getConversationId(logA) === getConversationId(logB);
|
|
187
|
+
let elapsed = "";
|
|
188
|
+
if (logA.timestamp !== null && logB.timestamp !== null) {
|
|
189
|
+
const a = Date.parse(logA.timestamp);
|
|
190
|
+
const b = Date.parse(logB.timestamp);
|
|
191
|
+
if (!Number.isNaN(a) && !Number.isNaN(b)) {
|
|
192
|
+
const ms = Math.abs(b - a);
|
|
193
|
+
elapsed = formatElapsed(ms);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
logA,
|
|
198
|
+
logB,
|
|
199
|
+
sameSession,
|
|
200
|
+
elapsed,
|
|
201
|
+
};
|
|
202
|
+
}, [selectedLogIds, logs]);
|
|
203
|
+
|
|
204
|
+
function formatElapsed(ms: number): string {
|
|
205
|
+
if (ms < 1000) return `${ms}ms`;
|
|
206
|
+
const sec = Math.floor(ms / 1000);
|
|
207
|
+
if (sec < 60) return `${sec}s`;
|
|
208
|
+
const min = Math.floor(sec / 60);
|
|
209
|
+
if (min < 60) return `${min}m`;
|
|
210
|
+
const hr = Math.floor(min / 60);
|
|
211
|
+
return `${hr}h${min % 60}m`;
|
|
212
|
+
}
|
|
213
|
+
|
|
127
214
|
const groups = useMemo(() => groupLogsByConversation(logs), [logs]);
|
|
128
215
|
const cacheTrends = useMemo(() => computeCacheTrends(groups), [groups]);
|
|
129
216
|
|
|
@@ -310,6 +397,8 @@ export function ProxyViewer({
|
|
|
310
397
|
viewMode={viewMode}
|
|
311
398
|
strip={strip}
|
|
312
399
|
cacheTrends={cacheTrends}
|
|
400
|
+
selectedSet={selectedSet}
|
|
401
|
+
onToggleSelect={handleToggleSelect}
|
|
313
402
|
/>
|
|
314
403
|
</div>
|
|
315
404
|
);
|
|
@@ -334,6 +423,8 @@ export function ProxyViewer({
|
|
|
334
423
|
viewMode={viewMode}
|
|
335
424
|
strip={strip}
|
|
336
425
|
cacheTrend={cacheTrends.get(log.id) ?? null}
|
|
426
|
+
isSelected={selectedSet.has(log.id)}
|
|
427
|
+
onToggleSelect={handleToggleSelect}
|
|
337
428
|
/>
|
|
338
429
|
</div>
|
|
339
430
|
);
|
|
@@ -343,6 +434,39 @@ export function ProxyViewer({
|
|
|
343
434
|
</div>
|
|
344
435
|
)}
|
|
345
436
|
</div>
|
|
437
|
+
|
|
438
|
+
{/* Floating action bar — shown only when 2 logs are selected. */}
|
|
439
|
+
{selectedSummary !== null && (
|
|
440
|
+
<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">
|
|
441
|
+
<GitCompareArrows className="size-4 text-amber-400 shrink-0" />
|
|
442
|
+
<span className="text-muted-foreground font-mono">
|
|
443
|
+
#{selectedSummary.logA.id} ↔ #{selectedSummary.logB.id}
|
|
444
|
+
{" · "}
|
|
445
|
+
{selectedSummary.sameSession ? "same session" : "different sessions"}
|
|
446
|
+
{selectedSummary.elapsed !== "" && ` · ${selectedSummary.elapsed} apart`}
|
|
447
|
+
</span>
|
|
448
|
+
<button
|
|
449
|
+
type="button"
|
|
450
|
+
onClick={clearSelection}
|
|
451
|
+
className="text-muted-foreground hover:text-foreground transition-colors cursor-pointer inline-flex items-center gap-1"
|
|
452
|
+
>
|
|
453
|
+
<X className="size-3" />
|
|
454
|
+
Clear
|
|
455
|
+
</button>
|
|
456
|
+
<button
|
|
457
|
+
type="button"
|
|
458
|
+
onClick={openCompare}
|
|
459
|
+
className="bg-amber-400 text-amber-950 hover:bg-amber-300 transition-colors px-3 py-1 rounded font-medium cursor-pointer"
|
|
460
|
+
>
|
|
461
|
+
Compare 2 logs
|
|
462
|
+
</button>
|
|
463
|
+
</div>
|
|
464
|
+
)}
|
|
465
|
+
|
|
466
|
+
{/* Compare drawer — sibling of the log list, not a route change. */}
|
|
467
|
+
{compareOpen && comparePair !== null && (
|
|
468
|
+
<CompareDrawer left={comparePair[0]} right={comparePair[1]} onClose={closeCompare} />
|
|
469
|
+
)}
|
|
346
470
|
</div>
|
|
347
471
|
);
|
|
348
472
|
}
|