chatgpt-plugin/config/config.js
2025-04-10 13:33:39 +08:00

411 lines
12 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 fs from 'fs'
import path from 'path'
import yaml from 'js-yaml'
class ChatGPTConfig {
/**
* 版本号
* @type {string}
*/
version = '3.0.0'
/**
* 基本配置
* @type {{
* toggleMode: 'at' | 'prefix',
* debug: boolean,
* }}
*/
basic = {
// 触发方式at触发或者前缀触发
toggleMode: 'at',
// 触发前缀,仅在前缀触发时有效
togglePrefix: '#chat',
// 是否开启调试模式
debug: false,
// 一般命令的开头
commandPrefix: '#chatgpt'
}
/**
* 伪人模式,基于框架实现,因此机器人开启前缀后依然需要带上前缀。
* @type {{
* enable: boolean,
* hit: string[],
* probability: number,
* defaultPreset: string,
* presetPrefix?: string,
* presetMap: Array<{
* keywords: string[],
* presetId: string,
* priority: number,
* recall?: boolean
* }>,
* maxTokens: number,
* temperature: number,
* sendReasoning: boolean
* }}
* }}
*/
bym = {
// 开关
enable: false,
// 伪人必定触发词
hit: ['bym'],
// 不包含伪人必定触发词时的概率
probability: 0.02,
// 伪人模式的默认预设
defaultPreset: '',
// 伪人模式的预设前缀会加在在所有其他预设前。例如此处可以用于配置通用的伪人发言风格随意、模仿群友等presetMap中专心配置角色设定即可
presetPrefix: '',
// 包含关键词与预设的对应关系。包含特定触发词使用特定的预设,按照优先级排序
presetMap: [],
// 如果大于0会覆盖preset中的maxToken用于控制伪人模式发言长度
maxTokens: 0,
// 如果大于等于0会覆盖preset中的temperature用于控制伪人模式发言随机性
temperature: -1,
// 是否发送思考内容
sendReasoning: false
}
/**
* 模型和对话相关配置
* @type {{
* defaultModel: string,
* embeddingModel: string,
* defaultChatPresetId: string,
* enableCustomPreset: boolean,
* customPresetUserWhiteList: string[],
* customPresetUserBlackList: string[],
* promptBlockWords: string[],
* responseBlockWords: string[],
* blockStrategy: 'full' | 'mask',
* blockWordMask: string,
* enableGroupContext: boolean,
* groupContextLength: number,
* groupContextTemplatePrefix: string,
* groupContextTemplateMessage: string,
* groupContextTemplateSuffix: string
* }}
*/
llm = {
// 默认模型,初始化构建预设使用
defaultModel: '',
// 嵌入模型
embeddingModel: 'gemini-embedding-exp-03-07',
// 嵌入结果维度0表示自动
dimensions: 0,
// 默认对话预设ID
defaultChatPresetId: '',
// 是否启用允许其他人切换预设
enableCustomPreset: false,
// 允许切换预设的用户白名单
customPresetUserWhiteList: [],
// 禁止切换预设的用户黑名单
customPresetUserBlackList: [],
// 用户对话屏蔽词
promptBlockWords: [],
// 机器人回复屏蔽词
responseBlockWords: [],
// 触发屏蔽词的策略,完全屏蔽或仅屏蔽关键词
blockStrategy: 'full',
// 如果blockStrategy为mask屏蔽词的替换字符
blockWordMask: '***',
// 是否开启群组上下文
enableGroupContext: false,
// 群组上下文长度
groupContextLength: 20,
// 用于组装群聊上下文提示词的模板前缀
groupContextTemplatePrefix: 'Latest several messages in the group chat:\n' +
' 群名片 | 昵称 | qq号 | 群角色 | 群头衔 | 时间 | messageId | 消息内容 |\n' +
'|---|---|---|---|---|---|---|---|',
// 用于组装群聊上下文提示词的模板内容部分每一条消息作为message仿照示例填写
// eslint-disable-next-line no-template-curly-in-string
groupContextTemplateMessage: '| ${message.sender.card} | ${message.sender.nickname} | ${message.sender.user_id} | ${message.sender.role} | ${message.sender.title} | ${message.time} | ${message.messageId} | ${message.raw_message} |',
// 用于组装群聊上下文提示词的模板后缀
groupContextTemplateSuffix: '\n'
}
/**
* 管理相关配置
* @type {{
* blackGroups: number[],
* whiteGroups: number[],
* blackUsers: string[],
* whiteUsers: string[],
* defaultRateLimit: number
* }}
*/
management = {
blackGroups: [],
whiteGroups: [],
blackUsers: [],
whiteUsers: [],
// 默认对话速率限制0表示不限制数字表示每分钟最多对话次数
defaultRateLimit: 0
}
/**
* chaite相关配置
* @type {
* { dataDir: string,
* processorsDirPath: string,
* toolsDirPath: string,
* cloudBaseUrl: string,
* cloudApiKey: string,
* authKey: string,
* host: string,
* port: number}}
*/
chaite = {
// 数据目录,相对于插件下
dataDir: 'data',
// 处理器目录,相对于插件下
processorsDirPath: 'utils/processors',
// 工具目录,相对于插件目录下
toolsDirPath: 'utils/tools',
// 云端API url
cloudBaseUrl: 'https://api.chaite.cloud',
// 云端API Key
cloudApiKey: '',
// jwt key非必要勿修改修改需重启
authKey: '',
// 管理面板监听地址
host: '0.0.0.0',
// 管理面板监听端口
port: 48370,
// 存储实现 sqlite lowdb
storage: 'sqlite'
}
constructor () {
this.version = '3.0.0'
this.watcher = null
this.configPath = ''
}
/**
* Start config file sync
* call once!
* @param {string} configDir Directory containing config files
*/
startSync (configDir) {
// 配置路径设置
const jsonPath = path.join(configDir, 'config.json')
const yamlPath = path.join(configDir, 'config.yaml')
if (fs.existsSync(jsonPath)) {
this.configPath = jsonPath
} else if (fs.existsSync(yamlPath)) {
this.configPath = yamlPath
} else {
this.configPath = jsonPath
this.saveToFile()
}
// 加载初始配置
this.loadFromFile()
// 文件变更标志和保存定时器
this._saveOrigin = null
this._saveTimer = null
// 监听文件变化
this.watcher = fs.watchFile(this.configPath, (curr, prev) => {
if (curr.mtime !== prev.mtime && this._saveOrigin !== 'code') {
this.loadFromFile()
}
})
// 处理所有嵌套对象
return this._createProxyRecursively(this)
}
// 递归创建代理
_createProxyRecursively (obj, path = []) {
if (obj === null || typeof obj !== 'object' || obj instanceof Date) {
return obj
}
// 处理数组和对象
if (Array.isArray(obj)) {
// 创建一个新数组,递归地处理每个元素
const proxiedArray = [...obj].map((item, index) =>
this._createProxyRecursively(item, [...path, index])
)
// 代理数组,捕获数组方法调用
return new Proxy(proxiedArray, {
set: (target, prop, value) => {
// 处理数字属性(数组索引)和数组长度
if (typeof prop !== 'symbol' &&
((!isNaN(prop) && prop !== 'length') ||
prop === 'length')) {
// 直接设置值
target[prop] = value
// 触发保存
this._triggerSave('array')
} else {
target[prop] = value
}
return true
},
// 拦截数组方法调用
get: (target, prop) => {
const val = target[prop]
// 处理数组修改方法
if (typeof val === 'function' &&
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].includes(prop)) {
return function (...args) {
const result = Array.prototype[prop].apply(target, args)
// 方法调用后触发保存
this._triggerSave('array-method')
return result
}.bind(this)
}
return val
}
})
} else {
// 对普通对象的处理
const proxiedObj = {}
// 处理所有属性
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// 跳过内部属性
if (key === 'watcher' || key === 'configPath' ||
key.startsWith('_save') || key === '_isSaving') {
proxiedObj[key] = obj[key]
} else {
// 递归处理嵌套对象
proxiedObj[key] = this._createProxyRecursively(
obj[key], [...path, key]
)
}
}
}
// 创建对象的代理
return new Proxy(proxiedObj, {
set: (target, prop, value) => {
// 跳过内部属性的处理
if (prop === 'watcher' || prop === 'configPath' ||
prop.startsWith('_save') || prop === '_isSaving') {
target[prop] = value
return true
}
// 设置新值,如果是对象则递归创建代理
if (value !== null && typeof value === 'object') {
target[prop] = this._createProxyRecursively(
value, [...path, prop]
)
} else {
target[prop] = value
}
// 触发保存
this._triggerSave('object')
return true
}
})
}
}
loadFromFile () {
try {
if (!fs.existsSync(this.configPath)) {
// 如果文件不存在,直接返回
return
}
const content = fs.readFileSync(this.configPath, 'utf8')
const loadedConfig = this.configPath.endsWith('.json')
? JSON.parse(content)
: yaml.load(content)
// 只更新存在的配置项
if (loadedConfig) {
Object.keys(loadedConfig).forEach(key => {
if (key === 'version' || key === 'basic' || key === 'bym' || key === 'llm' ||
key === 'management' || key === 'chaite') {
if (typeof loadedConfig[key] === 'object' && loadedConfig[key] !== null) {
// 对象的合并
if (!this[key]) this[key] = {}
Object.assign(this[key], loadedConfig[key])
} else {
// 基本类型直接赋值
this[key] = loadedConfig[key]
}
}
})
}
logger.debug('Config loaded successfully')
} catch (error) {
logger.error('Failed to load config:', error)
}
}
// 合并触发保存,防抖处理
_triggerSave (origin) {
// 清除之前的定时器
if (this._saveTimer) {
clearTimeout(this._saveTimer)
}
// 记录保存来源
this._saveOrigin = origin || 'code'
// 设置定时器延迟保存
this._saveTimer = setTimeout(() => {
this.saveToFile()
// 保存完成后延迟一下再清除来源标记
setTimeout(() => {
this._saveOrigin = null
}, 100)
}, 200)
}
saveToFile () {
logger.debug('Saving config to file...')
try {
const config = {
version: this.version,
basic: this.basic,
bym: this.bym,
llm: this.llm,
management: this.management,
chaite: this.chaite
}
const content = this.configPath.endsWith('.json')
? JSON.stringify(config, null, 2)
: yaml.dump(config)
fs.writeFileSync(this.configPath, content, 'utf8')
} catch (error) {
console.error('Failed to save config:', error)
}
}
toJSON () {
return {
version: this.version,
basic: this.basic,
bym: this.bym,
llm: this.llm,
management: this.management,
chaite: this.chaite
}
}
}
export default new ChatGPTConfig()