From 403c3f0690aa0e70d13459d6cd6033c876589f16 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Mon, 26 Jan 2026 14:11:13 +0800 Subject: [PATCH] feat(form): extract custom config difference on provider save Update ProviderForm to extract only the custom (provider-specific) configuration when saving, removing common config values that would be merged at runtime. Changes: - Import configMerge utilities (extractDifference, extractTomlDifference) - Pass finalConfig from hooks to config editor components - On save, extract difference between current config and common snippet - Apply extraction for all three app types (Claude JSON, Codex TOML, Gemini env) This ensures the database stores only the provider's unique configuration, while the common config is stored separately and merged at runtime when writing to live files. --- .../providers/forms/ProviderForm.tsx | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 6e1c32dfb..0145b86f6 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -36,6 +36,8 @@ import type { OpenCodeModel } from "@/types"; import type { UniversalProviderPreset } from "@/config/universalProviderPresets"; import { applyTemplateValues } from "@/utils/providerConfigUtils"; import { mergeProviderMeta } from "@/utils/providerMetaUtils"; +import { extractDifference, isPlainObject } from "@/utils/configMerge"; +import { extractTomlDifference } from "@/utils/tomlConfigMerge"; import { getCodexCustomTemplate } from "@/config/codexTemplates"; import CodexConfigEditor from "./CodexConfigEditor"; import { CommonConfigEditor } from "./CommonConfigEditor"; @@ -413,6 +415,7 @@ export function ProviderForm({ isExtracting: isClaudeExtracting, handleExtract: handleClaudeExtract, isLoading: isClaudeCommonConfigLoading, + finalConfig: claudeFinalConfig, } = useCommonConfigSnippet({ settingsConfig: form.getValues("settingsConfig"), onConfigChange: (config) => form.setValue("settingsConfig", config), @@ -432,6 +435,7 @@ export function ProviderForm({ isExtracting: isCodexExtracting, handleExtract: handleCodexExtract, isLoading: isCodexCommonConfigLoading, + finalConfig: codexFinalConfig, } = useCodexCommonConfig({ codexConfig, onConfigChange: handleCodexConfigChange, @@ -520,6 +524,7 @@ export function ProviderForm({ isExtracting: isGeminiExtracting, handleExtract: handleGeminiExtract, isLoading: isGeminiCommonConfigLoading, + finalEnv: geminiFinalEnv, } = useGeminiCommonConfig({ envValue: geminiEnv, onEnvChange: handleGeminiEnvChange, @@ -909,9 +914,20 @@ export function ProviderForm({ if (appId === "codex") { try { const authJson = JSON.parse(codexAuth); + // 如果启用了通用配置,只保存与通用配置不同的部分(自定义配置) + let configToSave = codexConfig ?? ""; + if (useCodexCommonConfigFlag && codexCommonConfigSnippet.trim()) { + const { customToml, error } = extractTomlDifference( + codexConfig ?? "", + codexCommonConfigSnippet.trim(), + ); + if (!error) { + configToSave = customToml; + } + } const configObj = { auth: authJson, - config: codexConfig ?? "", + config: configToSave, }; settingsConfig = JSON.stringify(configObj); } catch (err) { @@ -921,8 +937,16 @@ export function ProviderForm({ } else if (appId === "gemini") { // Gemini: 组合 env 和 config try { - const envObj = envStringToObj(geminiEnv); + let envObj = envStringToObj(geminiEnv); const configObj = geminiConfig.trim() ? JSON.parse(geminiConfig) : {}; + // 如果启用了通用配置,只保存与通用配置不同的部分(自定义配置) + if (useGeminiCommonConfigFlag && geminiCommonConfigSnippet.trim()) { + const commonEnvObj = envStringToObj(geminiCommonConfigSnippet.trim()); + if (isPlainObject(envObj) && isPlainObject(commonEnvObj)) { + const { customConfig } = extractDifference(envObj, commonEnvObj); + envObj = customConfig as Record; + } + } const combined = { env: envObj, config: configObj, @@ -934,7 +958,27 @@ export function ProviderForm({ } } else { // Claude: 使用表单配置 - settingsConfig = values.settingsConfig.trim(); + // 如果启用了通用配置,只保存与通用配置不同的部分(自定义配置) + if (useCommonConfig && commonConfigSnippet.trim()) { + try { + const currentConfig = JSON.parse(values.settingsConfig.trim()); + const commonConfig = JSON.parse(commonConfigSnippet.trim()); + if (isPlainObject(currentConfig) && isPlainObject(commonConfig)) { + const { customConfig } = extractDifference( + currentConfig, + commonConfig, + ); + settingsConfig = JSON.stringify(customConfig, null, 2); + } else { + settingsConfig = values.settingsConfig.trim(); + } + } catch { + // 如果解析失败,使用原始配置 + settingsConfig = values.settingsConfig.trim(); + } + } else { + settingsConfig = values.settingsConfig.trim(); + } } const payload: ProviderFormValues = { @@ -1447,6 +1491,7 @@ export function ProviderForm({ configError={codexConfigError} onExtract={handleCodexExtract} isExtracting={isCodexExtracting} + finalConfig={codexFinalConfig} /> {/* 配置验证错误显示 */} {/* 配置验证错误显示 */} setIsCommonConfigModalOpen(false)} onExtract={handleClaudeExtract} isExtracting={isClaudeExtracting} + finalConfig={claudeFinalConfig} /> {/* 配置验证错误显示 */}