@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,5 +1,5 @@
|
|
|
1
1
|
import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
|
|
2
|
-
import { C as CapturedLogSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, P as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema
|
|
2
|
+
import { C as CapturedLogSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-BoeSXWHG.mjs";
|
|
3
3
|
import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
|
|
4
4
|
import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
|
|
5
5
|
import { J as JSZip } from "../_libs/jszip.mjs";
|
|
@@ -10,9 +10,9 @@ import { d as diffLines, a as diffJson } from "../_libs/diff.mjs";
|
|
|
10
10
|
import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as Portal$1, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
|
|
11
11
|
import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
|
|
12
12
|
import "../_libs/modelcontextprotocol__server.mjs";
|
|
13
|
-
import { D as Download, L as LayoutGrid, a as List, G as GitCompareArrows, X, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, U as Upload, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, f as ChevronRight, g as Clock, M as MessageSquare, Z as Zap, h as LoaderCircle, W as Wrench, i as Globe, j as User, F as FileTerminal, k as Radio, l as Rows3, m as Columns2, n as Minus, o as Pencil, E as Equal, p as
|
|
13
|
+
import { D as Download, L as LayoutGrid, a as List, G as GitCompareArrows, X, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, U as Upload, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, f as ChevronRight, g as Clock, M as MessageSquare, Z as Zap, h as LoaderCircle, W as Wrench, i as Globe, j as User, F as FileTerminal, k as Radio, l as Rows3, m as Columns2, n as Minus, o as Pencil, E as Equal, p as EyeOff, q as Eye, r as ExternalLink, s as RotateCw, T as Trash2, A as ArrowUp, t as ArrowDown, u as TriangleAlert, v as CircleCheckBig, w as CircleStop, x as CircleQuestionMark, y as Server, z as Gauge, B as Lock, H as Wifi, I as WifiOff, J as ChevronsUp, K as ChevronsDown, N as Brain, O as Terminal } from "../_libs/lucide-react.mjs";
|
|
14
14
|
import { M as Markdown } from "../_libs/react-markdown.mjs";
|
|
15
|
-
import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean
|
|
15
|
+
import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean } from "../_libs/zod.mjs";
|
|
16
16
|
import { R as Root2$1, L as List$1, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
|
|
17
17
|
import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
|
|
18
18
|
import { P as Provider, R as Root3, T as Trigger$3, a as Portal$2, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
|
|
@@ -276,7 +276,7 @@ async function exportLogsAsZip(logs) {
|
|
|
276
276
|
document.body.removeChild(anchor);
|
|
277
277
|
URL.revokeObjectURL(url);
|
|
278
278
|
}
|
|
279
|
-
const version = "1.14.
|
|
279
|
+
const version = "1.14.2";
|
|
280
280
|
const packageJson = {
|
|
281
281
|
version
|
|
282
282
|
};
|
|
@@ -2861,13 +2861,13 @@ function SelectScrollDownButton({
|
|
|
2861
2861
|
}
|
|
2862
2862
|
);
|
|
2863
2863
|
}
|
|
2864
|
-
const KNOWN_PROVIDER_DOCS = {
|
|
2865
|
-
deepseek: "https://api-docs.deepseek.com/zh-cn/"
|
|
2866
|
-
};
|
|
2867
2864
|
function maskApiKey(apiKey) {
|
|
2868
2865
|
if (apiKey.length <= 8) return "••••••••";
|
|
2869
2866
|
return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
|
|
2870
2867
|
}
|
|
2868
|
+
const KNOWN_PROVIDER_DOCS = {
|
|
2869
|
+
deepseek: "https://api-docs.deepseek.com/zh-cn/"
|
|
2870
|
+
};
|
|
2871
2871
|
function hasSuccessField(result) {
|
|
2872
2872
|
return Object.prototype.hasOwnProperty.call(result, "success");
|
|
2873
2873
|
}
|
|
@@ -2984,6 +2984,19 @@ function TestStatus({ result }) {
|
|
|
2984
2984
|
] })
|
|
2985
2985
|
] });
|
|
2986
2986
|
}
|
|
2987
|
+
function formatTimeAgo(isoString) {
|
|
2988
|
+
const now = Date.now();
|
|
2989
|
+
const then = new Date(isoString).getTime();
|
|
2990
|
+
const diffMs = now - then;
|
|
2991
|
+
const diffSec = Math.floor(diffMs / 1e3);
|
|
2992
|
+
if (diffSec < 60) return "just now";
|
|
2993
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
2994
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
2995
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
2996
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
2997
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
2998
|
+
return `${diffDay}d ago`;
|
|
2999
|
+
}
|
|
2987
3000
|
function ProviderCard({
|
|
2988
3001
|
provider,
|
|
2989
3002
|
testResults,
|
|
@@ -2995,6 +3008,34 @@ function ProviderCard({
|
|
|
2995
3008
|
onTest
|
|
2996
3009
|
}) {
|
|
2997
3010
|
const [showApiKey, setShowApiKey] = reactExports.useState(false);
|
|
3011
|
+
const [copied, setCopied] = reactExports.useState(false);
|
|
3012
|
+
const [showModelResults, setShowModelResults] = reactExports.useState(false);
|
|
3013
|
+
const hasLoggedRef = reactExports.useRef(null);
|
|
3014
|
+
const lastTestedAtRef = reactExports.useRef(void 0);
|
|
3015
|
+
if (testResults?.testedAt !== void 0 && testResults.testedAt !== lastTestedAtRef.current) {
|
|
3016
|
+
lastTestedAtRef.current = testResults.testedAt;
|
|
3017
|
+
hasLoggedRef.current = null;
|
|
3018
|
+
}
|
|
3019
|
+
const handleToggleModelResults = reactExports.useCallback(() => {
|
|
3020
|
+
setShowModelResults((v) => {
|
|
3021
|
+
const next = !v;
|
|
3022
|
+
if (next && hasLoggedRef.current === null && testResults?.models !== void 0) {
|
|
3023
|
+
hasLoggedRef.current = testResults.testedAt ?? "";
|
|
3024
|
+
void fetch(`/api/providers/${provider.id}/test/log`, {
|
|
3025
|
+
method: "POST",
|
|
3026
|
+
headers: { "Content-Type": "application/json" },
|
|
3027
|
+
body: JSON.stringify(testResults)
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
return next;
|
|
3031
|
+
});
|
|
3032
|
+
}, [provider.id, testResults]);
|
|
3033
|
+
function handleCopy() {
|
|
3034
|
+
navigator.clipboard.writeText(provider.apiKey).catch(() => {
|
|
3035
|
+
});
|
|
3036
|
+
setCopied(true);
|
|
3037
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3038
|
+
}
|
|
2998
3039
|
const docsUrl = provider.apiDocsUrl ?? Object.entries(KNOWN_PROVIDER_DOCS).find(
|
|
2999
3040
|
([keyword]) => provider.name.toLowerCase().includes(keyword)
|
|
3000
3041
|
)?.[1];
|
|
@@ -3004,7 +3045,11 @@ function ProviderCard({
|
|
|
3004
3045
|
className: `border rounded-lg p-4 flex flex-col gap-3 bg-card transition-all duration-500 ${isHighlighted === true ? "ring-2 ring-primary shadow-md" : ""}`,
|
|
3005
3046
|
children: [
|
|
3006
3047
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
3007
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3048
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
3049
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: provider.name }),
|
|
3050
|
+
provider.source === "company" && /* @__PURE__ */ jsxRuntimeExports.jsx("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", children: "公司" }),
|
|
3051
|
+
provider.source === "personal" && /* @__PURE__ */ jsxRuntimeExports.jsx("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", children: "个人" })
|
|
3052
|
+
] }),
|
|
3008
3053
|
docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3009
3054
|
"a",
|
|
3010
3055
|
{
|
|
@@ -3020,6 +3065,7 @@ function ProviderCard({
|
|
|
3020
3065
|
}
|
|
3021
3066
|
)
|
|
3022
3067
|
] }),
|
|
3068
|
+
provider.models !== void 0 && provider.models.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1", children: provider.models.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground", children: m }, m)) }),
|
|
3023
3069
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3024
3070
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-xs text-muted-foreground bg-muted px-2 py-1 rounded flex-1 truncate", children: showApiKey ? provider.apiKey : maskApiKey(provider.apiKey) }),
|
|
3025
3071
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -3031,6 +3077,16 @@ function ProviderCard({
|
|
|
3031
3077
|
"aria-label": showApiKey ? "Hide API key" : "Show API key",
|
|
3032
3078
|
children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-4" })
|
|
3033
3079
|
}
|
|
3080
|
+
),
|
|
3081
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3082
|
+
"button",
|
|
3083
|
+
{
|
|
3084
|
+
type: "button",
|
|
3085
|
+
onClick: handleCopy,
|
|
3086
|
+
className: "text-muted-foreground hover:text-foreground transition-colors p-1",
|
|
3087
|
+
"aria-label": "Copy API key",
|
|
3088
|
+
children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-4" })
|
|
3089
|
+
}
|
|
3034
3090
|
)
|
|
3035
3091
|
] }),
|
|
3036
3092
|
provider.anthropicBaseUrl !== void 0 && provider.anthropicBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
@@ -3049,6 +3105,41 @@ function ProviderCard({
|
|
|
3049
3105
|
] }),
|
|
3050
3106
|
testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.openai.nonStreaming })
|
|
3051
3107
|
] }),
|
|
3108
|
+
testResults?.testedAt !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-1", children: [
|
|
3109
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
|
|
3110
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
3111
|
+
"Tested ",
|
|
3112
|
+
formatTimeAgo(testResults.testedAt)
|
|
3113
|
+
] })
|
|
3114
|
+
] }),
|
|
3115
|
+
testResults?.models !== void 0 && Object.keys(testResults.models).length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t pt-2", children: [
|
|
3116
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3117
|
+
"button",
|
|
3118
|
+
{
|
|
3119
|
+
type: "button",
|
|
3120
|
+
onClick: handleToggleModelResults,
|
|
3121
|
+
className: "text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1",
|
|
3122
|
+
children: [
|
|
3123
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: showModelResults ? "▾" : "▸" }),
|
|
3124
|
+
"Model Test Results (",
|
|
3125
|
+
Object.keys(testResults.models).length,
|
|
3126
|
+
")"
|
|
3127
|
+
]
|
|
3128
|
+
}
|
|
3129
|
+
),
|
|
3130
|
+
showModelResults && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-xs border-collapse", children: [
|
|
3131
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-border", children: [
|
|
3132
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "text-left py-1 px-2 font-medium text-muted-foreground", children: "Model" }),
|
|
3133
|
+
provider.anthropicBaseUrl !== void 0 && provider.anthropicBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "text-left py-1 px-2 font-medium text-muted-foreground", children: "Anthropic" }),
|
|
3134
|
+
provider.openaiBaseUrl !== void 0 && provider.openaiBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "text-left py-1 px-2 font-medium text-muted-foreground", children: "OpenAI" })
|
|
3135
|
+
] }) }),
|
|
3136
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(testResults.models).map(([modelName, modelResult]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-border/50 last:border-0", children: [
|
|
3137
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 px-2 font-medium", children: modelName }),
|
|
3138
|
+
provider.anthropicBaseUrl !== void 0 && provider.anthropicBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 px-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: modelResult.anthropic.nonStreaming }) }),
|
|
3139
|
+
provider.openaiBaseUrl !== void 0 && provider.openaiBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 px-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: modelResult.openai.nonStreaming }) })
|
|
3140
|
+
] }, modelName)) })
|
|
3141
|
+
] }) })
|
|
3142
|
+
] }),
|
|
3052
3143
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 pt-1 border-t", children: [
|
|
3053
3144
|
onTest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3054
3145
|
Button,
|
|
@@ -3124,42 +3215,59 @@ const ALIBABA_MODELS = ["glm-5", "glm-5.1", "qwen3.6-plus", "qwen3.7-max"];
|
|
|
3124
3215
|
function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
3125
3216
|
const [name, setName] = reactExports.useState(provider?.name ?? "Provider Name");
|
|
3126
3217
|
const [apiKey, setApiKey] = reactExports.useState(provider?.apiKey ?? "");
|
|
3127
|
-
const [
|
|
3128
|
-
const [
|
|
3129
|
-
const
|
|
3218
|
+
const [showApiKey, setShowApiKey] = reactExports.useState(false);
|
|
3219
|
+
const [copied, setCopied] = reactExports.useState(false);
|
|
3220
|
+
const initialModels = provider?.models;
|
|
3221
|
+
const [models, setModels] = reactExports.useState(
|
|
3222
|
+
initialModels !== void 0 && initialModels.length > 0 ? initialModels : [""]
|
|
3223
|
+
);
|
|
3224
|
+
const [activeTab, setActiveTab] = reactExports.useState("anthropic");
|
|
3225
|
+
const [anthropicBaseUrl, setAnthropicBaseUrl] = reactExports.useState(provider?.anthropicBaseUrl ?? "");
|
|
3226
|
+
const [openaiBaseUrl, setOpenaiBaseUrl] = reactExports.useState(provider?.openaiBaseUrl ?? "");
|
|
3130
3227
|
const [apiDocsUrl, setApiDocsUrl] = reactExports.useState(provider?.apiDocsUrl ?? "");
|
|
3228
|
+
const [source, setSource] = reactExports.useState(provider?.source);
|
|
3131
3229
|
const [errors, setErrors] = reactExports.useState({});
|
|
3132
3230
|
const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
|
|
3133
|
-
const [
|
|
3231
|
+
const [manualAnthropicUrlOverride, setManualAnthropicUrlOverride] = reactExports.useState(false);
|
|
3232
|
+
const [manualOpenaiUrlOverride, setManualOpenaiUrlOverride] = reactExports.useState(false);
|
|
3134
3233
|
const isMiniMax = name.toLowerCase().includes("minimax");
|
|
3135
3234
|
const isAlibaba = name.toLowerCase().includes("alibaba");
|
|
3235
|
+
function handleCopy() {
|
|
3236
|
+
navigator.clipboard.writeText(apiKey).catch(() => {
|
|
3237
|
+
});
|
|
3238
|
+
setCopied(true);
|
|
3239
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3240
|
+
}
|
|
3136
3241
|
reactExports.useEffect(() => {
|
|
3137
3242
|
if (provider) {
|
|
3138
3243
|
setName(provider.name);
|
|
3139
3244
|
setApiKey(provider.apiKey);
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3245
|
+
setModels((provider.models?.length ?? 0) > 0 ? provider.models : [""]);
|
|
3246
|
+
setAnthropicBaseUrl(provider.anthropicBaseUrl ?? "");
|
|
3247
|
+
setOpenaiBaseUrl(provider.openaiBaseUrl ?? "");
|
|
3143
3248
|
setApiDocsUrl(provider.apiDocsUrl ?? "");
|
|
3144
|
-
|
|
3249
|
+
setSource(provider.source);
|
|
3250
|
+
setManualAnthropicUrlOverride(false);
|
|
3251
|
+
setManualOpenaiUrlOverride(false);
|
|
3145
3252
|
}
|
|
3146
3253
|
}, [provider]);
|
|
3147
3254
|
reactExports.useEffect(() => {
|
|
3148
3255
|
const lowerName = name.toLowerCase();
|
|
3149
3256
|
for (const [keyword, preset] of Object.entries(KNOWN_PROVIDER_PRESETS)) {
|
|
3150
3257
|
if (lowerName.includes(keyword)) {
|
|
3151
|
-
if (!
|
|
3152
|
-
|
|
3153
|
-
|
|
3258
|
+
if (preset.format === "anthropic" && !manualAnthropicUrlOverride) {
|
|
3259
|
+
setAnthropicBaseUrl(preset.baseUrl);
|
|
3260
|
+
} else if (preset.format === "openai" && !manualOpenaiUrlOverride) {
|
|
3261
|
+
setOpenaiBaseUrl(preset.baseUrl);
|
|
3154
3262
|
}
|
|
3155
3263
|
if (preset.apiDocsUrl !== void 0 && !apiDocsUrl) {
|
|
3156
3264
|
setApiDocsUrl(preset.apiDocsUrl);
|
|
3157
3265
|
}
|
|
3158
|
-
if (keyword === "minimax" &&
|
|
3159
|
-
|
|
3266
|
+
if (keyword === "minimax" && models.length === 1 && models[0] === "") {
|
|
3267
|
+
setModels([MINIMAX_MODELS[0] ?? ""]);
|
|
3160
3268
|
}
|
|
3161
|
-
if (keyword === "alibaba" &&
|
|
3162
|
-
|
|
3269
|
+
if (keyword === "alibaba" && models.length === 1 && models[0] === "") {
|
|
3270
|
+
setModels([ALIBABA_MODELS[0] ?? ""]);
|
|
3163
3271
|
}
|
|
3164
3272
|
break;
|
|
3165
3273
|
}
|
|
@@ -3173,13 +3281,17 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3173
3281
|
if (!apiKey.trim()) {
|
|
3174
3282
|
newErrors.apiKey = "API key is required";
|
|
3175
3283
|
}
|
|
3176
|
-
if (!
|
|
3177
|
-
newErrors.
|
|
3284
|
+
if (models.length === 0 || models.every((m) => !m.trim())) {
|
|
3285
|
+
newErrors.models = "At least one model is required";
|
|
3286
|
+
}
|
|
3287
|
+
if (anthropicBaseUrl.trim() && !isValidUrl(anthropicBaseUrl.trim())) {
|
|
3288
|
+
newErrors.anthropicBaseUrl = "Invalid URL format";
|
|
3178
3289
|
}
|
|
3179
|
-
if (!
|
|
3180
|
-
newErrors.
|
|
3181
|
-
}
|
|
3182
|
-
|
|
3290
|
+
if (openaiBaseUrl.trim() && !isValidUrl(openaiBaseUrl.trim())) {
|
|
3291
|
+
newErrors.openaiBaseUrl = "Invalid URL format";
|
|
3292
|
+
}
|
|
3293
|
+
if (!anthropicBaseUrl.trim() && !openaiBaseUrl.trim()) {
|
|
3294
|
+
newErrors.format = "At least one format URL (Anthropic or OpenAI) is required";
|
|
3183
3295
|
}
|
|
3184
3296
|
setErrors(newErrors);
|
|
3185
3297
|
return Object.keys(newErrors).length === 0;
|
|
@@ -3200,10 +3312,11 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3200
3312
|
onSubmit({
|
|
3201
3313
|
name: name.trim(),
|
|
3202
3314
|
apiKey: apiKey.trim(),
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
apiDocsUrl: apiDocsUrl.trim() || void 0
|
|
3315
|
+
models: models.map((m) => m.trim()).filter((m) => m !== ""),
|
|
3316
|
+
anthropicBaseUrl: anthropicBaseUrl.trim() || void 0,
|
|
3317
|
+
openaiBaseUrl: openaiBaseUrl.trim() || void 0,
|
|
3318
|
+
apiDocsUrl: apiDocsUrl.trim() || void 0,
|
|
3319
|
+
source
|
|
3207
3320
|
});
|
|
3208
3321
|
} finally {
|
|
3209
3322
|
setIsSubmitting(false);
|
|
@@ -3228,92 +3341,194 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3228
3341
|
),
|
|
3229
3342
|
errors.name !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.name })
|
|
3230
3343
|
] }),
|
|
3344
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3345
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-source", className: "text-sm font-medium", children: "Token Source" }),
|
|
3346
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3347
|
+
"select",
|
|
3348
|
+
{
|
|
3349
|
+
id: "provider-source",
|
|
3350
|
+
value: source ?? "",
|
|
3351
|
+
onChange: (e) => setSource(
|
|
3352
|
+
e.target.value === "company" || e.target.value === "personal" ? e.target.value : void 0
|
|
3353
|
+
),
|
|
3354
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
3355
|
+
children: [
|
|
3356
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "—" }),
|
|
3357
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "personal", children: "个人 (Personal)" }),
|
|
3358
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "company", children: "公司 (Company)" })
|
|
3359
|
+
]
|
|
3360
|
+
}
|
|
3361
|
+
),
|
|
3362
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Label whether this API key is company-provided or personal." })
|
|
3363
|
+
] }),
|
|
3231
3364
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3232
3365
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-apikey", className: "text-sm font-medium", children: [
|
|
3233
3366
|
"API Key ",
|
|
3234
3367
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
|
|
3235
3368
|
] }),
|
|
3236
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3369
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3370
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3371
|
+
"input",
|
|
3372
|
+
{
|
|
3373
|
+
id: "provider-apikey",
|
|
3374
|
+
type: "text",
|
|
3375
|
+
value: showApiKey || apiKey.length === 0 ? apiKey : maskApiKey(apiKey),
|
|
3376
|
+
onChange: (e) => setApiKey(e.target.value),
|
|
3377
|
+
onFocus: () => {
|
|
3378
|
+
if (!showApiKey && apiKey.length > 0) setShowApiKey(true);
|
|
3379
|
+
},
|
|
3380
|
+
placeholder: "sk-ant-...",
|
|
3381
|
+
readOnly: !showApiKey && apiKey.length > 0,
|
|
3382
|
+
className: "flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
3383
|
+
}
|
|
3384
|
+
),
|
|
3385
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3386
|
+
"button",
|
|
3387
|
+
{
|
|
3388
|
+
type: "button",
|
|
3389
|
+
onClick: () => setShowApiKey((s) => !s),
|
|
3390
|
+
className: "text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0",
|
|
3391
|
+
"aria-label": showApiKey ? "Hide API key" : "Show API key",
|
|
3392
|
+
children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-4" })
|
|
3393
|
+
}
|
|
3394
|
+
),
|
|
3395
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3396
|
+
"button",
|
|
3397
|
+
{
|
|
3398
|
+
type: "button",
|
|
3399
|
+
onClick: handleCopy,
|
|
3400
|
+
className: "text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0",
|
|
3401
|
+
"aria-label": "Copy API key",
|
|
3402
|
+
children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-4" })
|
|
3403
|
+
}
|
|
3404
|
+
)
|
|
3405
|
+
] }),
|
|
3247
3406
|
errors.apiKey !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.apiKey })
|
|
3248
3407
|
] }),
|
|
3249
3408
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3250
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", {
|
|
3251
|
-
"
|
|
3409
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-sm font-medium", children: [
|
|
3410
|
+
"Models ",
|
|
3252
3411
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
|
|
3253
3412
|
] }),
|
|
3254
|
-
isMiniMax || isAlibaba ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3413
|
+
(isMiniMax || isAlibaba) && /* @__PURE__ */ jsxRuntimeExports.jsx("datalist", { id: "model-suggestions", children: (isMiniMax ? MINIMAX_MODELS : ALIBABA_MODELS).map((opt) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: opt }, opt)) }),
|
|
3414
|
+
models.map((m, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3415
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3416
|
+
"input",
|
|
3417
|
+
{
|
|
3418
|
+
type: "text",
|
|
3419
|
+
value: m,
|
|
3420
|
+
onChange: (e) => {
|
|
3421
|
+
setModels((prev) => {
|
|
3422
|
+
const next = [...prev];
|
|
3423
|
+
next[i] = e.target.value;
|
|
3424
|
+
return next;
|
|
3425
|
+
});
|
|
3426
|
+
},
|
|
3427
|
+
placeholder: isMiniMax || isAlibaba ? "Type or select a model..." : "Model name",
|
|
3428
|
+
list: isMiniMax || isAlibaba ? "model-suggestions" : void 0,
|
|
3429
|
+
className: "flex-1 rounded-md border border-input bg-background px-4 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
3430
|
+
}
|
|
3431
|
+
),
|
|
3432
|
+
models.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3433
|
+
"button",
|
|
3434
|
+
{
|
|
3435
|
+
type: "button",
|
|
3436
|
+
onClick: () => setModels((prev) => prev.filter((_, idx) => idx !== i)),
|
|
3437
|
+
className: "text-muted-foreground hover:text-destructive transition-colors p-1 shrink-0",
|
|
3438
|
+
"aria-label": "Remove model",
|
|
3439
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3440
|
+
"svg",
|
|
3441
|
+
{
|
|
3442
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3443
|
+
width: "16",
|
|
3444
|
+
height: "16",
|
|
3445
|
+
viewBox: "0 0 24 24",
|
|
3446
|
+
fill: "none",
|
|
3447
|
+
stroke: "currentColor",
|
|
3448
|
+
strokeWidth: "2",
|
|
3449
|
+
strokeLinecap: "round",
|
|
3450
|
+
strokeLinejoin: "round",
|
|
3451
|
+
children: [
|
|
3452
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 6h18" }),
|
|
3453
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
3454
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })
|
|
3455
|
+
]
|
|
3456
|
+
}
|
|
3457
|
+
)
|
|
3458
|
+
}
|
|
3459
|
+
)
|
|
3460
|
+
] }, i)),
|
|
3461
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3462
|
+
"button",
|
|
3265
3463
|
{
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
placeholder: "",
|
|
3271
|
-
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
3464
|
+
type: "button",
|
|
3465
|
+
onClick: () => setModels((prev) => [...prev, ""]),
|
|
3466
|
+
className: "text-xs text-primary hover:underline flex items-center gap-1",
|
|
3467
|
+
children: "+ Add Model"
|
|
3272
3468
|
}
|
|
3273
3469
|
),
|
|
3274
|
-
errors.
|
|
3470
|
+
errors.models !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.models })
|
|
3275
3471
|
] }),
|
|
3276
3472
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3277
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
3278
|
-
|
|
3279
|
-
|
|
3473
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 border-b border-border", children: [
|
|
3474
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3475
|
+
"button",
|
|
3476
|
+
{
|
|
3477
|
+
type: "button",
|
|
3478
|
+
onClick: () => setActiveTab("anthropic"),
|
|
3479
|
+
className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "anthropic" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
3480
|
+
children: "Anthropic Format"
|
|
3481
|
+
}
|
|
3482
|
+
),
|
|
3483
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3484
|
+
"button",
|
|
3485
|
+
{
|
|
3486
|
+
type: "button",
|
|
3487
|
+
onClick: () => setActiveTab("openai"),
|
|
3488
|
+
className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "openai" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
3489
|
+
children: "OpenAI Format"
|
|
3490
|
+
}
|
|
3491
|
+
)
|
|
3280
3492
|
] }),
|
|
3281
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3282
|
-
|
|
3493
|
+
errors.format !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.format })
|
|
3494
|
+
] }),
|
|
3495
|
+
activeTab === "anthropic" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3496
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-anthropic-base-url", className: "text-sm font-medium", children: "Anthropic Base URL" }),
|
|
3497
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3498
|
+
"input",
|
|
3283
3499
|
{
|
|
3284
|
-
id: "provider-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3500
|
+
id: "provider-anthropic-base-url",
|
|
3501
|
+
type: "text",
|
|
3502
|
+
value: anthropicBaseUrl,
|
|
3503
|
+
onChange: (e) => {
|
|
3504
|
+
setManualAnthropicUrlOverride(true);
|
|
3505
|
+
setAnthropicBaseUrl(e.target.value);
|
|
3506
|
+
},
|
|
3507
|
+
placeholder: "https://api.anthropic.com",
|
|
3508
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
3292
3509
|
}
|
|
3293
3510
|
),
|
|
3294
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-
|
|
3511
|
+
errors.anthropicBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.anthropicBaseUrl }),
|
|
3512
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Anthropic-compatible endpoint URL. Leave empty if this provider does not support Anthropic format." })
|
|
3295
3513
|
] }),
|
|
3296
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3297
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3298
|
-
"Base URL ",
|
|
3299
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
|
|
3300
|
-
] }),
|
|
3514
|
+
activeTab === "openai" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3515
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-openai-base-url", className: "text-sm font-medium", children: "OpenAI Base URL" }),
|
|
3301
3516
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3302
3517
|
"input",
|
|
3303
3518
|
{
|
|
3304
|
-
id: "provider-base-url",
|
|
3519
|
+
id: "provider-openai-base-url",
|
|
3305
3520
|
type: "text",
|
|
3306
|
-
value:
|
|
3521
|
+
value: openaiBaseUrl,
|
|
3307
3522
|
onChange: (e) => {
|
|
3308
|
-
|
|
3309
|
-
|
|
3523
|
+
setManualOpenaiUrlOverride(true);
|
|
3524
|
+
setOpenaiBaseUrl(e.target.value);
|
|
3310
3525
|
},
|
|
3311
|
-
placeholder:
|
|
3526
|
+
placeholder: "https://api.openai.com/v1",
|
|
3312
3527
|
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
3313
3528
|
}
|
|
3314
3529
|
),
|
|
3315
|
-
errors.
|
|
3316
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "
|
|
3530
|
+
errors.openaiBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.openaiBaseUrl }),
|
|
3531
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "OpenAI-compatible endpoint URL. Leave empty if this provider does not support OpenAI format." })
|
|
3317
3532
|
] }),
|
|
3318
3533
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3319
3534
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-api-docs-url", className: "text-sm font-medium", children: "API Docs URL" }),
|
|
@@ -3330,89 +3545,12 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3330
3545
|
),
|
|
3331
3546
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Optional API documentation URL. If not set, uses known provider docs." })
|
|
3332
3547
|
] }),
|
|
3333
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
|
|
3548
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sticky bottom-0 bg-card border-t flex gap-2 justify-end pt-2 pb-2", children: [
|
|
3334
3549
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isSubmitting, children: "Cancel" }),
|
|
3335
3550
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? "Saving..." : provider ? "Update Provider" : "Add Provider" })
|
|
3336
3551
|
] })
|
|
3337
3552
|
] });
|
|
3338
3553
|
}
|
|
3339
|
-
const ProviderTestErrorTypeSchema = _enum([
|
|
3340
|
-
"timeout",
|
|
3341
|
-
"network_unreachable",
|
|
3342
|
-
"connection_refused",
|
|
3343
|
-
"auth_failed",
|
|
3344
|
-
"rate_limited",
|
|
3345
|
-
"server_error",
|
|
3346
|
-
"invalid_response",
|
|
3347
|
-
"unknown"
|
|
3348
|
-
]);
|
|
3349
|
-
const ProviderTestErrorSchema = object({
|
|
3350
|
-
message: string(),
|
|
3351
|
-
type: ProviderTestErrorTypeSchema,
|
|
3352
|
-
details: string().optional(),
|
|
3353
|
-
hint: string().optional()
|
|
3354
|
-
});
|
|
3355
|
-
const ProviderTestContentSchema = object({
|
|
3356
|
-
type: _enum(["text", "thinking"]),
|
|
3357
|
-
text: string().optional(),
|
|
3358
|
-
thinking: string().optional()
|
|
3359
|
-
});
|
|
3360
|
-
const ProviderTestResultSchema = object({
|
|
3361
|
-
success: boolean(),
|
|
3362
|
-
error: ProviderTestErrorSchema.optional(),
|
|
3363
|
-
model: string().optional(),
|
|
3364
|
-
inputTokens: number().optional(),
|
|
3365
|
-
outputTokens: number().optional(),
|
|
3366
|
-
cacheCreationInputTokens: number().optional(),
|
|
3367
|
-
cacheReadInputTokens: number().optional(),
|
|
3368
|
-
latencyMs: number().optional(),
|
|
3369
|
-
content: array(ProviderTestContentSchema).optional(),
|
|
3370
|
-
rawResponse: string().optional(),
|
|
3371
|
-
streaming: boolean().optional(),
|
|
3372
|
-
streamingChunks: object({
|
|
3373
|
-
chunks: array(StreamingChunkSchema$1),
|
|
3374
|
-
truncated: boolean().default(false)
|
|
3375
|
-
}).optional(),
|
|
3376
|
-
requestHeaders: record(string(), string()).optional()
|
|
3377
|
-
});
|
|
3378
|
-
const ProviderTestStateSchema = union([
|
|
3379
|
-
ProviderTestResultSchema,
|
|
3380
|
-
object({ notConfigured: literal(true) }),
|
|
3381
|
-
object({ testing: literal(true) })
|
|
3382
|
-
]);
|
|
3383
|
-
const ProviderFormatTestResultsSchema = object({
|
|
3384
|
-
nonStreaming: ProviderTestStateSchema,
|
|
3385
|
-
streaming: ProviderTestStateSchema
|
|
3386
|
-
});
|
|
3387
|
-
const ProviderTestResultsSchema = object({
|
|
3388
|
-
anthropic: ProviderFormatTestResultsSchema,
|
|
3389
|
-
openai: ProviderFormatTestResultsSchema
|
|
3390
|
-
});
|
|
3391
|
-
function createPendingProviderTestResults() {
|
|
3392
|
-
return {
|
|
3393
|
-
anthropic: {
|
|
3394
|
-
nonStreaming: { testing: true },
|
|
3395
|
-
streaming: { testing: true }
|
|
3396
|
-
},
|
|
3397
|
-
openai: {
|
|
3398
|
-
nonStreaming: { testing: true },
|
|
3399
|
-
streaming: { testing: true }
|
|
3400
|
-
}
|
|
3401
|
-
};
|
|
3402
|
-
}
|
|
3403
|
-
function createFailedProviderTestResults(message, type) {
|
|
3404
|
-
const createFormatResult = () => ({
|
|
3405
|
-
nonStreaming: {
|
|
3406
|
-
success: false,
|
|
3407
|
-
error: { message, type }
|
|
3408
|
-
},
|
|
3409
|
-
streaming: { notConfigured: true }
|
|
3410
|
-
});
|
|
3411
|
-
return {
|
|
3412
|
-
anthropic: createFormatResult(),
|
|
3413
|
-
openai: createFormatResult()
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
3554
|
const ConfigPathsResponseSchema = object({
|
|
3417
3555
|
providerConfig: string()
|
|
3418
3556
|
});
|
|
@@ -3428,10 +3566,11 @@ function createProviderPayload(data) {
|
|
|
3428
3566
|
return {
|
|
3429
3567
|
name: data.name,
|
|
3430
3568
|
apiKey: data.apiKey,
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3569
|
+
models: data.models,
|
|
3570
|
+
anthropicBaseUrl: (data.anthropicBaseUrl?.length ?? 0) > 0 ? data.anthropicBaseUrl : void 0,
|
|
3571
|
+
openaiBaseUrl: (data.openaiBaseUrl?.length ?? 0) > 0 ? data.openaiBaseUrl : void 0,
|
|
3572
|
+
apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : void 0,
|
|
3573
|
+
source: data.source
|
|
3435
3574
|
};
|
|
3436
3575
|
}
|
|
3437
3576
|
function ProvidersPanel({
|
|
@@ -3456,9 +3595,11 @@ function ProvidersPanel({
|
|
|
3456
3595
|
const [configPath, setConfigPath] = reactExports.useState(null);
|
|
3457
3596
|
const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
|
|
3458
3597
|
const [highlightedProviderId, setHighlightedProviderId] = reactExports.useState(null);
|
|
3598
|
+
const [sourceFilter, setSourceFilter] = reactExports.useState("all");
|
|
3459
3599
|
const listScrollRef = reactExports.useRef(null);
|
|
3460
3600
|
const highlightTimeoutRef = reactExports.useRef(null);
|
|
3461
3601
|
const providers = externalProviders ?? [];
|
|
3602
|
+
const filteredProviders = sourceFilter === "all" ? providers : providers.filter((p) => p.source === sourceFilter);
|
|
3462
3603
|
const testResults = externalTestResults ?? internalTestResults;
|
|
3463
3604
|
const testingProviders = externalTestingProviders ?? internalTestingProviders;
|
|
3464
3605
|
const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
|
|
@@ -3586,51 +3727,50 @@ function ProvidersPanel({
|
|
|
3586
3727
|
);
|
|
3587
3728
|
function handleAddProvider(data) {
|
|
3588
3729
|
void (async () => {
|
|
3589
|
-
let res;
|
|
3590
3730
|
try {
|
|
3591
|
-
res = await fetch("/api/providers", {
|
|
3731
|
+
const res = await fetch("/api/providers", {
|
|
3592
3732
|
method: "POST",
|
|
3593
3733
|
headers: { "Content-Type": "application/json" },
|
|
3594
3734
|
body: JSON.stringify(createProviderPayload(data))
|
|
3595
3735
|
});
|
|
3736
|
+
if (!res.ok) {
|
|
3737
|
+
setError(await readApiError(res, "Failed to add provider"));
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3741
|
+
setShowForm(false);
|
|
3742
|
+
triggerHighlight(newProvider.id);
|
|
3743
|
+
refreshProviders();
|
|
3744
|
+
await runTest(newProvider.id);
|
|
3596
3745
|
} catch {
|
|
3597
3746
|
setError(NETWORK_ERROR_MESSAGE);
|
|
3598
|
-
return;
|
|
3599
|
-
}
|
|
3600
|
-
if (!res.ok) {
|
|
3601
|
-
setError(await readApiError(res, "Failed to add provider"));
|
|
3602
|
-
return;
|
|
3603
3747
|
}
|
|
3604
|
-
const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3605
|
-
setShowForm(false);
|
|
3606
|
-
triggerHighlight(newProvider.id);
|
|
3607
|
-
refreshProviders();
|
|
3608
|
-
await runTest(newProvider.id);
|
|
3609
3748
|
})();
|
|
3610
3749
|
}
|
|
3611
3750
|
function handleUpdateProvider(data) {
|
|
3612
3751
|
if (!editingProvider) return;
|
|
3613
3752
|
void (async () => {
|
|
3614
|
-
let res;
|
|
3615
3753
|
try {
|
|
3616
|
-
res = await fetch(`/api/providers/${editingProvider.id}`, {
|
|
3754
|
+
const res = await fetch(`/api/providers/${editingProvider.id}`, {
|
|
3617
3755
|
method: "PUT",
|
|
3618
3756
|
headers: { "Content-Type": "application/json" },
|
|
3619
3757
|
body: JSON.stringify(createProviderPayload(data))
|
|
3620
3758
|
});
|
|
3759
|
+
if (!res.ok) {
|
|
3760
|
+
setError(await readApiError(res, "Failed to update provider"));
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
const updated = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3764
|
+
setEditingProvider(void 0);
|
|
3765
|
+
triggerHighlight(updated.id);
|
|
3766
|
+
refreshProviders();
|
|
3767
|
+
const criticalFieldsChanged = (data.apiKey ?? "") !== (editingProvider.apiKey ?? "") || JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) || (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") || (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "");
|
|
3768
|
+
if (criticalFieldsChanged) {
|
|
3769
|
+
await runTest(updated.id);
|
|
3770
|
+
}
|
|
3621
3771
|
} catch {
|
|
3622
3772
|
setError(NETWORK_ERROR_MESSAGE);
|
|
3623
|
-
return;
|
|
3624
|
-
}
|
|
3625
|
-
if (!res.ok) {
|
|
3626
|
-
setError(await readApiError(res, "Failed to update provider"));
|
|
3627
|
-
return;
|
|
3628
3773
|
}
|
|
3629
|
-
const updated = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3630
|
-
setEditingProvider(void 0);
|
|
3631
|
-
triggerHighlight(updated.id);
|
|
3632
|
-
refreshProviders();
|
|
3633
|
-
await runTest(updated.id);
|
|
3634
3774
|
})();
|
|
3635
3775
|
}
|
|
3636
3776
|
function handleDeleteProvider(providerId) {
|
|
@@ -3799,22 +3939,34 @@ function ProvidersPanel({
|
|
|
3799
3939
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4" }),
|
|
3800
3940
|
"Add Your First Provider"
|
|
3801
3941
|
] })
|
|
3802
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3942
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
|
|
3943
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 border-b border-border", children: ["all", "personal", "company"].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3944
|
+
"button",
|
|
3945
|
+
{
|
|
3946
|
+
type: "button",
|
|
3947
|
+
onClick: () => setSourceFilter(tab),
|
|
3948
|
+
className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${sourceFilter === tab ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
3949
|
+
children: tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"
|
|
3950
|
+
},
|
|
3951
|
+
tab
|
|
3952
|
+
)) }),
|
|
3953
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: listScrollRef, className: "space-y-3", children: filteredProviders.map((provider) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3954
|
+
ProviderCard,
|
|
3955
|
+
{
|
|
3956
|
+
provider,
|
|
3957
|
+
testResults: testResults[provider.id],
|
|
3958
|
+
isTesting: testingProviders.has(provider.id),
|
|
3959
|
+
testingTimeLeft: testingTimeLeft[provider.id],
|
|
3960
|
+
isHighlighted: provider.id === highlightedProviderId,
|
|
3961
|
+
onEdit: (p) => setEditingProvider(p),
|
|
3962
|
+
onDelete: handleDeleteProvider,
|
|
3963
|
+
onTest: (id) => {
|
|
3964
|
+
void runTest(id);
|
|
3965
|
+
}
|
|
3966
|
+
},
|
|
3967
|
+
provider.id
|
|
3968
|
+
)) })
|
|
3969
|
+
] })
|
|
3818
3970
|
] });
|
|
3819
3971
|
}
|
|
3820
3972
|
async function fetcher(url) {
|