feat: restore Claude provider auth field selector (AUTH_TOKEN / API_KEY)

This commit is contained in:
Jason
2026-03-09 08:56:58 +08:00
parent 55509286eb
commit dd971246be
9 changed files with 133 additions and 7 deletions
@@ -10,7 +10,11 @@ import {
} from "@/components/ui/select";
import EndpointSpeedTest from "./EndpointSpeedTest";
import { ApiKeySection, EndpointField } from "./shared";
import type { ProviderCategory, ClaudeApiFormat } from "@/types";
import type {
ProviderCategory,
ClaudeApiFormat,
ClaudeApiKeyField,
} from "@/types";
import type { TemplateValueConfig } from "@/config/claudeProviderPresets";
interface EndpointCandidate {
@@ -68,6 +72,10 @@ interface ClaudeFormFieldsProps {
// API Format (for third-party providers that use OpenAI Chat Completions format)
apiFormat: ClaudeApiFormat;
onApiFormatChange: (format: ClaudeApiFormat) => void;
// Auth Field (ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY)
apiKeyField: ClaudeApiKeyField;
onApiKeyFieldChange: (field: ClaudeApiKeyField) => void;
}
export function ClaudeFormFields({
@@ -102,6 +110,8 @@ export function ClaudeFormFields({
speedTestEndpoints,
apiFormat,
onApiFormatChange,
apiKeyField,
onApiKeyFieldChange,
}: ClaudeFormFieldsProps) {
const { t } = useTranslation();
@@ -226,6 +236,40 @@ export function ClaudeFormFields({
</div>
)}
{/* 认证字段选择器 */}
{shouldShowModelSelector && (
<div className="space-y-2">
<FormLabel>
{t("providerForm.authField", { defaultValue: "认证字段" })}
</FormLabel>
<Select
value={apiKeyField}
onValueChange={(v) => onApiKeyFieldChange(v as ClaudeApiKeyField)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ANTHROPIC_AUTH_TOKEN">
{t("providerForm.authFieldAuthToken", {
defaultValue: "ANTHROPIC_AUTH_TOKEN(默认)",
})}
</SelectItem>
<SelectItem value="ANTHROPIC_API_KEY">
{t("providerForm.authFieldApiKey", {
defaultValue: "ANTHROPIC_API_KEY",
})}
</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("providerForm.authFieldHint", {
defaultValue: "选择写入配置的认证环境变量名",
})}
</p>
</div>
)}
{/* 模型选择器 */}
{shouldShowModelSelector && (
<div className="space-y-3">
@@ -14,6 +14,7 @@ import type {
ProviderTestConfig,
ProviderProxyConfig,
ClaudeApiFormat,
ClaudeApiKeyField,
} from "@/types";
import {
providerPresets,
@@ -244,6 +245,18 @@ export function ProviderForm({
[form],
);
const [localApiKeyField, setLocalApiKeyField] = useState<ClaudeApiKeyField>(
() => {
if (appId !== "claude") return "ANTHROPIC_AUTH_TOKEN";
if (initialData?.meta?.apiKeyField) return initialData.meta.apiKeyField;
// Infer from existing config env
const env = (initialData?.settingsConfig as Record<string, unknown>)
?.env as Record<string, unknown> | undefined;
if (env?.ANTHROPIC_API_KEY !== undefined) return "ANTHROPIC_API_KEY";
return "ANTHROPIC_AUTH_TOKEN";
},
);
const {
apiKey,
handleApiKeyChange,
@@ -254,6 +267,7 @@ export function ProviderForm({
selectedPresetId,
category,
appType: appId,
apiKeyField: appId === "claude" ? localApiKeyField : undefined,
});
const { baseUrl, handleClaudeBaseUrlChange } = useBaseUrlState({
@@ -286,6 +300,30 @@ export function ProviderForm({
setLocalApiFormat(format);
}, []);
const handleApiKeyFieldChange = useCallback(
(field: ClaudeApiKeyField) => {
const prev = localApiKeyField;
setLocalApiKeyField(field);
// Swap the env key name in settingsConfig
try {
const raw = form.getValues("settingsConfig");
const config = JSON.parse(raw || "{}");
if (config?.env && prev in config.env) {
const value = config.env[prev];
delete config.env[prev];
config.env[field] = value;
const updated = JSON.stringify(config, null, 2);
form.setValue("settingsConfig", updated);
handleSettingsConfigChange(updated);
}
} catch {
// ignore parse errors during editing
}
},
[localApiKeyField, form, handleSettingsConfigChange],
);
const {
codexAuth,
codexConfig,
@@ -844,6 +882,12 @@ export function ProviderForm({
appId === "claude" && category !== "official"
? localApiFormat
: undefined,
apiKeyField:
appId === "claude" &&
category !== "official" &&
localApiKeyField !== "ANTHROPIC_AUTH_TOKEN"
? localApiKeyField
: undefined,
};
onSubmit(payload);
@@ -1082,6 +1126,8 @@ export function ProviderForm({
setLocalApiFormat("anthropic");
}
setLocalApiKeyField(preset.apiKeyField ?? "ANTHROPIC_AUTH_TOKEN");
form.reset({
name: preset.name,
websiteUrl: preset.websiteUrl ?? "",
@@ -1287,6 +1333,8 @@ export function ProviderForm({
speedTestEndpoints={speedTestEndpoints}
apiFormat={localApiFormat}
onApiFormatChange={handleApiFormatChange}
apiKeyField={localApiKeyField}
onApiKeyFieldChange={handleApiKeyFieldChange}
/>
)}
@@ -12,6 +12,7 @@ interface UseApiKeyStateProps {
selectedPresetId: string | null;
category?: ProviderCategory;
appType?: string;
apiKeyField?: string;
}
/**
@@ -24,6 +25,7 @@ export function useApiKeyState({
selectedPresetId,
category,
appType,
apiKeyField,
}: UseApiKeyStateProps) {
const [apiKey, setApiKey] = useState(() => {
if (initialConfig) {
@@ -58,7 +60,7 @@ export function useApiKeyState({
initialConfig || "{}",
key.trim(),
{
// 最佳实践:仅在新增模式”且“非官方类别时补齐缺失字段
// 最佳实践:仅在"新增模式"且"非官方类别"时补齐缺失字段
// - 新增模式:selectedPresetId !== null
// - 非官方类别:category !== undefined && category !== "official"
// - 官方类别:不创建字段(UI 也会禁用输入框)
@@ -68,12 +70,20 @@ export function useApiKeyState({
category !== undefined &&
category !== "official",
appType,
apiKeyField,
},
);
onConfigChange(configString);
},
[initialConfig, selectedPresetId, category, appType, onConfigChange],
[
initialConfig,
selectedPresetId,
category,
appType,
apiKeyField,
onConfigChange,
],
);
const showApiKey = useCallback(