crystelf-plugin/guoba/configHandler.js

352 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Object>} 当前配置对象
*/
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<Object>} 操作结果
*/
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<Object>} 操作结果
*/
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<Object>} 操作结果,包含配置数据
*/
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<Object>} 操作结果
*/
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,
};
}