import ConfigControl from '../lib/config/configControl.js'; import UserConfigManager from '../lib/ai/userConfigManager.js'; import lodash from 'lodash'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; /** * 配置处理逻辑 * 处理锅巴WebUI的配置读取、设置和保存 */ /** * 将嵌套对象转换为扁平化的点分隔路径 * @param {Object} obj - 要扁平化的对象 * @param {string} prefix - 前缀 * @returns {Object} 扁平化的对象 */ function flattenObject(obj, prefix = '') { const result = {}; for (const [key, value] of Object.entries(obj)) { // 跳过以?开头的注释字段 if (key.startsWith('?')) continue; const newKey = prefix ? `${prefix}.${key}` : key; if (value !== null && typeof value === 'object' && !Array.isArray(value)) { // 递归处理嵌套对象 Object.assign(result, flattenObject(value, newKey)); } else { result[newKey] = value; } } return result; } /** * 获取当前配置 * @returns {Promise} 当前配置对象 */ export function getConfigData() { // 获取所有配置文件 const allConfigs = ConfigControl.get(); const result = {}; // 将各个配置文件的内容扁平化到结果对象中 for (const [configName, configData] of Object.entries(allConfigs)) { if (configName === 'feeds' || configName === 'newcomer') continue; // 将配置数据扁平化 const flattened = flattenObject(configData, configName); Object.assign(result, flattened); } return result; } /** * 设置配置数据 * @param {Object} data - 新的配置数据 * @param {Object} options - 选项对象,包含Result等 * @returns {Promise} 操作结果 */ export async function setConfigData(data, { Result }) { try { // 将扁平化的数据重新组织成配置文件结构 const configUpdates = {}; for (const [fieldPath, value] of Object.entries(data)) { const parts = fieldPath.split('.'); const configName = parts[0]; // 跳过feeds和newcomer配置 if (configName === 'feeds' || configName === 'newcomer') continue; if (!configUpdates[configName]) { configUpdates[configName] = {}; } // 使用lodash.set设置嵌套属性 const keyPath = parts.slice(1).join('.'); lodash.set(configUpdates[configName], keyPath, value); } // 只更新实际有变化的配置文件 for (const [configName, newConfigData] of Object.entries(configUpdates)) { // 获取现有配置 const existingConfig = ConfigControl.get(configName) || {}; // 检查配置是否真的发生了变化 const isChanged = !lodash.isEqual( newConfigData, lodash.pick(existingConfig, Object.keys(newConfigData)) ); if (isChanged) { // 合并配置(保留注释字段) const updatedConfig = lodash.merge({}, existingConfig, newConfigData); // 保存配置 await ConfigControl.set(configName, updatedConfig); } } return Result.ok({}, '保存成功~'); } catch (error) { logger.error('[crystelf-plugin] 保存配置失败:', error); return Result.error('保存配置失败: ' + error.message); } } /** * 重置配置为默认值 * @param {Object} options - 选项对象,包含Result等 * @returns {Promise} 操作结果 */ export async function resetConfig({ Result }) { try { // 获取插件目录路径 const __filename = fileURLToPath(import.meta.url); const pluginDir = path.dirname(__filename); const configDir = path.join(pluginDir, '..', '..', 'config'); // 获取数据目录路径 const dataConfigPath = path.join(process.cwd(), 'data', 'crystelf'); // 确保数据目录存在 if (!fs.existsSync(dataConfigPath)) { fs.mkdirSync(dataConfigPath, { recursive: true }); } // 读取所有配置文件 const configFiles = fs.readdirSync(configDir).filter((file) => file.endsWith('.json')); const defaultConfigs = {}; // 复制每个配置文件 for (const file of configFiles) { const configName = path.basename(file, '.json'); const sourcePath = path.join(configDir, file); const targetPath = path.join(dataConfigPath, file); try { // 读取源配置文件 const configContent = fs.readFileSync(sourcePath, 'utf8'); const configData = JSON.parse(configContent); // 写入目标配置文件 fs.writeFileSync(targetPath, configContent, 'utf8'); // 添加到默认配置对象 defaultConfigs[configName] = configData; } catch (error) { logger.error(`[crystelf-ai] 复制配置文件失败 ${file}: ${error.message}`); return Result.error({}, `复制配置文件失败 ${file}: ${error.message}`); } } // 使用 ConfigControl.setMultiple 重置所有配置 await ConfigControl.setMultiple(defaultConfigs); // 清除用户配置缓存 UserConfigManager.clearCache(); return Result.ok({}, '重置成功~'); } catch (error) { logger.error(`[crystelf-ai] 重置配置失败: ${error.message}`); return Result.error({}, `重置失败: ${error.message}`); } } /** * 导出配置 * @param {Object} options - 选项对象,包含Result等 * @returns {Promise} 操作结果,包含配置数据 */ export async function exportConfig({ Result }) { try { const config = await getConfigData(); return Result.ok({ config }, '导出成功~'); } catch (error) { logger.error(`[crystelf-ai] 导出配置失败: ${error.message}`); return Result.error({}, `导出失败: ${error.message}`); } } /** * 导入配置 * @param {Object} data - 包含配置数据的对象 * @param {Object} options - 选项对象,包含Result等 * @returns {Promise} 操作结果 */ export async function importConfig(data, { Result }) { try { if (!data.config) { return Result.error({}, '导入数据格式错误'); } // 验证配置 const validationResult = validateConfig(data.config); if (!validationResult.valid) { return Result.error({}, `配置验证失败: ${validationResult.errors.join(', ')}`); } // 使用 ConfigControl.setMultiple 保存配置 await ConfigControl.setMultiple(data.config); // 清除用户配置缓存 UserConfigManager.clearCache(); return Result.ok({}, '导入成功~'); } catch (error) { logger.error(`[crystelf-ai] 导入配置失败: ${error.message}`); return Result.error({}, `导入失败: ${error.message}`); } } /** * 验证配置 * @param {string|Object} configType - 配置类型或配置对象 * @param {Object} config - 要验证的配置对象(当第一个参数是配置类型时) * @returns {Object} 验证结果,包含valid和errors */ function validateConfig(configType, config = null) { // 如果只有一个参数,则认为是配置对象,进行通用验证 if (config === null) { config = configType; configType = 'general'; } const errors = []; // 根据配置类型进行特定验证 switch (configType) { case 'ai': // 验证AI配置 if (!config.baseApi) { errors.push('API基础地址不能为空'); } if (!config.mode) { errors.push('对话模式不能为空'); } if (!config.apiKey) { errors.push('API密钥不能为空'); } if (!config.modelType) { errors.push('模型名称不能为空'); } if (!config.multimodalModel) { errors.push('多模态模型名称不能为空'); } if (!config.character) { errors.push('表情包角色不能为空'); } if (!config.botPersona) { errors.push('机器人人设不能为空'); } // 验证数值范围 if (config.temperature !== undefined && (config.temperature < 0 || config.temperature > 2)) { errors.push('温度值必须在0-2之间'); } if (config.concurrency !== undefined && (config.concurrency < 1 || config.concurrency > 10)) { errors.push('并发数必须在1-10之间'); } if ( config.chatHistory !== undefined && (config.chatHistory < 1 || config.chatHistory > 100) ) { errors.push('聊天历史长度必须在1-100之间'); } // 验证数组字段 if (config.blockGroup && !Array.isArray(config.blockGroup)) { errors.push('禁用群聊必须是数组'); } if (config.whiteGroup && !Array.isArray(config.whiteGroup)) { errors.push('白名单群聊必须是数组'); } break; case '60s': // 验证60s新闻配置 if (!config.url) { errors.push('60s新闻API地址不能为空'); } break; case 'auth': // 验证验证配置 if (!config.url) { errors.push('验证API地址不能为空'); } break; case 'music': // 验证音乐配置 if (!config.url) { errors.push('音乐服务器url不能为空'); } if (!config.username) { errors.push('音乐服务器用户名不能为空'); } if (!config.password) { errors.push('音乐服务器密码不能为空'); } if (!config.quality) { errors.push('音乐质量不能为空'); } break; case 'poke': // 验证戳一戳配置 if (config.replyPoke !== undefined && (config.replyPoke < 0 || config.replyPoke > 1)) { errors.push('戳一戳概率必须在0-1之间'); } break; case 'profile': // 验证个人资料配置 if (!config.nickName) { errors.push('机器人昵称不能为空'); } break; case 'coreConfig': // 验证核心配置 if (!config.coreUrl) { errors.push('核心url不能为空'); } break; default: // 通用验证 break; } return { valid: errors.length === 0, errors, }; }