diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 4b336b2..9c94882 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -12,7 +12,7 @@ import { validateJsonConfig, } from "../utils/providerConfigUtils"; import { providerPresets } from "../config/providerPresets"; -import { codexProviderPresets } from "../config/codexProviderPresets"; +import { codexProviderPresets, generateThirdPartyAuth, generateThirdPartyConfig } from "../config/codexProviderPresets"; import PresetSelector from "./ProviderForm/PresetSelector"; import ApiKeyInput from "./ProviderForm/ApiKeyInput"; import ClaudeConfigEditor from "./ProviderForm/ClaudeConfigEditor"; @@ -72,6 +72,7 @@ const ProviderForm: React.FC = ({ const [codexAuth, setCodexAuthState] = useState(""); const [codexConfig, setCodexConfigState] = useState(""); const [codexApiKey, setCodexApiKey] = useState(""); + const [isCodexTemplateModalOpen, setIsCodexTemplateModalOpen] = useState(false); // -1 表示自定义,null 表示未选择,>= 0 表示预设索引 const [selectedCodexPreset, setSelectedCodexPreset] = useState( showPresets && isCodex ? -1 : null, @@ -628,14 +629,23 @@ const ProviderForm: React.FC = ({ // Codex: 处理点击自定义按钮 const handleCodexCustomClick = () => { setSelectedCodexPreset(-1); + + // 设置自定义模板 + const customAuth = generateThirdPartyAuth(""); + const customConfig = generateThirdPartyConfig( + "custom", + "https://your-api-endpoint.com/v1", + "gpt-5-codex" + ); + setFormData({ name: "", websiteUrl: "", settingsConfig: "", }); setSettingsConfigError(validateSettingsConfig("")); - setCodexAuth(""); - setCodexConfig(""); + setCodexAuth(JSON.stringify(customAuth, null, 2)); + setCodexConfig(customConfig); setCodexApiKey(""); setCategory("custom"); }; @@ -1027,6 +1037,18 @@ const ProviderForm: React.FC = ({ applyCodexPreset(codexProviderPresets[index], index) } onCustomClick={handleCodexCustomClick} + renderCustomDescription={() => ( + <> + 手动配置供应商,需要填写完整的配置信息,或者 + + + )} /> )} @@ -1196,6 +1218,15 @@ const ProviderForm: React.FC = ({ } commonConfigError={codexCommonConfigError} authError={codexAuthError} + isCustomMode={selectedCodexPreset === -1} + onWebsiteUrlChange={(url) => { + setFormData({ + ...formData, + websiteUrl: url + }); + }} + isTemplateModalOpen={isCodexTemplateModalOpen} + setIsTemplateModalOpen={setIsCodexTemplateModalOpen} /> ) : ( <> diff --git a/src/components/ProviderForm/CodexConfigEditor.tsx b/src/components/ProviderForm/CodexConfigEditor.tsx index 415a7d5..f354b19 100644 --- a/src/components/ProviderForm/CodexConfigEditor.tsx +++ b/src/components/ProviderForm/CodexConfigEditor.tsx @@ -1,36 +1,102 @@ import React, { useState, useEffect } from "react"; + import { X, Save } from "lucide-react"; + import { isLinux } from "../../lib/platform"; +import { + generateThirdPartyAuth, + generateThirdPartyConfig, +} from "../../config/codexProviderPresets"; + interface CodexConfigEditorProps { authValue: string; + configValue: string; + onAuthChange: (value: string) => void; + onConfigChange: (value: string) => void; + onAuthBlur?: () => void; + useCommonConfig: boolean; + onCommonConfigToggle: (checked: boolean) => void; + commonConfigSnippet: string; + onCommonConfigSnippetChange: (value: string) => void; + commonConfigError: string; + authError: string; + + isCustomMode?: boolean; // 新增:是否为自定义模式 + + onWebsiteUrlChange?: (url: string) => void; // 新增:更新网址回调 + + isTemplateModalOpen?: boolean; // 新增:模态框状态 + + setIsTemplateModalOpen?: (open: boolean) => void; // 新增:设置模态框状态 } const CodexConfigEditor: React.FC = ({ authValue, + configValue, + onAuthChange, + onConfigChange, + onAuthBlur, + useCommonConfig, + onCommonConfigToggle, + commonConfigSnippet, + onCommonConfigSnippetChange, + commonConfigError, + authError, + + onWebsiteUrlChange, + + isTemplateModalOpen: externalTemplateModalOpen, + + setIsTemplateModalOpen: externalSetTemplateModalOpen, }) => { const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); + // 使用内部状态或外部状态 + + const [internalTemplateModalOpen, setInternalTemplateModalOpen] = + useState(false); + + const isTemplateModalOpen = + externalTemplateModalOpen ?? internalTemplateModalOpen; + + const setIsTemplateModalOpen = + externalSetTemplateModalOpen ?? setInternalTemplateModalOpen; + + const [templateApiKey, setTemplateApiKey] = useState(""); + + const [templateProviderName, setTemplateProviderName] = useState(""); + + const [templateBaseUrl, setTemplateBaseUrl] = useState(""); + + const [templateWebsiteUrl, setTemplateWebsiteUrl] = useState(""); + + const [templateModelName, setTemplateModelName] = useState("gpt-5-codex"); + + const [templateError, setTemplateError] = useState(""); + + // 移除自动填充逻辑,因为现在在点击自定义按钮时就已经填充 + useEffect(() => { if (commonConfigError && !isCommonConfigModalOpen) { setIsCommonConfigModalOpen(true); @@ -38,16 +104,20 @@ const CodexConfigEditor: React.FC = ({ }, [commonConfigError, isCommonConfigModalOpen]); // 支持按下 ESC 关闭弹窗 + useEffect(() => { if (!isCommonConfigModalOpen) return; const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { e.preventDefault(); + closeModal(); } }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); }, [isCommonConfigModalOpen]); @@ -55,6 +125,68 @@ const CodexConfigEditor: React.FC = ({ setIsCommonConfigModalOpen(false); }; + const applyTemplate = () => { + const trimmedKey = templateApiKey.trim(); + + const trimmedBaseUrl = templateBaseUrl.trim(); + + const trimmedModel = templateModelName.trim(); + + if (!trimmedKey || !trimmedBaseUrl || !trimmedModel) { + setTemplateError("请填写 API 密钥、API 基础地址和模型名称"); + + return; + } + + const auth = generateThirdPartyAuth(trimmedKey); + + const config = generateThirdPartyConfig( + templateProviderName || "custom", + + trimmedBaseUrl, + + trimmedModel + ); + + onAuthChange(JSON.stringify(auth, null, 2)); + + onConfigChange(config); + + if (onWebsiteUrlChange) { + const trimmedWebsite = templateWebsiteUrl.trim(); + + if (trimmedWebsite) { + onWebsiteUrlChange(trimmedWebsite); + } + } + + setTemplateApiKey(""); + + setTemplateProviderName(""); + + setTemplateBaseUrl(""); + + setTemplateWebsiteUrl(""); + + setTemplateModelName("gpt-5-codex"); + + setTemplateError(""); + + setIsTemplateModalOpen(false); + }; + + const handleTemplateInputKeyDown = ( + e: React.KeyboardEvent + ) => { + if (e.key === "Enter") { + e.preventDefault(); + + e.stopPropagation(); + + applyTemplate(); + } + }; + const handleAuthChange = (value: string) => { onAuthChange(value); }; @@ -76,13 +208,20 @@ const CodexConfigEditor: React.FC = ({ > auth.json (JSON) * +