diff --git a/apps/chat.js b/apps/chat.js index bdb4795..1e3a34b 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -1,9 +1,9 @@ import Config from '../config/config.js' import { Chaite, SendMessageOption } from 'chaite' +import { getPreset, intoUserMessage } from '../utils/message.js' + export class Chat extends plugin { constructor () { - let toggleMode = Config.basic.toggleMode - let prefix = Config.basic.togglePrefix super({ name: 'ChatGPT-Plugin对话', dsc: 'ChatGPT-Plugin对话', @@ -11,24 +11,39 @@ export class Chat extends plugin { priority: 0, rule: [ { - reg: toggleMode === 'at' ? '^[^#][sS]*' : `^#?(图片)?${prefix}[^gpt][sS]*`, - fnc: 'chat' + reg: '^[^#][sS]*', + fnc: 'chat', + log: false } ] }) } async chat (e) { - const state = await Chaite.getInstance().getUserStateStorage().getItem(e.sender.user_id + '') - const userSettings = state.settings - const sendMessageOptions = SendMessageOption.create({ - model: userSettings.model, - temperature: userSettings.temperature, - max_tokens: userSettings.maxToken, - systemOverride: userSettings.systemOverride, - + const sendMessageOptions = SendMessageOption.create(state.settings) + const preset = await getPreset(e, state.settings.preset, Config.basic.toggleMode, Config.basic.togglePrefix) + if (!preset) { + logger.debug('未找到预设,不进入对话') + return false + } + const userMessage = await intoUserMessage(e, { + handleReplyText: false, + handleReplyImage: true, + useRawMessage: false, + handleAtMsg: true, + excludeAtBot: false, + toggleMode: Config.basic.toggleMode, + togglePrefix: Config.basic.togglePrefix }) - Chaite.getInstance().sendMessage(msg, e, ) + const response = await Chaite.getInstance().sendMessage(userMessage, e, { + ...sendMessageOptions, + chatPreset: preset + }) + const responseText = response.contents + .filter(c => c.type === 'text') + .map(c => (/** @type {import('chaite').TextContent} **/ c).text) + .reduce((a, b) => a + b, '') + await this.reply(responseText) } } diff --git a/apps/management.js b/apps/management.js new file mode 100644 index 0000000..b28fbfb --- /dev/null +++ b/apps/management.js @@ -0,0 +1,39 @@ +import ChatGPTConfig from '../config/config.js' +import { createCRUDCommandRules, createSwitchCommandRules } from '../utils/command.js' + +export class ChatGPTManagement extends plugin { + constructor () { + const cmdPrefix = ChatGPTConfig.basic.commandPrefix + super({ + name: 'ChatGPT-Plugin管理', + dsc: 'ChatGPT-Plugin管理', + event: 'message', + priority: 20, + rule: [ + { + reg: `^${cmdPrefix}管理面板$`, + fnc: 'managementPanel', + permission: 'master' + }, + { + reg: `^${cmdPrefix}结束(全部)?对话$`, + fnc: 'destroyConversation' + }, + ...createCRUDCommandRules(cmdPrefix, '渠道', 'channels'), + ...createCRUDCommandRules(cmdPrefix, '预设', 'presets'), + ...createCRUDCommandRules(cmdPrefix, '工具', 'tools'), + ...createCRUDCommandRules(cmdPrefix, '处理器', 'processors'), + createSwitchCommandRules(cmdPrefix, '(预设切换|其他人切换预设)', 'customPreset', 1), + createSwitchCommandRules(cmdPrefix, '(调试|debug)(模式)?', 'debug'), + ...createCRUDCommandRules(cmdPrefix, '预设切换黑名单', 'blackCustomPreset', false), + ...createCRUDCommandRules(cmdPrefix, '预设切换白名单', 'whiteCustomPreset', false), + ...createCRUDCommandRules(cmdPrefix, '输入屏蔽词', 'blackPromptWords', false), + ...createCRUDCommandRules(cmdPrefix, '输出屏蔽词', 'blackResponseWords', false), + ...createCRUDCommandRules(cmdPrefix, '黑名单群', 'blackGroups', false), + ...createCRUDCommandRules(cmdPrefix, '白名单群', 'whiteGroups', false), + ...createCRUDCommandRules(cmdPrefix, '黑名单用户', 'blackUsers', false), + ...createCRUDCommandRules(cmdPrefix, '白名单用户', 'whiteUsers', false) + ] + }) + } +} diff --git a/config/config.js b/config/config.js index 5962fa1..f14ffd4 100644 --- a/config/config.js +++ b/config/config.js @@ -18,7 +18,9 @@ class ChatGPTConfig { // 触发前缀,仅在前缀触发时有效 togglePrefix: '#chat', // 是否开启调试模式 - debug: false + debug: false, + // 一般命令的开头 + commandPrefix: '^#chatgpt' } /** diff --git a/models/chaite/chat_preset_storage.js b/models/chaite/chat_preset_storage.js index ba076a1..9708d6e 100644 --- a/models/chaite/chat_preset_storage.js +++ b/models/chaite/chat_preset_storage.js @@ -24,7 +24,7 @@ export class LowDBChatPresetsStorage extends ChaiteStorage { * @returns {Promise} */ async getItem (key) { - + return this.collection.findOne({ id: key }) } /** diff --git a/utils/command.js b/utils/command.js new file mode 100644 index 0000000..e8c00cf --- /dev/null +++ b/utils/command.js @@ -0,0 +1,49 @@ +/** + * 模板 + * @param cmdPrefix + * @param name + * @param variable + * @param detail + * @returns {{reg: string, fnc: string}[]} + */ +export function createCRUDCommandRules (cmdPrefix, name, variable, detail = true) { + // make the first letter of variable capable + variable = variable.charAt(0).toUpperCase() + variable.slice(1) + const rules = [ + { + reg: cmdPrefix + `${name}列表$`, + fnc: `list${variable}` + }, + { + reg: cmdPrefix + `(编辑|修改)${name}`, + fnc: `edit${variable}` + }, + { + reg: cmdPrefix + `(添加|新增)${name}$`, + fnc: `add${variable}` + }, + + { + reg: cmdPrefix + `删除${name}`, + fnc: `remove${variable}` + } + ] + if (detail) { + rules.push({ + reg: cmdPrefix + `${name}详情$`, + fnc: `detail${variable}` + }) + } + return rules +} +const switchCommandPreset = { + 0: ['开启', '关闭'], + 1: ['允许', '禁止'] +} +export function createSwitchCommandRules (cmdPrefix, name, variable, preset = 0) { + variable = variable.charAt(0).toUpperCase() + variable.slice(1) + return { + reg: cmdPrefix + `(${switchCommandPreset[preset][0]}|${switchCommandPreset[preset][1]})${name}$`, + fnc: `switch${variable}` + } +} diff --git a/utils/message.js b/utils/message.js new file mode 100644 index 0000000..5fa8c71 --- /dev/null +++ b/utils/message.js @@ -0,0 +1,145 @@ +import { Chaite } from 'chaite' + +/** + * 将e中的消息转换为chaite的UserMessage + * + * @param e + * @param {{ + * handleReplyText: boolean, + * handleReplyImage: boolean, + * useRawMessage: boolean, + * handleAtMsg: boolean, + * excludeAtBot: boolean, + * toggleMode: 'at' | 'prefix', + * togglePrefix: string + * }} options + * @returns {Promise} + */ +export async function intoUserMessage (e, options = {}) { + const { + handleReplyText = false, + handleReplyImage = true, + useRawMessage = false, + handleAtMsg = true, + excludeAtBot = false, + toggleMode = 'at', + togglePrefix = null + } = options + const contents = [] + let text = '' + if (e.source && (handleReplyImage || handleReplyText)) { + let seq = e.isGroup ? e.source.seq : e.source.time + let reply = e.isGroup + ? (await e.group.getChatHistory(seq, 1)).pop()?.message + : (await e.friend.getChatHistory(seq, 1)).pop()?.message + if (reply) { + for (let val of reply) { + if (val.type === 'image' && handleReplyImage) { + contents.push({ + type: 'image', + url: val.url + }) + } else if (val.type === 'text' && handleReplyText) { + text = `本条消息对以下消息进行了引用回复:${val.text}\n\n本条消息内容:\n` + } + } + } + } + if (useRawMessage) { + text += e.raw_message + } else { + for (let val of e.message) { + switch (val.type) { + case 'at': { + if (handleAtMsg) { + const { qq, text: atCard } = val + if ((toggleMode === 'at' || excludeAtBot) && qq === e.bot.uin) { + break + } + text += ` @${atCard || qq} ` + } + break + } + case 'text': { + text += val.text + break + } + default: + } + } + } + e.message?.filter(element => element.type === 'image').forEach(element => { + contents.push({ + type: 'image', + url: element.url + }) + }) + if (toggleMode === 'prefix') { + const regex = new RegExp(`^#?(图片)?${togglePrefix}[^gpt]`) + text = text.replace(regex, '') + } + if (text) { + contents.push({ + type: 'text', + content: text + }) + } + return { + role: 'user', + content: contents + } +} + +/** + * 找到本次对话使用的预设 + * @param e + * @param {string} presetId + * @param {'at' | 'prefix'} toggleMode + * @param {string} togglePrefix + * @returns {Promise} + */ +export async function getPreset (e, presetId, toggleMode, togglePrefix) { + const isValidChat = checkChatMsg(e, toggleMode, togglePrefix) + const manager = Chaite.getInstance().getChatPresetManager() + const presets = await manager.getAllPresets() + const prefixHitPresets = presets.filter(p => e.msg.startsWith(p.prefix)) + if (!isValidChat && prefixHitPresets.length === 0) { + return null + } + let preset + // 如果不是at且不满足通用前缀,查看是否满足其他预设 + if (!isValidChat) { + // 找到其中prefix最长的 + if (prefixHitPresets.length > 1) { + preset = prefixHitPresets.sort((a, b) => b.prefix.length - a.prefix.length)[0] + } else { + preset = prefixHitPresets[0] + } + } else { + // 命中at或通用前缀,直接走用户默认预设 + preset = await manager.getInstance(presetId) + } + // 如果没找到再查一次 + if (!preset) { + preset = await manager.getInstance(presetId) + } + return preset +} + +/** + * + * @param e + * @param {'at' | 'prefix'} toggleMode + * @param {string} togglePrefix + * @returns {boolean} + */ +export function checkChatMsg (e, toggleMode, togglePrefix) { + if (toggleMode === 'at' && e.atBot) { + return true + } + const prefixReg = new RegExp(`^#?(图片)?${togglePrefix}[^gpt][sS]*`) + if (toggleMode === 'prefix' && e.msg.startsWith(prefixReg)) { + return true + } + return false +}