@tonyclaw/llm-inspector 1.14.0 → 1.14.2
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-DDrUlr6L.js +105 -0
- package/.output/public/assets/index-DOG5AdQ9.css +1 -0
- package/.output/public/assets/{main-C1k6vRnH.js → main-BElVT2p3.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +3 -3
- package/.output/server/_ssr/{index-AxruZp16.mjs → index-DmLit8Ad.mjs} +364 -212
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DtleGqN8.mjs → router-BoeSXWHG.mjs} +555 -293
- package/.output/server/{_tanstack-start-manifest_v-B1WAHWIa.mjs → _tanstack-start-manifest_v-B6idtbmL.mjs} +1 -1
- package/.output/server/index.mjs +27 -27
- package/package.json +1 -1
- package/src/components/providers/ProviderCard.tsx +139 -11
- package/src/components/providers/ProviderForm.tsx +240 -98
- package/src/components/providers/ProvidersPanel.tsx +75 -49
- package/src/lib/mask.ts +4 -0
- package/src/lib/providerContract.ts +2 -0
- package/src/lib/providerTestContract.ts +8 -0
- package/src/proxy/providers.ts +132 -22
- package/src/routes/api/providers.$providerId.test.log.ts +293 -0
- package/src/routes/api/providers.$providerId.ts +3 -1
- package/src/routes/api/providers.ts +9 -6
- package/.output/public/assets/index-B5q3Llgm.css +0 -1
- package/.output/public/assets/index-C6tbslcs.js +0 -105
|
@@ -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/mcp", "/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/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-BElVT2p3.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DDrUlr6L.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/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/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", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-BElVT2p3.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-10T13:27:43.020Z",
|
|
42
42
|
"size": 5915,
|
|
43
43
|
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
44
44
|
},
|
|
45
|
-
"/assets/
|
|
45
|
+
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
46
|
+
"type": "image/jpeg",
|
|
47
|
+
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
48
|
+
"mtime": "2026-06-10T13:27:43.023Z",
|
|
49
|
+
"size": 6918,
|
|
50
|
+
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
51
|
+
},
|
|
52
|
+
"/assets/index-DOG5AdQ9.css": {
|
|
46
53
|
"type": "text/css; charset=utf-8",
|
|
47
|
-
"etag": '"
|
|
48
|
-
"mtime": "2026-06-
|
|
49
|
-
"size":
|
|
50
|
-
"path": "../public/assets/index-
|
|
54
|
+
"etag": '"142da-qXGGlxiu6GXrwGw4lzJEDsR446o"',
|
|
55
|
+
"mtime": "2026-06-10T13:27:43.023Z",
|
|
56
|
+
"size": 82650,
|
|
57
|
+
"path": "../public/assets/index-DOG5AdQ9.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-10T13:27:43.023Z",
|
|
56
63
|
"size": 11256,
|
|
57
64
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
58
65
|
},
|
|
59
|
-
"/assets/main-C1k6vRnH.js": {
|
|
60
|
-
"type": "text/javascript; charset=utf-8",
|
|
61
|
-
"etag": '"50599-AYrUeIFAuGF/4DPZ+5lAMslWd7Q"',
|
|
62
|
-
"mtime": "2026-06-10T06:24:38.532Z",
|
|
63
|
-
"size": 329113,
|
|
64
|
-
"path": "../public/assets/main-C1k6vRnH.js"
|
|
65
|
-
},
|
|
66
|
-
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
67
|
-
"type": "image/jpeg",
|
|
68
|
-
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
69
|
-
"mtime": "2026-06-10T06:24:38.532Z",
|
|
70
|
-
"size": 6918,
|
|
71
|
-
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
72
|
-
},
|
|
73
66
|
"/assets/qwen-CONDcHqt.png": {
|
|
74
67
|
"type": "image/png",
|
|
75
68
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
76
|
-
"mtime": "2026-06-
|
|
69
|
+
"mtime": "2026-06-10T13:27:43.023Z",
|
|
77
70
|
"size": 357059,
|
|
78
71
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
79
72
|
},
|
|
80
|
-
"/assets/
|
|
73
|
+
"/assets/main-BElVT2p3.js": {
|
|
74
|
+
"type": "text/javascript; charset=utf-8",
|
|
75
|
+
"etag": '"50599-c0Re5vg5+KI0J9dHMI2SM+wdp9A"',
|
|
76
|
+
"mtime": "2026-06-10T13:27:43.023Z",
|
|
77
|
+
"size": 329113,
|
|
78
|
+
"path": "../public/assets/main-BElVT2p3.js"
|
|
79
|
+
},
|
|
80
|
+
"/assets/index-DDrUlr6L.js": {
|
|
81
81
|
"type": "text/javascript; charset=utf-8",
|
|
82
|
-
"etag": '"
|
|
83
|
-
"mtime": "2026-06-
|
|
84
|
-
"size":
|
|
85
|
-
"path": "../public/assets/index-
|
|
82
|
+
"etag": '"91fb4-sGd/w9UW0TGYuvQ5WfgvmwcNukI"',
|
|
83
|
+
"mtime": "2026-06-10T13:27:43.024Z",
|
|
84
|
+
"size": 597940,
|
|
85
|
+
"path": "../public/assets/index-DDrUlr6L.js"
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
function readAsset(id) {
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSX, type ReactNode, useState } from "react";
|
|
1
|
+
import { type JSX, type ReactNode, useCallback, useRef, useState } from "react";
|
|
2
2
|
import { Button } from "../ui/button";
|
|
3
3
|
import {
|
|
4
4
|
Eye,
|
|
@@ -17,8 +17,11 @@ import {
|
|
|
17
17
|
Server,
|
|
18
18
|
HelpCircle,
|
|
19
19
|
Clock,
|
|
20
|
+
Copy,
|
|
21
|
+
Check,
|
|
20
22
|
} from "lucide-react";
|
|
21
23
|
import type { ProviderConfig } from "../../proxy/providers";
|
|
24
|
+
import { maskApiKey } from "../../lib/mask";
|
|
22
25
|
import type {
|
|
23
26
|
ProviderTestErrorType as ErrorType,
|
|
24
27
|
ProviderTestResult as TestResult,
|
|
@@ -42,11 +45,6 @@ type ProviderCardProps = {
|
|
|
42
45
|
onTest?: (providerId: string) => void;
|
|
43
46
|
};
|
|
44
47
|
|
|
45
|
-
function maskApiKey(apiKey: string): string {
|
|
46
|
-
if (apiKey.length <= 8) return "••••••••";
|
|
47
|
-
return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
48
|
function hasSuccessField(result: ProviderTestState): result is TestResult {
|
|
51
49
|
return Object.prototype.hasOwnProperty.call(result, "success");
|
|
52
50
|
}
|
|
@@ -171,6 +169,20 @@ function TestStatus({ result }: { result: ProviderTestState }): JSX.Element {
|
|
|
171
169
|
);
|
|
172
170
|
}
|
|
173
171
|
|
|
172
|
+
function formatTimeAgo(isoString: string): string {
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
const then = new Date(isoString).getTime();
|
|
175
|
+
const diffMs = now - then;
|
|
176
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
177
|
+
if (diffSec < 60) return "just now";
|
|
178
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
179
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
180
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
181
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
182
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
183
|
+
return `${diffDay}d ago`;
|
|
184
|
+
}
|
|
185
|
+
|
|
174
186
|
export function ProviderCard({
|
|
175
187
|
provider,
|
|
176
188
|
testResults,
|
|
@@ -182,6 +194,39 @@ export function ProviderCard({
|
|
|
182
194
|
onTest,
|
|
183
195
|
}: ProviderCardProps): JSX.Element {
|
|
184
196
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
197
|
+
const [copied, setCopied] = useState(false);
|
|
198
|
+
const [showModelResults, setShowModelResults] = useState(false);
|
|
199
|
+
const hasLoggedRef = useRef<string | null>(null);
|
|
200
|
+
const lastTestedAtRef = useRef<string | undefined>(undefined);
|
|
201
|
+
|
|
202
|
+
// Reset log state when new test results arrive
|
|
203
|
+
if (testResults?.testedAt !== undefined && testResults.testedAt !== lastTestedAtRef.current) {
|
|
204
|
+
lastTestedAtRef.current = testResults.testedAt;
|
|
205
|
+
hasLoggedRef.current = null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Call log API when user expands model test results for the first time
|
|
209
|
+
const handleToggleModelResults = useCallback(() => {
|
|
210
|
+
setShowModelResults((v) => {
|
|
211
|
+
const next = !v;
|
|
212
|
+
if (next && hasLoggedRef.current === null && testResults?.models !== undefined) {
|
|
213
|
+
hasLoggedRef.current = testResults.testedAt ?? "";
|
|
214
|
+
// Fire-and-forget: commit test results to dashboard log
|
|
215
|
+
void fetch(`/api/providers/${provider.id}/test/log`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: { "Content-Type": "application/json" },
|
|
218
|
+
body: JSON.stringify(testResults),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return next;
|
|
222
|
+
});
|
|
223
|
+
}, [provider.id, testResults]);
|
|
224
|
+
|
|
225
|
+
function handleCopy() {
|
|
226
|
+
navigator.clipboard.writeText(provider.apiKey).catch(() => {});
|
|
227
|
+
setCopied(true);
|
|
228
|
+
setTimeout(() => setCopied(false), 2000);
|
|
229
|
+
}
|
|
185
230
|
|
|
186
231
|
// Get docs URL: use provider.apiDocsUrl if configured, otherwise check KNOWN_PROVIDER_DOCS
|
|
187
232
|
const docsUrl =
|
|
@@ -196,11 +241,17 @@ export function ProviderCard({
|
|
|
196
241
|
>
|
|
197
242
|
<div className="flex items-start justify-between gap-2">
|
|
198
243
|
<div className="flex items-center gap-2 min-w-0">
|
|
199
|
-
<span className="font-medium truncate">
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
244
|
+
<span className="font-medium truncate">{provider.name}</span>
|
|
245
|
+
{provider.source === "company" && (
|
|
246
|
+
<span className="text-xs px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 shrink-0">
|
|
247
|
+
公司
|
|
248
|
+
</span>
|
|
249
|
+
)}
|
|
250
|
+
{provider.source === "personal" && (
|
|
251
|
+
<span className="text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 shrink-0">
|
|
252
|
+
个人
|
|
253
|
+
</span>
|
|
254
|
+
)}
|
|
204
255
|
</div>
|
|
205
256
|
{docsUrl !== undefined && (
|
|
206
257
|
<a
|
|
@@ -216,6 +267,16 @@ export function ProviderCard({
|
|
|
216
267
|
)}
|
|
217
268
|
</div>
|
|
218
269
|
|
|
270
|
+
{provider.models !== undefined && provider.models.length > 0 && (
|
|
271
|
+
<div className="flex flex-wrap gap-1">
|
|
272
|
+
{provider.models.map((m) => (
|
|
273
|
+
<span key={m} className="text-xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground">
|
|
274
|
+
{m}
|
|
275
|
+
</span>
|
|
276
|
+
))}
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
|
|
219
280
|
<div className="flex items-center gap-2">
|
|
220
281
|
<code className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded flex-1 truncate">
|
|
221
282
|
{showApiKey ? provider.apiKey : maskApiKey(provider.apiKey)}
|
|
@@ -228,6 +289,14 @@ export function ProviderCard({
|
|
|
228
289
|
>
|
|
229
290
|
{showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
|
230
291
|
</button>
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
onClick={handleCopy}
|
|
295
|
+
className="text-muted-foreground hover:text-foreground transition-colors p-1"
|
|
296
|
+
aria-label="Copy API key"
|
|
297
|
+
>
|
|
298
|
+
{copied ? <Check className="size-4 text-green-500" /> : <Copy className="size-4" />}
|
|
299
|
+
</button>
|
|
231
300
|
</div>
|
|
232
301
|
|
|
233
302
|
{provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl !== "" && (
|
|
@@ -250,6 +319,65 @@ export function ProviderCard({
|
|
|
250
319
|
</div>
|
|
251
320
|
)}
|
|
252
321
|
|
|
322
|
+
{testResults?.testedAt !== undefined && (
|
|
323
|
+
<div className="text-xs text-muted-foreground flex items-center gap-1">
|
|
324
|
+
<Clock className="size-3" />
|
|
325
|
+
<span>Tested {formatTimeAgo(testResults.testedAt)}</span>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
{testResults?.models !== undefined && Object.keys(testResults.models).length > 0 && (
|
|
329
|
+
<div className="border-t pt-2">
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
onClick={handleToggleModelResults}
|
|
333
|
+
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
|
334
|
+
>
|
|
335
|
+
<span className="font-mono">{showModelResults ? "▾" : "▸"}</span>
|
|
336
|
+
Model Test Results ({Object.keys(testResults.models).length})
|
|
337
|
+
</button>
|
|
338
|
+
{showModelResults && (
|
|
339
|
+
<div className="mt-2 overflow-x-auto">
|
|
340
|
+
<table className="w-full text-xs border-collapse">
|
|
341
|
+
<thead>
|
|
342
|
+
<tr className="border-b border-border">
|
|
343
|
+
<th className="text-left py-1 px-2 font-medium text-muted-foreground">Model</th>
|
|
344
|
+
{provider.anthropicBaseUrl !== undefined &&
|
|
345
|
+
provider.anthropicBaseUrl !== "" && (
|
|
346
|
+
<th className="text-left py-1 px-2 font-medium text-muted-foreground">
|
|
347
|
+
Anthropic
|
|
348
|
+
</th>
|
|
349
|
+
)}
|
|
350
|
+
{provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl !== "" && (
|
|
351
|
+
<th className="text-left py-1 px-2 font-medium text-muted-foreground">
|
|
352
|
+
OpenAI
|
|
353
|
+
</th>
|
|
354
|
+
)}
|
|
355
|
+
</tr>
|
|
356
|
+
</thead>
|
|
357
|
+
<tbody>
|
|
358
|
+
{Object.entries(testResults.models).map(([modelName, modelResult]) => (
|
|
359
|
+
<tr key={modelName} className="border-b border-border/50 last:border-0">
|
|
360
|
+
<td className="py-1 px-2 font-medium">{modelName}</td>
|
|
361
|
+
{provider.anthropicBaseUrl !== undefined &&
|
|
362
|
+
provider.anthropicBaseUrl !== "" && (
|
|
363
|
+
<td className="py-1 px-2">
|
|
364
|
+
<TestStatus result={modelResult.anthropic.nonStreaming} />
|
|
365
|
+
</td>
|
|
366
|
+
)}
|
|
367
|
+
{provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl !== "" && (
|
|
368
|
+
<td className="py-1 px-2">
|
|
369
|
+
<TestStatus result={modelResult.openai.nonStreaming} />
|
|
370
|
+
</td>
|
|
371
|
+
)}
|
|
372
|
+
</tr>
|
|
373
|
+
))}
|
|
374
|
+
</tbody>
|
|
375
|
+
</table>
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
</div>
|
|
379
|
+
)}
|
|
380
|
+
|
|
253
381
|
<div className="flex gap-2 pt-1 border-t">
|
|
254
382
|
{onTest !== undefined && (
|
|
255
383
|
<Button
|