diff --git a/src/components/AppSwitcher.tsx b/src/components/AppSwitcher.tsx index 5c21cd5..c09cc46 100644 --- a/src/components/AppSwitcher.tsx +++ b/src/components/AppSwitcher.tsx @@ -26,8 +26,8 @@ export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) { diff --git a/src/components/JsonEditor.tsx b/src/components/JsonEditor.tsx index 65868e3..bdd1c16 100644 --- a/src/components/JsonEditor.tsx +++ b/src/components/JsonEditor.tsx @@ -62,7 +62,7 @@ const JsonEditor: React.FC = ({ return diagnostics; }), - [showValidation], + [showValidation] ); useEffect(() => { diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 9045e78..ed04869 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -128,26 +128,25 @@ const ProviderForm: React.FC = ({ const [settingsConfigError, setSettingsConfigError] = useState(""); // 用于跟踪是否正在通过通用配置更新 const isUpdatingFromCommonConfig = useRef(false); - + // Codex 通用配置状态 const [useCodexCommonConfig, setUseCodexCommonConfig] = useState(false); - const [codexCommonConfigSnippet, setCodexCommonConfigSnippetState] = - useState(() => { - if (typeof window === "undefined") { - return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET; - } - try { - const stored = window.localStorage.getItem( - CODEX_COMMON_CONFIG_STORAGE_KEY, - ); - if (stored && stored.trim()) { - return stored; - } - } catch { - // ignore localStorage 读取失败 - } + const [codexCommonConfigSnippet, setCodexCommonConfigSnippetState] = useState(() => { + if (typeof window === "undefined") { return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET; - }); + } + try { + const stored = window.localStorage.getItem( + CODEX_COMMON_CONFIG_STORAGE_KEY, + ); + if (stored && stored.trim()) { + return stored; + } + } catch { + // ignore localStorage 读取失败 + } + return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET; + }); const [codexCommonConfigError, setCodexCommonConfigError] = useState(""); const isUpdatingFromCodexCommonConfig = useRef(false); // -1 表示自定义,null 表示未选择,>= 0 表示预设索引 @@ -218,11 +217,7 @@ const ProviderForm: React.FC = ({ useEffect(() => { if (initialData) { if (!isCodex) { - const configString = JSON.stringify( - initialData.settingsConfig, - null, - 2, - ); + const configString = JSON.stringify(initialData.settingsConfig, null, 2); const hasCommon = hasCommonConfigSnippet( configString, commonConfigSnippet, @@ -240,9 +235,7 @@ const ProviderForm: React.FC = ({ }; if (config.env) { setClaudeModel(config.env.ANTHROPIC_MODEL || ""); - setClaudeSmallFastModel( - config.env.ANTHROPIC_SMALL_FAST_MODEL || "", - ); + setClaudeSmallFastModel(config.env.ANTHROPIC_SMALL_FAST_MODEL || ""); setBaseUrl(config.env.ANTHROPIC_BASE_URL || ""); // 初始化基础 URL // 初始化 Kimi 模型选择 @@ -261,13 +254,7 @@ const ProviderForm: React.FC = ({ setUseCodexCommonConfig(hasCommon); } } - }, [ - initialData, - commonConfigSnippet, - codexCommonConfigSnippet, - isCodex, - codexConfig, - ]); + }, [initialData, commonConfigSnippet, codexCommonConfigSnippet, isCodex, codexConfig]); // 当选择预设变化时,同步类别 useEffect(() => { @@ -510,7 +497,7 @@ const ProviderForm: React.FC = ({ isUpdatingFromCommonConfig.current = false; }, 0); } - + // 保存通用配置到 localStorage if (!validationError && typeof window !== "undefined") { try { @@ -542,7 +529,10 @@ const ProviderForm: React.FC = ({ setBaseUrl(""); // 清空基础 URL // 同步通用配置状态 - const hasCommon = hasCommonConfigSnippet(configString, commonConfigSnippet); + const hasCommon = hasCommonConfigSnippet( + configString, + commonConfigSnippet, + ); setUseCommonConfig(hasCommon); setCommonConfigError(""); @@ -653,7 +643,10 @@ const ProviderForm: React.FC = ({ updateSettingsConfigValue(configString); // 同步通用配置开关 - const hasCommon = hasCommonConfigSnippet(configString, commonConfigSnippet); + const hasCommon = hasCommonConfigSnippet( + configString, + commonConfigSnippet, + ); setUseCommonConfig(hasCommon); }; @@ -688,12 +681,11 @@ const ProviderForm: React.FC = ({ // Codex: 处理通用配置开关 const handleCodexCommonConfigToggle = (checked: boolean) => { - const { updatedConfig, error: snippetError } = - updateTomlCommonConfigSnippet( - codexConfig, - codexCommonConfigSnippet, - checked, - ); + const { updatedConfig, error: snippetError } = updateTomlCommonConfigSnippet( + codexConfig, + codexCommonConfigSnippet, + checked, + ); if (snippetError) { setCodexCommonConfigError(snippetError); @@ -761,7 +753,10 @@ const ProviderForm: React.FC = ({ // 保存 Codex 通用配置到 localStorage if (typeof window !== "undefined") { try { - window.localStorage.setItem(CODEX_COMMON_CONFIG_STORAGE_KEY, value); + window.localStorage.setItem( + CODEX_COMMON_CONFIG_STORAGE_KEY, + value, + ); } catch { // ignore localStorage 写入失败 } @@ -1182,9 +1177,7 @@ const ProviderForm: React.FC = ({ useCommonConfig={useCodexCommonConfig} onCommonConfigToggle={handleCodexCommonConfigToggle} commonConfigSnippet={codexCommonConfigSnippet} - onCommonConfigSnippetChange={ - handleCodexCommonConfigSnippetChange - } + onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange} commonConfigError={codexCommonConfigError} authError={codexAuthError} /> diff --git a/src/components/ProviderForm/ClaudeConfigEditor.tsx b/src/components/ProviderForm/ClaudeConfigEditor.tsx index 03aa98b..2b75e6d 100644 --- a/src/components/ProviderForm/ClaudeConfigEditor.tsx +++ b/src/components/ProviderForm/ClaudeConfigEditor.tsx @@ -60,7 +60,7 @@ const ClaudeConfigEditor: React.FC = ({ // 支持按下 ESC 关闭弹窗 useEffect(() => { if (!isCommonConfigModalOpen) return; - + const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { e.preventDefault(); @@ -120,13 +120,15 @@ const ClaudeConfigEditor: React.FC = ({ rows={12} /> {configError && ( - {configError} + + {configError} + )} 完整的 Claude Code settings.json 配置内容 {isCommonConfigModalOpen && ( - { if (e.target === e.currentTarget) closeModal(); @@ -134,7 +136,7 @@ const ClaudeConfigEditor: React.FC = ({ > {/* Backdrop - 统一背景样式 */} - + {/* Modal - 统一窗口样式 */} {/* Header - 统一标题栏样式 */} @@ -151,7 +153,7 @@ const ClaudeConfigEditor: React.FC = ({ - + {/* Content - 统一内容区域样式 */} @@ -169,7 +171,7 @@ const ClaudeConfigEditor: React.FC = ({ )} - + {/* Footer - 统一底部按钮样式 */} = ({ const [vscodeError, setVscodeError] = useState(""); const [vscodeSuccess, setVscodeSuccess] = useState(""); const [isWritingVscode, setIsWritingVscode] = useState(false); - const lastAppliedBaseUrlRef = useRef(null); useEffect(() => { if (commonConfigError && !isCommonConfigModalOpen) { @@ -50,61 +49,6 @@ const CodexConfigEditor: React.FC = ({ return () => window.clearTimeout(timer); }, [vscodeSuccess]); - const ensureVscodeApiAvailable = () => { - if (typeof window === "undefined" || !window.api?.writeVscodeSettings) { - setVscodeError("当前环境暂不支持写入 VS Code 配置"); - return false; - } - return true; - }; - - const applyVscodeConfig = async ( - baseUrl: string, - successMessage = "已写入 VS Code 配置", - ) => { - if (!ensureVscodeApiAvailable()) { - return false; - } - - setIsWritingVscode(true); - try { - const success = await window.api.writeVscodeSettings(baseUrl); - if (success) { - setVscodeSuccess(successMessage); - lastAppliedBaseUrlRef.current = baseUrl; - return true; - } - setVscodeError("写入 VS Code 配置失败,请稍后重试"); - } catch (error) { - setVscodeError(`写入 VS Code 配置失败: ${String(error)}`); - } finally { - setIsWritingVscode(false); - } - return false; - }; - - const removeVscodeConfig = async () => { - if (!ensureVscodeApiAvailable()) { - return false; - } - - setIsWritingVscode(true); - try { - const success = await window.api.writeVscodeSettings(); - if (success) { - setVscodeSuccess("已移除 VS Code 配置"); - lastAppliedBaseUrlRef.current = null; - return true; - } - setVscodeError("移除 VS Code 配置失败,请稍后重试"); - } catch (error) { - setVscodeError(`移除 VS Code 配置失败: ${String(error)}`); - } finally { - setIsWritingVscode(false); - } - return false; - }; - const handleVscodeConfigToggle = async (checked: boolean) => { if (isWritingVscode) return; @@ -112,6 +56,12 @@ const CodexConfigEditor: React.FC = ({ setVscodeError(""); setVscodeSuccess(""); + if (typeof window === "undefined" || !window.api?.writeVscodeSettings) { + setVscodeError("当前环境暂不支持写入 VS Code 配置"); + setWriteVscodeConfig(!checked); + return; + } + if (checked) { const trimmed = configValue.trim(); if (!trimmed) { @@ -127,70 +77,46 @@ const CodexConfigEditor: React.FC = ({ return; } - const success = await applyVscodeConfig(baseUrl); - if (!success) { - setWriteVscodeConfig(false); - } - return; - } - - const success = await removeVscodeConfig(); - if (!success) { - setWriteVscodeConfig(true); - } - }; - - useEffect(() => { - if (!writeVscodeConfig || isWritingVscode) { - return; - } - - const trimmed = configValue.trim(); - if (!trimmed) { - return; - } - - const baseUrl = extractBaseUrlFromToml(trimmed); - if (!baseUrl) { - setVscodeError("未在 config.toml 中找到 base_url 字段"); - setWriteVscodeConfig(false); - return; - } - - if (lastAppliedBaseUrlRef.current === baseUrl) { - return; - } - - const sync = async () => { - // 直接调用 API 而不依赖 applyVscodeConfig 函数,避免闭包问题 - if (typeof window === "undefined" || !window.api?.writeVscodeSettings) { - setVscodeError("当前环境暂不支持写入 VS Code 配置"); - return; - } - setIsWritingVscode(true); try { const success = await window.api.writeVscodeSettings(baseUrl); if (success) { - setVscodeSuccess("已更新 VS Code 配置"); - lastAppliedBaseUrlRef.current = baseUrl; + setVscodeSuccess("已写入 VS Code 配置"); } else { setVscodeError("写入 VS Code 配置失败,请稍后重试"); + setWriteVscodeConfig(false); } } catch (error) { setVscodeError(`写入 VS Code 配置失败: ${String(error)}`); + setWriteVscodeConfig(false); } finally { setIsWritingVscode(false); } - }; - sync(); - }, [configValue, writeVscodeConfig, isWritingVscode]); + return; + } + + setIsWritingVscode(true); + try { + const success = await window.api.writeVscodeSettings(); + if (success) { + setVscodeSuccess("已移除 VS Code 配置"); + } else { + setVscodeError("移除 VS Code 配置失败,请稍后重试"); + setWriteVscodeConfig(true); + } + } catch (error) { + setVscodeError(`移除 VS Code 配置失败: ${String(error)}`); + setWriteVscodeConfig(true); + } finally { + setIsWritingVscode(false); + } + }; // 支持按下 ESC 关闭弹窗 useEffect(() => { if (!isCommonConfigModalOpen) return; - + const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { e.preventDefault(); @@ -236,7 +162,9 @@ const CodexConfigEditor: React.FC = ({ data-enable-grammarly="false" /> {authError && ( - {authError} + + {authError} + )} Codex auth.json 配置内容 @@ -251,7 +179,7 @@ const CodexConfigEditor: React.FC = ({ > config.toml (TOML) - + {/* 右侧对齐的双列布局 - 使用flex而非grid以更好控制宽度 */} {/* 左列:VS Code 配置 */} @@ -273,16 +201,13 @@ const CodexConfigEditor: React.FC = ({ )} {vscodeError && ( - + {vscodeError} )} - + {/* 右列:通用配置 - 不设置宽度,让内容自然收缩 */} @@ -302,17 +227,14 @@ const CodexConfigEditor: React.FC = ({ 编辑通用配置 {commonConfigError && !isCommonConfigModalOpen && ( - + {commonConfigError} )} - + = ({ {isCommonConfigModalOpen && ( - { if (e.target === e.currentTarget) closeModal(); @@ -344,7 +266,7 @@ const CodexConfigEditor: React.FC = ({ > {/* Backdrop - 统一背景样式 */} - + {/* Modal - 统一窗口样式 */} {/* Header - 统一标题栏样式 */} @@ -361,7 +283,7 @@ const CodexConfigEditor: React.FC = ({ - + {/* Content - 统一内容区域样式 */} @@ -390,7 +312,7 @@ const CodexConfigEditor: React.FC = ({ )} - + {/* Footer - 统一底部按钮样式 */} { } return ""; -}; +}; \ No newline at end of file diff --git a/src/utils/providerConfigUtils.ts b/src/utils/providerConfigUtils.ts index 4c05c70..e50b845 100644 --- a/src/utils/providerConfigUtils.ts +++ b/src/utils/providerConfigUtils.ts @@ -22,10 +22,7 @@ const deepMerge = ( return target; }; -const deepRemove = ( - target: Record, - source: Record, -) => { +const deepRemove = (target: Record, source: Record) => { Object.entries(source).forEach(([key, value]) => { if (!(key in target)) return; @@ -62,7 +59,7 @@ const isSubset = (target: any, source: any): boolean => { const deepClone = (obj: T): T => { if (obj === null || typeof obj !== "object") return obj; if (obj instanceof Date) return new Date(obj.getTime()) as T; - if (obj instanceof Array) return obj.map((item) => deepClone(item)) as T; + if (obj instanceof Array) return obj.map(item => deepClone(item)) as T; if (obj instanceof Object) { const clonedObj = {} as T; for (const key in obj) { @@ -81,10 +78,7 @@ export interface UpdateCommonConfigResult { } // 验证JSON配置格式 -export const validateJsonConfig = ( - value: string, - fieldName: string = "配置", -): string => { +export const validateJsonConfig = (value: string, fieldName: string = "配置"): string => { if (!value.trim()) { return ""; } @@ -129,7 +123,7 @@ export const updateCommonConfigSnippet = ( error: snippetError, }; } - + const snippet = JSON.parse(snippetString) as Record; if (enabled) { @@ -253,23 +247,23 @@ export const updateTomlCommonConfigSnippet = ( const removeTomlCommonConfig = (tomlString: string): string => { const startIdx = tomlString.indexOf(COMMON_CONFIG_MARKER_START); const endIdx = tomlString.indexOf(COMMON_CONFIG_MARKER_END); - + if (startIdx === -1 || endIdx === -1) { return tomlString; } - + // 找到标记前的换行符(如果有) let realStartIdx = startIdx; - if (startIdx > 0 && tomlString[startIdx - 1] === "\n") { + if (startIdx > 0 && tomlString[startIdx - 1] === '\n') { realStartIdx = startIdx - 1; } - + // 找到标记后的换行符(如果有) let realEndIdx = endIdx + COMMON_CONFIG_MARKER_END.length; - if (realEndIdx < tomlString.length && tomlString[realEndIdx] === "\n") { + if (realEndIdx < tomlString.length && tomlString[realEndIdx] === '\n') { realEndIdx = realEndIdx + 1; } - + return tomlString.slice(0, realStartIdx) + tomlString.slice(realEndIdx); }; @@ -279,19 +273,19 @@ export const hasTomlCommonConfigSnippet = ( snippetString: string, ): boolean => { if (!snippetString.trim()) return false; - + const startIdx = tomlString.indexOf(COMMON_CONFIG_MARKER_START); const endIdx = tomlString.indexOf(COMMON_CONFIG_MARKER_END); - + if (startIdx === -1 || endIdx === -1 || startIdx >= endIdx) { return false; } - + // 提取标记之间的内容 const existingSnippet = tomlString .slice(startIdx + COMMON_CONFIG_MARKER_START.length, endIdx) .trim(); - + return existingSnippet === snippetString.trim(); };
{configError}
+ {configError} +
完整的 Claude Code settings.json 配置内容
@@ -169,7 +171,7 @@ const ClaudeConfigEditor: React.FC = ({
{authError}
+ {authError} +
Codex auth.json 配置内容 @@ -251,7 +179,7 @@ const CodexConfigEditor: React.FC = ({ > config.toml (TOML) - + {/* 右侧对齐的双列布局 - 使用flex而非grid以更好控制宽度 */} {/* 左列:VS Code 配置 */} @@ -273,16 +201,13 @@ const CodexConfigEditor: React.FC = ({ )} {vscodeError && ( - + {vscodeError} )}
+
{vscodeError}
{commonConfigError}
@@ -390,7 +312,7 @@ const CodexConfigEditor: React.FC = ({