import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { toast } from "sonner"; import { FormLabel } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ChevronDown, ChevronRight, Loader2 } from "lucide-react"; import EndpointSpeedTest from "./EndpointSpeedTest"; import { ApiKeySection, EndpointField } from "./shared"; import { CopilotAuthSection } from "./CopilotAuthSection"; import { copilotGetModels, copilotGetModelsForAccount, } from "@/lib/api/copilot"; import type { CopilotModel } from "@/lib/api/copilot"; import type { ProviderCategory, ClaudeApiFormat, ClaudeApiKeyField, } from "@/types"; import type { TemplateValueConfig } from "@/config/claudeProviderPresets"; interface EndpointCandidate { url: string; } interface ClaudeFormFieldsProps { providerId?: string; // API Key shouldShowApiKey: boolean; apiKey: string; onApiKeyChange: (key: string) => void; category?: ProviderCategory; shouldShowApiKeyLink: boolean; websiteUrl: string; isPartner?: boolean; partnerPromotionKey?: string; // GitHub Copilot OAuth isCopilotPreset?: boolean; usesOAuth?: boolean; isCopilotAuthenticated?: boolean; /** 当前选中的 GitHub 账号 ID(多账号支持) */ selectedGitHubAccountId?: string | null; /** GitHub 账号选择回调(多账号支持) */ onGitHubAccountSelect?: (accountId: string | null) => void; // Template Values templateValueEntries: Array<[string, TemplateValueConfig]>; templateValues: Record; templatePresetName: string; onTemplateValueChange: (key: string, value: string) => void; // Base URL shouldShowSpeedTest: boolean; baseUrl: string; onBaseUrlChange: (url: string) => void; isEndpointModalOpen: boolean; onEndpointModalToggle: (open: boolean) => void; onCustomEndpointsChange?: (endpoints: string[]) => void; autoSelect: boolean; onAutoSelectChange: (checked: boolean) => void; // Model Selector shouldShowModelSelector: boolean; claudeModel: string; reasoningModel: string; defaultHaikuModel: string; defaultSonnetModel: string; defaultOpusModel: string; onModelChange: ( field: | "ANTHROPIC_MODEL" | "ANTHROPIC_REASONING_MODEL" | "ANTHROPIC_DEFAULT_HAIKU_MODEL" | "ANTHROPIC_DEFAULT_SONNET_MODEL" | "ANTHROPIC_DEFAULT_OPUS_MODEL", value: string, ) => void; // Speed Test Endpoints speedTestEndpoints: EndpointCandidate[]; // 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; // Full URL mode isFullUrl: boolean; onFullUrlChange: (value: boolean) => void; } export function ClaudeFormFields({ providerId, shouldShowApiKey, apiKey, onApiKeyChange, category, shouldShowApiKeyLink, websiteUrl, isPartner, partnerPromotionKey, isCopilotPreset, usesOAuth, isCopilotAuthenticated, selectedGitHubAccountId, onGitHubAccountSelect, templateValueEntries, templateValues, templatePresetName, onTemplateValueChange, shouldShowSpeedTest, baseUrl, onBaseUrlChange, isEndpointModalOpen, onEndpointModalToggle, onCustomEndpointsChange, autoSelect, onAutoSelectChange, shouldShowModelSelector, claudeModel, reasoningModel, defaultHaikuModel, defaultSonnetModel, defaultOpusModel, onModelChange, speedTestEndpoints, apiFormat, onApiFormatChange, apiKeyField, onApiKeyFieldChange, isFullUrl, onFullUrlChange, }: ClaudeFormFieldsProps) { const { t } = useTranslation(); const hasAnyAdvancedValue = !!( claudeModel || reasoningModel || defaultHaikuModel || defaultSonnetModel || defaultOpusModel || apiFormat !== "anthropic" || apiKeyField !== "ANTHROPIC_AUTH_TOKEN" ); const [advancedExpanded, setAdvancedExpanded] = useState(hasAnyAdvancedValue); // 预设填充高级值后自动展开(仅从折叠→展开,不会自动折叠) useEffect(() => { if (hasAnyAdvancedValue) { setAdvancedExpanded(true); } }, [hasAnyAdvancedValue]); // Copilot 可用模型列表 const [copilotModels, setCopilotModels] = useState([]); const [modelsLoading, setModelsLoading] = useState(false); // 当 Copilot 预设且已认证时,加载可用模型 useEffect(() => { // 如果不是 Copilot 预设或未认证,清空模型列表 if (!isCopilotPreset || !isCopilotAuthenticated) { setCopilotModels([]); setModelsLoading(false); return; } let cancelled = false; setModelsLoading(true); const fetchModels = selectedGitHubAccountId ? copilotGetModelsForAccount(selectedGitHubAccountId) : copilotGetModels(); fetchModels .then((models) => { if (!cancelled) setCopilotModels(models); }) .catch((err) => { console.warn("[Copilot] Failed to fetch models:", err); if (!cancelled) { toast.error( t("copilot.loadModelsFailed", { defaultValue: "加载 Copilot 模型列表失败", }), ); } }) .finally(() => { if (!cancelled) setModelsLoading(false); }); return () => { cancelled = true; }; }, [isCopilotPreset, isCopilotAuthenticated, selectedGitHubAccountId]); // 模型输入框:支持手动输入 + 下拉选择 const renderModelInput = ( id: string, value: string, field: ClaudeFormFieldsProps["onModelChange"] extends ( f: infer F, v: string, ) => void ? F : never, placeholder?: string, ) => { if (isCopilotPreset && copilotModels.length > 0) { // 按 vendor 分组 const grouped: Record = {}; for (const model of copilotModels) { const vendor = model.vendor || "Other"; if (!grouped[vendor]) grouped[vendor] = []; grouped[vendor].push(model); } const vendors = Object.keys(grouped).sort(); return (
onModelChange(field, e.target.value)} placeholder={placeholder} autoComplete="off" className="flex-1" /> {vendors.map((vendor, vi) => (
{vi > 0 && } {vendor} {grouped[vendor].map((model) => ( onModelChange(field, model.id)} > {model.id} ))}
))}
); } if (isCopilotPreset && modelsLoading) { return (
onModelChange(field, e.target.value)} placeholder={placeholder} autoComplete="off" className="flex-1" />
); } return ( onModelChange(field, e.target.value)} placeholder={placeholder} autoComplete="off" /> ); }; return ( <> {/* GitHub Copilot OAuth 认证 */} {isCopilotPreset && ( )} {/* API Key 输入框(非 OAuth 预设时显示) */} {shouldShowApiKey && !usesOAuth && ( )} {/* 模板变量输入 */} {templateValueEntries.length > 0 && (
{t("providerForm.parameterConfig", { name: templatePresetName, defaultValue: `${templatePresetName} 参数配置`, })}
{templateValueEntries.map(([key, config]) => (
{config.label} onTemplateValueChange(key, e.target.value)} placeholder={config.placeholder || config.label} autoComplete="off" />
))}
)} {/* Base URL 输入框 */} {shouldShowSpeedTest && ( onEndpointModalToggle(true)} showFullUrlToggle={true} isFullUrl={isFullUrl} onFullUrlChange={onFullUrlChange} /> )} {/* 端点测速弹窗 */} {shouldShowSpeedTest && isEndpointModalOpen && ( onEndpointModalToggle(false)} autoSelect={autoSelect} onAutoSelectChange={onAutoSelectChange} onCustomEndpointsChange={onCustomEndpointsChange} /> )} {/* 高级选项(API 格式 + 认证字段 + 模型映射) */} {shouldShowModelSelector && ( {!advancedExpanded && (

{t("providerForm.advancedOptionsHint")}

)} {/* API 格式选择(仅非云服务商显示) */} {category !== "cloud_provider" && (
{t("providerForm.apiFormat", { defaultValue: "API 格式" })}

{t("providerForm.apiFormatHint", { defaultValue: "选择供应商 API 的输入格式", })}

)} {/* 认证字段选择器 */}
{t("providerForm.authField", { defaultValue: "认证字段" })}

{t("providerForm.authFieldHint", { defaultValue: "选择写入配置的认证环境变量名", })}

{/* 模型映射 */}
{t("providerForm.modelMappingLabel")}

{t("providerForm.modelMappingHint")}

{/* 主模型 */}
{t("providerForm.anthropicModel", { defaultValue: "主模型", })} {renderModelInput( "claudeModel", claudeModel, "ANTHROPIC_MODEL", t("providerForm.modelPlaceholder", { defaultValue: "" }), )}
{/* 推理模型 */}
{t("providerForm.anthropicReasoningModel")} {renderModelInput( "reasoningModel", reasoningModel, "ANTHROPIC_REASONING_MODEL", )}
{/* 默认 Haiku */}
{t("providerForm.anthropicDefaultHaikuModel", { defaultValue: "Haiku 默认模型", })} {renderModelInput( "claudeDefaultHaikuModel", defaultHaikuModel, "ANTHROPIC_DEFAULT_HAIKU_MODEL", t("providerForm.haikuModelPlaceholder", { defaultValue: "" }), )}
{/* 默认 Sonnet */}
{t("providerForm.anthropicDefaultSonnetModel", { defaultValue: "Sonnet 默认模型", })} {renderModelInput( "claudeDefaultSonnetModel", defaultSonnetModel, "ANTHROPIC_DEFAULT_SONNET_MODEL", t("providerForm.modelPlaceholder", { defaultValue: "" }), )}
{/* 默认 Opus */}
{t("providerForm.anthropicDefaultOpusModel", { defaultValue: "Opus 默认模型", })} {renderModelInput( "claudeDefaultOpusModel", defaultOpusModel, "ANTHROPIC_DEFAULT_OPUS_MODEL", t("providerForm.modelPlaceholder", { defaultValue: "" }), )}
)} ); }