feat(dialog): extract custom config from live settings on edit

Update EditProviderDialog to extract custom configuration from live
settings when common config is enabled, ensuring the edit form shows
only the provider-specific values.

When loading live settings for editing:
- Check if common config is enabled for the provider and app type
- Fetch the common config snippet from database
- Extract the difference (custom config) from live settings
- Handle all three formats: Claude (JSON), Codex (TOML), Gemini (env)

This prevents the merged common config values from appearing in the
edit form, which would cause them to be saved as custom config and
result in duplicate values after the next merge.
This commit is contained in:
YoVinchen
2026-01-26 14:12:42 +08:00
Unverified
parent 403c3f0690
commit e54e4d47ae
@@ -8,7 +8,9 @@ import {
ProviderForm,
type ProviderFormValues,
} from "@/components/providers/forms/ProviderForm";
import { providersApi, vscodeApi, type AppId } from "@/lib/api";
import { providersApi, vscodeApi, configApi, type AppId } from "@/lib/api";
import { extractDifference, isPlainObject } from "@/utils/configMerge";
import { extractTomlDifference } from "@/utils/tomlConfigMerge";
interface EditProviderDialogProps {
open: boolean;
@@ -81,7 +83,91 @@ export function EditProviderDialog({
appId,
)) as Record<string, unknown>;
if (!cancelled && live && typeof live === "object") {
setLiveSettings(live);
// 检查是否启用了通用配置
const metaByApp = provider.meta?.commonConfigEnabledByApp;
const commonConfigEnabled =
metaByApp?.[appId as "claude" | "codex" | "gemini"] ??
provider.meta?.commonConfigEnabled ??
false;
if (commonConfigEnabled) {
// 从 live 配置中提取自定义部分(去除通用配置)
try {
const commonSnippet =
await configApi.getCommonConfigSnippet(appId);
if (commonSnippet && commonSnippet.trim()) {
if (appId === "codex") {
// Codex: 处理 TOML 格式的 config 字段
const liveConfig =
(live as { auth?: unknown; config?: string }).config ??
"";
const { customToml, error } = extractTomlDifference(
liveConfig,
commonSnippet.trim(),
);
if (!error) {
setLiveSettings({
...live,
config: customToml,
});
} else {
setLiveSettings(live);
}
} else if (appId === "gemini") {
// Gemini: 处理 env 格式
const liveEnv =
(live as { env?: Record<string, string> }).env ?? {};
// 将 env 格式的通用配置转换为对象
const commonEnvObj: Record<string, string> = {};
for (const line of commonSnippet.trim().split("\n")) {
const trimmedLine = line.trim();
if (trimmedLine && !trimmedLine.startsWith("#")) {
const eqIndex = trimmedLine.indexOf("=");
if (eqIndex > 0) {
const key = trimmedLine.slice(0, eqIndex).trim();
const value = trimmedLine.slice(eqIndex + 1).trim();
commonEnvObj[key] = value;
}
}
}
if (
isPlainObject(liveEnv) &&
isPlainObject(commonEnvObj)
) {
const { customConfig } = extractDifference(
liveEnv,
commonEnvObj,
);
setLiveSettings({
...live,
env: customConfig,
});
} else {
setLiveSettings(live);
}
} else {
// Claude: 处理 JSON 格式
const commonConfig = JSON.parse(commonSnippet.trim());
if (isPlainObject(live) && isPlainObject(commonConfig)) {
const { customConfig } = extractDifference(
live,
commonConfig,
);
setLiveSettings(customConfig);
} else {
setLiveSettings(live);
}
}
} else {
setLiveSettings(live);
}
} catch {
// 提取失败时使用原始 live 配置
setLiveSettings(live);
}
} else {
setLiveSettings(live);
}
setHasLoadedLive(true);
}
} catch {
@@ -105,7 +191,14 @@ export function EditProviderDialog({
return () => {
cancelled = true;
};
}, [open, provider?.id, appId, hasLoadedLive, isProxyTakeover]); // 只依赖 provider.id,不依赖整个 provider 对象
}, [
open,
provider?.id,
provider?.meta,
appId,
hasLoadedLive,
isProxyTakeover,
]); // 添加 provider?.meta 依赖
const initialSettingsConfig = useMemo(() => {
return (liveSettings ?? provider?.settingsConfig ?? {}) as Record<