2025-08-07 23:05:11 +08:00
|
|
|
|
// 供应商配置处理工具函数
|
|
|
|
|
|
|
2025-09-16 22:59:00 +08:00
|
|
|
|
const isPlainObject = (value: unknown): value is Record<string, any> => {
|
|
|
|
|
|
return Object.prototype.toString.call(value) === "[object Object]";
|
|
|
|
|
|
};
|
2025-08-27 11:00:53 +08:00
|
|
|
|
|
2025-09-16 22:59:00 +08:00
|
|
|
|
const deepMerge = (
|
|
|
|
|
|
target: Record<string, any>,
|
|
|
|
|
|
source: Record<string, any>,
|
|
|
|
|
|
): Record<string, any> => {
|
|
|
|
|
|
Object.entries(source).forEach(([key, value]) => {
|
|
|
|
|
|
if (isPlainObject(value)) {
|
|
|
|
|
|
if (!isPlainObject(target[key])) {
|
|
|
|
|
|
target[key] = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
deepMerge(target[key], value);
|
2025-08-07 23:05:11 +08:00
|
|
|
|
} else {
|
2025-09-16 22:59:00 +08:00
|
|
|
|
// 直接覆盖非对象字段(数组/基础类型)
|
|
|
|
|
|
target[key] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return target;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const deepRemove = (target: Record<string, any>, source: Record<string, any>) => {
|
|
|
|
|
|
Object.entries(source).forEach(([key, value]) => {
|
|
|
|
|
|
if (!(key in target)) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (isPlainObject(value) && isPlainObject(target[key])) {
|
|
|
|
|
|
// 只移除完全匹配的嵌套属性
|
|
|
|
|
|
deepRemove(target[key], value);
|
|
|
|
|
|
if (Object.keys(target[key]).length === 0) {
|
|
|
|
|
|
delete target[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (isSubset(target[key], value)) {
|
|
|
|
|
|
// 只有当值完全匹配时才删除
|
|
|
|
|
|
delete target[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const isSubset = (target: any, source: any): boolean => {
|
|
|
|
|
|
if (isPlainObject(source)) {
|
|
|
|
|
|
if (!isPlainObject(target)) return false;
|
|
|
|
|
|
return Object.entries(source).every(([key, value]) =>
|
|
|
|
|
|
isSubset(target[key], value),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(source)) {
|
|
|
|
|
|
if (!Array.isArray(target) || target.length !== source.length) return false;
|
|
|
|
|
|
return source.every((item, index) => isSubset(target[index], item));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return target === source;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 深拷贝函数
|
|
|
|
|
|
const deepClone = <T>(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 Object) {
|
|
|
|
|
|
const clonedObj = {} as T;
|
|
|
|
|
|
for (const key in obj) {
|
|
|
|
|
|
if (obj.hasOwnProperty(key)) {
|
|
|
|
|
|
clonedObj[key] = deepClone(obj[key]);
|
|
|
|
|
|
}
|
2025-08-07 23:05:11 +08:00
|
|
|
|
}
|
2025-09-16 22:59:00 +08:00
|
|
|
|
return clonedObj;
|
|
|
|
|
|
}
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateCommonConfigResult {
|
|
|
|
|
|
updatedConfig: string;
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
|
2025-09-16 22:59:00 +08:00
|
|
|
|
// 将通用配置片段写入/移除 settingsConfig
|
|
|
|
|
|
export const updateCommonConfigSnippet = (
|
|
|
|
|
|
jsonString: string,
|
|
|
|
|
|
snippetString: string,
|
|
|
|
|
|
enabled: boolean,
|
|
|
|
|
|
): UpdateCommonConfigResult => {
|
|
|
|
|
|
let config: Record<string, any>;
|
|
|
|
|
|
try {
|
|
|
|
|
|
config = jsonString ? JSON.parse(jsonString) : {};
|
2025-08-07 23:05:11 +08:00
|
|
|
|
} catch (err) {
|
2025-09-16 22:59:00 +08:00
|
|
|
|
return {
|
|
|
|
|
|
updatedConfig: jsonString,
|
|
|
|
|
|
error: "配置 JSON 解析失败,无法写入通用配置",
|
|
|
|
|
|
};
|
2025-08-07 23:05:11 +08:00
|
|
|
|
}
|
2025-09-16 22:59:00 +08:00
|
|
|
|
|
|
|
|
|
|
if (!snippetString.trim()) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
updatedConfig: JSON.stringify(config, null, 2),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let snippet: Record<string, any>;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(snippetString);
|
|
|
|
|
|
if (!isPlainObject(parsed)) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
updatedConfig: JSON.stringify(config, null, 2),
|
|
|
|
|
|
error: "通用配置片段必须是 JSON 对象",
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
snippet = parsed;
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
updatedConfig: JSON.stringify(config, null, 2),
|
|
|
|
|
|
error: "通用配置片段格式错误,需为合法 JSON",
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (enabled) {
|
|
|
|
|
|
const merged = deepMerge(deepClone(config), snippet);
|
|
|
|
|
|
return {
|
|
|
|
|
|
updatedConfig: JSON.stringify(merged, null, 2),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cloned = deepClone(config);
|
|
|
|
|
|
deepRemove(cloned, snippet);
|
|
|
|
|
|
return {
|
|
|
|
|
|
updatedConfig: JSON.stringify(cloned, null, 2),
|
|
|
|
|
|
};
|
2025-08-27 11:00:53 +08:00
|
|
|
|
};
|
2025-08-07 23:05:11 +08:00
|
|
|
|
|
2025-09-16 22:59:00 +08:00
|
|
|
|
// 检查当前配置是否已包含通用配置片段
|
|
|
|
|
|
export const hasCommonConfigSnippet = (
|
|
|
|
|
|
jsonString: string,
|
|
|
|
|
|
snippetString: string,
|
|
|
|
|
|
): boolean => {
|
2025-08-07 23:05:11 +08:00
|
|
|
|
try {
|
2025-09-16 22:59:00 +08:00
|
|
|
|
if (!snippetString.trim()) return false;
|
|
|
|
|
|
const config = jsonString ? JSON.parse(jsonString) : {};
|
|
|
|
|
|
const snippet = JSON.parse(snippetString);
|
|
|
|
|
|
if (!isPlainObject(snippet)) return false;
|
|
|
|
|
|
return isSubset(config, snippet);
|
2025-08-07 23:05:11 +08:00
|
|
|
|
} catch (err) {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
return false;
|
2025-08-07 23:05:11 +08:00
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
};
|
2025-08-07 23:05:11 +08:00
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// 读取配置中的 API Key(env.ANTHROPIC_AUTH_TOKEN)
|
|
|
|
|
|
export const getApiKeyFromConfig = (jsonString: string): string => {
|
|
|
|
|
|
try {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
const config = JSON.parse(jsonString);
|
|
|
|
|
|
const key = config?.env?.ANTHROPIC_AUTH_TOKEN;
|
|
|
|
|
|
return typeof key === "string" ? key : "";
|
2025-08-26 10:37:44 +08:00
|
|
|
|
} catch (err) {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
return "";
|
2025-08-26 10:37:44 +08:00
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
};
|
2025-08-26 10:37:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 判断配置中是否存在 API Key 字段
|
|
|
|
|
|
export const hasApiKeyField = (jsonString: string): boolean => {
|
|
|
|
|
|
try {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
const config = JSON.parse(jsonString);
|
|
|
|
|
|
return Object.prototype.hasOwnProperty.call(
|
|
|
|
|
|
config?.env ?? {},
|
|
|
|
|
|
"ANTHROPIC_AUTH_TOKEN",
|
|
|
|
|
|
);
|
2025-08-26 10:37:44 +08:00
|
|
|
|
} catch (err) {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
return false;
|
2025-08-26 10:37:44 +08:00
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
};
|
2025-08-26 10:37:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 写入/更新配置中的 API Key,默认不新增缺失字段
|
|
|
|
|
|
export const setApiKeyInConfig = (
|
|
|
|
|
|
jsonString: string,
|
|
|
|
|
|
apiKey: string,
|
2025-08-27 11:00:53 +08:00
|
|
|
|
options: { createIfMissing?: boolean } = {},
|
2025-08-26 10:37:44 +08:00
|
|
|
|
): string => {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
const { createIfMissing = false } = options;
|
2025-08-26 10:37:44 +08:00
|
|
|
|
try {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
const config = JSON.parse(jsonString);
|
2025-08-26 10:37:44 +08:00
|
|
|
|
if (!config.env) {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
if (!createIfMissing) return jsonString;
|
|
|
|
|
|
config.env = {};
|
2025-08-26 10:37:44 +08:00
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
if (!("ANTHROPIC_AUTH_TOKEN" in config.env) && !createIfMissing) {
|
|
|
|
|
|
return jsonString;
|
2025-08-26 10:37:44 +08:00
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
config.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
|
|
|
|
return JSON.stringify(config, null, 2);
|
2025-08-26 10:37:44 +08:00
|
|
|
|
} catch (err) {
|
2025-08-27 11:00:53 +08:00
|
|
|
|
return jsonString;
|
2025-08-26 10:37:44 +08:00
|
|
|
|
}
|
2025-08-27 11:00:53 +08:00
|
|
|
|
};
|