@tonyclaw/agent-inspector 3.0.23 → 3.0.24
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/{CompareDrawer-CFedvZHU.js → CompareDrawer-BC52FYwn.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-WuChOrSS.js +127 -0
- package/.output/public/assets/{ReplayDialog-X-fRSKAu.js → ReplayDialog-BcKsarKt.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-S8qB5nVg.js → RequestAnatomy-DR0uGpCj.js} +1 -1
- package/.output/public/assets/{ResponseView-_rcOWSwJ.js → ResponseView-kxAAPfmn.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-Bc15AVpN.js → StreamingChunkSequence-YQR0PjQj.js} +1 -1
- package/.output/public/assets/{_sessionId-Cz-HW2Wf.js → _sessionId-DRl-Y9om.js} +1 -1
- package/.output/public/assets/{index-CEgwWY3E.js → index-CQbrc-hi.js} +71 -7
- package/.output/public/assets/{index-CXYBU0j8.js → index-CcSIBI4L.js} +1 -1
- package/.output/public/assets/index-DQeXi4Rv.css +1 -0
- package/.output/public/assets/{index-ClKXooQd.js → index-ZrMgyEZV.js} +1 -1
- package/.output/public/assets/{json-viewer-QfU4qGdd.js → json-viewer-CS5pwnEA.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +266 -234
- package/.output/server/{_sessionId-9FAmYZVf.mjs → _sessionId-BLk2xPkR.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BLKjOzwD.mjs → CompareDrawer-Cw7Dseo3.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-Dcs2Mq8l.mjs → ProxyViewerContainer-D8SB0zyQ.mjs} +1124 -877
- package/.output/server/_ssr/{ReplayDialog-oyiF4LIo.mjs → ReplayDialog-B-YMhZkE.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-DkyQXSuj.mjs → RequestAnatomy-a9d8UTYZ.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-3NTDEiDh.mjs → ResponseView-PzaZS4CN.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-DRq-5Bgn.mjs → StreamingChunkSequence-C21_TCXk.mjs} +2 -2
- package/.output/server/_ssr/{index-dtMiNUa1.mjs → index-CrCeNP7H.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BhUwicvp.mjs → json-viewer-Di2gxs9H.mjs} +3 -3
- package/.output/server/_ssr/{router-DO0OntUK.mjs → router-BJzdUbcH.mjs} +296 -91
- package/.output/server/{_tanstack-start-manifest_v-DO5v3BHY.mjs → _tanstack-start-manifest_v-CGBTdAYC.mjs} +1 -1
- package/.output/server/index.mjs +76 -76
- package/package.json +3 -2
- package/src/components/ProxyViewer.tsx +136 -460
- package/src/components/ProxyViewerContainer.tsx +5 -47
- package/src/components/alerts/AlertsDialog.tsx +6 -2
- package/src/components/providers/ProviderCard.tsx +20 -579
- package/src/components/providers/ProviderTestResultsView.tsx +732 -0
- package/src/components/proxy-viewer/LogEntry.tsx +5 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +7 -0
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +33 -36
- package/src/components/proxy-viewer/TurnGroup.tsx +1 -5
- package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +46 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +469 -0
- package/src/lib/themeMode.ts +45 -0
- package/src/lib/uiScale.ts +112 -0
- package/src/lib/useThemeMode.ts +40 -0
- package/src/lib/useUiScale.ts +52 -0
- package/src/routes/__root.tsx +78 -1
- package/styles/globals.css +305 -23
- package/.output/public/assets/ProxyViewerContainer-BlCrBvdm.js +0 -127
- package/.output/public/assets/index-B_SIQ47i.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSX,
|
|
1
|
+
import { type JSX, useState } from "react";
|
|
2
2
|
import { Button } from "../ui/button";
|
|
3
3
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
4
4
|
import {
|
|
@@ -8,15 +8,7 @@ import {
|
|
|
8
8
|
Trash2,
|
|
9
9
|
RotateCw,
|
|
10
10
|
CheckCircle,
|
|
11
|
-
Minus,
|
|
12
11
|
ExternalLink,
|
|
13
|
-
AlertCircle,
|
|
14
|
-
Wifi,
|
|
15
|
-
WifiOff,
|
|
16
|
-
Lock,
|
|
17
|
-
Gauge,
|
|
18
|
-
Server,
|
|
19
|
-
HelpCircle,
|
|
20
12
|
Clock,
|
|
21
13
|
Copy,
|
|
22
14
|
Check,
|
|
@@ -31,13 +23,12 @@ import {
|
|
|
31
23
|
providerHasContextMetadata,
|
|
32
24
|
} from "../../lib/providerModelMetadata";
|
|
33
25
|
import { formatContextWindowTokens } from "../../lib/utils";
|
|
34
|
-
import type {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "../../lib/providerTestContract";
|
|
26
|
+
import type { ProviderTestResults as TestResults } from "../../lib/providerTestContract";
|
|
27
|
+
import {
|
|
28
|
+
ProviderModelTestResultsView,
|
|
29
|
+
ProviderTestMatrix,
|
|
30
|
+
ProviderTestPill,
|
|
31
|
+
} from "./ProviderTestResultsView";
|
|
41
32
|
|
|
42
33
|
// Known provider documentation links (fallback when apiDocsUrl is not configured)
|
|
43
34
|
const KNOWN_PROVIDER_DOCS: Record<string, string> = {
|
|
@@ -57,218 +48,14 @@ type ProviderCardProps = {
|
|
|
57
48
|
isRefreshingMetadata?: boolean;
|
|
58
49
|
};
|
|
59
50
|
|
|
60
|
-
function hasSuccessField(result: ProviderTestState): result is TestResult {
|
|
61
|
-
return Object.prototype.hasOwnProperty.call(result, "success");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Using Object.prototype.hasOwnProperty.call to avoid 'in' operator
|
|
65
|
-
function isNotConfiguredState(result: ProviderTestState): result is { notConfigured: true } {
|
|
66
|
-
return Object.prototype.hasOwnProperty.call(result, "notConfigured");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function hasText(value: string | undefined): value is string {
|
|
70
|
-
return value !== undefined && value.trim() !== "";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function formatHeaders(headers: Record<string, string> | undefined): string | undefined {
|
|
74
|
-
if (headers === undefined) {
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const lines = Object.entries(headers).map(([name, value]) => `${name}: ${value}`);
|
|
79
|
-
if (lines.length === 0) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return lines.join("\n");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function hasHeaderDetails(headers: Record<string, string> | undefined): boolean {
|
|
87
|
-
return formatHeaders(headers) !== undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function formatElapsed(ms: number): string {
|
|
91
|
-
if (ms < 1000) return `${ms}ms`;
|
|
92
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function formatTokenRate(tokensPerSecond: number): string {
|
|
96
|
-
if (tokensPerSecond >= 10) return `${tokensPerSecond.toFixed(0)}/s`;
|
|
97
|
-
return `${tokensPerSecond.toFixed(1)}/s`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function hasDiagnosticDetails(result: TestResult): boolean {
|
|
101
|
-
const debug = result.debug;
|
|
102
|
-
return (
|
|
103
|
-
hasText(result.error?.hint) ||
|
|
104
|
-
hasText(result.error?.details) ||
|
|
105
|
-
hasText(result.rawResponse) ||
|
|
106
|
-
hasText(debug?.requestUrl) ||
|
|
107
|
-
hasText(debug?.requestBody) ||
|
|
108
|
-
hasText(debug?.responseBody) ||
|
|
109
|
-
hasHeaderDetails(debug?.requestHeaders) ||
|
|
110
|
-
hasHeaderDetails(debug?.responseHeaders) ||
|
|
111
|
-
debug?.responseStatus !== undefined
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function DiagnosticSection({
|
|
116
|
-
title,
|
|
117
|
-
children,
|
|
118
|
-
}: {
|
|
119
|
-
title: string;
|
|
120
|
-
children: ReactNode;
|
|
121
|
-
}): JSX.Element {
|
|
122
|
-
return (
|
|
123
|
-
<div className="space-y-1">
|
|
124
|
-
<div className="font-medium text-foreground/80">{title}</div>
|
|
125
|
-
{children}
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function DiagnosticPre({ value }: { value: string }): JSX.Element {
|
|
131
|
-
return (
|
|
132
|
-
<pre className="max-h-40 overflow-auto whitespace-pre-wrap break-all rounded-md bg-background/80 p-2 font-mono text-[11px] leading-relaxed text-foreground/80">
|
|
133
|
-
{value}
|
|
134
|
-
</pre>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function ProviderTestDetails({ result }: { result: TestResult }): JSX.Element {
|
|
139
|
-
const debug = result.debug;
|
|
140
|
-
const requestUrl = debug?.requestUrl;
|
|
141
|
-
const requestMethod = debug?.requestMethod ?? "POST";
|
|
142
|
-
const requestHeaders = formatHeaders(debug?.requestHeaders);
|
|
143
|
-
const responseHeaders = formatHeaders(debug?.responseHeaders);
|
|
144
|
-
const responseStatus =
|
|
145
|
-
debug?.responseStatus !== undefined
|
|
146
|
-
? `${debug.responseStatus}${
|
|
147
|
-
hasText(debug.responseStatusText) ? ` ${debug.responseStatusText}` : ""
|
|
148
|
-
}`
|
|
149
|
-
: undefined;
|
|
150
|
-
const responseBody = debug?.responseBody ?? result.rawResponse;
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<div className="max-h-80 max-w-full space-y-2 overflow-auto rounded-md border bg-muted/50 p-2 text-xs text-muted-foreground shadow-sm sm:w-[520px]">
|
|
154
|
-
{hasText(result.error?.hint) && (
|
|
155
|
-
<DiagnosticSection title="Hint">
|
|
156
|
-
<div>{result.error.hint}</div>
|
|
157
|
-
</DiagnosticSection>
|
|
158
|
-
)}
|
|
159
|
-
{hasText(result.error?.details) && (
|
|
160
|
-
<DiagnosticSection title="Error">
|
|
161
|
-
<DiagnosticPre value={result.error.details} />
|
|
162
|
-
</DiagnosticSection>
|
|
163
|
-
)}
|
|
164
|
-
{hasText(requestUrl) && (
|
|
165
|
-
<DiagnosticSection title="Request">
|
|
166
|
-
<div className="rounded-md bg-background/80 p-2 font-mono text-[11px] break-all text-foreground/80">
|
|
167
|
-
{requestMethod} {requestUrl}
|
|
168
|
-
</div>
|
|
169
|
-
</DiagnosticSection>
|
|
170
|
-
)}
|
|
171
|
-
{requestHeaders !== undefined && (
|
|
172
|
-
<DiagnosticSection title="Request Headers">
|
|
173
|
-
<DiagnosticPre value={requestHeaders} />
|
|
174
|
-
</DiagnosticSection>
|
|
175
|
-
)}
|
|
176
|
-
{hasText(debug?.requestBody) && (
|
|
177
|
-
<DiagnosticSection title="Request Body">
|
|
178
|
-
<DiagnosticPre value={debug.requestBody} />
|
|
179
|
-
</DiagnosticSection>
|
|
180
|
-
)}
|
|
181
|
-
{hasText(responseStatus) && (
|
|
182
|
-
<DiagnosticSection title="Response Status">
|
|
183
|
-
<div className="rounded-md bg-background/80 p-2 font-mono text-[11px] text-foreground/80">
|
|
184
|
-
{responseStatus}
|
|
185
|
-
</div>
|
|
186
|
-
</DiagnosticSection>
|
|
187
|
-
)}
|
|
188
|
-
{responseHeaders !== undefined && (
|
|
189
|
-
<DiagnosticSection title="Response Headers">
|
|
190
|
-
<DiagnosticPre value={responseHeaders} />
|
|
191
|
-
</DiagnosticSection>
|
|
192
|
-
)}
|
|
193
|
-
{hasText(responseBody) && (
|
|
194
|
-
<DiagnosticSection title="Response Body">
|
|
195
|
-
<DiagnosticPre value={responseBody} />
|
|
196
|
-
</DiagnosticSection>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function ProviderFormatTestStatus({
|
|
203
|
-
results,
|
|
204
|
-
}: {
|
|
205
|
-
results: ProviderFormatTestResults;
|
|
206
|
-
}): JSX.Element {
|
|
207
|
-
return (
|
|
208
|
-
<div className="grid min-w-0 gap-2 sm:grid-cols-2">
|
|
209
|
-
<div className="min-w-0 rounded-md border border-border/60 bg-muted/20 px-2 py-1.5">
|
|
210
|
-
<span className="text-[10px] text-muted-foreground">Non-stream</span>
|
|
211
|
-
<TestStatus result={results.nonStreaming} />
|
|
212
|
-
</div>
|
|
213
|
-
<div className="min-w-0 rounded-md border border-border/60 bg-muted/20 px-2 py-1.5">
|
|
214
|
-
<span className="text-[10px] text-muted-foreground">Stream</span>
|
|
215
|
-
<TestStatus result={results.streaming} />
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function ProviderFormatStatusBlock({
|
|
222
|
-
label,
|
|
223
|
-
children,
|
|
224
|
-
}: {
|
|
225
|
-
label: string;
|
|
226
|
-
children?: ReactNode;
|
|
227
|
-
}): JSX.Element {
|
|
228
|
-
return (
|
|
229
|
-
<div className="min-w-0 space-y-2 rounded-md border border-border/60 bg-muted/15 p-2">
|
|
230
|
-
<div className="text-xs font-medium text-foreground/80">{label}</div>
|
|
231
|
-
{children}
|
|
232
|
-
</div>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
51
|
function providerHasEndpoint(value: string | undefined): value is string {
|
|
237
52
|
return value !== undefined && value !== "";
|
|
238
53
|
}
|
|
239
54
|
|
|
240
|
-
function
|
|
241
|
-
const targets: string[] = [];
|
|
242
|
-
if (providerHasEndpoint(provider.anthropicBaseUrl)) {
|
|
243
|
-
targets.push("Anthropic");
|
|
244
|
-
}
|
|
245
|
-
if (providerHasEndpoint(provider.openaiBaseUrl)) {
|
|
246
|
-
targets.push("Chat");
|
|
247
|
-
}
|
|
248
|
-
if (providerHasEndpoint(provider.openaiResponsesBaseUrl)) {
|
|
249
|
-
targets.push("Responses");
|
|
250
|
-
}
|
|
251
|
-
return targets;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
type CapabilityChipTone = "active" | "quiet";
|
|
255
|
-
|
|
256
|
-
function CapabilityChip({
|
|
257
|
-
label,
|
|
258
|
-
enabled,
|
|
259
|
-
tone,
|
|
260
|
-
}: {
|
|
261
|
-
label: string;
|
|
262
|
-
enabled: boolean;
|
|
263
|
-
tone?: CapabilityChipTone;
|
|
264
|
-
}): JSX.Element {
|
|
265
|
-
const resolvedTone: CapabilityChipTone = tone ?? (enabled ? "active" : "quiet");
|
|
266
|
-
const className = enabled
|
|
267
|
-
? "border-border/70 bg-muted/25 text-foreground/80"
|
|
268
|
-
: "border-border/50 bg-transparent text-muted-foreground/70";
|
|
55
|
+
function CapabilityChip({ label }: { label: string }): JSX.Element {
|
|
269
56
|
return (
|
|
270
|
-
<span className=
|
|
271
|
-
|
|
57
|
+
<span className="inline-flex items-center gap-1 rounded border border-border/70 bg-muted/25 px-1.5 py-0.5 text-foreground/80">
|
|
58
|
+
<CheckCircle className="size-3" />
|
|
272
59
|
{label}
|
|
273
60
|
</span>
|
|
274
61
|
);
|
|
@@ -289,123 +76,13 @@ function ProviderCapabilitySummary({ provider }: { provider: ProviderConfig }):
|
|
|
289
76
|
|
|
290
77
|
return (
|
|
291
78
|
<div className="flex min-w-0 flex-wrap items-center gap-1.5 text-xs text-muted-foreground">
|
|
292
|
-
{hasAnthropic && <CapabilityChip label="Anthropic"
|
|
293
|
-
{hasChat && <CapabilityChip label="OpenAI Chat"
|
|
294
|
-
{hasResponses && <CapabilityChip label="OpenAI Responses"
|
|
79
|
+
{hasAnthropic && <CapabilityChip label="Anthropic" />}
|
|
80
|
+
{hasChat && <CapabilityChip label="OpenAI Chat" />}
|
|
81
|
+
{hasResponses && <CapabilityChip label="OpenAI Responses" />}
|
|
295
82
|
</div>
|
|
296
83
|
);
|
|
297
84
|
}
|
|
298
85
|
|
|
299
|
-
type ProviderTestSummary = {
|
|
300
|
-
total: number;
|
|
301
|
-
passed: number;
|
|
302
|
-
failed: number;
|
|
303
|
-
testing: number;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
function configuredFormatResults(
|
|
307
|
-
provider: ProviderConfig,
|
|
308
|
-
testResults: TestResults | undefined,
|
|
309
|
-
): ProviderFormatTestResults[] {
|
|
310
|
-
if (testResults === undefined) return [];
|
|
311
|
-
const results: ProviderFormatTestResults[] = [];
|
|
312
|
-
if (providerHasEndpoint(provider.anthropicBaseUrl)) {
|
|
313
|
-
results.push(testResults.anthropic);
|
|
314
|
-
}
|
|
315
|
-
if (providerHasEndpoint(provider.openaiBaseUrl)) {
|
|
316
|
-
results.push(testResults.openaiChat);
|
|
317
|
-
}
|
|
318
|
-
if (providerHasEndpoint(provider.openaiResponsesBaseUrl)) {
|
|
319
|
-
results.push(testResults.openaiResponses);
|
|
320
|
-
}
|
|
321
|
-
return results;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function summarizeTestResults(
|
|
325
|
-
provider: ProviderConfig,
|
|
326
|
-
testResults: TestResults | undefined,
|
|
327
|
-
): ProviderTestSummary {
|
|
328
|
-
const summary: ProviderTestSummary = { total: 0, passed: 0, failed: 0, testing: 0 };
|
|
329
|
-
for (const results of configuredFormatResults(provider, testResults)) {
|
|
330
|
-
for (const state of [results.nonStreaming, results.streaming]) {
|
|
331
|
-
if (isNotConfiguredState(state)) continue;
|
|
332
|
-
summary.total += 1;
|
|
333
|
-
if (hasSuccessField(state)) {
|
|
334
|
-
if (state.success) {
|
|
335
|
-
summary.passed += 1;
|
|
336
|
-
} else {
|
|
337
|
-
summary.failed += 1;
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
summary.testing += 1;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return summary;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function ProviderTestPill({
|
|
348
|
-
provider,
|
|
349
|
-
testResults,
|
|
350
|
-
isTesting,
|
|
351
|
-
}: {
|
|
352
|
-
provider: ProviderConfig;
|
|
353
|
-
testResults: TestResults | undefined;
|
|
354
|
-
isTesting: boolean;
|
|
355
|
-
}): JSX.Element {
|
|
356
|
-
const targets = providerTestTargets(provider);
|
|
357
|
-
const summary = summarizeTestResults(provider, testResults);
|
|
358
|
-
|
|
359
|
-
if (isTesting) {
|
|
360
|
-
return (
|
|
361
|
-
<span className="inline-flex items-center gap-1 rounded border border-cyan-300/25 bg-cyan-300/[0.08] px-2 py-1 text-xs text-cyan-100">
|
|
362
|
-
<RotateCw className="size-3 animate-spin" />
|
|
363
|
-
Testing
|
|
364
|
-
</span>
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (targets.length === 0) {
|
|
369
|
-
return (
|
|
370
|
-
<span className="rounded border border-border/70 bg-muted/20 px-2 py-1 text-xs text-muted-foreground">
|
|
371
|
-
No endpoint
|
|
372
|
-
</span>
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (testResults === undefined || summary.total === 0) {
|
|
377
|
-
return (
|
|
378
|
-
<span className="rounded border border-border/70 bg-muted/20 px-2 py-1 text-xs text-muted-foreground">
|
|
379
|
-
Not tested
|
|
380
|
-
</span>
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (summary.failed > 0) {
|
|
385
|
-
return (
|
|
386
|
-
<span className="inline-flex items-center gap-1 rounded border border-red-400/25 bg-red-500/[0.08] px-2 py-1 text-xs text-red-200">
|
|
387
|
-
<AlertCircle className="size-3" />
|
|
388
|
-
{summary.passed}/{summary.total} passed
|
|
389
|
-
</span>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (summary.testing > 0 || summary.passed < summary.total) {
|
|
394
|
-
return (
|
|
395
|
-
<span className="rounded border border-amber-300/25 bg-amber-400/[0.08] px-2 py-1 text-xs text-amber-100">
|
|
396
|
-
Partial {summary.passed}/{summary.total}
|
|
397
|
-
</span>
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return (
|
|
402
|
-
<span className="inline-flex items-center gap-1 rounded border border-cyan-300/25 bg-cyan-300/[0.08] px-2 py-1 text-xs text-cyan-100">
|
|
403
|
-
<CheckCircle className="size-3" />
|
|
404
|
-
OK {summary.passed}/{summary.total}
|
|
405
|
-
</span>
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
86
|
function compactModelLabel(provider: ProviderConfig): string {
|
|
410
87
|
const models = provider.models ?? [];
|
|
411
88
|
if (models.length === 0) return "No models configured";
|
|
@@ -447,139 +124,6 @@ function ModelChip({ provider, model }: { provider: ProviderConfig; model: strin
|
|
|
447
124
|
);
|
|
448
125
|
}
|
|
449
126
|
|
|
450
|
-
function getErrorIcon(type: ErrorType): JSX.Element {
|
|
451
|
-
const iconProps = { className: "size-3", strokeWidth: 2 };
|
|
452
|
-
switch (type) {
|
|
453
|
-
case "timeout":
|
|
454
|
-
return <Clock {...iconProps} />;
|
|
455
|
-
case "network_unreachable":
|
|
456
|
-
return <WifiOff {...iconProps} />;
|
|
457
|
-
case "connection_refused":
|
|
458
|
-
return <Wifi {...iconProps} />;
|
|
459
|
-
case "auth_failed":
|
|
460
|
-
return <Lock {...iconProps} />;
|
|
461
|
-
case "rate_limited":
|
|
462
|
-
return <Gauge {...iconProps} />;
|
|
463
|
-
case "server_error":
|
|
464
|
-
return <Server {...iconProps} />;
|
|
465
|
-
case "invalid_response":
|
|
466
|
-
return <HelpCircle {...iconProps} />;
|
|
467
|
-
case "unknown":
|
|
468
|
-
return <AlertCircle {...iconProps} />;
|
|
469
|
-
default:
|
|
470
|
-
return <AlertCircle {...iconProps} />;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function TestStatus({ result }: { result: ProviderTestState }): JSX.Element {
|
|
475
|
-
const [showDetails, setShowDetails] = useState(false);
|
|
476
|
-
|
|
477
|
-
if (!hasSuccessField(result)) {
|
|
478
|
-
// Not TestResult - check if NotConfigured or Testing
|
|
479
|
-
if (isNotConfiguredState(result)) {
|
|
480
|
-
return (
|
|
481
|
-
<div className="flex items-center gap-1 text-xs text-muted-foreground shrink-0">
|
|
482
|
-
<Minus className="size-3" />
|
|
483
|
-
<span>Not configured</span>
|
|
484
|
-
</div>
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
// Must be Testing state
|
|
488
|
-
return (
|
|
489
|
-
<div className="flex items-center gap-1 text-xs text-muted-foreground shrink-0">
|
|
490
|
-
<RotateCw className="size-3 animate-spin" />
|
|
491
|
-
<span>Testing...</span>
|
|
492
|
-
</div>
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
if (result.success) {
|
|
496
|
-
const tokenParts: ReactNode[] = [];
|
|
497
|
-
const timingParts: ReactNode[] = [];
|
|
498
|
-
if (result.inputTokens !== undefined) {
|
|
499
|
-
tokenParts.push(<span key="in">{result.inputTokens} in</span>);
|
|
500
|
-
}
|
|
501
|
-
if (result.outputTokens !== undefined) {
|
|
502
|
-
tokenParts.push(<span key="out">{result.outputTokens} out</span>);
|
|
503
|
-
}
|
|
504
|
-
if (result.cacheCreationInputTokens !== undefined && result.cacheCreationInputTokens > 0) {
|
|
505
|
-
tokenParts.push(
|
|
506
|
-
<span key="cache-create" className="font-mono tabular-nums text-emerald-400">
|
|
507
|
-
KV Cache +{result.cacheCreationInputTokens}
|
|
508
|
-
</span>,
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
if (result.cacheReadInputTokens !== undefined && result.cacheReadInputTokens > 0) {
|
|
512
|
-
tokenParts.push(
|
|
513
|
-
<span key="cache-read" className="font-mono tabular-nums text-purple-400">
|
|
514
|
-
KV Cache ~{result.cacheReadInputTokens}
|
|
515
|
-
</span>,
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
if (result.firstChunkMs !== undefined && result.firstChunkMs !== null) {
|
|
519
|
-
timingParts.push(<span key="first">first {formatElapsed(result.firstChunkMs)}</span>);
|
|
520
|
-
}
|
|
521
|
-
if (result.totalStreamMs !== undefined && result.totalStreamMs !== null) {
|
|
522
|
-
timingParts.push(<span key="total">total {formatElapsed(result.totalStreamMs)}</span>);
|
|
523
|
-
}
|
|
524
|
-
if (
|
|
525
|
-
result.tokensPerSecond !== undefined &&
|
|
526
|
-
result.tokensPerSecond !== null &&
|
|
527
|
-
result.tokensPerSecond > 0
|
|
528
|
-
) {
|
|
529
|
-
timingParts.push(<span key="rate">{formatTokenRate(result.tokensPerSecond)} tok/s</span>);
|
|
530
|
-
}
|
|
531
|
-
const displayTokens: ReactNode[] = [];
|
|
532
|
-
for (let i = 0; i < tokenParts.length; i++) {
|
|
533
|
-
if (i > 0) displayTokens.push(", ");
|
|
534
|
-
displayTokens.push(tokenParts[i]);
|
|
535
|
-
}
|
|
536
|
-
const displayTiming: ReactNode[] = [];
|
|
537
|
-
for (let i = 0; i < timingParts.length; i++) {
|
|
538
|
-
if (i > 0) displayTiming.push(", ");
|
|
539
|
-
displayTiming.push(timingParts[i]);
|
|
540
|
-
}
|
|
541
|
-
return (
|
|
542
|
-
<div className="flex shrink-0 items-center gap-1 text-xs text-cyan-100">
|
|
543
|
-
<CheckCircle className="size-3 text-cyan-300" />
|
|
544
|
-
<span>Connected</span>
|
|
545
|
-
{tokenParts.length > 0 && <span className="text-muted-foreground">({displayTokens})</span>}
|
|
546
|
-
{timingParts.length > 0 && (
|
|
547
|
-
<span className="hidden text-muted-foreground lg:inline">({displayTiming})</span>
|
|
548
|
-
)}
|
|
549
|
-
</div>
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const error = result.error;
|
|
554
|
-
const errorMessage = error?.message ?? "Connection failed";
|
|
555
|
-
const errorType = error?.type ?? "unknown";
|
|
556
|
-
const hasDetails = hasDiagnosticDetails(result);
|
|
557
|
-
|
|
558
|
-
return (
|
|
559
|
-
<div className="flex flex-col gap-1 shrink-0">
|
|
560
|
-
<div className="flex items-center gap-1 text-xs text-red-600 max-w-[200px]">
|
|
561
|
-
{getErrorIcon(errorType)}
|
|
562
|
-
<span className="truncate">{errorMessage}</span>
|
|
563
|
-
{hasDetails && (
|
|
564
|
-
<button
|
|
565
|
-
type="button"
|
|
566
|
-
onClick={() => setShowDetails((value) => !value)}
|
|
567
|
-
aria-expanded={showDetails}
|
|
568
|
-
aria-label={
|
|
569
|
-
showDetails ? "Hide provider test diagnostics" : "Show provider test diagnostics"
|
|
570
|
-
}
|
|
571
|
-
className="inline-flex h-5 shrink-0 items-center gap-1 rounded border border-border/70 bg-background/70 px-1.5 text-[10px] text-muted-foreground transition-colors hover:text-foreground"
|
|
572
|
-
>
|
|
573
|
-
{showDetails ? <EyeOff className="size-3" /> : <Eye className="size-3" />}
|
|
574
|
-
<span>{showDetails ? "Hide" : "Details"}</span>
|
|
575
|
-
</button>
|
|
576
|
-
)}
|
|
577
|
-
</div>
|
|
578
|
-
{showDetails && hasDetails && <ProviderTestDetails result={result} />}
|
|
579
|
-
</div>
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
127
|
function formatTimeAgo(isoString: string): string {
|
|
584
128
|
const now = Date.now();
|
|
585
129
|
const then = new Date(isoString).getTime();
|
|
@@ -633,7 +177,6 @@ export function ProviderCard({
|
|
|
633
177
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
634
178
|
const [copied, setCopied] = useState(false);
|
|
635
179
|
const [showDetails, setShowDetails] = useState(false);
|
|
636
|
-
const [showModelResults, setShowModelResults] = useState(false);
|
|
637
180
|
|
|
638
181
|
function handleCopy() {
|
|
639
182
|
void copyTextToClipboard(provider.apiKey).then((success) => {
|
|
@@ -666,6 +209,7 @@ export function ProviderCard({
|
|
|
666
209
|
|
|
667
210
|
return (
|
|
668
211
|
<div
|
|
212
|
+
data-provider-id={provider.id}
|
|
669
213
|
className={`flex flex-col gap-3 rounded-[8px] border border-border bg-card p-4 shadow-sm transition-all duration-500 ${isHighlighted === true ? "ring-2 ring-primary shadow-md" : ""}`}
|
|
670
214
|
>
|
|
671
215
|
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
@@ -771,116 +315,13 @@ export function ProviderCard({
|
|
|
771
315
|
</div>
|
|
772
316
|
)}
|
|
773
317
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
{providerHasEndpoint(provider.anthropicBaseUrl) && (
|
|
779
|
-
<ProviderFormatStatusBlock label="Anthropic">
|
|
780
|
-
{testResults !== undefined && (
|
|
781
|
-
<ProviderFormatTestStatus results={testResults.anthropic} />
|
|
782
|
-
)}
|
|
783
|
-
{testResults === undefined && (
|
|
784
|
-
<div className="text-xs text-muted-foreground">Not tested yet</div>
|
|
785
|
-
)}
|
|
786
|
-
</ProviderFormatStatusBlock>
|
|
787
|
-
)}
|
|
788
|
-
|
|
789
|
-
{providerHasEndpoint(provider.openaiBaseUrl) && (
|
|
790
|
-
<ProviderFormatStatusBlock label="OpenAI Chat">
|
|
791
|
-
{testResults !== undefined && (
|
|
792
|
-
<ProviderFormatTestStatus results={testResults.openaiChat} />
|
|
793
|
-
)}
|
|
794
|
-
{testResults === undefined && (
|
|
795
|
-
<div className="text-xs text-muted-foreground">Not tested yet</div>
|
|
796
|
-
)}
|
|
797
|
-
</ProviderFormatStatusBlock>
|
|
798
|
-
)}
|
|
799
|
-
|
|
800
|
-
{providerHasEndpoint(provider.openaiResponsesBaseUrl) && (
|
|
801
|
-
<ProviderFormatStatusBlock label="OpenAI Responses">
|
|
802
|
-
{testResults !== undefined && (
|
|
803
|
-
<ProviderFormatTestStatus results={testResults.openaiResponses} />
|
|
804
|
-
)}
|
|
805
|
-
{testResults === undefined && (
|
|
806
|
-
<div className="text-xs text-muted-foreground">Not tested yet</div>
|
|
807
|
-
)}
|
|
808
|
-
</ProviderFormatStatusBlock>
|
|
809
|
-
)}
|
|
318
|
+
<section className="space-y-2">
|
|
319
|
+
<h4 className="text-xs font-medium text-foreground/80">Connection probes</h4>
|
|
320
|
+
<ProviderTestMatrix provider={provider} results={testResults} />
|
|
321
|
+
</section>
|
|
810
322
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
OpenAI Responses not configured
|
|
814
|
-
</div>
|
|
815
|
-
)}
|
|
816
|
-
</div>
|
|
817
|
-
)}
|
|
818
|
-
|
|
819
|
-
{testResults?.models !== undefined && Object.keys(testResults.models).length > 0 && (
|
|
820
|
-
<div>
|
|
821
|
-
<button
|
|
822
|
-
type="button"
|
|
823
|
-
onClick={() => setShowModelResults((value) => !value)}
|
|
824
|
-
className="flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
825
|
-
>
|
|
826
|
-
{showModelResults ? (
|
|
827
|
-
<ChevronDown className="size-3" />
|
|
828
|
-
) : (
|
|
829
|
-
<ChevronRight className="size-3" />
|
|
830
|
-
)}
|
|
831
|
-
Model Test Results ({Object.keys(testResults.models).length})
|
|
832
|
-
</button>
|
|
833
|
-
{showModelResults && (
|
|
834
|
-
<div className="mt-2 overflow-x-auto">
|
|
835
|
-
<table className="w-full border-collapse text-xs">
|
|
836
|
-
<thead>
|
|
837
|
-
<tr className="border-b border-border">
|
|
838
|
-
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
839
|
-
Model
|
|
840
|
-
</th>
|
|
841
|
-
{providerHasEndpoint(provider.anthropicBaseUrl) && (
|
|
842
|
-
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
843
|
-
Anthropic
|
|
844
|
-
</th>
|
|
845
|
-
)}
|
|
846
|
-
{providerHasEndpoint(provider.openaiBaseUrl) && (
|
|
847
|
-
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
848
|
-
Chat
|
|
849
|
-
</th>
|
|
850
|
-
)}
|
|
851
|
-
{providerHasEndpoint(provider.openaiResponsesBaseUrl) && (
|
|
852
|
-
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
853
|
-
Responses
|
|
854
|
-
</th>
|
|
855
|
-
)}
|
|
856
|
-
</tr>
|
|
857
|
-
</thead>
|
|
858
|
-
<tbody>
|
|
859
|
-
{Object.entries(testResults.models).map(([modelName, modelResult]) => (
|
|
860
|
-
<tr key={modelName} className="border-b border-border/50 last:border-0">
|
|
861
|
-
<td className="px-2 py-1 font-medium">{modelName}</td>
|
|
862
|
-
{providerHasEndpoint(provider.anthropicBaseUrl) && (
|
|
863
|
-
<td className="px-2 py-1 align-top">
|
|
864
|
-
<ProviderFormatTestStatus results={modelResult.anthropic} />
|
|
865
|
-
</td>
|
|
866
|
-
)}
|
|
867
|
-
{providerHasEndpoint(provider.openaiBaseUrl) && (
|
|
868
|
-
<td className="px-2 py-1 align-top">
|
|
869
|
-
<ProviderFormatTestStatus results={modelResult.openaiChat} />
|
|
870
|
-
</td>
|
|
871
|
-
)}
|
|
872
|
-
{providerHasEndpoint(provider.openaiResponsesBaseUrl) && (
|
|
873
|
-
<td className="px-2 py-1 align-top">
|
|
874
|
-
<ProviderFormatTestStatus results={modelResult.openaiResponses} />
|
|
875
|
-
</td>
|
|
876
|
-
)}
|
|
877
|
-
</tr>
|
|
878
|
-
))}
|
|
879
|
-
</tbody>
|
|
880
|
-
</table>
|
|
881
|
-
</div>
|
|
882
|
-
)}
|
|
883
|
-
</div>
|
|
323
|
+
{testResults !== undefined && (
|
|
324
|
+
<ProviderModelTestResultsView provider={provider} testResults={testResults} />
|
|
884
325
|
)}
|
|
885
326
|
</div>
|
|
886
327
|
)}
|