@tonyclaw/agent-inspector 3.0.3 → 3.0.5
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-CufpM7_P.js → CompareDrawer-Co-35_Xw.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DPv1rBo7.js +106 -0
- package/.output/public/assets/{ReplayDialog-ClDmxv57.js → ReplayDialog-DghbvKdy.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DcuIghmp.js → RequestAnatomy-CRXE5v8X.js} +1 -1
- package/.output/public/assets/{ResponseView-C7Tl47j3.js → ResponseView-CzxoQRjp.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-DBK_R540.js → StreamingChunkSequence-DnFyF_Hr.js} +1 -1
- package/.output/public/assets/{_sessionId-D8Ke-EcI.js → _sessionId-DHRhWzKe.js} +1 -1
- package/.output/public/assets/index-D8cruW0P.css +1 -0
- package/.output/public/assets/index-DZyTpd2w.js +1 -0
- package/.output/public/assets/{index-CpWH7b2O.js → index-Dl1oki9E.js} +1 -1
- package/.output/public/assets/{json-viewer-BEifdKM_.js → json-viewer-B84f7oiG.js} +1 -1
- package/.output/public/assets/{main-DDfExS3_.js → main-DViDnJ9X.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +2 -2
- package/.output/server/{_sessionId-Di7z893V.mjs → _sessionId-C43vEAWe.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-ABv-8FMB.mjs → CompareDrawer-DOL6q0jD.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-CGX-1aUr.mjs → ProxyViewerContainer-S9izw3ND.mjs} +572 -272
- package/.output/server/_ssr/{ReplayDialog-DD31ftzG.mjs → ReplayDialog-tqthwJh0.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-54kNHAjH.mjs → RequestAnatomy-CgWDjFPR.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-CRnibtlX.mjs → ResponseView-Dk8Op622.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-Cb5WGfMm.mjs → StreamingChunkSequence-AlS7PK8f.mjs} +2 -2
- package/.output/server/_ssr/{index-BJ-8DomR.mjs → index-BGwvMN2M.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-DB5188I7.mjs → json-viewer-CKHkh5U3.mjs} +2 -2
- package/.output/server/_ssr/{router-BKf70uDr.mjs → router-BJMaHkr1.mjs} +9 -9
- package/.output/server/{_tanstack-start-manifest_v-DUuf3Rgf.mjs → _tanstack-start-manifest_v-C_HvRzgP.mjs} +1 -1
- package/.output/server/index.mjs +66 -66
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +92 -25
- package/src/components/ProxyViewerContainer.tsx +154 -55
- package/src/components/providers/ProviderCard.tsx +413 -230
- package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
- package/src/components/proxy-viewer/ConversationHeader.tsx +4 -5
- package/src/lib/stopReason.ts +4 -0
- package/.output/public/assets/ProxyViewerContainer-Dppr2zGE.js +0 -106
- package/.output/public/assets/index-BskTYcAH.css +0 -1
- package/.output/public/assets/index-DRHfTEM5.js +0 -1
|
@@ -275,6 +275,209 @@ function ProviderEndpointSummary({
|
|
|
275
275
|
);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
function providerHasEndpoint(value: string | undefined): value is string {
|
|
279
|
+
return value !== undefined && value !== "";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function providerTestTargets(provider: ProviderConfig): string[] {
|
|
283
|
+
const targets: string[] = [];
|
|
284
|
+
if (providerHasEndpoint(provider.anthropicBaseUrl)) {
|
|
285
|
+
targets.push("Anthropic");
|
|
286
|
+
}
|
|
287
|
+
if (providerHasEndpoint(provider.openaiBaseUrl)) {
|
|
288
|
+
targets.push("Chat");
|
|
289
|
+
}
|
|
290
|
+
if (providerHasEndpoint(provider.openaiResponsesBaseUrl)) {
|
|
291
|
+
targets.push("Responses");
|
|
292
|
+
}
|
|
293
|
+
return targets;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function ProviderTestCoverage({ provider }: { provider: ProviderConfig }): JSX.Element {
|
|
297
|
+
const targets = providerTestTargets(provider);
|
|
298
|
+
if (targets.length === 0) {
|
|
299
|
+
return (
|
|
300
|
+
<div className="text-xs text-muted-foreground">
|
|
301
|
+
Test requires at least one configured endpoint.
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<div className="flex min-w-0 flex-wrap items-center gap-1.5 text-xs text-muted-foreground">
|
|
308
|
+
<span>Test covers</span>
|
|
309
|
+
{targets.map((target) => (
|
|
310
|
+
<span
|
|
311
|
+
key={target}
|
|
312
|
+
className="rounded border border-border/70 bg-muted/25 px-1.5 py-0.5 text-foreground/80"
|
|
313
|
+
>
|
|
314
|
+
{target}
|
|
315
|
+
</span>
|
|
316
|
+
))}
|
|
317
|
+
<span>non-stream + stream</span>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
type ProviderTestSummary = {
|
|
323
|
+
total: number;
|
|
324
|
+
passed: number;
|
|
325
|
+
failed: number;
|
|
326
|
+
testing: number;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
function configuredFormatResults(
|
|
330
|
+
provider: ProviderConfig,
|
|
331
|
+
testResults: TestResults | undefined,
|
|
332
|
+
): ProviderFormatTestResults[] {
|
|
333
|
+
if (testResults === undefined) return [];
|
|
334
|
+
const results: ProviderFormatTestResults[] = [];
|
|
335
|
+
if (providerHasEndpoint(provider.anthropicBaseUrl)) {
|
|
336
|
+
results.push(testResults.anthropic);
|
|
337
|
+
}
|
|
338
|
+
if (providerHasEndpoint(provider.openaiBaseUrl)) {
|
|
339
|
+
results.push(testResults.openaiChat);
|
|
340
|
+
}
|
|
341
|
+
if (providerHasEndpoint(provider.openaiResponsesBaseUrl)) {
|
|
342
|
+
results.push(testResults.openaiResponses);
|
|
343
|
+
}
|
|
344
|
+
return results;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function summarizeTestResults(
|
|
348
|
+
provider: ProviderConfig,
|
|
349
|
+
testResults: TestResults | undefined,
|
|
350
|
+
): ProviderTestSummary {
|
|
351
|
+
const summary: ProviderTestSummary = { total: 0, passed: 0, failed: 0, testing: 0 };
|
|
352
|
+
for (const results of configuredFormatResults(provider, testResults)) {
|
|
353
|
+
for (const state of [results.nonStreaming, results.streaming]) {
|
|
354
|
+
if (isNotConfiguredState(state)) continue;
|
|
355
|
+
summary.total += 1;
|
|
356
|
+
if (hasSuccessField(state)) {
|
|
357
|
+
if (state.success) {
|
|
358
|
+
summary.passed += 1;
|
|
359
|
+
} else {
|
|
360
|
+
summary.failed += 1;
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
summary.testing += 1;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return summary;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function ProviderTestPill({
|
|
371
|
+
provider,
|
|
372
|
+
testResults,
|
|
373
|
+
isTesting,
|
|
374
|
+
}: {
|
|
375
|
+
provider: ProviderConfig;
|
|
376
|
+
testResults: TestResults | undefined;
|
|
377
|
+
isTesting: boolean;
|
|
378
|
+
}): JSX.Element {
|
|
379
|
+
const targets = providerTestTargets(provider);
|
|
380
|
+
const summary = summarizeTestResults(provider, testResults);
|
|
381
|
+
|
|
382
|
+
if (isTesting) {
|
|
383
|
+
return (
|
|
384
|
+
<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">
|
|
385
|
+
<RotateCw className="size-3 animate-spin" />
|
|
386
|
+
Testing
|
|
387
|
+
</span>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (targets.length === 0) {
|
|
392
|
+
return (
|
|
393
|
+
<span className="rounded border border-border/70 bg-muted/20 px-2 py-1 text-xs text-muted-foreground">
|
|
394
|
+
No endpoint
|
|
395
|
+
</span>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (testResults === undefined || summary.total === 0) {
|
|
400
|
+
return (
|
|
401
|
+
<span className="rounded border border-border/70 bg-muted/20 px-2 py-1 text-xs text-muted-foreground">
|
|
402
|
+
Not tested
|
|
403
|
+
</span>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (summary.failed > 0) {
|
|
408
|
+
return (
|
|
409
|
+
<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">
|
|
410
|
+
<AlertCircle className="size-3" />
|
|
411
|
+
{summary.passed}/{summary.total} passed
|
|
412
|
+
</span>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (summary.testing > 0 || summary.passed < summary.total) {
|
|
417
|
+
return (
|
|
418
|
+
<span className="rounded border border-amber-300/25 bg-amber-400/[0.08] px-2 py-1 text-xs text-amber-100">
|
|
419
|
+
Partial {summary.passed}/{summary.total}
|
|
420
|
+
</span>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<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">
|
|
426
|
+
<CheckCircle className="size-3" />
|
|
427
|
+
OK {summary.passed}/{summary.total}
|
|
428
|
+
</span>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function compactModelLabel(provider: ProviderConfig): string {
|
|
433
|
+
const models = provider.models ?? [];
|
|
434
|
+
if (models.length === 0) return "No models configured";
|
|
435
|
+
const visible = models.slice(0, 2).join(", ");
|
|
436
|
+
const hiddenCount = models.length - 2;
|
|
437
|
+
return hiddenCount > 0 ? `${visible} +${hiddenCount} more` : visible;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function ModelChip({ provider, model }: { provider: ProviderConfig; model: string }): JSX.Element {
|
|
441
|
+
const metadata = findProviderModelMetadata(provider, model);
|
|
442
|
+
const sourceLink = getModelSourceLink(metadata?.sourceUrl);
|
|
443
|
+
return (
|
|
444
|
+
<span className="inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
|
445
|
+
<span>{model}</span>
|
|
446
|
+
{metadata !== null && metadata.contextWindow !== undefined && (
|
|
447
|
+
<span className="font-mono text-foreground/80">
|
|
448
|
+
{formatContextWindowTokens(metadata.contextWindow)}
|
|
449
|
+
</span>
|
|
450
|
+
)}
|
|
451
|
+
{sourceLink !== null && (
|
|
452
|
+
<TooltipProvider>
|
|
453
|
+
<Tooltip>
|
|
454
|
+
<TooltipTrigger asChild>
|
|
455
|
+
<a
|
|
456
|
+
href={sourceLink.url}
|
|
457
|
+
target="_blank"
|
|
458
|
+
rel="noopener noreferrer"
|
|
459
|
+
className="inline-flex items-center gap-0.5 rounded border border-border/70 bg-background/70 px-1 font-mono text-[10px] leading-4 text-muted-foreground transition-colors hover:text-foreground"
|
|
460
|
+
aria-label={sourceLink.title}
|
|
461
|
+
>
|
|
462
|
+
{sourceLink.label}
|
|
463
|
+
</a>
|
|
464
|
+
</TooltipTrigger>
|
|
465
|
+
<TooltipContent>{sourceLink.title}</TooltipContent>
|
|
466
|
+
</Tooltip>
|
|
467
|
+
</TooltipProvider>
|
|
468
|
+
)}
|
|
469
|
+
</span>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function modelResultsLabel(testResults: TestResults | undefined): string | null {
|
|
474
|
+
const models = testResults?.models;
|
|
475
|
+
if (models === undefined) return null;
|
|
476
|
+
const count = Object.keys(models).length;
|
|
477
|
+
if (count === 0) return null;
|
|
478
|
+
return `${count} model${count === 1 ? "" : "s"} tested`;
|
|
479
|
+
}
|
|
480
|
+
|
|
278
481
|
function getErrorIcon(type: ErrorType): JSX.Element {
|
|
279
482
|
const iconProps = { className: "size-3", strokeWidth: 2 };
|
|
280
483
|
switch (type) {
|
|
@@ -476,6 +679,7 @@ export function ProviderCard({
|
|
|
476
679
|
}: ProviderCardProps): JSX.Element {
|
|
477
680
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
478
681
|
const [copied, setCopied] = useState(false);
|
|
682
|
+
const [showDetails, setShowDetails] = useState(false);
|
|
479
683
|
const [showModelResults, setShowModelResults] = useState(false);
|
|
480
684
|
|
|
481
685
|
function handleCopy() {
|
|
@@ -486,7 +690,6 @@ export function ProviderCard({
|
|
|
486
690
|
});
|
|
487
691
|
}
|
|
488
692
|
|
|
489
|
-
// Get docs URL: use provider.apiDocsUrl if configured, otherwise check KNOWN_PROVIDER_DOCS
|
|
490
693
|
const docsUrl =
|
|
491
694
|
provider.apiDocsUrl ??
|
|
492
695
|
Object.entries(KNOWN_PROVIDER_DOCS).find(([keyword]) =>
|
|
@@ -496,6 +699,8 @@ export function ProviderCard({
|
|
|
496
699
|
const canRefreshMetadata =
|
|
497
700
|
provider.modelMetadataUrl !== undefined && provider.modelMetadataUrl.trim() !== "";
|
|
498
701
|
const metadataImportLabel = canRefreshMetadata ? "Refresh" : "Import defaults";
|
|
702
|
+
const isTestingValue = isTesting ?? false;
|
|
703
|
+
const testedModelsLabel = modelResultsLabel(testResults);
|
|
499
704
|
const providerSourceBadge =
|
|
500
705
|
provider.source === "company" ? (
|
|
501
706
|
<TooltipProvider>
|
|
@@ -523,90 +728,53 @@ export function ProviderCard({
|
|
|
523
728
|
|
|
524
729
|
return (
|
|
525
730
|
<div
|
|
526
|
-
className={`
|
|
731
|
+
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" : ""}`}
|
|
527
732
|
>
|
|
528
733
|
<div className="flex items-start justify-between gap-2">
|
|
529
|
-
<div className="
|
|
530
|
-
<
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
{
|
|
545
|
-
<
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
</
|
|
554
|
-
</
|
|
734
|
+
<div className="min-w-0 space-y-1">
|
|
735
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
736
|
+
<span className="truncate font-medium">{provider.name}</span>
|
|
737
|
+
{providerSourceBadge}
|
|
738
|
+
</div>
|
|
739
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
740
|
+
{compactModelLabel(provider)}
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
744
|
+
<ProviderTestPill
|
|
745
|
+
provider={provider}
|
|
746
|
+
testResults={testResults}
|
|
747
|
+
isTesting={isTestingValue}
|
|
748
|
+
/>
|
|
749
|
+
{docsUrl !== undefined && (
|
|
750
|
+
<a
|
|
751
|
+
href={docsUrl}
|
|
752
|
+
target="_blank"
|
|
753
|
+
rel="noopener noreferrer"
|
|
754
|
+
className="flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
755
|
+
title="View API documentation"
|
|
756
|
+
>
|
|
757
|
+
<ExternalLink className="size-3" />
|
|
758
|
+
<span className="sr-only">Docs</span>
|
|
759
|
+
</a>
|
|
555
760
|
)}
|
|
556
761
|
</div>
|
|
557
|
-
{docsUrl !== undefined && (
|
|
558
|
-
<a
|
|
559
|
-
href={docsUrl}
|
|
560
|
-
target="_blank"
|
|
561
|
-
rel="noopener noreferrer"
|
|
562
|
-
className="text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1 text-xs"
|
|
563
|
-
title="View API documentation"
|
|
564
|
-
>
|
|
565
|
-
<ExternalLink className="size-3" />
|
|
566
|
-
<span className="sr-only">Docs</span>
|
|
567
|
-
</a>
|
|
568
|
-
)}
|
|
569
762
|
</div>
|
|
570
763
|
|
|
571
|
-
|
|
572
|
-
<
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
<span className="font-mono text-foreground/80">
|
|
584
|
-
{formatContextWindowTokens(metadata.contextWindow)}
|
|
585
|
-
</span>
|
|
586
|
-
)}
|
|
587
|
-
{sourceLink !== null && (
|
|
588
|
-
<TooltipProvider>
|
|
589
|
-
<Tooltip>
|
|
590
|
-
<TooltipTrigger asChild>
|
|
591
|
-
<a
|
|
592
|
-
href={sourceLink.url}
|
|
593
|
-
target="_blank"
|
|
594
|
-
rel="noopener noreferrer"
|
|
595
|
-
className="inline-flex items-center gap-0.5 rounded border border-border/70 bg-background/70 px-1 font-mono text-[10px] leading-4 text-muted-foreground transition-colors hover:text-foreground"
|
|
596
|
-
aria-label={sourceLink.title}
|
|
597
|
-
>
|
|
598
|
-
{sourceLink.label}
|
|
599
|
-
</a>
|
|
600
|
-
</TooltipTrigger>
|
|
601
|
-
<TooltipContent>{sourceLink.title}</TooltipContent>
|
|
602
|
-
</Tooltip>
|
|
603
|
-
</TooltipProvider>
|
|
604
|
-
)}
|
|
605
|
-
</span>
|
|
606
|
-
);
|
|
607
|
-
})}
|
|
764
|
+
<div className="flex flex-col gap-2 text-xs text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
|
|
765
|
+
<ProviderTestCoverage provider={provider} />
|
|
766
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
767
|
+
<span>Key configured</span>
|
|
768
|
+
<span>{provider.authHeader === "x-api-key" ? "x-api-key" : "Bearer"}</span>
|
|
769
|
+
{testedModelsLabel !== null && <span>{testedModelsLabel}</span>}
|
|
770
|
+
{testResults?.testedAt !== undefined && (
|
|
771
|
+
<span className="inline-flex items-center gap-1">
|
|
772
|
+
<Clock className="size-3" />
|
|
773
|
+
{formatTimeAgo(testResults.testedAt)}
|
|
774
|
+
</span>
|
|
775
|
+
)}
|
|
608
776
|
</div>
|
|
609
|
-
|
|
777
|
+
</div>
|
|
610
778
|
|
|
611
779
|
{!hasContextMetadata && (
|
|
612
780
|
<div className="flex items-center justify-between gap-2 rounded-md border border-amber-500/25 bg-amber-500/6 px-2 py-1.5 text-xs text-muted-foreground">
|
|
@@ -638,179 +806,194 @@ export function ProviderCard({
|
|
|
638
806
|
</div>
|
|
639
807
|
)}
|
|
640
808
|
|
|
641
|
-
|
|
642
|
-
<
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
809
|
+
{showDetails && (
|
|
810
|
+
<div className="space-y-3 border-t border-border/60 pt-3">
|
|
811
|
+
<div className="flex items-center gap-2">
|
|
812
|
+
<code className="flex-1 truncate rounded bg-muted px-2 py-1 text-xs text-muted-foreground">
|
|
813
|
+
{showApiKey ? provider.apiKey : maskApiKey(provider.apiKey)}
|
|
814
|
+
</code>
|
|
815
|
+
<button
|
|
816
|
+
type="button"
|
|
817
|
+
onClick={() => setShowApiKey((s) => !s)}
|
|
818
|
+
className="p-1 text-muted-foreground transition-colors hover:text-foreground"
|
|
819
|
+
aria-label={showApiKey ? "Hide API key" : "Show API key"}
|
|
820
|
+
>
|
|
821
|
+
{showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
|
822
|
+
</button>
|
|
823
|
+
<button
|
|
824
|
+
type="button"
|
|
825
|
+
onClick={handleCopy}
|
|
826
|
+
className="p-1 text-muted-foreground transition-colors hover:text-foreground"
|
|
827
|
+
aria-label="Copy API key"
|
|
828
|
+
>
|
|
829
|
+
{copied ? <Check className="size-4 text-cyan-300" /> : <Copy className="size-4" />}
|
|
830
|
+
</button>
|
|
831
|
+
</div>
|
|
662
832
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
>
|
|
670
|
-
{testResults !== undefined && (
|
|
671
|
-
<ProviderFormatTestStatus results={testResults.anthropic} />
|
|
833
|
+
{provider.models !== undefined && provider.models.length > 0 && (
|
|
834
|
+
<div className="flex flex-wrap gap-1">
|
|
835
|
+
{provider.models.map((model) => (
|
|
836
|
+
<ModelChip key={model} provider={provider} model={model} />
|
|
837
|
+
))}
|
|
838
|
+
</div>
|
|
672
839
|
)}
|
|
673
|
-
</ProviderEndpointSummary>
|
|
674
|
-
)}
|
|
675
840
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
841
|
+
{providerHasEndpoint(provider.anthropicBaseUrl) && (
|
|
842
|
+
<ProviderEndpointSummary
|
|
843
|
+
label="Anthropic"
|
|
844
|
+
baseUrl={provider.anthropicBaseUrl}
|
|
845
|
+
endpoint={PATH_V1_MESSAGES}
|
|
846
|
+
providerName={provider.name}
|
|
847
|
+
>
|
|
848
|
+
{testResults !== undefined && (
|
|
849
|
+
<ProviderFormatTestStatus results={testResults.anthropic} />
|
|
850
|
+
)}
|
|
851
|
+
</ProviderEndpointSummary>
|
|
685
852
|
)}
|
|
686
|
-
</ProviderEndpointSummary>
|
|
687
|
-
)}
|
|
688
853
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
854
|
+
{providerHasEndpoint(provider.openaiBaseUrl) && (
|
|
855
|
+
<ProviderEndpointSummary
|
|
856
|
+
label="Chat"
|
|
857
|
+
baseUrl={provider.openaiBaseUrl}
|
|
858
|
+
endpoint={PATH_V1_CHAT_COMPLETIONS}
|
|
859
|
+
providerName={provider.name}
|
|
860
|
+
>
|
|
861
|
+
{testResults !== undefined && (
|
|
862
|
+
<ProviderFormatTestStatus results={testResults.openaiChat} />
|
|
863
|
+
)}
|
|
864
|
+
</ProviderEndpointSummary>
|
|
698
865
|
)}
|
|
699
|
-
</ProviderEndpointSummary>
|
|
700
|
-
)}
|
|
701
866
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
Chat
|
|
737
|
-
</th>
|
|
738
|
-
)}
|
|
739
|
-
{provider.openaiResponsesBaseUrl !== undefined &&
|
|
740
|
-
provider.openaiResponsesBaseUrl !== "" && (
|
|
741
|
-
<th className="text-left py-1 px-2 font-medium text-muted-foreground">
|
|
742
|
-
Responses
|
|
867
|
+
{providerHasEndpoint(provider.openaiResponsesBaseUrl) && (
|
|
868
|
+
<ProviderEndpointSummary
|
|
869
|
+
label="Responses"
|
|
870
|
+
baseUrl={provider.openaiResponsesBaseUrl}
|
|
871
|
+
endpoint={PATH_V1_RESPONSES}
|
|
872
|
+
providerName={provider.name}
|
|
873
|
+
>
|
|
874
|
+
{testResults !== undefined && (
|
|
875
|
+
<ProviderFormatTestStatus results={testResults.openaiResponses} />
|
|
876
|
+
)}
|
|
877
|
+
</ProviderEndpointSummary>
|
|
878
|
+
)}
|
|
879
|
+
|
|
880
|
+
{testResults?.models !== undefined && Object.keys(testResults.models).length > 0 && (
|
|
881
|
+
<div>
|
|
882
|
+
<button
|
|
883
|
+
type="button"
|
|
884
|
+
onClick={() => setShowModelResults((value) => !value)}
|
|
885
|
+
className="flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
886
|
+
>
|
|
887
|
+
{showModelResults ? (
|
|
888
|
+
<ChevronDown className="size-3" />
|
|
889
|
+
) : (
|
|
890
|
+
<ChevronRight className="size-3" />
|
|
891
|
+
)}
|
|
892
|
+
Model Test Results ({Object.keys(testResults.models).length})
|
|
893
|
+
</button>
|
|
894
|
+
{showModelResults && (
|
|
895
|
+
<div className="mt-2 overflow-x-auto">
|
|
896
|
+
<table className="w-full border-collapse text-xs">
|
|
897
|
+
<thead>
|
|
898
|
+
<tr className="border-b border-border">
|
|
899
|
+
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
900
|
+
Model
|
|
743
901
|
</th>
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
{Object.entries(testResults.models).map(([modelName, modelResult]) => (
|
|
749
|
-
<tr key={modelName} className="border-b border-border/50 last:border-0">
|
|
750
|
-
<td className="py-1 px-2 font-medium">{modelName}</td>
|
|
751
|
-
{provider.anthropicBaseUrl !== undefined &&
|
|
752
|
-
provider.anthropicBaseUrl !== "" && (
|
|
753
|
-
<td className="py-1 px-2 align-top">
|
|
754
|
-
<ProviderFormatTestStatus results={modelResult.anthropic} />
|
|
755
|
-
</td>
|
|
902
|
+
{providerHasEndpoint(provider.anthropicBaseUrl) && (
|
|
903
|
+
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
904
|
+
Anthropic
|
|
905
|
+
</th>
|
|
756
906
|
)}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
)}
|
|
762
|
-
{provider.openaiResponsesBaseUrl !== undefined &&
|
|
763
|
-
provider.openaiResponsesBaseUrl !== "" && (
|
|
764
|
-
<td className="py-1 px-2 align-top">
|
|
765
|
-
<ProviderFormatTestStatus results={modelResult.openaiResponses} />
|
|
766
|
-
</td>
|
|
907
|
+
{providerHasEndpoint(provider.openaiBaseUrl) && (
|
|
908
|
+
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
909
|
+
Chat
|
|
910
|
+
</th>
|
|
767
911
|
)}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
912
|
+
{providerHasEndpoint(provider.openaiResponsesBaseUrl) && (
|
|
913
|
+
<th className="px-2 py-1 text-left font-medium text-muted-foreground">
|
|
914
|
+
Responses
|
|
915
|
+
</th>
|
|
916
|
+
)}
|
|
917
|
+
</tr>
|
|
918
|
+
</thead>
|
|
919
|
+
<tbody>
|
|
920
|
+
{Object.entries(testResults.models).map(([modelName, modelResult]) => (
|
|
921
|
+
<tr key={modelName} className="border-b border-border/50 last:border-0">
|
|
922
|
+
<td className="px-2 py-1 font-medium">{modelName}</td>
|
|
923
|
+
{providerHasEndpoint(provider.anthropicBaseUrl) && (
|
|
924
|
+
<td className="px-2 py-1 align-top">
|
|
925
|
+
<ProviderFormatTestStatus results={modelResult.anthropic} />
|
|
926
|
+
</td>
|
|
927
|
+
)}
|
|
928
|
+
{providerHasEndpoint(provider.openaiBaseUrl) && (
|
|
929
|
+
<td className="px-2 py-1 align-top">
|
|
930
|
+
<ProviderFormatTestStatus results={modelResult.openaiChat} />
|
|
931
|
+
</td>
|
|
932
|
+
)}
|
|
933
|
+
{providerHasEndpoint(provider.openaiResponsesBaseUrl) && (
|
|
934
|
+
<td className="px-2 py-1 align-top">
|
|
935
|
+
<ProviderFormatTestStatus results={modelResult.openaiResponses} />
|
|
936
|
+
</td>
|
|
937
|
+
)}
|
|
938
|
+
</tr>
|
|
939
|
+
))}
|
|
940
|
+
</tbody>
|
|
941
|
+
</table>
|
|
942
|
+
</div>
|
|
943
|
+
)}
|
|
772
944
|
</div>
|
|
773
945
|
)}
|
|
774
946
|
</div>
|
|
775
947
|
)}
|
|
776
948
|
|
|
777
|
-
<div className="flex gap-2 pt-1
|
|
778
|
-
|
|
949
|
+
<div className="flex flex-col gap-2 border-t pt-1 sm:flex-row sm:items-center sm:justify-between">
|
|
950
|
+
<button
|
|
951
|
+
type="button"
|
|
952
|
+
onClick={() => setShowDetails((value) => !value)}
|
|
953
|
+
className="inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
954
|
+
aria-expanded={showDetails}
|
|
955
|
+
>
|
|
956
|
+
{showDetails ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
|
|
957
|
+
Details
|
|
958
|
+
</button>
|
|
959
|
+
<div className="flex gap-2">
|
|
960
|
+
{onTest !== undefined && (
|
|
961
|
+
<Button
|
|
962
|
+
variant="outline"
|
|
963
|
+
size="sm"
|
|
964
|
+
onClick={() => onTest(provider.id)}
|
|
965
|
+
className="text-xs h-7 gap-1"
|
|
966
|
+
disabled={isTestingValue}
|
|
967
|
+
>
|
|
968
|
+
<RotateCw className={`size-3 ${isTestingValue ? "animate-spin" : ""}`} />
|
|
969
|
+
{isTestingValue
|
|
970
|
+
? testingTimeLeft !== undefined
|
|
971
|
+
? `Testing (${testingTimeLeft}s)`
|
|
972
|
+
: "Testing..."
|
|
973
|
+
: "Test"}
|
|
974
|
+
</Button>
|
|
975
|
+
)}
|
|
779
976
|
<Button
|
|
780
977
|
variant="outline"
|
|
781
978
|
size="sm"
|
|
782
|
-
onClick={() =>
|
|
979
|
+
onClick={() => onEdit(provider)}
|
|
783
980
|
className="text-xs h-7 gap-1"
|
|
784
|
-
disabled={
|
|
981
|
+
disabled={isTestingValue}
|
|
785
982
|
>
|
|
786
|
-
<
|
|
787
|
-
|
|
788
|
-
? testingTimeLeft !== undefined
|
|
789
|
-
? `Testing (${testingTimeLeft}s)`
|
|
790
|
-
: "Testing..."
|
|
791
|
-
: "Test"}
|
|
983
|
+
<Pencil className="size-3" />
|
|
984
|
+
Edit
|
|
792
985
|
</Button>
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
</
|
|
804
|
-
<Button
|
|
805
|
-
variant="outline"
|
|
806
|
-
size="sm"
|
|
807
|
-
onClick={() => onDelete(provider.id)}
|
|
808
|
-
className="text-xs h-7 gap-1 text-destructive hover:text-destructive"
|
|
809
|
-
disabled={isTesting ?? false}
|
|
810
|
-
>
|
|
811
|
-
<Trash2 className="size-3" />
|
|
812
|
-
Delete
|
|
813
|
-
</Button>
|
|
986
|
+
<Button
|
|
987
|
+
variant="outline"
|
|
988
|
+
size="sm"
|
|
989
|
+
onClick={() => onDelete(provider.id)}
|
|
990
|
+
className="text-xs h-7 gap-1 text-destructive hover:text-destructive"
|
|
991
|
+
disabled={isTestingValue}
|
|
992
|
+
>
|
|
993
|
+
<Trash2 className="size-3" />
|
|
994
|
+
Delete
|
|
995
|
+
</Button>
|
|
996
|
+
</div>
|
|
814
997
|
</div>
|
|
815
998
|
</div>
|
|
816
999
|
);
|