mirror of
https://github.com/Jerryplusy/crystelf-plugin.git
synced 2026-01-29 09:17:27 +00:00
Compare commits
No commits in common. "a9843beed7385780efd7c3c68da5583e2a65ab09" and "a57c9b2c55975d104285b57470fd5a25bbd0b48a" have entirely different histories.
a9843beed7
...
a57c9b2c55
@ -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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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: '机器人的昵称',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user