@tonyclaw/llm-inspector 1.7.6 → 1.7.8
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-BKkFFKAM.js → index-C8o6bEv6.js} +13 -13
- package/.output/public/assets/{main-B3Cmykkm.js → main-Bxc5pKCu.js} +1 -1
- package/.output/server/_ssr/{index-DAvMem8_.mjs → index-hNquJMfH.mjs} +176 -22
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-tRLZo2WT.mjs → router-MmnX-LYh.mjs} +1 -1
- package/.output/server/{_tanstack-start-manifest_v-_KPj4BcN.mjs → _tanstack-start-manifest_v-CYKtU_9S.mjs} +1 -1
- package/.output/server/index.mjs +26 -26
- package/package.json +1 -1
- package/src/components/providers/ProviderCard.tsx +7 -1
- package/src/components/providers/ProvidersPanel.tsx +160 -22
- package/src/components/providers/SettingsDialog.tsx +51 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSX, useState, useEffect, useCallback } from "react";
|
|
1
|
+
import { type JSX, useState, useEffect, useCallback, useRef } from "react";
|
|
2
2
|
import { Button } from "../ui/button";
|
|
3
3
|
import { Plus, AlertCircle, Copy, Check } from "lucide-react";
|
|
4
4
|
import { ProviderCard } from "./ProviderCard";
|
|
@@ -36,22 +36,71 @@ type StreamingTestResults = {
|
|
|
36
36
|
streaming: TestResult | NotConfigured;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
type TestResults = {
|
|
39
|
+
export type TestResults = {
|
|
40
40
|
anthropic: StreamingTestResults;
|
|
41
41
|
openai: StreamingTestResults;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
type ProvidersPanelProps = {
|
|
45
|
+
externalProviders?: ProviderConfig[];
|
|
46
|
+
externalTestResults?: Record<string, TestResults>;
|
|
47
|
+
externalTestingProviders?: Set<string>;
|
|
48
|
+
externalTestingTimeLeft?: Record<string, number>;
|
|
49
|
+
onProvidersChange?: (providers: ProviderConfig[]) => void;
|
|
50
|
+
onTestResultsChange?: (providerId: string, results: TestResults) => void;
|
|
51
|
+
onTestingProvidersChange?: (providerId: string, isTesting: boolean) => void;
|
|
52
|
+
onTestingTimeLeftChange?: (providerId: string, seconds: number | undefined) => void;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function ProvidersPanel({
|
|
56
|
+
externalProviders,
|
|
57
|
+
externalTestResults,
|
|
58
|
+
externalTestingProviders,
|
|
59
|
+
externalTestingTimeLeft,
|
|
60
|
+
onProvidersChange,
|
|
61
|
+
onTestResultsChange,
|
|
62
|
+
onTestingProvidersChange,
|
|
63
|
+
onTestingTimeLeftChange,
|
|
64
|
+
}: ProvidersPanelProps): JSX.Element {
|
|
65
|
+
const [internalProviders, setInternalProviders] = useState<ProviderConfig[]>([]);
|
|
46
66
|
const [isLoading, setIsLoading] = useState(true);
|
|
47
67
|
const [showForm, setShowForm] = useState(false);
|
|
48
68
|
const [editingProvider, setEditingProvider] = useState<ProviderConfig | undefined>();
|
|
49
69
|
const [error, setError] = useState<string | null>(null);
|
|
50
|
-
const [
|
|
51
|
-
const [
|
|
70
|
+
const [internalTestResults, setInternalTestResults] = useState<Record<string, TestResults>>({});
|
|
71
|
+
const [internalTestingProviders, setInternalTestingProviders] = useState<Set<string>>(new Set());
|
|
72
|
+
const [internalTestingTimeLeft, setInternalTestingTimeLeft] = useState<Record<string, number>>(
|
|
73
|
+
{},
|
|
74
|
+
);
|
|
52
75
|
const [configPath, setConfigPath] = useState<string | null>(null);
|
|
53
76
|
const [configPathCopied, setConfigPathCopied] = useState(false);
|
|
54
77
|
|
|
78
|
+
// Use external state if provided, otherwise use internal state
|
|
79
|
+
const providers = externalProviders ?? internalProviders;
|
|
80
|
+
const setProviders = onProvidersChange ?? setInternalProviders;
|
|
81
|
+
const testResults = externalTestResults ?? internalTestResults;
|
|
82
|
+
const setTestResults = onTestResultsChange
|
|
83
|
+
? (id: string, results: TestResults) => onTestResultsChange(id, results)
|
|
84
|
+
: setInternalTestResults;
|
|
85
|
+
const testingProviders = externalTestingProviders ?? internalTestingProviders;
|
|
86
|
+
const setTestingProviders = onTestingProvidersChange
|
|
87
|
+
? (id: string, isTesting: boolean) => onTestingProvidersChange(id, isTesting)
|
|
88
|
+
: setInternalTestingProviders;
|
|
89
|
+
const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
|
|
90
|
+
const setTestingTimeLeft = onTestingTimeLeftChange
|
|
91
|
+
? (id: string, seconds: number | undefined) => onTestingTimeLeftChange(id, seconds)
|
|
92
|
+
: (id: string, seconds: number | undefined) => {
|
|
93
|
+
if (seconds === undefined) {
|
|
94
|
+
setInternalTestingTimeLeft((prev) => {
|
|
95
|
+
const next = { ...prev };
|
|
96
|
+
delete next[id];
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
setInternalTestingTimeLeft((prev) => ({ ...prev, [id]: seconds }));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
55
104
|
const fetchProviders = useCallback(async (): Promise<void> => {
|
|
56
105
|
try {
|
|
57
106
|
const providersRes = await fetch("/api/providers");
|
|
@@ -81,23 +130,111 @@ export function ProvidersPanel(): JSX.Element {
|
|
|
81
130
|
})();
|
|
82
131
|
}, [fetchProviders]);
|
|
83
132
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
133
|
+
const TEST_TIMEOUT_SECONDS = 30;
|
|
134
|
+
|
|
135
|
+
const runTest = useCallback(
|
|
136
|
+
async (providerId: string): Promise<void> => {
|
|
137
|
+
// Use callback form if available, otherwise direct set
|
|
138
|
+
if (onTestingProvidersChange) {
|
|
139
|
+
onTestingProvidersChange(providerId, true);
|
|
140
|
+
} else {
|
|
141
|
+
setInternalTestingProviders((prev) => new Set(prev).add(providerId));
|
|
92
142
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
143
|
+
|
|
144
|
+
// Create abort controller for this test request
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
|
|
147
|
+
// Start countdown
|
|
148
|
+
let remaining = TEST_TIMEOUT_SECONDS;
|
|
149
|
+
setTestingTimeLeft(providerId, remaining);
|
|
150
|
+
const intervalId = setInterval(() => {
|
|
151
|
+
remaining--;
|
|
152
|
+
setTestingTimeLeft(providerId, remaining);
|
|
153
|
+
if (remaining <= 0) {
|
|
154
|
+
clearInterval(intervalId);
|
|
155
|
+
// Abort the fetch request when time runs out
|
|
156
|
+
controller.abort();
|
|
157
|
+
}
|
|
158
|
+
}, 1000);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const res = await fetch(`/api/providers/${providerId}/test`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
});
|
|
165
|
+
if (res.ok) {
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
167
|
+
const results = (await res.json()) as TestResults;
|
|
168
|
+
if (onTestResultsChange) {
|
|
169
|
+
onTestResultsChange(providerId, results);
|
|
170
|
+
} else {
|
|
171
|
+
setInternalTestResults((prev) => ({ ...prev, [providerId]: results }));
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// Non-ok response, create error result
|
|
175
|
+
const errorResult: TestResults = {
|
|
176
|
+
anthropic: {
|
|
177
|
+
nonStreaming: {
|
|
178
|
+
success: false,
|
|
179
|
+
error: { message: `HTTP ${res.status}: ${res.statusText}`, type: "server_error" },
|
|
180
|
+
},
|
|
181
|
+
streaming: { notConfigured: true },
|
|
182
|
+
},
|
|
183
|
+
openai: {
|
|
184
|
+
nonStreaming: {
|
|
185
|
+
success: false,
|
|
186
|
+
error: { message: `HTTP ${res.status}: ${res.statusText}`, type: "server_error" },
|
|
187
|
+
},
|
|
188
|
+
streaming: { notConfigured: true },
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
if (onTestResultsChange) {
|
|
192
|
+
onTestResultsChange(providerId, errorResult);
|
|
193
|
+
} else {
|
|
194
|
+
setInternalTestResults((prev) => ({ ...prev, [providerId]: errorResult }));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
// Check if this was an abort (timeout)
|
|
199
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
200
|
+
const timeoutResult: TestResults = {
|
|
201
|
+
anthropic: {
|
|
202
|
+
nonStreaming: {
|
|
203
|
+
success: false,
|
|
204
|
+
error: { message: "Request timed out", type: "timeout" },
|
|
205
|
+
},
|
|
206
|
+
streaming: { notConfigured: true },
|
|
207
|
+
},
|
|
208
|
+
openai: {
|
|
209
|
+
nonStreaming: {
|
|
210
|
+
success: false,
|
|
211
|
+
error: { message: "Request timed out", type: "timeout" },
|
|
212
|
+
},
|
|
213
|
+
streaming: { notConfigured: true },
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
if (onTestResultsChange) {
|
|
217
|
+
onTestResultsChange(providerId, timeoutResult);
|
|
218
|
+
} else {
|
|
219
|
+
setInternalTestResults((prev) => ({ ...prev, [providerId]: timeoutResult }));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} finally {
|
|
223
|
+
clearInterval(intervalId);
|
|
224
|
+
setTestingTimeLeft(providerId, undefined);
|
|
225
|
+
if (onTestingProvidersChange) {
|
|
226
|
+
onTestingProvidersChange(providerId, false);
|
|
227
|
+
} else {
|
|
228
|
+
setInternalTestingProviders((prev) => {
|
|
229
|
+
const next = new Set(prev);
|
|
230
|
+
next.delete(providerId);
|
|
231
|
+
return next;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
[onTestingProvidersChange, onTestResultsChange, setTestingTimeLeft],
|
|
237
|
+
);
|
|
101
238
|
|
|
102
239
|
function handleAddProvider(data: {
|
|
103
240
|
name: string;
|
|
@@ -279,6 +416,7 @@ export function ProvidersPanel(): JSX.Element {
|
|
|
279
416
|
provider={provider}
|
|
280
417
|
testResults={testResults[provider.id]}
|
|
281
418
|
isTesting={testingProviders.has(provider.id)}
|
|
419
|
+
testingTimeLeft={testingTimeLeft[provider.id]}
|
|
282
420
|
onEdit={(p) => setEditingProvider(p)}
|
|
283
421
|
onDelete={handleDeleteProvider}
|
|
284
422
|
onTest={(id: string) => {
|
|
@@ -1,13 +1,53 @@
|
|
|
1
|
-
import { type JSX, useState } from "react";
|
|
1
|
+
import { type JSX, useState, useCallback } from "react";
|
|
2
2
|
import { Settings } from "lucide-react";
|
|
3
3
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
|
4
4
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs";
|
|
5
5
|
import { Button } from "../ui/button";
|
|
6
6
|
import { ProvidersPanel } from "./ProvidersPanel";
|
|
7
|
+
import type { ProviderConfig } from "../../proxy/providers";
|
|
7
8
|
|
|
8
9
|
export function SettingsDialog(): JSX.Element {
|
|
9
10
|
const [open, setOpen] = useState(false);
|
|
10
11
|
const [activeTab, setActiveTab] = useState("providers");
|
|
12
|
+
const [providers, setProviders] = useState<ProviderConfig[]>([]);
|
|
13
|
+
const [testResults, setTestResults] = useState<
|
|
14
|
+
Record<string, import("./ProvidersPanel").TestResults>
|
|
15
|
+
>({});
|
|
16
|
+
const [testingProviders, setTestingProviders] = useState<Set<string>>(new Set());
|
|
17
|
+
const [testingTimeLeft, setTestingTimeLeft] = useState<Record<string, number>>({});
|
|
18
|
+
|
|
19
|
+
const handleTestResultsChange = useCallback(
|
|
20
|
+
(providerId: string, results: import("./ProvidersPanel").TestResults) => {
|
|
21
|
+
setTestResults((prev) => ({ ...prev, [providerId]: results }));
|
|
22
|
+
},
|
|
23
|
+
[],
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const handleTestingProvidersChange = useCallback((providerId: string, isTesting: boolean) => {
|
|
27
|
+
setTestingProviders((prev) => {
|
|
28
|
+
const next = new Set(prev);
|
|
29
|
+
if (isTesting) {
|
|
30
|
+
next.add(providerId);
|
|
31
|
+
} else {
|
|
32
|
+
next.delete(providerId);
|
|
33
|
+
}
|
|
34
|
+
return next;
|
|
35
|
+
});
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const handleTestingTimeLeftChange = useCallback(
|
|
39
|
+
(providerId: string, seconds: number | undefined) => {
|
|
40
|
+
setTestingTimeLeft((prev) => {
|
|
41
|
+
if (seconds === undefined) {
|
|
42
|
+
const next = { ...prev };
|
|
43
|
+
delete next[providerId];
|
|
44
|
+
return next;
|
|
45
|
+
}
|
|
46
|
+
return { ...prev, [providerId]: seconds };
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
[],
|
|
50
|
+
);
|
|
11
51
|
|
|
12
52
|
return (
|
|
13
53
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
@@ -29,7 +69,16 @@ export function SettingsDialog(): JSX.Element {
|
|
|
29
69
|
|
|
30
70
|
<div className="mt-4 overflow-y-auto flex-1">
|
|
31
71
|
<TabsContent value="providers">
|
|
32
|
-
<ProvidersPanel
|
|
72
|
+
<ProvidersPanel
|
|
73
|
+
externalProviders={providers}
|
|
74
|
+
externalTestResults={testResults}
|
|
75
|
+
externalTestingProviders={testingProviders}
|
|
76
|
+
externalTestingTimeLeft={testingTimeLeft}
|
|
77
|
+
onProvidersChange={setProviders}
|
|
78
|
+
onTestResultsChange={handleTestResultsChange}
|
|
79
|
+
onTestingProvidersChange={handleTestingProvidersChange}
|
|
80
|
+
onTestingTimeLeftChange={handleTestingTimeLeftChange}
|
|
81
|
+
/>
|
|
33
82
|
</TabsContent>
|
|
34
83
|
</div>
|
|
35
84
|
</Tabs>
|