refactor: simplify TOML common config handling by removing markers
- Remove COMMON_CONFIG_MARKER_START/END constants - Simplify config snippet addition/removal logic - Use natural append/replace approach instead of markers - Fix unused variable warning - Improve user experience with cleaner config output
This commit is contained in:
@@ -62,7 +62,7 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
|
|
||||||
return diagnostics;
|
return diagnostics;
|
||||||
}),
|
}),
|
||||||
[showValidation]
|
[showValidation],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
|
|
||||||
// Codex 通用配置状态
|
// Codex 通用配置状态
|
||||||
const [useCodexCommonConfig, setUseCodexCommonConfig] = useState(false);
|
const [useCodexCommonConfig, setUseCodexCommonConfig] = useState(false);
|
||||||
const [codexCommonConfigSnippet, setCodexCommonConfigSnippetState] = useState<string>(() => {
|
const [codexCommonConfigSnippet, setCodexCommonConfigSnippetState] =
|
||||||
|
useState<string>(() => {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
||||||
}
|
}
|
||||||
@@ -217,7 +218,11 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
if (!isCodex) {
|
if (!isCodex) {
|
||||||
const configString = JSON.stringify(initialData.settingsConfig, null, 2);
|
const configString = JSON.stringify(
|
||||||
|
initialData.settingsConfig,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
const hasCommon = hasCommonConfigSnippet(
|
const hasCommon = hasCommonConfigSnippet(
|
||||||
configString,
|
configString,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
@@ -235,7 +240,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
};
|
};
|
||||||
if (config.env) {
|
if (config.env) {
|
||||||
setClaudeModel(config.env.ANTHROPIC_MODEL || "");
|
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
|
setBaseUrl(config.env.ANTHROPIC_BASE_URL || ""); // 初始化基础 URL
|
||||||
|
|
||||||
// 初始化 Kimi 模型选择
|
// 初始化 Kimi 模型选择
|
||||||
@@ -254,7 +261,13 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
setUseCodexCommonConfig(hasCommon);
|
setUseCodexCommonConfig(hasCommon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialData, commonConfigSnippet, codexCommonConfigSnippet, isCodex, codexConfig]);
|
}, [
|
||||||
|
initialData,
|
||||||
|
commonConfigSnippet,
|
||||||
|
codexCommonConfigSnippet,
|
||||||
|
isCodex,
|
||||||
|
codexConfig,
|
||||||
|
]);
|
||||||
|
|
||||||
// 当选择预设变化时,同步类别
|
// 当选择预设变化时,同步类别
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -529,10 +542,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
setBaseUrl(""); // 清空基础 URL
|
setBaseUrl(""); // 清空基础 URL
|
||||||
|
|
||||||
// 同步通用配置状态
|
// 同步通用配置状态
|
||||||
const hasCommon = hasCommonConfigSnippet(
|
const hasCommon = hasCommonConfigSnippet(configString, commonConfigSnippet);
|
||||||
configString,
|
|
||||||
commonConfigSnippet,
|
|
||||||
);
|
|
||||||
setUseCommonConfig(hasCommon);
|
setUseCommonConfig(hasCommon);
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
|
|
||||||
@@ -643,10 +653,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
updateSettingsConfigValue(configString);
|
updateSettingsConfigValue(configString);
|
||||||
|
|
||||||
// 同步通用配置开关
|
// 同步通用配置开关
|
||||||
const hasCommon = hasCommonConfigSnippet(
|
const hasCommon = hasCommonConfigSnippet(configString, commonConfigSnippet);
|
||||||
configString,
|
|
||||||
commonConfigSnippet,
|
|
||||||
);
|
|
||||||
setUseCommonConfig(hasCommon);
|
setUseCommonConfig(hasCommon);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -681,7 +688,8 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
|
|
||||||
// Codex: 处理通用配置开关
|
// Codex: 处理通用配置开关
|
||||||
const handleCodexCommonConfigToggle = (checked: boolean) => {
|
const handleCodexCommonConfigToggle = (checked: boolean) => {
|
||||||
const { updatedConfig, error: snippetError } = updateTomlCommonConfigSnippet(
|
const { updatedConfig, error: snippetError } =
|
||||||
|
updateTomlCommonConfigSnippet(
|
||||||
codexConfig,
|
codexConfig,
|
||||||
codexCommonConfigSnippet,
|
codexCommonConfigSnippet,
|
||||||
checked,
|
checked,
|
||||||
@@ -753,10 +761,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
// 保存 Codex 通用配置到 localStorage
|
// 保存 Codex 通用配置到 localStorage
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(CODEX_COMMON_CONFIG_STORAGE_KEY, value);
|
||||||
CODEX_COMMON_CONFIG_STORAGE_KEY,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore localStorage 写入失败
|
// ignore localStorage 写入失败
|
||||||
}
|
}
|
||||||
@@ -1177,7 +1182,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
useCommonConfig={useCodexCommonConfig}
|
useCommonConfig={useCodexCommonConfig}
|
||||||
onCommonConfigToggle={handleCodexCommonConfigToggle}
|
onCommonConfigToggle={handleCodexCommonConfigToggle}
|
||||||
commonConfigSnippet={codexCommonConfigSnippet}
|
commonConfigSnippet={codexCommonConfigSnippet}
|
||||||
onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
|
onCommonConfigSnippetChange={
|
||||||
|
handleCodexCommonConfigSnippetChange
|
||||||
|
}
|
||||||
commonConfigError={codexCommonConfigError}
|
commonConfigError={codexCommonConfigError}
|
||||||
authError={codexAuthError}
|
authError={codexAuthError}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -120,9 +120,7 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
|
|||||||
rows={12}
|
rows={12}
|
||||||
/>
|
/>
|
||||||
{configError && (
|
{configError && (
|
||||||
<p className="text-xs text-red-500 dark:text-red-400">
|
<p className="text-xs text-red-500 dark:text-red-400">{configError}</p>
|
||||||
{configError}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
完整的 Claude Code settings.json 配置内容
|
完整的 Claude Code settings.json 配置内容
|
||||||
|
|||||||
@@ -97,9 +97,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
data-enable-grammarly="false"
|
data-enable-grammarly="false"
|
||||||
/>
|
/>
|
||||||
{authError && (
|
{authError && (
|
||||||
<p className="text-xs text-red-500 dark:text-red-400">
|
<p className="text-xs text-red-500 dark:text-red-400">{authError}</p>
|
||||||
{authError}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
Codex auth.json 配置内容
|
Codex auth.json 配置内容
|
||||||
@@ -194,7 +192,9 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
</p>
|
</p>
|
||||||
<textarea
|
<textarea
|
||||||
value={commonConfigSnippet}
|
value={commonConfigSnippet}
|
||||||
onChange={(e) => handleCommonConfigSnippetChange(e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleCommonConfigSnippetChange(e.target.value)
|
||||||
|
}
|
||||||
placeholder={`# Common Codex config
|
placeholder={`# Common Codex config
|
||||||
# Add your common TOML configuration here`}
|
# Add your common TOML configuration here`}
|
||||||
rows={12}
|
rows={12}
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ const deepMerge = (
|
|||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deepRemove = (target: Record<string, any>, source: Record<string, any>) => {
|
const deepRemove = (
|
||||||
|
target: Record<string, any>,
|
||||||
|
source: Record<string, any>,
|
||||||
|
) => {
|
||||||
Object.entries(source).forEach(([key, value]) => {
|
Object.entries(source).forEach(([key, value]) => {
|
||||||
if (!(key in target)) return;
|
if (!(key in target)) return;
|
||||||
|
|
||||||
@@ -59,7 +62,7 @@ const isSubset = (target: any, source: any): boolean => {
|
|||||||
const deepClone = <T>(obj: T): T => {
|
const deepClone = <T>(obj: T): T => {
|
||||||
if (obj === null || typeof obj !== "object") return obj;
|
if (obj === null || typeof obj !== "object") return obj;
|
||||||
if (obj instanceof Date) return new Date(obj.getTime()) as T;
|
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) {
|
if (obj instanceof Object) {
|
||||||
const clonedObj = {} as T;
|
const clonedObj = {} as T;
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
@@ -78,7 +81,10 @@ export interface UpdateCommonConfigResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证JSON配置格式
|
// 验证JSON配置格式
|
||||||
export const validateJsonConfig = (value: string, fieldName: string = "配置"): string => {
|
export const validateJsonConfig = (
|
||||||
|
value: string,
|
||||||
|
fieldName: string = "配置",
|
||||||
|
): string => {
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -205,14 +211,14 @@ export const setApiKeyInConfig = (
|
|||||||
|
|
||||||
// ========== TOML Config Utilities ==========
|
// ========== TOML Config Utilities ==========
|
||||||
|
|
||||||
const COMMON_CONFIG_MARKER_START = "# === COMMON CONFIG START ===";
|
|
||||||
const COMMON_CONFIG_MARKER_END = "# === COMMON CONFIG END ===";
|
|
||||||
|
|
||||||
export interface UpdateTomlCommonConfigResult {
|
export interface UpdateTomlCommonConfigResult {
|
||||||
updatedConfig: string;
|
updatedConfig: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存之前的通用配置片段,用于替换操作
|
||||||
|
let previousCommonSnippet = "";
|
||||||
|
|
||||||
// 将通用配置片段写入/移除 TOML 配置
|
// 将通用配置片段写入/移除 TOML 配置
|
||||||
export const updateTomlCommonConfigSnippet = (
|
export const updateTomlCommonConfigSnippet = (
|
||||||
tomlString: string,
|
tomlString: string,
|
||||||
@@ -220,51 +226,50 @@ export const updateTomlCommonConfigSnippet = (
|
|||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
): UpdateTomlCommonConfigResult => {
|
): UpdateTomlCommonConfigResult => {
|
||||||
if (!snippetString.trim()) {
|
if (!snippetString.trim()) {
|
||||||
// 如果片段为空,移除已存在的通用配置部分
|
// 如果片段为空,直接返回原始配置
|
||||||
const cleaned = removeTomlCommonConfig(tomlString);
|
|
||||||
return {
|
return {
|
||||||
updatedConfig: cleaned,
|
updatedConfig: tomlString,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
// 添加通用配置
|
// 添加通用配置
|
||||||
const withoutOld = removeTomlCommonConfig(tomlString);
|
// 先移除旧的通用配置(如果有)
|
||||||
const commonSection = `\n${COMMON_CONFIG_MARKER_START}\n${snippetString}\n${COMMON_CONFIG_MARKER_END}\n`;
|
let updatedConfig = tomlString;
|
||||||
|
if (previousCommonSnippet && tomlString.includes(previousCommonSnippet)) {
|
||||||
|
updatedConfig = tomlString.replace(previousCommonSnippet, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在文件末尾添加新的通用配置
|
||||||
|
// 确保有适当的换行
|
||||||
|
const needsNewline = updatedConfig && !updatedConfig.endsWith("\n");
|
||||||
|
updatedConfig =
|
||||||
|
updatedConfig + (needsNewline ? "\n\n" : "\n") + snippetString;
|
||||||
|
|
||||||
|
// 保存当前通用配置片段
|
||||||
|
previousCommonSnippet = snippetString;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updatedConfig: withoutOld + commonSection,
|
updatedConfig: updatedConfig.trim() + "\n",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 移除通用配置
|
// 移除通用配置
|
||||||
const cleaned = removeTomlCommonConfig(tomlString);
|
if (tomlString.includes(snippetString)) {
|
||||||
|
const updatedConfig = tomlString.replace(snippetString, "");
|
||||||
|
// 清理多余的空行
|
||||||
|
const cleaned = updatedConfig.replace(/\n{3,}/g, "\n\n").trim();
|
||||||
|
|
||||||
|
// 清空保存的状态
|
||||||
|
previousCommonSnippet = "";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updatedConfig: cleaned,
|
updatedConfig: cleaned ? cleaned + "\n" : "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
updatedConfig: tomlString,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从 TOML 中移除通用配置部分
|
|
||||||
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') {
|
|
||||||
realStartIdx = startIdx - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找到标记后的换行符(如果有)
|
|
||||||
let realEndIdx = endIdx + COMMON_CONFIG_MARKER_END.length;
|
|
||||||
if (realEndIdx < tomlString.length && tomlString[realEndIdx] === '\n') {
|
|
||||||
realEndIdx = realEndIdx + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tomlString.slice(0, realStartIdx) + tomlString.slice(realEndIdx);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查 TOML 配置是否已包含通用配置片段
|
// 检查 TOML 配置是否已包含通用配置片段
|
||||||
@@ -274,17 +279,11 @@ export const hasTomlCommonConfigSnippet = (
|
|||||||
): boolean => {
|
): boolean => {
|
||||||
if (!snippetString.trim()) return false;
|
if (!snippetString.trim()) return false;
|
||||||
|
|
||||||
const startIdx = tomlString.indexOf(COMMON_CONFIG_MARKER_START);
|
// 简单检查配置是否包含片段内容
|
||||||
const endIdx = tomlString.indexOf(COMMON_CONFIG_MARKER_END);
|
// 去除空白字符后比较,避免格式差异影响
|
||||||
|
const normalizeWhitespace = (str: string) => str.replace(/\s+/g, " ").trim();
|
||||||
|
|
||||||
if (startIdx === -1 || endIdx === -1 || startIdx >= endIdx) {
|
return normalizeWhitespace(tomlString).includes(
|
||||||
return false;
|
normalizeWhitespace(snippetString),
|
||||||
}
|
);
|
||||||
|
|
||||||
// 提取标记之间的内容
|
|
||||||
const existingSnippet = tomlString
|
|
||||||
.slice(startIdx + COMMON_CONFIG_MARKER_START.length, endIdx)
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
return existingSnippet === snippetString.trim();
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user