@tonyclaw/llm-inspector 1.6.3 → 1.7.1
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/cli.js +4 -68
- package/.output/nitro.json +3 -3
- package/.output/public/assets/{index-s4lwsWvq.js → index-Bf_WGooQ.js} +18 -18
- package/.output/public/assets/{main-Cp8AM0Pa.js → main-CpIX1ZHy.js} +1 -1
- package/.output/server/_chunks/ssr-renderer.mjs +4 -0
- package/.output/server/_libs/h3-v2.mjs +5 -5
- package/.output/server/_libs/h3.mjs +5 -5
- package/.output/server/_libs/lucide-react.mjs +115 -76
- package/.output/server/_libs/react-dom.mjs +2074 -1228
- package/.output/server/_libs/readable-stream.mjs +3 -3
- package/.output/server/_libs/srvx.mjs +443 -47
- package/.output/server/_libs/util-deprecate.mjs +2 -2
- package/.output/server/_ssr/{index-ByCLZu7J.mjs → index-BZkxgx8f.mjs} +70 -13
- package/.output/server/_ssr/index.mjs +9 -2
- package/.output/server/_ssr/{router-Bq_mxeNz.mjs → router-D7g2K6y6.mjs} +109 -34
- package/.output/server/{_tanstack-start-manifest_v-C4E0e9my.mjs → _tanstack-start-manifest_v-b6u6g-Cr.mjs} +1 -1
- package/.output/server/index.mjs +31 -29
- package/package.json +1 -1
- package/src/cli.ts +4 -87
- package/src/components/providers/ProviderCard.tsx +82 -7
- package/src/components/providers/ProvidersPanel.tsx +40 -7
package/src/cli.ts
CHANGED
|
@@ -1,89 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
5
|
|
|
7
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
7
|
const __dirname = dirname(__filename);
|
|
9
8
|
|
|
10
9
|
const DEFAULT_PORT = 25947;
|
|
11
10
|
|
|
12
|
-
// Find bun executable
|
|
13
|
-
const findBun = (): string | null => {
|
|
14
|
-
// Try running `where bun` (Windows) or `which bun` (Unix) to find in PATH
|
|
15
|
-
try {
|
|
16
|
-
const cmd = process.platform === "win32" ? "where bun" : "which bun";
|
|
17
|
-
const output = execSync(cmd, { encoding: "utf8", timeout: 5000 }).trim();
|
|
18
|
-
if (!output) return null;
|
|
19
|
-
|
|
20
|
-
// Take first line and handle Windows .cmd shim
|
|
21
|
-
const firstLine = output.split("\n")[0] ?? "";
|
|
22
|
-
const firstPath = firstLine.trim();
|
|
23
|
-
if (!firstPath) return null;
|
|
24
|
-
|
|
25
|
-
if (process.platform === "win32" && firstPath.endsWith(".cmd")) {
|
|
26
|
-
// npm shim - find actual exe
|
|
27
|
-
const dir = join(firstPath, "..");
|
|
28
|
-
const actualPath = join(dir, "node_modules", "bun", "bin", "bun.exe");
|
|
29
|
-
if (existsSync(actualPath)) {
|
|
30
|
-
return actualPath;
|
|
31
|
-
}
|
|
32
|
-
// Also try .exe directly in same directory
|
|
33
|
-
const exePath = join(dir, "bun.exe");
|
|
34
|
-
if (existsSync(exePath)) {
|
|
35
|
-
return exePath;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (existsSync(firstPath)) {
|
|
39
|
-
return firstPath;
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// Command failed, continue with other methods
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Check PATH directories directly
|
|
46
|
-
const pathEnv = process.env.PATH ?? "";
|
|
47
|
-
const pathDirs = pathEnv.split(process.platform === "win32" ? ";" : ":");
|
|
48
|
-
|
|
49
|
-
for (const dir of pathDirs) {
|
|
50
|
-
const bunPath = join(dir, process.platform === "win32" ? "bun.exe" : "bun");
|
|
51
|
-
if (existsSync(bunPath)) {
|
|
52
|
-
// On Windows, npm shim is a script, not the actual exe
|
|
53
|
-
if (process.platform === "win32" && !bunPath.endsWith(".exe")) {
|
|
54
|
-
const actualPath = join(dir, "node_modules", "bun", "bin", "bun.exe");
|
|
55
|
-
if (existsSync(actualPath)) {
|
|
56
|
-
return actualPath;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return bunPath;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Common Windows paths
|
|
64
|
-
if (process.platform === "win32") {
|
|
65
|
-
const localAppData = process.env.LOCALAPPDATA ?? "";
|
|
66
|
-
const appData = process.env.APPDATA ?? "";
|
|
67
|
-
const userProfile = process.env.USERPROFILE ?? "";
|
|
68
|
-
|
|
69
|
-
const commonPaths = [
|
|
70
|
-
join(localAppData, "bun", "bin", "bun.exe"),
|
|
71
|
-
join(appData, "bun", "bin", "bun.exe"),
|
|
72
|
-
join(userProfile, "AppData", "Local", "bun", "bin", "bun.exe"),
|
|
73
|
-
join(userProfile, "AppData", "Roaming", "npm", "node_modules", "bun", "bin", "bun.exe"),
|
|
74
|
-
join(userProfile, "AppData", "Roaming", "npm", "bun.exe"),
|
|
75
|
-
join(userProfile, "AppData", "Roaming", "npm", "bun"),
|
|
76
|
-
];
|
|
77
|
-
for (const bunPath of commonPaths) {
|
|
78
|
-
if (existsSync(bunPath)) {
|
|
79
|
-
return bunPath;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return null;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
11
|
const envPort = process.env["PORT"];
|
|
88
12
|
const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
|
|
89
13
|
|
|
@@ -157,22 +81,15 @@ if (open) {
|
|
|
157
81
|
openBrowser(url);
|
|
158
82
|
}
|
|
159
83
|
|
|
160
|
-
// Find bun and start server
|
|
161
|
-
const bunPath = findBun();
|
|
162
|
-
if (bunPath === null) {
|
|
163
|
-
console.error("\nError: bun is not installed or not in PATH.");
|
|
164
|
-
console.error("Please install bun from https://bun.sh");
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
84
|
// Compute server path
|
|
169
85
|
const outputDir = __dirname;
|
|
170
86
|
const serverPath = join(outputDir, "../.output/server/index.mjs");
|
|
171
87
|
|
|
172
|
-
// Start server with
|
|
173
|
-
const serverProcess = spawn(
|
|
88
|
+
// Start server with node
|
|
89
|
+
const serverProcess = spawn(process.execPath, [serverPath], {
|
|
174
90
|
stdio: ["ignore", "inherit", "inherit"],
|
|
175
91
|
detached: true,
|
|
92
|
+
env: { ...process.env },
|
|
176
93
|
});
|
|
177
94
|
|
|
178
95
|
serverProcess.unref();
|
|
@@ -10,6 +10,14 @@ import {
|
|
|
10
10
|
XCircle,
|
|
11
11
|
Minus,
|
|
12
12
|
ExternalLink,
|
|
13
|
+
AlertCircle,
|
|
14
|
+
Wifi,
|
|
15
|
+
WifiOff,
|
|
16
|
+
Lock,
|
|
17
|
+
Gauge,
|
|
18
|
+
Server,
|
|
19
|
+
HelpCircle,
|
|
20
|
+
Clock,
|
|
13
21
|
} from "lucide-react";
|
|
14
22
|
import type { ProviderConfig } from "../../proxy/providers";
|
|
15
23
|
|
|
@@ -18,9 +26,26 @@ const KNOWN_PROVIDER_DOCS: Record<string, string> = {
|
|
|
18
26
|
deepseek: "https://api-docs.deepseek.com/zh-cn/",
|
|
19
27
|
};
|
|
20
28
|
|
|
29
|
+
type ErrorType =
|
|
30
|
+
| "timeout"
|
|
31
|
+
| "network_unreachable"
|
|
32
|
+
| "connection_refused"
|
|
33
|
+
| "auth_failed"
|
|
34
|
+
| "rate_limited"
|
|
35
|
+
| "server_error"
|
|
36
|
+
| "invalid_response"
|
|
37
|
+
| "unknown";
|
|
38
|
+
|
|
39
|
+
type EnhancedError = {
|
|
40
|
+
message: string;
|
|
41
|
+
type: ErrorType;
|
|
42
|
+
details?: string;
|
|
43
|
+
hint?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
21
46
|
type TestResult = {
|
|
22
47
|
success: boolean;
|
|
23
|
-
error?:
|
|
48
|
+
error?: EnhancedError;
|
|
24
49
|
};
|
|
25
50
|
|
|
26
51
|
type NotConfigured = { notConfigured: true };
|
|
@@ -53,7 +78,37 @@ function hasSuccessField(result: TestResult | NotConfigured): result is TestResu
|
|
|
53
78
|
return Object.prototype.hasOwnProperty.call(result, "success");
|
|
54
79
|
}
|
|
55
80
|
|
|
56
|
-
function
|
|
81
|
+
function getErrorIcon(type: ErrorType): JSX.Element {
|
|
82
|
+
const iconProps = { className: "size-3", strokeWidth: 2 };
|
|
83
|
+
switch (type) {
|
|
84
|
+
case "timeout":
|
|
85
|
+
return <Clock {...iconProps} />;
|
|
86
|
+
case "network_unreachable":
|
|
87
|
+
return <WifiOff {...iconProps} />;
|
|
88
|
+
case "connection_refused":
|
|
89
|
+
return <Wifi {...iconProps} />;
|
|
90
|
+
case "auth_failed":
|
|
91
|
+
return <Lock {...iconProps} />;
|
|
92
|
+
case "rate_limited":
|
|
93
|
+
return <Gauge {...iconProps} />;
|
|
94
|
+
case "server_error":
|
|
95
|
+
return <Server {...iconProps} />;
|
|
96
|
+
case "invalid_response":
|
|
97
|
+
return <HelpCircle {...iconProps} />;
|
|
98
|
+
case "unknown":
|
|
99
|
+
return <AlertCircle {...iconProps} />;
|
|
100
|
+
default:
|
|
101
|
+
return <AlertCircle {...iconProps} />;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function TestStatus({
|
|
106
|
+
result,
|
|
107
|
+
isTesting,
|
|
108
|
+
}: {
|
|
109
|
+
result: TestResult | NotConfigured;
|
|
110
|
+
isTesting?: boolean;
|
|
111
|
+
}): JSX.Element {
|
|
57
112
|
if (!hasSuccessField(result)) {
|
|
58
113
|
return (
|
|
59
114
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
@@ -70,10 +125,26 @@ function TestStatus({ result }: { result: TestResult | NotConfigured }): JSX.Ele
|
|
|
70
125
|
</div>
|
|
71
126
|
);
|
|
72
127
|
}
|
|
128
|
+
|
|
129
|
+
const error = result.error;
|
|
130
|
+
const errorMessage = error?.message ?? "Connection failed";
|
|
131
|
+
const errorHint = error?.hint;
|
|
132
|
+
const errorType = error?.type ?? "unknown";
|
|
133
|
+
|
|
73
134
|
return (
|
|
74
|
-
<div className="flex
|
|
75
|
-
<
|
|
76
|
-
|
|
135
|
+
<div className="flex flex-col gap-1 min-w-0">
|
|
136
|
+
<div
|
|
137
|
+
className="flex items-center gap-1 text-xs text-red-600 min-w-0"
|
|
138
|
+
title={error?.details ?? errorMessage}
|
|
139
|
+
>
|
|
140
|
+
{getErrorIcon(errorType)}
|
|
141
|
+
<span className="truncate">{errorMessage}</span>
|
|
142
|
+
</div>
|
|
143
|
+
{errorHint !== undefined && (
|
|
144
|
+
<div className="text-xs text-muted-foreground pl-4 truncate" title={errorHint}>
|
|
145
|
+
{errorHint}
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
77
148
|
</div>
|
|
78
149
|
);
|
|
79
150
|
}
|
|
@@ -135,7 +206,9 @@ export function ProviderCard({
|
|
|
135
206
|
<span className="font-medium">Anthropic:</span>{" "}
|
|
136
207
|
<span className="truncate">{provider.anthropicBaseUrl}</span>
|
|
137
208
|
</div>
|
|
138
|
-
{testResults &&
|
|
209
|
+
{testResults && (
|
|
210
|
+
<TestStatus result={testResults.anthropic.nonStreaming} isTesting={isTesting} />
|
|
211
|
+
)}
|
|
139
212
|
</div>
|
|
140
213
|
)}
|
|
141
214
|
|
|
@@ -145,7 +218,9 @@ export function ProviderCard({
|
|
|
145
218
|
<span className="font-medium">OpenAI:</span>{" "}
|
|
146
219
|
<span className="truncate">{provider.openaiBaseUrl}</span>
|
|
147
220
|
</div>
|
|
148
|
-
{testResults &&
|
|
221
|
+
{testResults && (
|
|
222
|
+
<TestStatus result={testResults.openai.nonStreaming} isTesting={isTesting} />
|
|
223
|
+
)}
|
|
149
224
|
</div>
|
|
150
225
|
)}
|
|
151
226
|
|
|
@@ -9,9 +9,24 @@ type ConfigPathsResponse = {
|
|
|
9
9
|
providerConfig: string;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
type EnhancedError = {
|
|
13
|
+
message: string;
|
|
14
|
+
type:
|
|
15
|
+
| "timeout"
|
|
16
|
+
| "network_unreachable"
|
|
17
|
+
| "connection_refused"
|
|
18
|
+
| "auth_failed"
|
|
19
|
+
| "rate_limited"
|
|
20
|
+
| "server_error"
|
|
21
|
+
| "invalid_response"
|
|
22
|
+
| "unknown";
|
|
23
|
+
details?: string;
|
|
24
|
+
hint?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
12
27
|
type TestResult = {
|
|
13
28
|
success: boolean;
|
|
14
|
-
error?:
|
|
29
|
+
error?: EnhancedError;
|
|
15
30
|
};
|
|
16
31
|
|
|
17
32
|
type NotConfigured = { notConfigured: true };
|
|
@@ -88,14 +103,23 @@ export function ProvidersPanel(): JSX.Element {
|
|
|
88
103
|
name: string;
|
|
89
104
|
apiKey: string;
|
|
90
105
|
model?: string;
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
format: "anthropic" | "openai";
|
|
107
|
+
baseUrl?: string;
|
|
93
108
|
}): void {
|
|
94
109
|
void (async () => {
|
|
110
|
+
// Convert baseUrl to format-specific URL
|
|
111
|
+
const payload = {
|
|
112
|
+
name: data.name,
|
|
113
|
+
apiKey: data.apiKey,
|
|
114
|
+
model: data.model,
|
|
115
|
+
format: data.format,
|
|
116
|
+
anthropicBaseUrl: data.format === "anthropic" ? data.baseUrl : undefined,
|
|
117
|
+
openaiBaseUrl: data.format === "openai" ? data.baseUrl : undefined,
|
|
118
|
+
};
|
|
95
119
|
const res = await fetch("/api/providers", {
|
|
96
120
|
method: "POST",
|
|
97
121
|
headers: { "Content-Type": "application/json" },
|
|
98
|
-
body: JSON.stringify(
|
|
122
|
+
body: JSON.stringify(payload),
|
|
99
123
|
});
|
|
100
124
|
if (!res.ok) {
|
|
101
125
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -116,15 +140,24 @@ export function ProvidersPanel(): JSX.Element {
|
|
|
116
140
|
name: string;
|
|
117
141
|
apiKey: string;
|
|
118
142
|
model?: string;
|
|
119
|
-
|
|
120
|
-
|
|
143
|
+
format: "anthropic" | "openai";
|
|
144
|
+
baseUrl?: string;
|
|
121
145
|
}): void {
|
|
122
146
|
if (!editingProvider) return;
|
|
123
147
|
void (async () => {
|
|
148
|
+
// Convert baseUrl to format-specific URL
|
|
149
|
+
const payload = {
|
|
150
|
+
name: data.name,
|
|
151
|
+
apiKey: data.apiKey,
|
|
152
|
+
model: data.model,
|
|
153
|
+
format: data.format,
|
|
154
|
+
anthropicBaseUrl: data.format === "anthropic" ? data.baseUrl : undefined,
|
|
155
|
+
openaiBaseUrl: data.format === "openai" ? data.baseUrl : undefined,
|
|
156
|
+
};
|
|
124
157
|
const res = await fetch(`/api/providers/${editingProvider.id}`, {
|
|
125
158
|
method: "PUT",
|
|
126
159
|
headers: { "Content-Type": "application/json" },
|
|
127
|
-
body: JSON.stringify(
|
|
160
|
+
body: JSON.stringify(payload),
|
|
128
161
|
});
|
|
129
162
|
if (!res.ok) {
|
|
130
163
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|