diff --git a/src/App.tsx b/src/App.tsx index a133d20..4590a07 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,7 @@ function App() { path: string; } | null>(null); const [editingProviderId, setEditingProviderId] = useState( - null + null, ); const [notification, setNotification] = useState<{ message: string; @@ -37,7 +37,7 @@ function App() { const showNotification = ( message: string, type: "success" | "error", - duration = 3000 + duration = 3000, ) => { // 清除之前的定时器 if (timeoutRef.current) { @@ -182,7 +182,7 @@ function App() { showNotification( `切换成功!请重启 ${appName} 终端以生效`, "success", - 2000 + 2000, ); // 更新托盘菜单 await window.api.updateTrayMenu(); diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 83dfeac..44eca8f 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -10,8 +10,12 @@ import { } from "../utils/providerConfigUtils"; import { providerPresets } from "../config/providerPresets"; import { codexProviderPresets } from "../config/codexProviderPresets"; -import JsonEditor from "./JsonEditor"; -import { X, AlertCircle, Save, Zap } from "lucide-react"; +import PresetSelector from "./ProviderForm/PresetSelector"; +import ApiKeyInput from "./ProviderForm/ApiKeyInput"; +import ClaudeConfigEditor from "./ProviderForm/ClaudeConfigEditor"; +import CodexConfigEditor from "./ProviderForm/CodexConfigEditor"; +import KimiModelSelector from "./ProviderForm/KimiModelSelector"; +import { X, AlertCircle, Save } from "lucide-react"; interface ProviderFormProps { appType?: AppType; @@ -49,7 +53,7 @@ const ProviderForm: React.FC = ({ const [codexApiKey, setCodexApiKey] = useState(""); // -1 表示自定义,null 表示未选择,>= 0 表示预设索引 const [selectedCodexPreset, setSelectedCodexPreset] = useState( - showPresets && isCodex ? -1 : null + showPresets && isCodex ? -1 : null, ); // 初始化 Codex 配置 @@ -70,20 +74,42 @@ const ProviderForm: React.FC = ({ } } }, [isCodex, initialData]); + const [error, setError] = useState(""); const [disableCoAuthored, setDisableCoAuthored] = useState(false); // -1 表示自定义,null 表示未选择,>= 0 表示预设索引 const [selectedPreset, setSelectedPreset] = useState( - showPresets ? -1 : null + showPresets ? -1 : null, ); const [apiKey, setApiKey] = useState(""); + // Kimi 模型选择状态 + const [kimiAnthropicModel, setKimiAnthropicModel] = useState(""); + const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] = + useState(""); + // 初始化时检查禁用签名状态 useEffect(() => { if (initialData) { const configString = JSON.stringify(initialData.settingsConfig, null, 2); const hasCoAuthoredDisabled = checkCoAuthoredSetting(configString); setDisableCoAuthored(hasCoAuthoredDisabled); + + // 初始化 Kimi 模型选择(编辑模式) + if ( + initialData.settingsConfig && + typeof initialData.settingsConfig === "object" + ) { + const config = initialData.settingsConfig as { + env?: Record; + }; + if (config.env) { + setKimiAnthropicModel(config.env.ANTHROPIC_MODEL || ""); + setKimiAnthropicSmallFastModel( + config.env.ANTHROPIC_SMALL_FAST_MODEL || "", + ); + } + } } }, [initialData]); @@ -155,7 +181,7 @@ const ProviderForm: React.FC = ({ }; const handleChange = ( - e: React.ChangeEvent + e: React.ChangeEvent, ) => { const { name, value } = e.target; @@ -188,7 +214,7 @@ const ProviderForm: React.FC = ({ // 更新JSON配置 const updatedConfig = updateCoAuthoredSetting( formData.settingsConfig, - checked + checked, ); setFormData({ ...formData, @@ -214,6 +240,24 @@ const ProviderForm: React.FC = ({ // 同步选择框状态 const hasCoAuthoredDisabled = checkCoAuthoredSetting(configString); setDisableCoAuthored(hasCoAuthoredDisabled); + + // 如果是 Kimi 预设,初始化模型选择 + if ( + preset.name?.includes("Kimi") && + preset.settingsConfig && + typeof preset.settingsConfig === "object" + ) { + const config = preset.settingsConfig as { env?: Record }; + if (config.env) { + setKimiAnthropicModel(config.env.ANTHROPIC_MODEL || ""); + setKimiAnthropicSmallFastModel( + config.env.ANTHROPIC_SMALL_FAST_MODEL || "", + ); + } + } else { + setKimiAnthropicModel(""); + setKimiAnthropicSmallFastModel(""); + } }; // 处理点击自定义按钮 @@ -226,22 +270,24 @@ const ProviderForm: React.FC = ({ }); setApiKey(""); setDisableCoAuthored(false); + setKimiAnthropicModel(""); + setKimiAnthropicSmallFastModel(""); }; // Codex: 应用预设 const applyCodexPreset = ( preset: (typeof codexProviderPresets)[0], - index: number + index: number, ) => { const authString = JSON.stringify(preset.auth || {}, null, 2); setCodexAuth(authString); setCodexConfig(preset.config || ""); - setFormData({ + setFormData((prev) => ({ + ...prev, name: preset.name, websiteUrl: preset.websiteUrl, - settingsConfig: formData.settingsConfig, - }); + })); setSelectedCodexPreset(index); @@ -269,7 +315,7 @@ const ProviderForm: React.FC = ({ const configString = setApiKeyInConfig( formData.settingsConfig, key.trim(), - { createIfMissing: selectedPreset !== null && selectedPreset !== -1 } + { createIfMissing: selectedPreset !== null && selectedPreset !== -1 }, ); // 更新表单配置 @@ -307,6 +353,23 @@ const ProviderForm: React.FC = ({ selectedPreset >= 0 && providerPresets[selectedPreset]?.isOfficial === true; + // 判断当前选中的预设是否是 Kimi + const isKimiPreset = + selectedPreset !== null && + selectedPreset >= 0 && + providerPresets[selectedPreset]?.name?.includes("Kimi"); + + // 判断当前编辑的是否是 Kimi 提供商(通过名称或配置判断) + const isEditingKimi = + initialData && + (formData.name.includes("Kimi") || + formData.name.includes("kimi") || + (formData.settingsConfig.includes("api.moonshot.cn") && + formData.settingsConfig.includes("ANTHROPIC_MODEL"))); + + // 综合判断是否应该显示 Kimi 模型选择器 + const shouldShowKimiSelector = isKimiPreset || isEditingKimi; + // Codex: 控制显示 API Key 与官方标记 const getCodexAuthApiKey = (authString: string): string => { try { @@ -316,20 +379,49 @@ const ProviderForm: React.FC = ({ return ""; } }; + // 自定义模式(-1)不显示独立的 API Key 输入框 const showCodexApiKey = (selectedCodexPreset !== null && selectedCodexPreset !== -1) || (!showPresets && getCodexAuthApiKey(codexAuth) !== ""); + const isCodexOfficialPreset = selectedCodexPreset !== null && selectedCodexPreset >= 0 && codexProviderPresets[selectedCodexPreset]?.isOfficial === true; + // Kimi 模型选择处理函数 + const handleKimiModelChange = ( + field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL", + value: string, + ) => { + if (field === "ANTHROPIC_MODEL") { + setKimiAnthropicModel(value); + } else { + setKimiAnthropicSmallFastModel(value); + } + + // 更新配置 JSON + try { + const currentConfig = JSON.parse(formData.settingsConfig || "{}"); + if (!currentConfig.env) currentConfig.env = {}; + currentConfig.env[field] = value; + + const updatedConfigString = JSON.stringify(currentConfig, null, 2); + setFormData((prev) => ({ + ...prev, + settingsConfig: updatedConfigString, + })); + } catch (err) { + console.error("更新 Kimi 模型配置失败:", err); + } + }; + // 初始时从配置中同步 API Key(编辑模式) useEffect(() => { if (initialData) { const parsedKey = getApiKeyFromConfig( - JSON.stringify(initialData.settingsConfig) + JSON.stringify(initialData.settingsConfig), ); if (parsedKey) setApiKey(parsedKey); } @@ -390,107 +482,25 @@ const ProviderForm: React.FC = ({ )} {showPresets && !isCodex && ( -
-
- -
- - {providerPresets.map((preset, index) => ( - - ))} -
-
- {selectedPreset === -1 && ( -

- 手动配置供应商,需要填写完整的配置信息 -

- )} - {selectedPreset !== -1 && selectedPreset !== null && ( -

- {isOfficialPreset - ? "Claude 官方登录,不需要填写 API Key" - : "使用预设配置,只需填写 API Key"} -

- )} -
+ + applyPreset(providerPresets[index], index) + } + onCustomClick={handleCustomClick} + /> )} {showPresets && isCodex && ( -
-
- -
- - {codexProviderPresets.map((preset, index) => ( - - ))} -
-
- {selectedCodexPreset === -1 && ( -

- 手动配置供应商,需要填写完整的配置信息 -

- )} - {selectedCodexPreset !== -1 && selectedCodexPreset !== null && ( -

- {isCodexOfficialPreset - ? "Codex 官方登录,不需要填写 API Key" - : "使用预设配置,只需填写 API Key"} -

- )} -
+ + applyCodexPreset(codexProviderPresets[index], index) + } + onCustomClick={handleCodexCustomClick} + /> )}
@@ -514,66 +524,48 @@ const ProviderForm: React.FC = ({
{!isCodex && showApiKey && ( -
- - handleApiKeyChange(e.target.value)} - placeholder={ - isOfficialPreset - ? "官方登录无需填写 API Key,直接保存即可" + -
+ } + disabled={isOfficialPreset} + /> + )} + + {!isCodex && shouldShowKimiSelector && apiKey.trim() && ( + )} {isCodex && showCodexApiKey && ( -
- - handleCodexApiKeyChange(e.target.value)} - placeholder={ - isCodexOfficialPreset - ? "官方无需填写 API Key,直接保存即可" - : "只需要填这里,下方 auth.json 会自动填充" - } - disabled={isCodexOfficialPreset} - required={ - selectedCodexPreset !== null && - selectedCodexPreset >= 0 && - !isCodexOfficialPreset - } - autoComplete="off" - className={`w-full px-3 py-2 border rounded-lg text-sm transition-colors ${ - isCodexOfficialPreset - ? "bg-[var(--color-bg-tertiary)] border-[var(--color-border)] text-[var(--color-text-tertiary)] cursor-not-allowed" - : "border-[var(--color-border)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)]" - }`} - /> -
+ = 0 && + !isCodexOfficialPreset + } + /> )}
@@ -597,103 +589,35 @@ const ProviderForm: React.FC = ({ {/* Claude 或 Codex 的配置部分 */} {isCodex ? ( - // Codex: 双编辑器 -
-
- -