Compare commits

..

No commits in common. "a9843beed7385780efd7c3c68da5583e2a65ab09" and "a57c9b2c55975d104285b57470fd5a25bbd0b48a" have entirely different histories.

3 changed files with 3 additions and 384 deletions

View File

@ -1,30 +0,0 @@
import path from 'path';
import { getConfigData, setConfigData } from './guoba/configHandler.js';
import guobaSchema from './guoba/configSchema.js';
export function supportGuoba() {
return {
pluginInfo: {
name: 'crystelf-plugin',
title: '晶灵插件',
description: '多功能娱乐插件支持AI对话、图像生成、音乐点播、60s新闻、验证管理等功能',
author: 'Jerry',
authorLink: 'https://github.com/jerryplusy',
link: 'https://github.com/jerryplusy/crystelf-plugin',
isV3: true,
isV2: false,
showInMenu: 'auto',
icon: 'mdi:crystal',
iconColor: '#7c4dff',
iconPath: path.join(process.cwd(), '/plugins/crystelf-plugin/resources/img/logo.png'),
},
configInfo: {
schemas: guobaSchema,
// 获取配置数据方法(用于前端填充显示数据)
getConfigData,
// 设置配置的方法(前端点确定后调用的方法)
setConfigData,
},
};
}

View File

@ -1,351 +0,0 @@
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,
};
}

View File

@ -430,7 +430,7 @@ const guobaSchema = [
field: 'ai.character', field: 'ai.character',
label: '表情包角色', label: '表情包角色',
component: 'Select', component: 'Select',
bottomHelpMessage: '回复表情包时的角色(能力有限,目前仅支持一种角色qwq)', bottomHelpMessage: '回复表情包时的角色(能力有限,目前仅支持一种角色)',
componentProps: { componentProps: {
options: [{ label: '真寻', value: 'zhenxun' }], options: [{ label: '真寻', value: 'zhenxun' }],
placeholder: '请选择表情包角色', placeholder: '请选择表情包角色',
@ -601,7 +601,7 @@ const guobaSchema = [
field: 'ai.blockGroup', field: 'ai.blockGroup',
label: '禁用群聊', label: '禁用群聊',
component: 'InputArray', component: 'InputArray',
bottomHelpMessage: '黑名单群聊,AI不会在这些群聊中工作', bottomHelpMessage: '黑名单群聊,插件不会在这些群聊中工作',
componentProps: { componentProps: {
placeholder: '请输入群号,按回车添加', placeholder: '请输入群号,按回车添加',
}, },
@ -776,7 +776,7 @@ const guobaSchema = [
component: 'SOFT_GROUP_BEGIN', component: 'SOFT_GROUP_BEGIN',
}, },
{ {
field: 'profile.nickName', field: 'profile.nickname',
label: '机器人昵称', label: '机器人昵称',
component: 'Input', component: 'Input',
bottomHelpMessage: '机器人的昵称', bottomHelpMessage: '机器人的昵称',