From b6bef8e1ef7c28affb1be0019b037bbd34de0d25 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 3 Mar 2026 14:58:12 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=85=88=E6=94=B9=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{ => cmd}/privilege.ts | 18 +- src/cmd/root.ts | 1309 +++++++++++++++++++++++++++++++ src/cmd/sub_cmd/privilege.ts | 156 ++++ src/cmd/sub_cmd/prompt.ts | 19 + src/index.ts | 1416 +--------------------------------- 5 files changed, 1499 insertions(+), 1419 deletions(-) rename src/{ => cmd}/privilege.ts (91%) create mode 100644 src/cmd/root.ts create mode 100644 src/cmd/sub_cmd/privilege.ts create mode 100644 src/cmd/sub_cmd/prompt.ts diff --git a/src/privilege.ts b/src/cmd/privilege.ts similarity index 91% rename from src/privilege.ts rename to src/cmd/privilege.ts index f865f3f..570ad83 100644 --- a/src/privilege.ts +++ b/src/cmd/privilege.ts @@ -1,8 +1,8 @@ -import { AI } from "./AI/AI"; -import { logger } from "./logger"; -import { ConfigManager } from "./config/configManager"; -import { aliasToCmd } from "./utils/utils"; -import { PRIVILEGELEVELMAP } from "./config/config"; +import { AI } from "../AI/AI"; +import { logger } from "../logger"; +import { ConfigManager } from "../config/configManager"; +import { aliasToCmd } from "../utils/utils"; +import { PRIVILEGELEVELMAP } from "../config/config"; export interface CmdPrivInfo { @@ -12,10 +12,10 @@ export interface CmdPrivInfo { export interface CmdPriv { [key: string]: CmdPrivInfo }; -const U: [number, number, number] = [0, PRIVILEGELEVELMAP.user, PRIVILEGELEVELMAP.user]; // user -const M: [number, number, number] = [0, PRIVILEGELEVELMAP.master, PRIVILEGELEVELMAP.master]; // master -const I: [number, number, number] = [0, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.inviter]; // inviter -const S: [number, number, number] = [1, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.master]; // spesial,会话所需权限为1,是才能被邀请者使用,否则需为骰主 +export const U: [number, number, number] = [0, PRIVILEGELEVELMAP.user, PRIVILEGELEVELMAP.user]; // user +export const M: [number, number, number] = [0, PRIVILEGELEVELMAP.master, PRIVILEGELEVELMAP.master]; // master +export const I: [number, number, number] = [0, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.inviter]; // inviter +export const S: [number, number, number] = [1, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.master]; // spesial,会话所需权限为1,是才能被邀请者使用,否则需为骰主 export const defaultCmdPriv: CmdPriv = { ai: { diff --git a/src/cmd/root.ts b/src/cmd/root.ts new file mode 100644 index 0000000..6afd20f --- /dev/null +++ b/src/cmd/root.ts @@ -0,0 +1,1309 @@ +import { AI, AIManager } from "../AI/AI"; +import { ToolManager } from "../tool/tool"; +import { ConfigManager } from "../config/configManager"; +import { getRoleSetting } from "../utils/utils_message"; +import { logger } from "../logger"; +import { transformArrayToContent, transformTextToArray } from "../utils/utils_string"; +import { get_chart_url } from "../service"; +import { TimerManager } from "../timer"; +import { CmdPrivInfo, PrivilegeManager, U } from "./privilege"; +import { aliasToCmd } from "../utils/utils"; +import { ImageManager } from "../AI/image"; +import { getCmdPrivilege } from "./sub_cmd/privilege"; +import { getCmdPrompt } from "./sub_cmd/prompt"; + +export interface SubCmdContext { + ctx: seal.MsgContext; + msg: seal.Message; + cmdArgs: seal.CmdArgs; + epId: string; + uid: string; + gid: string; + sid: string; + ai: AI; + page: number; + ret: seal.CmdExecuteResult; +} + +export class SubCmd { + name: string; + help: string; + priv: CmdPrivInfo; + solve: (scc: SubCmdContext) => seal.CmdExecuteResult | Promise; + + constructor(name: string) { + this.name = name; + this.help = ''; + this.priv = { priv: U }; + this.solve = async () => { return seal.ext.newCmdExecuteResult(false); }; + } +} + +export const cmd = seal.ext.newCmdItemInfo(); +cmd.name = 'ai'; +cmd.help = `帮助: +【.ai priv】权限相关 +【.ai prompt】查看system prompt +【.ai status】查看当前AI状态 +【.ai ctxn】上下文名字相关 +【.ai timer】定时器相关 +【.ai on】开启AI +【.ai sb】开启待机模式,此时AI将记录聊天内容 +【.ai off】关闭AI,此时仍能用正则匹配触发 +【.ai fgt】遗忘上下文 +【.ai role】角色设定相关 +【.ai img】图片相关 +【.ai memo】AI的记忆相关 +【.ai tool】AI的工具相关 +【.ai ign】AI的忽略名单相关 +【.ai tk】AI的token相关 +【.ai shut】终止AI当前流式输出`; +cmd.allowDelegate = true; +cmd.solve = (ctx, msg, cmdArgs) => { + try { + const val = cmdArgs.getArgN(1); + const uid = ctx.player.userId; + const gid = ctx.group.groupId; + const epId = ctx.endPoint.userId; + const sid = ctx.isPrivate ? uid : gid; + + const ret = seal.ext.newCmdExecuteResult(true); + const ai = AIManager.getAI(sid); + const { success, exist } = PrivilegeManager.checkPriv(ctx, cmdArgs, ai); + if (!success) { + seal.replyToSender(ctx, msg, exist ? '权限不足' : '命令不存在'); + return ret; + } + + let page = 1; + const kwargPage = cmdArgs.kwargs.find((kwarg) => kwarg.name === 'page' || kwarg.name === 'p'); + if (kwargPage && kwargPage.valueExists) { + page = parseInt(kwargPage.value); + if (isNaN(page)) { + seal.replyToSender(ctx, msg, '页码必须为数字'); + return ret; + } + if (page < 1) { + seal.replyToSender(ctx, msg, '页码必须大于等于1'); + return ret; + } + } + + const scc: SubCmdContext = { ctx, msg, cmdArgs, epId, uid, gid, sid, ai, page, ret }; + + switch (aliasToCmd(val)) { + case 'privilege': return getCmdPrivilege().solve(scc); + case 'prompt': return getCmdPrompt().solve(scc); + case 'status': { + const setting = ai.setting; + const { start, end, segs } = setting.activeTimeInfo; + + seal.replyToSender(ctx, msg, `${sid} +权限: ${setting.priv} +上下文轮数: ${ai.context.messages.filter(m => m.role === 'user').length} +计数器模式(c): ${setting.counter > -1 ? `${setting.counter}条` : '关闭'} +计时器模式(t): ${setting.timer > -1 ? `${setting.timer}秒` : '关闭'} +概率模式(p): ${setting.prob > -1 ? `${setting.prob}%` : '关闭'} +活跃时间段: ${(start !== 0 || end !== 0) ? `${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}` : '未设置'} +活跃次数: ${segs > 0 ? segs : '未设置'} +待机模式: ${setting.standby ? '开启' : '关闭'}`); + return ret; + } + case 'ctxn': { + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'status': { + seal.replyToSender(ctx, msg, `自动修改上下文里的名字状态:${ai.context.autoNameMod} +上下文里的名字有:\n${ai.context.userInfoList.map(ui => `${ui.name}(${ui.id})`).join('\n')}`); + return ret; + } + case 'set': { + const val3 = cmdArgs.getArgN(3); + const mod = aliasToCmd(val3); + if (mod !== 'nickname' && mod !== 'card') { + seal.replyToSender(ctx, msg, `帮助: +【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片`); + return ret; + } + const promises = ai.context.userInfoList.map(ui => ai.context.setName(epId, gid, ui.id, mod)); + Promise.all(promises).then(() => { + seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.userInfoList.map(uni => `${uni.name}(${uni.id})`).join('\n')}`); + }); + return ret; + } + case 'mod': { + const val3 = cmdArgs.getArgN(3); + const mod = parseInt(val3); + if (isNaN(mod) || mod < 0 || mod > 2) { + seal.replyToSender(ctx, msg, `帮助: +【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) +0: 不自动修改 +1: 自动修改为昵称 +2: 自动修改为群名片`); + return ret; + } + ai.context.autoNameMod = mod; + seal.replyToSender(ctx, msg, `设置成功,将自动修改上下文里的名字为${mod === 1 ? '昵称' : '群名片'}`); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai ctxn status】查看上下文里的名字和自动修改状态 +【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片 +【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) +0: 不自动修改 +1: 自动修改为昵称 +2: 自动修改为群名片`); + return ret; + } + } + } + case 'timer': { + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'list': { + seal.replyToSender(ctx, msg, TimerManager.getTimerListText(sid, page) || '当前对话没有定时器'); + return ret; + } + case 'clear': { + TimerManager.removeTimers(sid, '', [], []); + seal.replyToSender(ctx, msg, '所有定时器已清除'); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai timer lst】查看当前聊天定时器 +【.ai timer clr】清除当前聊天定时器`); + return ret; + } + } + } + case 'on': { + const setting = ai.setting; + + const kwargs = cmdArgs.kwargs; + if (kwargs.length == 0) { + seal.replyToSender(ctx, msg, `帮助: +【.ai on --<参数>=<数字>】 + +<参数>: +【c】计数器模式,接收消息数达到后触发 +单位/条,默认10条 +【t】计时器模式,最后一条消息后达到时限触发 +单位/秒,默认60秒 +【p】概率模式,每条消息按概率触发 +单位/%,默认10% +【a】活跃时间段和活跃次数 +格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5") + +【.ai on --t --p=42】使用示例`); + return ret; + } + + let text = `AI已开启:`; + for (const kwarg of kwargs) { + const name = kwarg.name; + const exist = kwarg.valueExists; + const valInt = parseInt(kwarg.value); + const valFloat = parseFloat(kwarg.value); + const valStr = kwarg.value.trim(); + + switch (name) { + case 'c': + case 'counter': { + ai.context.counter = 0; + setting.counter = exist && !isNaN(valInt) ? valInt : 10; + text += `\n计数器模式:${setting.counter}条`; + break; + } + case 't': + case 'timer': { + clearTimeout(ai.context.timer); + ai.context.timer = null; + setting.timer = exist && !isNaN(valFloat) ? valFloat : 60; + text += `\n计时器模式:${setting.timer}秒`; + break; + } + case 'p': + case 'prob': { + setting.prob = exist && !isNaN(valFloat) ? valFloat : 10; + text += `\n概率模式:${setting.prob}%`; + break; + } + case 'a': + case 'active': { + if (!exist) { + seal.replyToSender(ctx, msg, '请输入活跃时间段'); + return ret; + } + + const arr = valStr.split('-').map((item, index) => { + const parts = item.split(/[::,,]+/).map(Number).map(i => isNaN(i) ? 0 : i); + if (index < 2) { + return Math.ceil((parts[0] * 60 + (parts[1] || 0)) % (24 * 60)); + } else { + return parts[0]; + } + }) + + const [start = 0, end = 0, segs = 1] = arr; + + if (start === end) { + seal.replyToSender(ctx, msg, '活跃时间段开始时间和结束时间不能相同'); + return ret; + } + + if (!Number.isInteger(segs)) { + seal.replyToSender(ctx, msg, '活跃次数必须为整数'); + return ret; + } + + const endReal = end >= start ? end : end + 24 * 60; + if (segs > endReal - start) { + seal.replyToSender(ctx, msg, '活跃次数不能大于活跃时间段分钟数'); + return ret; + } + + TimerManager.removeTimers(sid, '', ['activeTime'], []); + setting.activeTimeInfo = { + start, + end, + segs, + } + + text += `\n活跃时间段:${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}`; + text += `\n活跃次数:${segs}`; + + const curSegIndex = ai.curActiveTimeSegIndex; + const nextTimePoint = ai.getNextTimePoint(curSegIndex); + if (nextTimePoint !== -1) { + TimerManager.addActiveTimeTimer(ctx, ai, nextTimePoint); + } + break; + } + } + }; + + setting.standby = true; + + seal.replyToSender(ctx, msg, text); + AIManager.saveAI(sid); + return ret; + } + case 'standby': { + const setting = ai.setting; + + ai.resetState(); + TimerManager.removeTimers(sid, '', ['activeTime'], []); + + setting.counter = -1; + setting.timer = -1; + setting.prob = -1; + setting.standby = true; + setting.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } + + seal.replyToSender(ctx, msg, 'AI已开启待机模式'); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + const setting = ai.setting; + + const kwargs = cmdArgs.kwargs; + if (kwargs.length == 0) { + ai.resetState(); + TimerManager.removeTimers(sid, '', ['activeTime'], []); + + setting.counter = -1; + setting.timer = -1; + setting.prob = -1; + setting.standby = false; + setting.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } + + seal.replyToSender(ctx, msg, 'AI已关闭'); + AIManager.saveAI(sid); + return ret; + } + + let text = `AI已关闭:`; + kwargs.forEach(kwarg => { + const name = kwarg.name; + + switch (name) { + case 'c': + case 'counter': { + ai.context.counter = 0; + setting.counter = -1; + text += `\n计数器模式`; + break; + } + case 't': + case 'timer': { + clearTimeout(ai.context.timer); + ai.context.timer = null; + setting.timer = -1; + text += `\n计时器模式`; + break; + } + case 'p': + case 'prob': { + setting.prob = -1; + text += `\n概率模式`; + break; + } + case 'a': + case 'active': { + TimerManager.removeTimers(sid, '', ['activeTime'], []); + setting.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } + text += `\n活跃时间段`; + break; + } + } + }); + + seal.replyToSender(ctx, msg, text); + AIManager.saveAI(sid); + return ret; + } + case 'forget': { + ai.resetState(); + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'assistant': { + ai.context.clearMessages('assistant', 'tool'); + seal.replyToSender(ctx, msg, 'ai上下文已清除'); + AIManager.saveAI(sid); + return ret; + } + case 'user': { + ai.context.clearMessages('user'); + seal.replyToSender(ctx, msg, '用户上下文已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + ai.context.clearMessages(); + seal.replyToSender(ctx, msg, '上下文已清除'); + AIManager.saveAI(sid); + return ret; + } + } + } + case 'role': { + const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; + const { roleName } = getRoleSetting(ctx); + const val2 = cmdArgs.getArgN(2); + if (!val2) { + seal.replyToSender(ctx, msg, `当前角色设定名称为[${roleName}],名称有:\n${roleSettingNames.join('、')}`); + return ret; + } + if (!roleSettingNames.includes(val2)) { + seal.replyToSender(ctx, msg, `【.ai role <名称>】切换角色设定\n角色设定名称错误,名称有:\n${roleSettingNames.join('、')}`); + return ret; + } + const roleSettingIndex = roleSettingNames.indexOf(val2); + if (roleSettingIndex < 0 || roleSettingIndex >= roleSettingTemplate.length) { + seal.replyToSender(ctx, msg, `角色设定名称[${val2}]没有对应的角色设定`); + } + seal.vars.strSet(ctx, "$gSYSPROMPT", val2); + seal.replyToSender(ctx, msg, `角色设定已切换到[${val2}]`); + return ret; + } + case 'image': { + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'list': { + const type = cmdArgs.getArgN(3); + switch (aliasToCmd(type)) { + case 'steal': { + seal.replyToSender(ctx, msg, ai.imageManager.getStolenImageListText(page) || '暂无偷取图片'); + return ret; + } + case 'local': { + seal.replyToSender(ctx, msg, ImageManager.getLocalImageListText(page) || '暂无本地图片'); + return ret; + } + default: { + seal.replyToSender(ctx, msg, '【.ai img list [stl/lcl]】展示偷取的图片/本地图片'); + return ret; + } + } + } + case 'steal': { + const op = cmdArgs.getArgN(3); + switch (aliasToCmd(op)) { + case 'on': { + ai.imageManager.stealStatus = true; + seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.imageManager.stolenImages.length}`); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + ai.imageManager.stealStatus = false; + seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.imageManager.stolenImages.length}`); + AIManager.saveAI(sid); + return ret; + } + case 'forget': { + ai.imageManager.stolenImages = []; + seal.replyToSender(ctx, msg, '偷取图片已遗忘'); + AIManager.saveAI(sid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `图片偷取状态:${ai.imageManager.stealStatus},当前偷取数量:${ai.imageManager.stolenImages.length}`); + return ret; + } + } + } + case 'itt': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + seal.replyToSender(ctx, msg, '【.ai img itt [图片] (附加提示词)】图片转文字'); + return ret; + } + const messageArray = transformTextToArray(val3); + transformArrayToContent(ctx, ai, messageArray).then(({ images }) => { + if (images.length === 0) seal.replyToSender(ctx, msg, '请附带图片'); + const img = images[0]; + img.imageToText(cmdArgs.getRestArgsFrom(4)) + .then(() => seal.replyToSender(ctx, msg, img.CQCode + `\n` + img.content)); + }); + return ret; + } + case 'find': { + const id = cmdArgs.getArgN(3); + if (!id) { + seal.replyToSender(ctx, msg, '【.ai img find <图片ID>】查找图片'); + return ret; + } + ai.context.findImage(ctx, id) + .then((img) => seal.replyToSender(ctx, msg, img ? img.CQCode : '未找到该图片')); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai img list [stl/lcl]】展示偷取的图片/本地图片 +【.ai img stl [on/off/f]】偷图 开启/关闭/遗忘 +【.ai img itt [图片] (附加提示词)】图片转文字 +【.ai img find <图片ID>】查找图片`); + return ret; + } + } + } + case 'memory': { + const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); + const muid = mctx.player.userId; + + const ai2 = AIManager.getAI(muid); + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'status': { + let ai3 = ai; + if (cmdArgs.at.length > 0 && (cmdArgs.at.length !== 1 || cmdArgs.at[0].userId !== epId)) { + ai3 = ai2; + } + const { isMemory, isShortMemory } = ConfigManager.memory; + seal.replyToSender(ctx, msg, `${ai3.id} +长期记忆开启状态: ${isMemory ? '是' : '否'} +长期记忆条数: ${ai3.memory.memoryIds.length} +关键词库: ${ai3.memory.keywords.join('、') || '无'} +短期记忆开启状态: ${(isShortMemory && ai3.memory.useShortMemory) ? '是' : '否'} +短期记忆条数: ${ai3.memory.shortMemoryList.length}`); + return ret; + } + case 'private': { + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'set': { + const s = cmdArgs.getRestArgsFrom(4); + switch (aliasToCmd(s)) { + case '': { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p st <内容>】设置个人设定,【.ai memo p st clr】清除个人设定'); + return ret; + } + case 'clear': { + ai2.memory.persona = '无'; + seal.replyToSender(ctx, msg, '设定已清除'); + AIManager.saveAI(muid); + return ret; + } + default: { + if (s.length > 20) { + seal.replyToSender(ctx, msg, '设定过长,请控制在20字以内'); + return ret; + } + ai2.memory.persona = s; + seal.replyToSender(ctx, msg, '设定已修改'); + AIManager.saveAI(muid); + return ret; + } + } + } + case 'delete': { + const idList = cmdArgs.args.slice(3); + const kw = cmdArgs.kwargs.map(item => item.name); + if (idList.length === 0 && kw.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p del --关键词1 --关键词2】删除个人记忆'); + return ret; + } + ai2.memory.deleteMemory(idList, kw); + seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ + isPrivate: true, + id: mctx.player.userId, + name: mctx.player.name + }, page) || '记忆已全部清除'); + AIManager.saveAI(muid); + return ret; + } + case 'list': { + seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ + isPrivate: true, + id: mctx.player.userId, + name: mctx.player.name + }, page) || '无记忆'); + return ret; + } + case 'clear': { + ai2.memory.clearMemory(); + seal.replyToSender(ctx, msg, '个人记忆已清除'); + AIManager.saveAI(muid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `参数缺失: +【.ai memo p st <内容>】设置个人设定 +【.ai memo p st clr】清除个人设定 +【.ai memo p del --关键词1 --关键词2】删除个人记忆 +【.ai memo p list】展示个人记忆 +【.ai memo p clr】清除个人记忆`); + return ret; + } + } + } + case 'group': { + if (ctx.isPrivate) { + seal.replyToSender(ctx, msg, '群聊记忆仅在群聊可用'); + return ret; + } + + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'set': { + const s = cmdArgs.getRestArgsFrom(4); + switch (aliasToCmd(s)) { + case '': { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g st <内容>】设置群聊设定,【.ai memo g st clr】清除群聊设定'); + return ret; + } + case 'clear': { + ai.memory.persona = '无'; + seal.replyToSender(ctx, msg, '设定已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + if (s.length > 30) { + seal.replyToSender(ctx, msg, '设定过长,请控制在30字以内'); + return ret; + } + ai.memory.persona = s; + seal.replyToSender(ctx, msg, '设定已修改'); + AIManager.saveAI(sid); + return ret; + } + } + } + case 'delete': { + const idList = cmdArgs.args.slice(3); + const kw = cmdArgs.kwargs.map(item => item.name); + if (idList.length === 0 && kw.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g del 】删除群聊记忆'); + return ret; + } + ai.memory.deleteMemory(idList, kw); + seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, page) || '记忆已全部清除'); + AIManager.saveAI(sid); + return ret; + } + case 'list': { + seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, page) || '无记忆'); + return ret; + } + case 'clear': { + ai.memory.clearMemory(); + seal.replyToSender(ctx, msg, '群聊记忆已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `参数缺失: +【.ai memo g st <内容>】设置群聊设定 +【.ai memo g st clr】清除群聊设定 +【.ai memo g del --关键词1 --关键词2】删除群聊记忆 +【.ai memo g list】展示群聊记忆 +【.ai memo g clr】清除群聊记忆`); + return ret; + } + } + } + case 'short': { + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'on': { + ai.memory.useShortMemory = true; + seal.replyToSender(ctx, msg, '短期记忆已开启'); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + ai.memory.useShortMemory = false; + seal.replyToSender(ctx, msg, '短期记忆已关闭'); + AIManager.saveAI(sid); + return ret; + } + case 'list': { + if (ai.memory.shortMemoryList.length === 0) { + seal.replyToSender(ctx, msg, '短期记忆为空'); + return ret; + } + seal.replyToSender(ctx, msg, ai.memory.shortMemoryList + .map((item, index) => `${index + 1}. ${item}`) + .slice((page - 1) * 10, page * 10) + .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); + return ret; + } + case 'clear': { + ai.memory.clearShortMemory(); + seal.replyToSender(ctx, msg, '短期记忆已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `参数缺失 +【.ai memo short list】展示短期记忆 +【.ai memo short clr】清除短期记忆 +【.ai memo short [on/off]】开启/关闭短期记忆`); + return ret; + } + } + } + case 'sum': { + ai.context.summaryCounter = 0; + ai.memory.updateShortMemory(ctx, msg, ai) + .then(() => { + seal.replyToSender(ctx, msg, ai.memory.shortMemoryList + .map((item, index) => `${index + 1}. ${item}`) + .slice((page - 1) * 10, page * 10) + .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); + }); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai memo status (@xxx)】查看记忆状态,@为查看个人记忆状态 +【.ai memo [p/g] st <内容>】设置个人/群聊设定 +【.ai memo [p/g] st clr】清除个人/群聊设定 +【.ai memo [p/g] del --关键词1 --关键词2】删除个人/群聊记忆 +【.ai memo [p/g/short] list】展示个人/群聊/短期记忆 +【.ai memo [p/g/short] clr】清除个人/群聊/短期记忆 +【.ai memo short [on/off]】开启/关闭短期记忆 +【.ai memo sum】立即总结一次短期记忆`); + return ret; + } + } + } + case 'tool': { + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'on': { + const val3 = cmdArgs.getArgN(3); + if (val3) { + const toolsNotAllow = ConfigManager.tool.toolsNotAllow; + if (toolsNotAllow.includes(val3)) { + seal.replyToSender(ctx, msg, `工具函数 ${val3} 不被允许开启`); + return ret; + } + + ai.tool.toolStatus[val3] = true; + seal.replyToSender(ctx, msg, `已开启工具函数 ${val3}`); + AIManager.saveAI(sid); + return ret; + } + const toolsNotAllow = ConfigManager.tool.toolsNotAllow; + for (const key in ai.tool.toolStatus) { + ai.tool.toolStatus[key] = toolsNotAllow.includes(key) ? false : true; + } + seal.replyToSender(ctx, msg, '已开启全部工具函数'); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + const val3 = cmdArgs.getArgN(3); + if (val3) { + ai.tool.toolStatus[val3] = false; + seal.replyToSender(ctx, msg, `已关闭工具函数 ${val3}`); + AIManager.saveAI(sid); + return ret; + } + for (const key in ai.tool.toolStatus) { + ai.tool.toolStatus[key] = false; + } + seal.replyToSender(ctx, msg, '已关闭全部工具函数'); + AIManager.saveAI(sid); + return ret; + } + case 'help': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + seal.replyToSender(ctx, msg, `帮助: +【.ai tool】列出所有工具 +【.ai tool [on/off] <函数名>】开启或关闭工具函数 +【.ai tool help <函数名>】查看工具详情 +【.ai tool call <函数名> --参数名=具体参数】试用工具函数`); + return ret; + } + + if (!ToolManager.toolMap.hasOwnProperty(val3)) { + seal.replyToSender(ctx, msg, '没有这个工具函数'); + return ret; + } + + const tool = ToolManager.toolMap[val3]; + const s = `${tool.info.function.name} +描述:${tool.info.function.description} + +参数信息: +${JSON.stringify(tool.info.function.parameters.properties, null, 2)} + +必需参数:${tool.info.function.parameters.required.join(',')}`; + + seal.replyToSender(ctx, msg, s); + return ret; + } + case 'call': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + seal.replyToSender(ctx, msg, `调用函数缺少工具函数名`); + return ret; + } + if (!ToolManager.toolMap.hasOwnProperty(val3)) { + seal.replyToSender(ctx, msg, `调用函数失败:未注册的函数:${val3}`); + return ret; + } + const tool = ToolManager.toolMap[val3]; + if (tool.cmdInfo.ext !== '' && ToolManager.cmdArgs == null) { + seal.replyToSender(ctx, msg, `暂时无法调用函数,请先使用 .r 指令`); + return ret; + } + + try { + const args = cmdArgs.kwargs.reduce((acc, kwarg) => { + const valueString = kwarg.value; + try { + acc[kwarg.name] = JSON.parse(`[${valueString}]`)[0]; + } catch (e) { + acc[kwarg.name] = valueString; + } + return acc; + }, {}); + + for (const key of tool.info.function.parameters.required) { + if (!args.hasOwnProperty(key)) { + logger.warning(`调用函数失败:缺少必需参数 ${key}`); + seal.replyToSender(ctx, msg, `调用函数失败:缺少必需参数 ${key}`); + return ret; + } + } + + tool.solve(ctx, msg, ai, args) + .then(({ content, images }) => seal.replyToSender(ctx, msg, `返回内容: +${content} +返回图片: +${images.map(img => img.CQCode).join('\n')}`)); + return ret; + } catch (e) { + const s = `调用函数 (${val3}) 失败:${e.message}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + } + default: { + const toolStatus = ai.tool.toolStatus; + + let i = 1; + let s = '工具函数如下:'; + Object.keys(toolStatus).forEach(key => { + const status = toolStatus[key] ? '开' : '关'; + s += `\n${i++}. ${key}[${status}]`; + }); + + seal.replyToSender(ctx, msg, s); + return ret; + } + } + } + case 'ignore': { + if (ctx.isPrivate) { + seal.replyToSender(ctx, msg, '忽略名单仅在群聊可用'); + return ret; + } + + const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); + const muid = cmdArgs.amIBeMentionedFirst ? epId : mctx.player.userId; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'add': { + if (cmdArgs.at.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai ign add @xxx】添加忽略名单'); + return ret; + } + if (ai.context.ignoreList.includes(muid)) { + seal.replyToSender(ctx, msg, '已经在忽略名单中'); + return ret; + } + ai.context.ignoreList.push(muid); + seal.replyToSender(ctx, msg, '已添加到忽略名单'); + AIManager.saveAI(sid); + return ret; + } + case 'remove': { + if (cmdArgs.at.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai ign rm @xxx】移除忽略名单'); + return ret; + } + if (!ai.context.ignoreList.includes(muid)) { + seal.replyToSender(ctx, msg, '不在忽略名单中'); + return ret; + } + ai.context.ignoreList = ai.context.ignoreList.filter(item => item !== muid); + seal.replyToSender(ctx, msg, '已从忽略名单中移除'); + AIManager.saveAI(sid); + return ret; + } + case 'list': { + const s = ai.context.ignoreList.length === 0 ? '忽略名单为空' : `忽略名单如下:\n${ai.context.ignoreList.join('\n')}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai ign add @xxx】添加忽略名单 +【.ai ign rm @xxx】移除忽略名单 +【.ai ign lst】列出忽略名单 + +忽略名单中的对象仍能正常对话,但无法被选中QQ号`); + return ret; + } + } + } + case 'token': { + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'list': { + const s = Object.keys(AIManager.usageMap).join('\n'); + seal.replyToSender(ctx, msg, `有使用记录的模型:\n${s}`); + return ret; + } + case 'sum': { + const usage = { + prompt_tokens: 0, + completion_tokens: 0 + }; + + for (const model in AIManager.usageMap) { + const modelUsage = AIManager.getModelUsage(model); + usage.prompt_tokens += modelUsage.prompt_tokens; + usage.completion_tokens += modelUsage.completion_tokens; + } + + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + const s = `输入token:${usage.prompt_tokens} +输出token:${usage.completion_tokens} +总token:${usage.prompt_tokens + usage.completion_tokens}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + case 'all': { + const s = Object.keys(AIManager.usageMap).map((model, index) => { + const usage = AIManager.getModelUsage(model); + + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return `${index + 1}. ${model}: 没有使用记录`; + } + + return `${index + 1}. ${model}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + if (!s) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + seal.replyToSender(ctx, msg, `全部使用记录如下:\n${s}`); + return ret; + } + case 'year': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentYM = currentYear * 12 + currentMonth; + for (const model in AIManager.usageMap) { + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, _] = key.split('-').map(v => parseInt(v)); + const ym = year * 12 + month; + + if (ym >= currentYM - 11 && ym <= currentYM) { + const key = `${year}-${month}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + } + + const val3 = cmdArgs.getArgN(3); + if (val3 === 'chart') { + get_chart_url('year', obj) + .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 + monthA) - (yearB * 12 + monthB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + if (!s) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); + return ret; + } + case 'month': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentDay = now.getDate(); + const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; + for (const model in AIManager.usageMap) { + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, day] = key.split('-').map(v => parseInt(v)); + const ymd = year * 12 * 31 + month * 31 + day; + + if (ymd >= currentYMD - 30 && ymd <= currentYMD) { + const key = `${year}-${month}-${day}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + } + + const val3 = cmdArgs.getArgN(3); + if (val3 === 'chart') { + get_chart_url('month', obj) + .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); + return ret; + } + case 'clear': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + AIManager.clearUsageMap(); + seal.replyToSender(ctx, msg, '已清除token使用记录'); + AIManager.saveUsageMap(); + return ret; + } + + if (!AIManager.usageMap.hasOwnProperty(val3)) { + seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); + return ret; + } + + delete AIManager.usageMap[val3]; + seal.replyToSender(ctx, msg, `已清除 ${val3} 的token使用记录`); + AIManager.saveUsageMap(); + return ret; + } + case '': + case 'help': { + seal.replyToSender(ctx, msg, `帮助: +【.ai tk lst】查看所有模型 +【.ai tk sum】查看所有模型的token使用记录总和 +【.ai tk all】查看所有模型的token使用记录 +【.ai tk [y/m] (chart)】查看所有模型今年/这个月的token使用记录 +【.ai tk <模型名称>】查看模型的token使用记录 +【.ai tk <模型名称> [y/m] (chart)】查看模型今年/这个月的token使用记录 +【.ai tk clr】清除token使用记录 +【.ai tk clr <模型名称>】清除token使用记录`); + return ret; + } + default: { + if (!AIManager.usageMap.hasOwnProperty(val2)) { + seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); + return ret; + } + + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'year': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentYM = currentYear * 12 + currentMonth; + const model = val2; + + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, _] = key.split('-').map(v => parseInt(v)); + const ym = year * 12 + month; + + if (ym >= currentYM - 11 && ym <= currentYM) { + const key = `${year}-${month}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + + const val4 = cmdArgs.getArgN(4); + if (val4 === 'chart') { + get_chart_url('year', obj) + .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 + monthA) - (yearB * 12 + monthB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + if (!s) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); + return ret; + } + case 'month': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentDay = now.getDate(); + const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; + const model = val2; + + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, day] = key.split('-').map(v => parseInt(v)); + const ymd = year * 12 * 31 + month * 31 + day; + + if (ymd >= currentYMD - 30 && ymd <= currentYMD) { + const key = `${year}-${month}-${day}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + + const val4 = cmdArgs.getArgN(4); + if (val4 === 'chart') { + get_chart_url('month', obj) + .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); + return ret; + } + default: { + const usage = AIManager.getModelUsage(val2); + + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + const s = `输入token:${usage.prompt_tokens} +输出token:${usage.completion_tokens} +总token:${usage.prompt_tokens + usage.completion_tokens}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + } + } + } + } + case 'shut': { + if (ai.stream.id === '') { + seal.replyToSender(ctx, msg, '当前没有正在进行的对话'); + return ret; + } + + ai.stopCurrentChatStream() + .then(() => seal.replyToSender(ctx, msg, '已停止当前对话')); + return ret; + } + default: { + ret.showHelp = true; + return ret; + } + } + } catch (e) { + logger.error(`指令.ai执行失败:${e.message}`); + seal.replyToSender(ctx, msg, `指令.ai执行失败:${e.message}`); + return seal.ext.newCmdExecuteResult(true); + } +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/privilege.ts b/src/cmd/sub_cmd/privilege.ts new file mode 100644 index 0000000..4ecff19 --- /dev/null +++ b/src/cmd/sub_cmd/privilege.ts @@ -0,0 +1,156 @@ +import { AIManager } from "../../AI/AI"; +import { HELPMAP } from "../../config/config"; +import { aliasToCmd } from "../../utils/utils"; +import { M, PrivilegeManager, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function getCmdPrivilege(): SubCmd { + const cmd = new SubCmd('privilege'); + cmd.help = `帮助: +【.ai priv ses st <会话权限>】修改会话权限 +【.ai priv ses ck 】检查会话权限 +【.ai priv st <指令> <权限限制>】修改指令权限 +【.ai priv show <指令>】检查指令权限 +【.ai priv reset】重置指令权限 +${HELPMAP["ID"]} +${HELPMAP["会话权限"]} +${HELPMAP["指令"]} +${HELPMAP["权限限制"]}`; + cmd.priv = { + priv: M, args: { + session: { + priv: U, args: { + set: { priv: U }, + check: { priv: U } + } + }, + set: { priv: U }, + show: { priv: U }, + reset: { priv: U } + } + }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, ret } = scc; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'session': { + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'set': { + const val4 = cmdArgs.getArgN(4); + if (!val4 || val4 == 'help') { + seal.replyToSender(ctx, msg, `帮助: +【.ai priv ses st <会话权限>】修改会话权限 +${HELPMAP["ID"]} +${HELPMAP["会话权限"]}`); + return ret; + } + + const val5 = cmdArgs.getArgN(5); + const limit = parseInt(val5); + if (isNaN(limit)) { + seal.replyToSender(ctx, msg, '权限值必须为数字'); + return ret; + } + + const id2 = val4 === 'now' ? sid : val4; + const ai2 = AIManager.getAI(id2); + + ai2.setting.priv = limit; + + seal.replyToSender(ctx, msg, '权限修改完成'); + AIManager.saveAI(id2); + return ret; + } + case 'check': { + const val4 = cmdArgs.getArgN(4); + if (!val4 || val4 == 'help') { + seal.replyToSender(ctx, msg, `帮助: +【.ai priv ses ck 】检查会话权限 +${HELPMAP["ID"]}`); + return ret; + } + + const id2 = val4 === 'now' ? sid : val4; + const ai2 = AIManager.getAI(id2); + seal.replyToSender(ctx, msg, `${id2}\n会话权限:${ai2.setting.priv}`); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai priv ses st <会话权限>】修改会话权限 +【.ai priv ses ck 】检查会话权限 +${HELPMAP["ID"]} +${HELPMAP["会话权限"]}`); + return ret; + } + } + } + case 'set': { + const val3 = cmdArgs.getArgN(3); + if (!val3 || val3 == 'help') { + seal.replyToSender(ctx, msg, `帮助: +【.ai priv st <指令> <权限限制>】修改指令权限 +${HELPMAP["指令"]} +${HELPMAP["权限限制"]}`); + return ret; + } + const cmdChain = val3.split('-').map(cmd => aliasToCmd(cmd)); + if (cmdChain?.[1] === 'privilege') { + seal.replyToSender(ctx, msg, `你不能修改priv指令的权限`); + return ret; + } + const cpi = PrivilegeManager.getCmdPrivInfo(cmdChain); + if (!cpi) { + seal.replyToSender(ctx, msg, `指令${val3}不存在`); + return ret; + } + const val4 = cmdArgs.getArgN(4); + const priv = val4.split('-').map(p => parseInt(p)); + if (priv.length !== 3) { + seal.replyToSender(ctx, msg, '权限值必须为3个数字'); + return ret; + } + for (const p of priv) { + if (isNaN(p)) { + seal.replyToSender(ctx, msg, '权限值必须为数字'); + return ret; + } + } + cpi.priv = priv as [number, number, number]; + PrivilegeManager.saveCmdPriv(); + seal.replyToSender(ctx, msg, '权限修改完成'); + return ret; + } + case 'show': { + const val3 = cmdArgs.getArgN(3); + if (!val3 || val3 == 'help') { + seal.replyToSender(ctx, msg, `帮助: +【.ai priv show <指令>】检查指令权限 +${HELPMAP["指令"]}`); + return ret; + } + const cmdChain = val3.split('-'); + const cpi = PrivilegeManager.getCmdPrivInfo(cmdChain); + if (!cpi) { + seal.replyToSender(ctx, msg, `指令${val3}不存在`); + return ret; + } + seal.replyToSender(ctx, msg, `指令${val3}权限限制:${cpi.priv.join('-')}`); + return ret; + } + case 'reset': { + PrivilegeManager.resetCmdPriv(); + seal.replyToSender(ctx, msg, '指令权限重置完成'); + return ret; + } + default: { + seal.replyToSender(ctx, msg, cmd.help); + return ret; + } + } + } + + return cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/prompt.ts b/src/cmd/sub_cmd/prompt.ts new file mode 100644 index 0000000..6d34565 --- /dev/null +++ b/src/cmd/sub_cmd/prompt.ts @@ -0,0 +1,19 @@ +import { logger } from "../../logger"; +import { buildSystemMessage } from "../../utils/utils_message"; +import { M } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function getCmdPrompt(): SubCmd { + const cmd = new SubCmd('prompt'); + cmd.help = ''; + cmd.priv = { priv: M }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, ai, ret } = scc; + const systemMessage = await buildSystemMessage(ctx, ai); + logger.info(`system prompt:\n`, systemMessage.msgArray[0].content); + seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); + return ret; + } + + return cmd; +} diff --git a/src/index.ts b/src/index.ts index dbf04bb..2e1087a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,16 @@ import { AIManager } from "./AI/AI"; import { ToolManager } from "./tool/tool"; import { ConfigManager } from "./config/configManager"; -import { buildSystemMessage, getRoleSetting } from "./utils/utils_message"; import { triggerConditionMap } from "./tool/tool_trigger"; import { logger } from "./logger"; -import { transformArrayToContent, transformTextToArray } from "./utils/utils_string"; +import { transformTextToArray } from "./utils/utils_string"; import { checkUpdate } from "./utils/utils_update"; -import { get_chart_url } from "./service"; import { TimerManager } from "./timer"; import { createMsg } from "./utils/utils_seal"; -import { PrivilegeManager } from "./privilege"; -import { aliasToCmd } from "./utils/utils"; +import { PrivilegeManager } from "./cmd/privilege"; import { knowledgeMM } from "./AI/memory"; -import { HELPMAP, CQTYPESALLOW } from "./config/config"; -import { ImageManager } from "./AI/image"; +import { CQTYPESALLOW } from "./config/config"; +import { cmd } from "./cmd/root"; function main() { ConfigManager.registerConfig(); @@ -25,1410 +22,9 @@ function main() { const ext = ConfigManager.ext; - const cmdAI = seal.ext.newCmdItemInfo(); - cmdAI.name = 'ai'; - cmdAI.help = `帮助: -【.ai priv】权限相关 -【.ai prompt】查看system prompt -【.ai status】查看当前AI状态 -【.ai ctxn】上下文名字相关 -【.ai timer】定时器相关 -【.ai on】开启AI -【.ai sb】开启待机模式,此时AI将记录聊天内容 -【.ai off】关闭AI,此时仍能用正则匹配触发 -【.ai fgt】遗忘上下文 -【.ai role】角色设定相关 -【.ai img】图片相关 -【.ai memo】AI的记忆相关 -【.ai tool】AI的工具相关 -【.ai ign】AI的忽略名单相关 -【.ai tk】AI的token相关 -【.ai shut】终止AI当前流式输出`; - cmdAI.allowDelegate = true; - cmdAI.solve = (ctx, msg, cmdArgs) => { - try { - const val = cmdArgs.getArgN(1); - const uid = ctx.player.userId; - const gid = ctx.group.groupId; - const epId = ctx.endPoint.userId; - const sid = ctx.isPrivate ? uid : gid; - - const ret = seal.ext.newCmdExecuteResult(true); - const ai = AIManager.getAI(sid); - const { success, exist } = PrivilegeManager.checkPriv(ctx, cmdArgs, ai); - if (!success) { - seal.replyToSender(ctx, msg, exist ? '权限不足' : '命令不存在'); - return ret; - } - - let page = 1; - const kwargPage = cmdArgs.kwargs.find((kwarg) => kwarg.name === 'page' || kwarg.name === 'p'); - if (kwargPage && kwargPage.valueExists) { - page = parseInt(kwargPage.value); - if (isNaN(page)) { - seal.replyToSender(ctx, msg, '页码必须为数字'); - return ret; - } - if (page < 1) { - seal.replyToSender(ctx, msg, '页码必须大于等于1'); - return ret; - } - } - - switch (aliasToCmd(val)) { - case 'privilege': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'session': { - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'set': { - const val4 = cmdArgs.getArgN(4); - if (!val4 || val4 == 'help') { - seal.replyToSender(ctx, msg, `帮助: -【.ai priv ses st <会话权限>】修改会话权限 -${HELPMAP["ID"]} -${HELPMAP["会话权限"]}`); - return ret; - } - - const val5 = cmdArgs.getArgN(5); - const limit = parseInt(val5); - if (isNaN(limit)) { - seal.replyToSender(ctx, msg, '权限值必须为数字'); - return ret; - } - - const id2 = val4 === 'now' ? sid : val4; - const ai2 = AIManager.getAI(id2); - - ai2.setting.priv = limit; - - seal.replyToSender(ctx, msg, '权限修改完成'); - AIManager.saveAI(id2); - return ret; - } - case 'check': { - const val4 = cmdArgs.getArgN(4); - if (!val4 || val4 == 'help') { - seal.replyToSender(ctx, msg, `帮助: -【.ai priv ses ck 】检查会话权限 -${HELPMAP["ID"]}`); - return ret; - } - - const id2 = val4 === 'now' ? sid : val4; - const ai2 = AIManager.getAI(id2); - seal.replyToSender(ctx, msg, `${id2}\n会话权限:${ai2.setting.priv}`); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai priv ses st <会话权限>】修改会话权限 -【.ai priv ses ck 】检查会话权限 -${HELPMAP["ID"]} -${HELPMAP["会话权限"]}`); - return ret; - } - } - } - case 'set': { - const val3 = cmdArgs.getArgN(3); - if (!val3 || val3 == 'help') { - seal.replyToSender(ctx, msg, `帮助: -【.ai priv st <指令> <权限限制>】修改指令权限 -${HELPMAP["指令"]} -${HELPMAP["权限限制"]}`); - return ret; - } - const cmdChain = val3.split('-').map(cmd => aliasToCmd(cmd)); - if (cmdChain?.[1] === 'privilege') { - seal.replyToSender(ctx, msg, `你不能修改priv指令的权限`); - return ret; - } - const cpi = PrivilegeManager.getCmdPrivInfo(cmdChain); - if (!cpi) { - seal.replyToSender(ctx, msg, `指令${val3}不存在`); - return ret; - } - const val4 = cmdArgs.getArgN(4); - const priv = val4.split('-').map(p => parseInt(p)); - if (priv.length !== 3) { - seal.replyToSender(ctx, msg, '权限值必须为3个数字'); - return ret; - } - for (const p of priv) { - if (isNaN(p)) { - seal.replyToSender(ctx, msg, '权限值必须为数字'); - return ret; - } - } - cpi.priv = priv as [number, number, number]; - PrivilegeManager.saveCmdPriv(); - seal.replyToSender(ctx, msg, '权限修改完成'); - return ret; - } - case 'show': { - const val3 = cmdArgs.getArgN(3); - if (!val3 || val3 == 'help') { - seal.replyToSender(ctx, msg, `帮助: -【.ai priv show <指令>】检查指令权限 -${HELPMAP["指令"]}`); - return ret; - } - const cmdChain = val3.split('-'); - const cpi = PrivilegeManager.getCmdPrivInfo(cmdChain); - if (!cpi) { - seal.replyToSender(ctx, msg, `指令${val3}不存在`); - return ret; - } - seal.replyToSender(ctx, msg, `指令${val3}权限限制:${cpi.priv.join('-')}`); - return ret; - } - case 'reset': { - PrivilegeManager.resetCmdPriv(); - seal.replyToSender(ctx, msg, '指令权限重置完成'); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai priv ses st <会话权限>】修改会话权限 -【.ai priv ses ck 】检查会话权限 -【.ai priv st <指令> <权限限制>】修改指令权限 -【.ai priv show <指令>】检查指令权限 -【.ai priv reset】重置指令权限 -${HELPMAP["ID"]} -${HELPMAP["会话权限"]} -${HELPMAP["指令"]} -${HELPMAP["权限限制"]}`); - return ret; - } - } - } - case 'prompt': { - buildSystemMessage(ctx, ai).then(systemMessage => { - logger.info(`system prompt:\n`, systemMessage.msgArray[0].content); - seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); - }); - return ret; - } - case 'status': { - const setting = ai.setting; - const { start, end, segs } = setting.activeTimeInfo; - - seal.replyToSender(ctx, msg, `${sid} -权限: ${setting.priv} -上下文轮数: ${ai.context.messages.filter(m => m.role === 'user').length} -计数器模式(c): ${setting.counter > -1 ? `${setting.counter}条` : '关闭'} -计时器模式(t): ${setting.timer > -1 ? `${setting.timer}秒` : '关闭'} -概率模式(p): ${setting.prob > -1 ? `${setting.prob}%` : '关闭'} -活跃时间段: ${(start !== 0 || end !== 0) ? `${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}` : '未设置'} -活跃次数: ${segs > 0 ? segs : '未设置'} -待机模式: ${setting.standby ? '开启' : '关闭'}`); - return ret; - } - case 'ctxn': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'status': { - seal.replyToSender(ctx, msg, `自动修改上下文里的名字状态:${ai.context.autoNameMod} -上下文里的名字有:\n${ai.context.userInfoList.map(ui => `${ui.name}(${ui.id})`).join('\n')}`); - return ret; - } - case 'set': { - const val3 = cmdArgs.getArgN(3); - const mod = aliasToCmd(val3); - if (mod !== 'nickname' && mod !== 'card') { - seal.replyToSender(ctx, msg, `帮助: -【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片`); - return ret; - } - const promises = ai.context.userInfoList.map(ui => ai.context.setName(epId, gid, ui.id, mod)); - Promise.all(promises).then(() => { - seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.userInfoList.map(uni => `${uni.name}(${uni.id})`).join('\n')}`); - }); - return ret; - } - case 'mod': { - const val3 = cmdArgs.getArgN(3); - const mod = parseInt(val3); - if (isNaN(mod) || mod < 0 || mod > 2) { - seal.replyToSender(ctx, msg, `帮助: -【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) -0: 不自动修改 -1: 自动修改为昵称 -2: 自动修改为群名片`); - return ret; - } - ai.context.autoNameMod = mod; - seal.replyToSender(ctx, msg, `设置成功,将自动修改上下文里的名字为${mod === 1 ? '昵称' : '群名片'}`); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai ctxn status】查看上下文里的名字和自动修改状态 -【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片 -【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) -0: 不自动修改 -1: 自动修改为昵称 -2: 自动修改为群名片`); - return ret; - } - } - } - case 'timer': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'list': { - seal.replyToSender(ctx, msg, TimerManager.getTimerListText(sid, page) || '当前对话没有定时器'); - return ret; - } - case 'clear': { - TimerManager.removeTimers(sid, '', [], []); - seal.replyToSender(ctx, msg, '所有定时器已清除'); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai timer lst】查看当前聊天定时器 -【.ai timer clr】清除当前聊天定时器`); - return ret; - } - } - } - case 'on': { - const setting = ai.setting; - - const kwargs = cmdArgs.kwargs; - if (kwargs.length == 0) { - seal.replyToSender(ctx, msg, `帮助: -【.ai on --<参数>=<数字>】 - -<参数>: -【c】计数器模式,接收消息数达到后触发 -单位/条,默认10条 -【t】计时器模式,最后一条消息后达到时限触发 -单位/秒,默认60秒 -【p】概率模式,每条消息按概率触发 -单位/%,默认10% -【a】活跃时间段和活跃次数 -格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5") - -【.ai on --t --p=42】使用示例`); - return ret; - } - - let text = `AI已开启:`; - for (const kwarg of kwargs) { - const name = kwarg.name; - const exist = kwarg.valueExists; - const valInt = parseInt(kwarg.value); - const valFloat = parseFloat(kwarg.value); - const valStr = kwarg.value.trim(); - - switch (name) { - case 'c': - case 'counter': { - ai.context.counter = 0; - setting.counter = exist && !isNaN(valInt) ? valInt : 10; - text += `\n计数器模式:${setting.counter}条`; - break; - } - case 't': - case 'timer': { - clearTimeout(ai.context.timer); - ai.context.timer = null; - setting.timer = exist && !isNaN(valFloat) ? valFloat : 60; - text += `\n计时器模式:${setting.timer}秒`; - break; - } - case 'p': - case 'prob': { - setting.prob = exist && !isNaN(valFloat) ? valFloat : 10; - text += `\n概率模式:${setting.prob}%`; - break; - } - case 'a': - case 'active': { - if (!exist) { - seal.replyToSender(ctx, msg, '请输入活跃时间段'); - return ret; - } - - const arr = valStr.split('-').map((item, index) => { - const parts = item.split(/[::,,]+/).map(Number).map(i => isNaN(i) ? 0 : i); - if (index < 2) { - return Math.ceil((parts[0] * 60 + (parts[1] || 0)) % (24 * 60)); - } else { - return parts[0]; - } - }) - - const [start = 0, end = 0, segs = 1] = arr; - - if (start === end) { - seal.replyToSender(ctx, msg, '活跃时间段开始时间和结束时间不能相同'); - return ret; - } - - if (!Number.isInteger(segs)) { - seal.replyToSender(ctx, msg, '活跃次数必须为整数'); - return ret; - } - - const endReal = end >= start ? end : end + 24 * 60; - if (segs > endReal - start) { - seal.replyToSender(ctx, msg, '活跃次数不能大于活跃时间段分钟数'); - return ret; - } - - TimerManager.removeTimers(sid, '', ['activeTime'], []); - setting.activeTimeInfo = { - start, - end, - segs, - } - - text += `\n活跃时间段:${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}`; - text += `\n活跃次数:${segs}`; - - const curSegIndex = ai.curActiveTimeSegIndex; - const nextTimePoint = ai.getNextTimePoint(curSegIndex); - if (nextTimePoint !== -1) { - TimerManager.addActiveTimeTimer(ctx, ai, nextTimePoint); - } - break; - } - } - }; - - setting.standby = true; - - seal.replyToSender(ctx, msg, text); - AIManager.saveAI(sid); - return ret; - } - case 'standby': { - const setting = ai.setting; - - ai.resetState(); - TimerManager.removeTimers(sid, '', ['activeTime'], []); - - setting.counter = -1; - setting.timer = -1; - setting.prob = -1; - setting.standby = true; - setting.activeTimeInfo = { - start: 0, - end: 0, - segs: 0, - } - - seal.replyToSender(ctx, msg, 'AI已开启待机模式'); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - const setting = ai.setting; - - const kwargs = cmdArgs.kwargs; - if (kwargs.length == 0) { - ai.resetState(); - TimerManager.removeTimers(sid, '', ['activeTime'], []); - - setting.counter = -1; - setting.timer = -1; - setting.prob = -1; - setting.standby = false; - setting.activeTimeInfo = { - start: 0, - end: 0, - segs: 0, - } - - seal.replyToSender(ctx, msg, 'AI已关闭'); - AIManager.saveAI(sid); - return ret; - } - - let text = `AI已关闭:`; - kwargs.forEach(kwarg => { - const name = kwarg.name; - - switch (name) { - case 'c': - case 'counter': { - ai.context.counter = 0; - setting.counter = -1; - text += `\n计数器模式`; - break; - } - case 't': - case 'timer': { - clearTimeout(ai.context.timer); - ai.context.timer = null; - setting.timer = -1; - text += `\n计时器模式`; - break; - } - case 'p': - case 'prob': { - setting.prob = -1; - text += `\n概率模式`; - break; - } - case 'a': - case 'active': { - TimerManager.removeTimers(sid, '', ['activeTime'], []); - setting.activeTimeInfo = { - start: 0, - end: 0, - segs: 0, - } - text += `\n活跃时间段`; - break; - } - } - }); - - seal.replyToSender(ctx, msg, text); - AIManager.saveAI(sid); - return ret; - } - case 'forget': { - ai.resetState(); - - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'assistant': { - ai.context.clearMessages('assistant', 'tool'); - seal.replyToSender(ctx, msg, 'ai上下文已清除'); - AIManager.saveAI(sid); - return ret; - } - case 'user': { - ai.context.clearMessages('user'); - seal.replyToSender(ctx, msg, '用户上下文已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - ai.context.clearMessages(); - seal.replyToSender(ctx, msg, '上下文已清除'); - AIManager.saveAI(sid); - return ret; - } - } - } - case 'role': { - const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; - const { roleName } = getRoleSetting(ctx); - const val2 = cmdArgs.getArgN(2); - if (!val2) { - seal.replyToSender(ctx, msg, `当前角色设定名称为[${roleName}],名称有:\n${roleSettingNames.join('、')}`); - return ret; - } - if (!roleSettingNames.includes(val2)) { - seal.replyToSender(ctx, msg, `【.ai role <名称>】切换角色设定\n角色设定名称错误,名称有:\n${roleSettingNames.join('、')}`); - return ret; - } - const roleSettingIndex = roleSettingNames.indexOf(val2); - if (roleSettingIndex < 0 || roleSettingIndex >= roleSettingTemplate.length) { - seal.replyToSender(ctx, msg, `角色设定名称[${val2}]没有对应的角色设定`); - } - seal.vars.strSet(ctx, "$gSYSPROMPT", val2); - seal.replyToSender(ctx, msg, `角色设定已切换到[${val2}]`); - return ret; - } - case 'image': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'list': { - const type = cmdArgs.getArgN(3); - switch (aliasToCmd(type)) { - case 'steal': { - seal.replyToSender(ctx, msg, ai.imageManager.getStolenImageListText(page) || '暂无偷取图片'); - return ret; - } - case 'local': { - seal.replyToSender(ctx, msg, ImageManager.getLocalImageListText(page) || '暂无本地图片'); - return ret; - } - default: { - seal.replyToSender(ctx, msg, '【.ai img list [stl/lcl]】展示偷取的图片/本地图片'); - return ret; - } - } - } - case 'steal': { - const op = cmdArgs.getArgN(3); - switch (aliasToCmd(op)) { - case 'on': { - ai.imageManager.stealStatus = true; - seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.imageManager.stolenImages.length}`); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - ai.imageManager.stealStatus = false; - seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.imageManager.stolenImages.length}`); - AIManager.saveAI(sid); - return ret; - } - case 'forget': { - ai.imageManager.stolenImages = []; - seal.replyToSender(ctx, msg, '偷取图片已遗忘'); - AIManager.saveAI(sid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `图片偷取状态:${ai.imageManager.stealStatus},当前偷取数量:${ai.imageManager.stolenImages.length}`); - return ret; - } - } - } - case 'itt': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - seal.replyToSender(ctx, msg, '【.ai img itt [图片] (附加提示词)】图片转文字'); - return ret; - } - const messageArray = transformTextToArray(val3); - transformArrayToContent(ctx, ai, messageArray).then(({ images }) => { - if (images.length === 0) seal.replyToSender(ctx, msg, '请附带图片'); - const img = images[0]; - img.imageToText(cmdArgs.getRestArgsFrom(4)) - .then(() => seal.replyToSender(ctx, msg, img.CQCode + `\n` + img.content)); - }); - return ret; - } - case 'find': { - const id = cmdArgs.getArgN(3); - if (!id) { - seal.replyToSender(ctx, msg, '【.ai img find <图片ID>】查找图片'); - return ret; - } - ai.context.findImage(ctx, id) - .then((img) => seal.replyToSender(ctx, msg, img ? img.CQCode : '未找到该图片')); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai img list [stl/lcl]】展示偷取的图片/本地图片 -【.ai img stl [on/off/f]】偷图 开启/关闭/遗忘 -【.ai img itt [图片] (附加提示词)】图片转文字 -【.ai img find <图片ID>】查找图片`); - return ret; - } - } - } - case 'memory': { - const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); - const muid = mctx.player.userId; - - const ai2 = AIManager.getAI(muid); - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'status': { - let ai3 = ai; - if (cmdArgs.at.length > 0 && (cmdArgs.at.length !== 1 || cmdArgs.at[0].userId !== epId)) { - ai3 = ai2; - } - const { isMemory, isShortMemory } = ConfigManager.memory; - seal.replyToSender(ctx, msg, `${ai3.id} -长期记忆开启状态: ${isMemory ? '是' : '否'} -长期记忆条数: ${ai3.memory.memoryIds.length} -关键词库: ${ai3.memory.keywords.join('、') || '无'} -短期记忆开启状态: ${(isShortMemory && ai3.memory.useShortMemory) ? '是' : '否'} -短期记忆条数: ${ai3.memory.shortMemoryList.length}`); - return ret; - } - case 'private': { - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'set': { - const s = cmdArgs.getRestArgsFrom(4); - switch (aliasToCmd(s)) { - case '': { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p st <内容>】设置个人设定,【.ai memo p st clr】清除个人设定'); - return ret; - } - case 'clear': { - ai2.memory.persona = '无'; - seal.replyToSender(ctx, msg, '设定已清除'); - AIManager.saveAI(muid); - return ret; - } - default: { - if (s.length > 20) { - seal.replyToSender(ctx, msg, '设定过长,请控制在20字以内'); - return ret; - } - ai2.memory.persona = s; - seal.replyToSender(ctx, msg, '设定已修改'); - AIManager.saveAI(muid); - return ret; - } - } - } - case 'delete': { - const idList = cmdArgs.args.slice(3); - const kw = cmdArgs.kwargs.map(item => item.name); - if (idList.length === 0 && kw.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p del --关键词1 --关键词2】删除个人记忆'); - return ret; - } - ai2.memory.deleteMemory(idList, kw); - seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ - isPrivate: true, - id: mctx.player.userId, - name: mctx.player.name - }, page) || '记忆已全部清除'); - AIManager.saveAI(muid); - return ret; - } - case 'list': { - seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ - isPrivate: true, - id: mctx.player.userId, - name: mctx.player.name - }, page) || '无记忆'); - return ret; - } - case 'clear': { - ai2.memory.clearMemory(); - seal.replyToSender(ctx, msg, '个人记忆已清除'); - AIManager.saveAI(muid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `参数缺失: -【.ai memo p st <内容>】设置个人设定 -【.ai memo p st clr】清除个人设定 -【.ai memo p del --关键词1 --关键词2】删除个人记忆 -【.ai memo p list】展示个人记忆 -【.ai memo p clr】清除个人记忆`); - return ret; - } - } - } - case 'group': { - if (ctx.isPrivate) { - seal.replyToSender(ctx, msg, '群聊记忆仅在群聊可用'); - return ret; - } - - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'set': { - const s = cmdArgs.getRestArgsFrom(4); - switch (aliasToCmd(s)) { - case '': { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g st <内容>】设置群聊设定,【.ai memo g st clr】清除群聊设定'); - return ret; - } - case 'clear': { - ai.memory.persona = '无'; - seal.replyToSender(ctx, msg, '设定已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - if (s.length > 30) { - seal.replyToSender(ctx, msg, '设定过长,请控制在30字以内'); - return ret; - } - ai.memory.persona = s; - seal.replyToSender(ctx, msg, '设定已修改'); - AIManager.saveAI(sid); - return ret; - } - } - } - case 'delete': { - const idList = cmdArgs.args.slice(3); - const kw = cmdArgs.kwargs.map(item => item.name); - if (idList.length === 0 && kw.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g del 】删除群聊记忆'); - return ret; - } - ai.memory.deleteMemory(idList, kw); - seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ - isPrivate: false, - id: ctx.group.groupId, - name: ctx.group.groupName - }, page) || '记忆已全部清除'); - AIManager.saveAI(sid); - return ret; - } - case 'list': { - seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ - isPrivate: false, - id: ctx.group.groupId, - name: ctx.group.groupName - }, page) || '无记忆'); - return ret; - } - case 'clear': { - ai.memory.clearMemory(); - seal.replyToSender(ctx, msg, '群聊记忆已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `参数缺失: -【.ai memo g st <内容>】设置群聊设定 -【.ai memo g st clr】清除群聊设定 -【.ai memo g del --关键词1 --关键词2】删除群聊记忆 -【.ai memo g list】展示群聊记忆 -【.ai memo g clr】清除群聊记忆`); - return ret; - } - } - } - case 'short': { - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'on': { - ai.memory.useShortMemory = true; - seal.replyToSender(ctx, msg, '短期记忆已开启'); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - ai.memory.useShortMemory = false; - seal.replyToSender(ctx, msg, '短期记忆已关闭'); - AIManager.saveAI(sid); - return ret; - } - case 'list': { - if (ai.memory.shortMemoryList.length === 0) { - seal.replyToSender(ctx, msg, '短期记忆为空'); - return ret; - } - seal.replyToSender(ctx, msg, ai.memory.shortMemoryList - .map((item, index) => `${index + 1}. ${item}`) - .slice((page - 1) * 10, page * 10) - .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); - return ret; - } - case 'clear': { - ai.memory.clearShortMemory(); - seal.replyToSender(ctx, msg, '短期记忆已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `参数缺失 -【.ai memo short list】展示短期记忆 -【.ai memo short clr】清除短期记忆 -【.ai memo short [on/off]】开启/关闭短期记忆`); - return ret; - } - } - } - case 'sum': { - ai.context.summaryCounter = 0; - ai.memory.updateShortMemory(ctx, msg, ai) - .then(() => { - seal.replyToSender(ctx, msg, ai.memory.shortMemoryList - .map((item, index) => `${index + 1}. ${item}`) - .slice((page - 1) * 10, page * 10) - .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); - }); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai memo status (@xxx)】查看记忆状态,@为查看个人记忆状态 -【.ai memo [p/g] st <内容>】设置个人/群聊设定 -【.ai memo [p/g] st clr】清除个人/群聊设定 -【.ai memo [p/g] del --关键词1 --关键词2】删除个人/群聊记忆 -【.ai memo [p/g/short] list】展示个人/群聊/短期记忆 -【.ai memo [p/g/short] clr】清除个人/群聊/短期记忆 -【.ai memo short [on/off]】开启/关闭短期记忆 -【.ai memo sum】立即总结一次短期记忆`); - return ret; - } - } - } - case 'tool': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'on': { - const val3 = cmdArgs.getArgN(3); - if (val3) { - const toolsNotAllow = ConfigManager.tool.toolsNotAllow; - if (toolsNotAllow.includes(val3)) { - seal.replyToSender(ctx, msg, `工具函数 ${val3} 不被允许开启`); - return ret; - } - - ai.tool.toolStatus[val3] = true; - seal.replyToSender(ctx, msg, `已开启工具函数 ${val3}`); - AIManager.saveAI(sid); - return ret; - } - const toolsNotAllow = ConfigManager.tool.toolsNotAllow; - for (const key in ai.tool.toolStatus) { - ai.tool.toolStatus[key] = toolsNotAllow.includes(key) ? false : true; - } - seal.replyToSender(ctx, msg, '已开启全部工具函数'); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - const val3 = cmdArgs.getArgN(3); - if (val3) { - ai.tool.toolStatus[val3] = false; - seal.replyToSender(ctx, msg, `已关闭工具函数 ${val3}`); - AIManager.saveAI(sid); - return ret; - } - for (const key in ai.tool.toolStatus) { - ai.tool.toolStatus[key] = false; - } - seal.replyToSender(ctx, msg, '已关闭全部工具函数'); - AIManager.saveAI(sid); - return ret; - } - case 'help': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - seal.replyToSender(ctx, msg, `帮助: -【.ai tool】列出所有工具 -【.ai tool [on/off] <函数名>】开启或关闭工具函数 -【.ai tool help <函数名>】查看工具详情 -【.ai tool call <函数名> --参数名=具体参数】试用工具函数`); - return ret; - } - - if (!ToolManager.toolMap.hasOwnProperty(val3)) { - seal.replyToSender(ctx, msg, '没有这个工具函数'); - return ret; - } - - const tool = ToolManager.toolMap[val3]; - const s = `${tool.info.function.name} -描述:${tool.info.function.description} - -参数信息: -${JSON.stringify(tool.info.function.parameters.properties, null, 2)} - -必需参数:${tool.info.function.parameters.required.join(',')}`; - - seal.replyToSender(ctx, msg, s); - return ret; - } - case 'call': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - seal.replyToSender(ctx, msg, `调用函数缺少工具函数名`); - return ret; - } - if (!ToolManager.toolMap.hasOwnProperty(val3)) { - seal.replyToSender(ctx, msg, `调用函数失败:未注册的函数:${val3}`); - return ret; - } - const tool = ToolManager.toolMap[val3]; - if (tool.cmdInfo.ext !== '' && ToolManager.cmdArgs == null) { - seal.replyToSender(ctx, msg, `暂时无法调用函数,请先使用 .r 指令`); - return ret; - } - - try { - const args = cmdArgs.kwargs.reduce((acc, kwarg) => { - const valueString = kwarg.value; - try { - acc[kwarg.name] = JSON.parse(`[${valueString}]`)[0]; - } catch (e) { - acc[kwarg.name] = valueString; - } - return acc; - }, {}); - - for (const key of tool.info.function.parameters.required) { - if (!args.hasOwnProperty(key)) { - logger.warning(`调用函数失败:缺少必需参数 ${key}`); - seal.replyToSender(ctx, msg, `调用函数失败:缺少必需参数 ${key}`); - return ret; - } - } - - tool.solve(ctx, msg, ai, args) - .then(({ content, images }) => seal.replyToSender(ctx, msg, `返回内容: -${content} -返回图片: -${images.map(img => img.CQCode).join('\n')}`)); - return ret; - } catch (e) { - const s = `调用函数 (${val3}) 失败:${e.message}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - } - default: { - const toolStatus = ai.tool.toolStatus; - - let i = 1; - let s = '工具函数如下:'; - Object.keys(toolStatus).forEach(key => { - const status = toolStatus[key] ? '开' : '关'; - s += `\n${i++}. ${key}[${status}]`; - }); - - seal.replyToSender(ctx, msg, s); - return ret; - } - } - } - case 'ignore': { - if (ctx.isPrivate) { - seal.replyToSender(ctx, msg, '忽略名单仅在群聊可用'); - return ret; - } - - const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); - const muid = cmdArgs.amIBeMentionedFirst ? epId : mctx.player.userId; - - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'add': { - if (cmdArgs.at.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai ign add @xxx】添加忽略名单'); - return ret; - } - if (ai.context.ignoreList.includes(muid)) { - seal.replyToSender(ctx, msg, '已经在忽略名单中'); - return ret; - } - ai.context.ignoreList.push(muid); - seal.replyToSender(ctx, msg, '已添加到忽略名单'); - AIManager.saveAI(sid); - return ret; - } - case 'remove': { - if (cmdArgs.at.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai ign rm @xxx】移除忽略名单'); - return ret; - } - if (!ai.context.ignoreList.includes(muid)) { - seal.replyToSender(ctx, msg, '不在忽略名单中'); - return ret; - } - ai.context.ignoreList = ai.context.ignoreList.filter(item => item !== muid); - seal.replyToSender(ctx, msg, '已从忽略名单中移除'); - AIManager.saveAI(sid); - return ret; - } - case 'list': { - const s = ai.context.ignoreList.length === 0 ? '忽略名单为空' : `忽略名单如下:\n${ai.context.ignoreList.join('\n')}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai ign add @xxx】添加忽略名单 -【.ai ign rm @xxx】移除忽略名单 -【.ai ign lst】列出忽略名单 - -忽略名单中的对象仍能正常对话,但无法被选中QQ号`); - return ret; - } - } - } - case 'token': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'list': { - const s = Object.keys(AIManager.usageMap).join('\n'); - seal.replyToSender(ctx, msg, `有使用记录的模型:\n${s}`); - return ret; - } - case 'sum': { - const usage = { - prompt_tokens: 0, - completion_tokens: 0 - }; - - for (const model in AIManager.usageMap) { - const modelUsage = AIManager.getModelUsage(model); - usage.prompt_tokens += modelUsage.prompt_tokens; - usage.completion_tokens += modelUsage.completion_tokens; - } - - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - const s = `输入token:${usage.prompt_tokens} -输出token:${usage.completion_tokens} -总token:${usage.prompt_tokens + usage.completion_tokens}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - case 'all': { - const s = Object.keys(AIManager.usageMap).map((model, index) => { - const usage = AIManager.getModelUsage(model); - - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return `${index + 1}. ${model}: 没有使用记录`; - } - - return `${index + 1}. ${model}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - if (!s) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - seal.replyToSender(ctx, msg, `全部使用记录如下:\n${s}`); - return ret; - } - case 'year': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentYM = currentYear * 12 + currentMonth; - for (const model in AIManager.usageMap) { - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, _] = key.split('-').map(v => parseInt(v)); - const ym = year * 12 + month; - - if (ym >= currentYM - 11 && ym <= currentYM) { - const key = `${year}-${month}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - } - - const val3 = cmdArgs.getArgN(3); - if (val3 === 'chart') { - get_chart_url('year', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 + monthA) - (yearB * 12 + monthB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - if (!s) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); - return ret; - } - case 'month': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentDay = now.getDate(); - const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; - for (const model in AIManager.usageMap) { - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, day] = key.split('-').map(v => parseInt(v)); - const ymd = year * 12 * 31 + month * 31 + day; - - if (ymd >= currentYMD - 30 && ymd <= currentYMD) { - const key = `${year}-${month}-${day}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - } - - const val3 = cmdArgs.getArgN(3); - if (val3 === 'chart') { - get_chart_url('month', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); - return ret; - } - case 'clear': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - AIManager.clearUsageMap(); - seal.replyToSender(ctx, msg, '已清除token使用记录'); - AIManager.saveUsageMap(); - return ret; - } - - if (!AIManager.usageMap.hasOwnProperty(val3)) { - seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); - return ret; - } - - delete AIManager.usageMap[val3]; - seal.replyToSender(ctx, msg, `已清除 ${val3} 的token使用记录`); - AIManager.saveUsageMap(); - return ret; - } - case '': - case 'help': { - seal.replyToSender(ctx, msg, `帮助: -【.ai tk lst】查看所有模型 -【.ai tk sum】查看所有模型的token使用记录总和 -【.ai tk all】查看所有模型的token使用记录 -【.ai tk [y/m] (chart)】查看所有模型今年/这个月的token使用记录 -【.ai tk <模型名称>】查看模型的token使用记录 -【.ai tk <模型名称> [y/m] (chart)】查看模型今年/这个月的token使用记录 -【.ai tk clr】清除token使用记录 -【.ai tk clr <模型名称>】清除token使用记录`); - return ret; - } - default: { - if (!AIManager.usageMap.hasOwnProperty(val2)) { - seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); - return ret; - } - - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'year': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentYM = currentYear * 12 + currentMonth; - const model = val2; - - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, _] = key.split('-').map(v => parseInt(v)); - const ym = year * 12 + month; - - if (ym >= currentYM - 11 && ym <= currentYM) { - const key = `${year}-${month}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - - const val4 = cmdArgs.getArgN(4); - if (val4 === 'chart') { - get_chart_url('year', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 + monthA) - (yearB * 12 + monthB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - if (!s) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); - return ret; - } - case 'month': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentDay = now.getDate(); - const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; - const model = val2; - - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, day] = key.split('-').map(v => parseInt(v)); - const ymd = year * 12 * 31 + month * 31 + day; - - if (ymd >= currentYMD - 30 && ymd <= currentYMD) { - const key = `${year}-${month}-${day}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - - const val4 = cmdArgs.getArgN(4); - if (val4 === 'chart') { - get_chart_url('month', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); - return ret; - } - default: { - const usage = AIManager.getModelUsage(val2); - - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - const s = `输入token:${usage.prompt_tokens} -输出token:${usage.completion_tokens} -总token:${usage.prompt_tokens + usage.completion_tokens}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - } - } - } - } - case 'shut': { - if (ai.stream.id === '') { - seal.replyToSender(ctx, msg, '当前没有正在进行的对话'); - return ret; - } - - ai.stopCurrentChatStream() - .then(() => seal.replyToSender(ctx, msg, '已停止当前对话')); - return ret; - } - default: { - ret.showHelp = true; - return ret; - } - } - } catch (e) { - logger.error(`指令.ai执行失败:${e.message}`); - seal.replyToSender(ctx, msg, `指令.ai执行失败:${e.message}`); - return seal.ext.newCmdExecuteResult(true); - } - } - // 将命令注册到扩展中 - ext.cmdMap['AI'] = cmdAI; - ext.cmdMap['ai'] = cmdAI; + ext.cmdMap['AI'] = cmd; + ext.cmdMap['ai'] = cmd; ext.onPoke = (ctx, event) => { const msg = createMsg(event.isPrivate ? 'private' : 'group', event.senderId, event.groupId); From 5ea4898e706b60d69d3a44f8dfa62d8379a53cac Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 3 Mar 2026 15:01:27 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=B8=8D=E6=83=B3=E6=94=B9=E4=BA=86?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmd/root.ts | 17 ++--------------- src/cmd/sub_cmd/status.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 src/cmd/sub_cmd/status.ts diff --git a/src/cmd/root.ts b/src/cmd/root.ts index 6afd20f..43ec84c 100644 --- a/src/cmd/root.ts +++ b/src/cmd/root.ts @@ -11,6 +11,7 @@ import { aliasToCmd } from "../utils/utils"; import { ImageManager } from "../AI/image"; import { getCmdPrivilege } from "./sub_cmd/privilege"; import { getCmdPrompt } from "./sub_cmd/prompt"; +import { getCmdStatus } from "./sub_cmd/status"; export interface SubCmdContext { ctx: seal.MsgContext; @@ -94,21 +95,7 @@ cmd.solve = (ctx, msg, cmdArgs) => { switch (aliasToCmd(val)) { case 'privilege': return getCmdPrivilege().solve(scc); case 'prompt': return getCmdPrompt().solve(scc); - case 'status': { - const setting = ai.setting; - const { start, end, segs } = setting.activeTimeInfo; - - seal.replyToSender(ctx, msg, `${sid} -权限: ${setting.priv} -上下文轮数: ${ai.context.messages.filter(m => m.role === 'user').length} -计数器模式(c): ${setting.counter > -1 ? `${setting.counter}条` : '关闭'} -计时器模式(t): ${setting.timer > -1 ? `${setting.timer}秒` : '关闭'} -概率模式(p): ${setting.prob > -1 ? `${setting.prob}%` : '关闭'} -活跃时间段: ${(start !== 0 || end !== 0) ? `${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}` : '未设置'} -活跃次数: ${segs > 0 ? segs : '未设置'} -待机模式: ${setting.standby ? '开启' : '关闭'}`); - return ret; - } + case 'status': return getCmdStatus().solve(scc); case 'ctxn': { const val2 = cmdArgs.getArgN(2); switch (aliasToCmd(val2)) { diff --git a/src/cmd/sub_cmd/status.ts b/src/cmd/sub_cmd/status.ts new file mode 100644 index 0000000..5aa663c --- /dev/null +++ b/src/cmd/sub_cmd/status.ts @@ -0,0 +1,26 @@ +import { U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function getCmdStatus(): SubCmd { + const cmd = new SubCmd('status'); + cmd.help = ''; + cmd.priv = { priv: U }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, sid, ai, ret } = scc; + const setting = ai.setting; + const { start, end, segs } = setting.activeTimeInfo; + + seal.replyToSender(ctx, msg, `${sid} + 权限: ${setting.priv} + 上下文轮数: ${ai.context.messages.filter(m => m.role === 'user').length} + 计数器模式(c): ${setting.counter > -1 ? `${setting.counter}条` : '关闭'} + 计时器模式(t): ${setting.timer > -1 ? `${setting.timer}秒` : '关闭'} + 概率模式(p): ${setting.prob > -1 ? `${setting.prob}%` : '关闭'} + 活跃时间段: ${(start !== 0 || end !== 0) ? `${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}` : '未设置'} + 活跃次数: ${segs > 0 ? segs : '未设置'} + 待机模式: ${setting.standby ? '开启' : '关闭'}`); + return ret; + } + + return cmd; +} From cad3df58d9d9ae539a8fff0486d81f0d40ee55eb Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 3 Mar 2026 18:25:42 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=93=88=E5=93=88=E6=88=91=E6=99=95?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmd/privilege.ts | 141 +--- src/cmd/root.ts | 1326 ++-------------------------------- src/cmd/sub_cmd/ctxn.ts | 68 ++ src/cmd/sub_cmd/forget.ts | 45 ++ src/cmd/sub_cmd/ignore.ts | 76 ++ src/cmd/sub_cmd/image.ts | 116 +++ src/cmd/sub_cmd/memory.ts | 283 ++++++++ src/cmd/sub_cmd/off.ts | 82 +++ src/cmd/sub_cmd/on.ts | 127 ++++ src/cmd/sub_cmd/privilege.ts | 5 +- src/cmd/sub_cmd/prompt.ts | 5 +- src/cmd/sub_cmd/role.ts | 35 + src/cmd/sub_cmd/sample.ts | 15 + src/cmd/sub_cmd/shut.ts | 23 + src/cmd/sub_cmd/standby.ts | 35 + src/cmd/sub_cmd/status.ts | 7 +- src/cmd/sub_cmd/timer.ts | 40 + src/cmd/sub_cmd/token.ts | 402 +++++++++++ src/cmd/sub_cmd/tool.ts | 156 ++++ src/index.ts | 8 +- 20 files changed, 1597 insertions(+), 1398 deletions(-) create mode 100644 src/cmd/sub_cmd/ctxn.ts create mode 100644 src/cmd/sub_cmd/forget.ts create mode 100644 src/cmd/sub_cmd/ignore.ts create mode 100644 src/cmd/sub_cmd/image.ts create mode 100644 src/cmd/sub_cmd/memory.ts create mode 100644 src/cmd/sub_cmd/off.ts create mode 100644 src/cmd/sub_cmd/on.ts create mode 100644 src/cmd/sub_cmd/role.ts create mode 100644 src/cmd/sub_cmd/sample.ts create mode 100644 src/cmd/sub_cmd/shut.ts create mode 100644 src/cmd/sub_cmd/standby.ts create mode 100644 src/cmd/sub_cmd/timer.ts create mode 100644 src/cmd/sub_cmd/token.ts create mode 100644 src/cmd/sub_cmd/tool.ts diff --git a/src/cmd/privilege.ts b/src/cmd/privilege.ts index 570ad83..4ec7854 100644 --- a/src/cmd/privilege.ts +++ b/src/cmd/privilege.ts @@ -17,146 +17,7 @@ export const M: [number, number, number] = [0, PRIVILEGELEVELMAP.master, PRIVILE export const I: [number, number, number] = [0, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.inviter]; // inviter export const S: [number, number, number] = [1, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.master]; // spesial,会话所需权限为1,是才能被邀请者使用,否则需为骰主 -export const defaultCmdPriv: CmdPriv = { - ai: { - priv: U, args: { - privilege: { - priv: M, args: { - session: { - priv: U, args: { - set: { priv: U }, - check: { priv: U } - } - }, - set: { priv: U }, - show: { priv: U }, - reset: { priv: U } - } - }, - prompt: { priv: M }, - status: { priv: U }, - ctxn: { - priv: U, args: { - status: { priv: U }, - set: { priv: I }, - mod: { priv: I } - } - }, - timer: { - priv: U, args: { - list: { priv: U }, - clear: { priv: I } - } - }, - on: { priv: S }, - standby: { priv: I }, - off: { priv: I }, - forget: { - priv: I, args: { - assistant: { priv: U }, - user: { priv: U } - } - }, - role: { priv: I }, - image: { - priv: U, args: { - list: { - priv: U, args: { - steal: { priv: U }, - local: { priv: M } - } - }, - steal: { - priv: I, args: { - on: { priv: U }, - off: { priv: U }, - forget: { priv: U }, - } - }, - itt: { priv: M }, - find: { priv: I } - } - }, - memory: { - priv: U, args: { - status: { priv: U }, - private: { - priv: U, args: { - set: { - priv: U, args: { - clear: { priv: U }, - "*": { priv: U } - } - }, - delete: { priv: U }, - list: { priv: U }, - clear: { priv: U } - } - }, - group: { - priv: I, args: { - set: { - priv: U, args: { - clear: { priv: U }, - "*": { priv: U } - } - }, - delete: { priv: U }, - list: { priv: U }, - clear: { priv: U } - } - }, - short: { - priv: S, args: { - list: { priv: U }, - clear: { priv: U }, - on: { priv: U }, - off: { priv: U } - } - }, - sum: { priv: U } - } - }, - tool: { - priv: U, args: { - on: { priv: I }, - off: { priv: I }, - help: { priv: U }, - call: { priv: M }, - "*": { priv: U } - } - }, - ignore: { - priv: U, args: { - add: { priv: U }, - remove: { priv: U }, - list: { priv: U } - } - }, - token: { - priv: S, args: { - list: { priv: U }, - sum: { priv: U }, - all: { priv: U }, - year: { - priv: U, args: { - chart: { priv: U } - } - }, - month: { - priv: U, args: { - chart: { priv: U } - } - }, - clear: { priv: U }, - help: { priv: U }, - "*": { priv: U } - } - }, - shut: { priv: U } - } - } -}; +export const defaultCmdPriv: CmdPriv = { ai: { priv: U } }; export class PrivilegeManager { static cmdPriv: CmdPriv = defaultCmdPriv; diff --git a/src/cmd/root.ts b/src/cmd/root.ts index 43ec84c..763a15e 100644 --- a/src/cmd/root.ts +++ b/src/cmd/root.ts @@ -1,17 +1,24 @@ import { AI, AIManager } from "../AI/AI"; -import { ToolManager } from "../tool/tool"; import { ConfigManager } from "../config/configManager"; -import { getRoleSetting } from "../utils/utils_message"; import { logger } from "../logger"; -import { transformArrayToContent, transformTextToArray } from "../utils/utils_string"; -import { get_chart_url } from "../service"; -import { TimerManager } from "../timer"; -import { CmdPrivInfo, PrivilegeManager, U } from "./privilege"; +import { CmdPrivInfo, defaultCmdPriv, PrivilegeManager, U } from "./privilege"; import { aliasToCmd } from "../utils/utils"; -import { ImageManager } from "../AI/image"; -import { getCmdPrivilege } from "./sub_cmd/privilege"; -import { getCmdPrompt } from "./sub_cmd/prompt"; -import { getCmdStatus } from "./sub_cmd/status"; +import { registerCmdPrivilege } from "./sub_cmd/privilege"; +import { registerCmdPrompt } from "./sub_cmd/prompt"; +import { registerCmdStatus } from "./sub_cmd/status"; +import { registerCmdCtxn } from "./sub_cmd/ctxn"; +import { registerCmdTimer } from "./sub_cmd/timer"; +import { registerCmdOn } from "./sub_cmd/on"; +import { registerCmdStandby } from "./sub_cmd/standby"; +import { registerCmdOff } from "./sub_cmd/off"; +import { registerCmdForget } from "./sub_cmd/forget"; +import { registerCmdRole } from "./sub_cmd/role"; +import { registerCmdImage } from "./sub_cmd/image"; +import { registerCmdMemory } from "./sub_cmd/memory"; +import { registerCmdTool } from "./sub_cmd/tool"; +import { registerCmdIgnore } from "./sub_cmd/ignore"; +import { registerCmdToken } from "./sub_cmd/token"; +import { registerCmdShut } from "./sub_cmd/shut"; export interface SubCmdContext { ctx: seal.MsgContext; @@ -28,1269 +35,96 @@ export interface SubCmdContext { export class SubCmd { name: string; + desc: string; help: string; priv: CmdPrivInfo; solve: (scc: SubCmdContext) => seal.CmdExecuteResult | Promise; constructor(name: string) { this.name = name; + this.desc = ''; this.help = ''; this.priv = { priv: U }; this.solve = async () => { return seal.ext.newCmdExecuteResult(false); }; } -} - -export const cmd = seal.ext.newCmdItemInfo(); -cmd.name = 'ai'; -cmd.help = `帮助: -【.ai priv】权限相关 -【.ai prompt】查看system prompt -【.ai status】查看当前AI状态 -【.ai ctxn】上下文名字相关 -【.ai timer】定时器相关 -【.ai on】开启AI -【.ai sb】开启待机模式,此时AI将记录聊天内容 -【.ai off】关闭AI,此时仍能用正则匹配触发 -【.ai fgt】遗忘上下文 -【.ai role】角色设定相关 -【.ai img】图片相关 -【.ai memo】AI的记忆相关 -【.ai tool】AI的工具相关 -【.ai ign】AI的忽略名单相关 -【.ai tk】AI的token相关 -【.ai shut】终止AI当前流式输出`; -cmd.allowDelegate = true; -cmd.solve = (ctx, msg, cmdArgs) => { - try { - const val = cmdArgs.getArgN(1); - const uid = ctx.player.userId; - const gid = ctx.group.groupId; - const epId = ctx.endPoint.userId; - const sid = ctx.isPrivate ? uid : gid; - - const ret = seal.ext.newCmdExecuteResult(true); - const ai = AIManager.getAI(sid); - const { success, exist } = PrivilegeManager.checkPriv(ctx, cmdArgs, ai); - if (!success) { - seal.replyToSender(ctx, msg, exist ? '权限不足' : '命令不存在'); - return ret; - } - - let page = 1; - const kwargPage = cmdArgs.kwargs.find((kwarg) => kwarg.name === 'page' || kwarg.name === 'p'); - if (kwargPage && kwargPage.valueExists) { - page = parseInt(kwargPage.value); - if (isNaN(page)) { - seal.replyToSender(ctx, msg, '页码必须为数字'); - return ret; - } - if (page < 1) { - seal.replyToSender(ctx, msg, '页码必须大于等于1'); - return ret; - } - } - - const scc: SubCmdContext = { ctx, msg, cmdArgs, epId, uid, gid, sid, ai, page, ret }; - - switch (aliasToCmd(val)) { - case 'privilege': return getCmdPrivilege().solve(scc); - case 'prompt': return getCmdPrompt().solve(scc); - case 'status': return getCmdStatus().solve(scc); - case 'ctxn': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'status': { - seal.replyToSender(ctx, msg, `自动修改上下文里的名字状态:${ai.context.autoNameMod} -上下文里的名字有:\n${ai.context.userInfoList.map(ui => `${ui.name}(${ui.id})`).join('\n')}`); - return ret; - } - case 'set': { - const val3 = cmdArgs.getArgN(3); - const mod = aliasToCmd(val3); - if (mod !== 'nickname' && mod !== 'card') { - seal.replyToSender(ctx, msg, `帮助: -【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片`); - return ret; - } - const promises = ai.context.userInfoList.map(ui => ai.context.setName(epId, gid, ui.id, mod)); - Promise.all(promises).then(() => { - seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.userInfoList.map(uni => `${uni.name}(${uni.id})`).join('\n')}`); - }); - return ret; - } - case 'mod': { - const val3 = cmdArgs.getArgN(3); - const mod = parseInt(val3); - if (isNaN(mod) || mod < 0 || mod > 2) { - seal.replyToSender(ctx, msg, `帮助: -【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) -0: 不自动修改 -1: 自动修改为昵称 -2: 自动修改为群名片`); - return ret; - } - ai.context.autoNameMod = mod; - seal.replyToSender(ctx, msg, `设置成功,将自动修改上下文里的名字为${mod === 1 ? '昵称' : '群名片'}`); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai ctxn status】查看上下文里的名字和自动修改状态 -【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片 -【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) -0: 不自动修改 -1: 自动修改为昵称 -2: 自动修改为群名片`); - return ret; - } - } - } - case 'timer': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'list': { - seal.replyToSender(ctx, msg, TimerManager.getTimerListText(sid, page) || '当前对话没有定时器'); - return ret; - } - case 'clear': { - TimerManager.removeTimers(sid, '', [], []); - seal.replyToSender(ctx, msg, '所有定时器已清除'); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai timer lst】查看当前聊天定时器 -【.ai timer clr】清除当前聊天定时器`); - return ret; - } - } - } - case 'on': { - const setting = ai.setting; - - const kwargs = cmdArgs.kwargs; - if (kwargs.length == 0) { - seal.replyToSender(ctx, msg, `帮助: -【.ai on --<参数>=<数字>】 - -<参数>: -【c】计数器模式,接收消息数达到后触发 -单位/条,默认10条 -【t】计时器模式,最后一条消息后达到时限触发 -单位/秒,默认60秒 -【p】概率模式,每条消息按概率触发 -单位/%,默认10% -【a】活跃时间段和活跃次数 -格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5") - -【.ai on --t --p=42】使用示例`); - return ret; - } - - let text = `AI已开启:`; - for (const kwarg of kwargs) { - const name = kwarg.name; - const exist = kwarg.valueExists; - const valInt = parseInt(kwarg.value); - const valFloat = parseFloat(kwarg.value); - const valStr = kwarg.value.trim(); - - switch (name) { - case 'c': - case 'counter': { - ai.context.counter = 0; - setting.counter = exist && !isNaN(valInt) ? valInt : 10; - text += `\n计数器模式:${setting.counter}条`; - break; - } - case 't': - case 'timer': { - clearTimeout(ai.context.timer); - ai.context.timer = null; - setting.timer = exist && !isNaN(valFloat) ? valFloat : 60; - text += `\n计时器模式:${setting.timer}秒`; - break; - } - case 'p': - case 'prob': { - setting.prob = exist && !isNaN(valFloat) ? valFloat : 10; - text += `\n概率模式:${setting.prob}%`; - break; - } - case 'a': - case 'active': { - if (!exist) { - seal.replyToSender(ctx, msg, '请输入活跃时间段'); - return ret; - } - - const arr = valStr.split('-').map((item, index) => { - const parts = item.split(/[::,,]+/).map(Number).map(i => isNaN(i) ? 0 : i); - if (index < 2) { - return Math.ceil((parts[0] * 60 + (parts[1] || 0)) % (24 * 60)); - } else { - return parts[0]; - } - }) - - const [start = 0, end = 0, segs = 1] = arr; - - if (start === end) { - seal.replyToSender(ctx, msg, '活跃时间段开始时间和结束时间不能相同'); - return ret; - } - - if (!Number.isInteger(segs)) { - seal.replyToSender(ctx, msg, '活跃次数必须为整数'); - return ret; - } - - const endReal = end >= start ? end : end + 24 * 60; - if (segs > endReal - start) { - seal.replyToSender(ctx, msg, '活跃次数不能大于活跃时间段分钟数'); - return ret; - } - - TimerManager.removeTimers(sid, '', ['activeTime'], []); - setting.activeTimeInfo = { - start, - end, - segs, - } - - text += `\n活跃时间段:${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}`; - text += `\n活跃次数:${segs}`; - - const curSegIndex = ai.curActiveTimeSegIndex; - const nextTimePoint = ai.getNextTimePoint(curSegIndex); - if (nextTimePoint !== -1) { - TimerManager.addActiveTimeTimer(ctx, ai, nextTimePoint); - } - break; - } - } - }; - - setting.standby = true; - - seal.replyToSender(ctx, msg, text); - AIManager.saveAI(sid); - return ret; - } - case 'standby': { - const setting = ai.setting; - ai.resetState(); - TimerManager.removeTimers(sid, '', ['activeTime'], []); - - setting.counter = -1; - setting.timer = -1; - setting.prob = -1; - setting.standby = true; - setting.activeTimeInfo = { - start: 0, - end: 0, - segs: 0, - } - - seal.replyToSender(ctx, msg, 'AI已开启待机模式'); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - const setting = ai.setting; - - const kwargs = cmdArgs.kwargs; - if (kwargs.length == 0) { - ai.resetState(); - TimerManager.removeTimers(sid, '', ['activeTime'], []); - - setting.counter = -1; - setting.timer = -1; - setting.prob = -1; - setting.standby = false; - setting.activeTimeInfo = { - start: 0, - end: 0, - segs: 0, - } - - seal.replyToSender(ctx, msg, 'AI已关闭'); - AIManager.saveAI(sid); - return ret; - } - - let text = `AI已关闭:`; - kwargs.forEach(kwarg => { - const name = kwarg.name; - - switch (name) { - case 'c': - case 'counter': { - ai.context.counter = 0; - setting.counter = -1; - text += `\n计数器模式`; - break; - } - case 't': - case 'timer': { - clearTimeout(ai.context.timer); - ai.context.timer = null; - setting.timer = -1; - text += `\n计时器模式`; - break; - } - case 'p': - case 'prob': { - setting.prob = -1; - text += `\n概率模式`; - break; - } - case 'a': - case 'active': { - TimerManager.removeTimers(sid, '', ['activeTime'], []); - setting.activeTimeInfo = { - start: 0, - end: 0, - segs: 0, - } - text += `\n活跃时间段`; - break; - } - } - }); - - seal.replyToSender(ctx, msg, text); - AIManager.saveAI(sid); - return ret; - } - case 'forget': { - ai.resetState(); - - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'assistant': { - ai.context.clearMessages('assistant', 'tool'); - seal.replyToSender(ctx, msg, 'ai上下文已清除'); - AIManager.saveAI(sid); - return ret; - } - case 'user': { - ai.context.clearMessages('user'); - seal.replyToSender(ctx, msg, '用户上下文已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - ai.context.clearMessages(); - seal.replyToSender(ctx, msg, '上下文已清除'); - AIManager.saveAI(sid); - return ret; - } - } - } - case 'role': { - const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; - const { roleName } = getRoleSetting(ctx); - const val2 = cmdArgs.getArgN(2); - if (!val2) { - seal.replyToSender(ctx, msg, `当前角色设定名称为[${roleName}],名称有:\n${roleSettingNames.join('、')}`); - return ret; - } - if (!roleSettingNames.includes(val2)) { - seal.replyToSender(ctx, msg, `【.ai role <名称>】切换角色设定\n角色设定名称错误,名称有:\n${roleSettingNames.join('、')}`); - return ret; - } - const roleSettingIndex = roleSettingNames.indexOf(val2); - if (roleSettingIndex < 0 || roleSettingIndex >= roleSettingTemplate.length) { - seal.replyToSender(ctx, msg, `角色设定名称[${val2}]没有对应的角色设定`); - } - seal.vars.strSet(ctx, "$gSYSPROMPT", val2); - seal.replyToSender(ctx, msg, `角色设定已切换到[${val2}]`); - return ret; - } - case 'image': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'list': { - const type = cmdArgs.getArgN(3); - switch (aliasToCmd(type)) { - case 'steal': { - seal.replyToSender(ctx, msg, ai.imageManager.getStolenImageListText(page) || '暂无偷取图片'); - return ret; - } - case 'local': { - seal.replyToSender(ctx, msg, ImageManager.getLocalImageListText(page) || '暂无本地图片'); - return ret; - } - default: { - seal.replyToSender(ctx, msg, '【.ai img list [stl/lcl]】展示偷取的图片/本地图片'); - return ret; - } - } - } - case 'steal': { - const op = cmdArgs.getArgN(3); - switch (aliasToCmd(op)) { - case 'on': { - ai.imageManager.stealStatus = true; - seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.imageManager.stolenImages.length}`); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - ai.imageManager.stealStatus = false; - seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.imageManager.stolenImages.length}`); - AIManager.saveAI(sid); - return ret; - } - case 'forget': { - ai.imageManager.stolenImages = []; - seal.replyToSender(ctx, msg, '偷取图片已遗忘'); - AIManager.saveAI(sid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `图片偷取状态:${ai.imageManager.stealStatus},当前偷取数量:${ai.imageManager.stolenImages.length}`); - return ret; - } - } - } - case 'itt': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - seal.replyToSender(ctx, msg, '【.ai img itt [图片] (附加提示词)】图片转文字'); - return ret; - } - const messageArray = transformTextToArray(val3); - transformArrayToContent(ctx, ai, messageArray).then(({ images }) => { - if (images.length === 0) seal.replyToSender(ctx, msg, '请附带图片'); - const img = images[0]; - img.imageToText(cmdArgs.getRestArgsFrom(4)) - .then(() => seal.replyToSender(ctx, msg, img.CQCode + `\n` + img.content)); - }); - return ret; - } - case 'find': { - const id = cmdArgs.getArgN(3); - if (!id) { - seal.replyToSender(ctx, msg, '【.ai img find <图片ID>】查找图片'); - return ret; - } - ai.context.findImage(ctx, id) - .then((img) => seal.replyToSender(ctx, msg, img ? img.CQCode : '未找到该图片')); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai img list [stl/lcl]】展示偷取的图片/本地图片 -【.ai img stl [on/off/f]】偷图 开启/关闭/遗忘 -【.ai img itt [图片] (附加提示词)】图片转文字 -【.ai img find <图片ID>】查找图片`); - return ret; - } - } - } - case 'memory': { - const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); - const muid = mctx.player.userId; - - const ai2 = AIManager.getAI(muid); - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'status': { - let ai3 = ai; - if (cmdArgs.at.length > 0 && (cmdArgs.at.length !== 1 || cmdArgs.at[0].userId !== epId)) { - ai3 = ai2; - } - const { isMemory, isShortMemory } = ConfigManager.memory; - seal.replyToSender(ctx, msg, `${ai3.id} -长期记忆开启状态: ${isMemory ? '是' : '否'} -长期记忆条数: ${ai3.memory.memoryIds.length} -关键词库: ${ai3.memory.keywords.join('、') || '无'} -短期记忆开启状态: ${(isShortMemory && ai3.memory.useShortMemory) ? '是' : '否'} -短期记忆条数: ${ai3.memory.shortMemoryList.length}`); - return ret; - } - case 'private': { - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'set': { - const s = cmdArgs.getRestArgsFrom(4); - switch (aliasToCmd(s)) { - case '': { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p st <内容>】设置个人设定,【.ai memo p st clr】清除个人设定'); - return ret; - } - case 'clear': { - ai2.memory.persona = '无'; - seal.replyToSender(ctx, msg, '设定已清除'); - AIManager.saveAI(muid); - return ret; - } - default: { - if (s.length > 20) { - seal.replyToSender(ctx, msg, '设定过长,请控制在20字以内'); - return ret; - } - ai2.memory.persona = s; - seal.replyToSender(ctx, msg, '设定已修改'); - AIManager.saveAI(muid); - return ret; - } - } - } - case 'delete': { - const idList = cmdArgs.args.slice(3); - const kw = cmdArgs.kwargs.map(item => item.name); - if (idList.length === 0 && kw.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p del --关键词1 --关键词2】删除个人记忆'); - return ret; - } - ai2.memory.deleteMemory(idList, kw); - seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ - isPrivate: true, - id: mctx.player.userId, - name: mctx.player.name - }, page) || '记忆已全部清除'); - AIManager.saveAI(muid); - return ret; - } - case 'list': { - seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ - isPrivate: true, - id: mctx.player.userId, - name: mctx.player.name - }, page) || '无记忆'); - return ret; - } - case 'clear': { - ai2.memory.clearMemory(); - seal.replyToSender(ctx, msg, '个人记忆已清除'); - AIManager.saveAI(muid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `参数缺失: -【.ai memo p st <内容>】设置个人设定 -【.ai memo p st clr】清除个人设定 -【.ai memo p del --关键词1 --关键词2】删除个人记忆 -【.ai memo p list】展示个人记忆 -【.ai memo p clr】清除个人记忆`); - return ret; - } - } - } - case 'group': { - if (ctx.isPrivate) { - seal.replyToSender(ctx, msg, '群聊记忆仅在群聊可用'); - return ret; - } - - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'set': { - const s = cmdArgs.getRestArgsFrom(4); - switch (aliasToCmd(s)) { - case '': { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g st <内容>】设置群聊设定,【.ai memo g st clr】清除群聊设定'); - return ret; - } - case 'clear': { - ai.memory.persona = '无'; - seal.replyToSender(ctx, msg, '设定已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - if (s.length > 30) { - seal.replyToSender(ctx, msg, '设定过长,请控制在30字以内'); - return ret; - } - ai.memory.persona = s; - seal.replyToSender(ctx, msg, '设定已修改'); - AIManager.saveAI(sid); - return ret; - } - } - } - case 'delete': { - const idList = cmdArgs.args.slice(3); - const kw = cmdArgs.kwargs.map(item => item.name); - if (idList.length === 0 && kw.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g del 】删除群聊记忆'); - return ret; - } - ai.memory.deleteMemory(idList, kw); - seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ - isPrivate: false, - id: ctx.group.groupId, - name: ctx.group.groupName - }, page) || '记忆已全部清除'); - AIManager.saveAI(sid); - return ret; - } - case 'list': { - seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ - isPrivate: false, - id: ctx.group.groupId, - name: ctx.group.groupName - }, page) || '无记忆'); - return ret; - } - case 'clear': { - ai.memory.clearMemory(); - seal.replyToSender(ctx, msg, '群聊记忆已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `参数缺失: -【.ai memo g st <内容>】设置群聊设定 -【.ai memo g st clr】清除群聊设定 -【.ai memo g del --关键词1 --关键词2】删除群聊记忆 -【.ai memo g list】展示群聊记忆 -【.ai memo g clr】清除群聊记忆`); - return ret; - } - } - } - case 'short': { - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'on': { - ai.memory.useShortMemory = true; - seal.replyToSender(ctx, msg, '短期记忆已开启'); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - ai.memory.useShortMemory = false; - seal.replyToSender(ctx, msg, '短期记忆已关闭'); - AIManager.saveAI(sid); - return ret; - } - case 'list': { - if (ai.memory.shortMemoryList.length === 0) { - seal.replyToSender(ctx, msg, '短期记忆为空'); - return ret; - } - seal.replyToSender(ctx, msg, ai.memory.shortMemoryList - .map((item, index) => `${index + 1}. ${item}`) - .slice((page - 1) * 10, page * 10) - .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); - return ret; - } - case 'clear': { - ai.memory.clearShortMemory(); - seal.replyToSender(ctx, msg, '短期记忆已清除'); - AIManager.saveAI(sid); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `参数缺失 -【.ai memo short list】展示短期记忆 -【.ai memo short clr】清除短期记忆 -【.ai memo short [on/off]】开启/关闭短期记忆`); - return ret; - } - } - } - case 'sum': { - ai.context.summaryCounter = 0; - ai.memory.updateShortMemory(ctx, msg, ai) - .then(() => { - seal.replyToSender(ctx, msg, ai.memory.shortMemoryList - .map((item, index) => `${index + 1}. ${item}`) - .slice((page - 1) * 10, page * 10) - .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); - }); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai memo status (@xxx)】查看记忆状态,@为查看个人记忆状态 -【.ai memo [p/g] st <内容>】设置个人/群聊设定 -【.ai memo [p/g] st clr】清除个人/群聊设定 -【.ai memo [p/g] del --关键词1 --关键词2】删除个人/群聊记忆 -【.ai memo [p/g/short] list】展示个人/群聊/短期记忆 -【.ai memo [p/g/short] clr】清除个人/群聊/短期记忆 -【.ai memo short [on/off]】开启/关闭短期记忆 -【.ai memo sum】立即总结一次短期记忆`); - return ret; - } - } - } - case 'tool': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'on': { - const val3 = cmdArgs.getArgN(3); - if (val3) { - const toolsNotAllow = ConfigManager.tool.toolsNotAllow; - if (toolsNotAllow.includes(val3)) { - seal.replyToSender(ctx, msg, `工具函数 ${val3} 不被允许开启`); - return ret; - } - - ai.tool.toolStatus[val3] = true; - seal.replyToSender(ctx, msg, `已开启工具函数 ${val3}`); - AIManager.saveAI(sid); - return ret; - } - const toolsNotAllow = ConfigManager.tool.toolsNotAllow; - for (const key in ai.tool.toolStatus) { - ai.tool.toolStatus[key] = toolsNotAllow.includes(key) ? false : true; - } - seal.replyToSender(ctx, msg, '已开启全部工具函数'); - AIManager.saveAI(sid); - return ret; - } - case 'off': { - const val3 = cmdArgs.getArgN(3); - if (val3) { - ai.tool.toolStatus[val3] = false; - seal.replyToSender(ctx, msg, `已关闭工具函数 ${val3}`); - AIManager.saveAI(sid); - return ret; - } - for (const key in ai.tool.toolStatus) { - ai.tool.toolStatus[key] = false; - } - seal.replyToSender(ctx, msg, '已关闭全部工具函数'); - AIManager.saveAI(sid); - return ret; - } - case 'help': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - seal.replyToSender(ctx, msg, `帮助: -【.ai tool】列出所有工具 -【.ai tool [on/off] <函数名>】开启或关闭工具函数 -【.ai tool help <函数名>】查看工具详情 -【.ai tool call <函数名> --参数名=具体参数】试用工具函数`); - return ret; - } - - if (!ToolManager.toolMap.hasOwnProperty(val3)) { - seal.replyToSender(ctx, msg, '没有这个工具函数'); - return ret; - } - - const tool = ToolManager.toolMap[val3]; - const s = `${tool.info.function.name} -描述:${tool.info.function.description} - -参数信息: -${JSON.stringify(tool.info.function.parameters.properties, null, 2)} - -必需参数:${tool.info.function.parameters.required.join(',')}`; - - seal.replyToSender(ctx, msg, s); - return ret; - } - case 'call': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - seal.replyToSender(ctx, msg, `调用函数缺少工具函数名`); - return ret; - } - if (!ToolManager.toolMap.hasOwnProperty(val3)) { - seal.replyToSender(ctx, msg, `调用函数失败:未注册的函数:${val3}`); - return ret; - } - const tool = ToolManager.toolMap[val3]; - if (tool.cmdInfo.ext !== '' && ToolManager.cmdArgs == null) { - seal.replyToSender(ctx, msg, `暂时无法调用函数,请先使用 .r 指令`); - return ret; - } - - try { - const args = cmdArgs.kwargs.reduce((acc, kwarg) => { - const valueString = kwarg.value; - try { - acc[kwarg.name] = JSON.parse(`[${valueString}]`)[0]; - } catch (e) { - acc[kwarg.name] = valueString; - } - return acc; - }, {}); - - for (const key of tool.info.function.parameters.required) { - if (!args.hasOwnProperty(key)) { - logger.warning(`调用函数失败:缺少必需参数 ${key}`); - seal.replyToSender(ctx, msg, `调用函数失败:缺少必需参数 ${key}`); - return ret; - } - } - - tool.solve(ctx, msg, ai, args) - .then(({ content, images }) => seal.replyToSender(ctx, msg, `返回内容: -${content} -返回图片: -${images.map(img => img.CQCode).join('\n')}`)); - return ret; - } catch (e) { - const s = `调用函数 (${val3}) 失败:${e.message}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - } - default: { - const toolStatus = ai.tool.toolStatus; + static map: { [key: string]: SubCmd } = {}; + static register() { + registerCmdPrivilege(); + registerCmdPrompt(); + registerCmdStatus(); + registerCmdCtxn(); + registerCmdTimer(); + registerCmdOn(); + registerCmdStandby(); + registerCmdOff(); + registerCmdForget(); + registerCmdRole(); + registerCmdImage(); + registerCmdMemory(); + registerCmdTool(); + registerCmdIgnore(); + registerCmdToken(); + registerCmdShut(); + + defaultCmdPriv.ai.args = Object.values(SubCmd.map).reduce((acc, sc) => { + acc[sc.name] = sc.priv; + return acc; + }, {}); + } +} - let i = 1; - let s = '工具函数如下:'; - Object.keys(toolStatus).forEach(key => { - const status = toolStatus[key] ? '开' : '关'; - s += `\n${i++}. ${key}[${status}]`; - }); +export function registerCmd() { + SubCmd.register(); - seal.replyToSender(ctx, msg, s); - return ret; - } - } - } - case 'ignore': { - if (ctx.isPrivate) { - seal.replyToSender(ctx, msg, '忽略名单仅在群聊可用'); - return ret; - } + const cmd = seal.ext.newCmdItemInfo(); + cmd.name = 'ai'; + cmd.help = `帮助:\n${Object.values(SubCmd.map).map((sc) => `【.ai ${sc.name}】${sc.desc}`).join('\n')}`; + cmd.allowDelegate = true; + cmd.solve = (ctx, msg, cmdArgs) => { + try { + const ret = seal.ext.newCmdExecuteResult(true); - const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); - const muid = cmdArgs.amIBeMentionedFirst ? epId : mctx.player.userId; + const subCmd = aliasToCmd(cmdArgs.getArgN(1)); + if (SubCmd.map.hasOwnProperty(aliasToCmd(subCmd))) { + const uid = ctx.player.userId; + const gid = ctx.group.groupId; + const epId = ctx.endPoint.userId; + const sid = ctx.isPrivate ? uid : gid; - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'add': { - if (cmdArgs.at.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai ign add @xxx】添加忽略名单'); - return ret; - } - if (ai.context.ignoreList.includes(muid)) { - seal.replyToSender(ctx, msg, '已经在忽略名单中'); - return ret; - } - ai.context.ignoreList.push(muid); - seal.replyToSender(ctx, msg, '已添加到忽略名单'); - AIManager.saveAI(sid); - return ret; - } - case 'remove': { - if (cmdArgs.at.length === 0) { - seal.replyToSender(ctx, msg, '参数缺失,【.ai ign rm @xxx】移除忽略名单'); - return ret; - } - if (!ai.context.ignoreList.includes(muid)) { - seal.replyToSender(ctx, msg, '不在忽略名单中'); - return ret; - } - ai.context.ignoreList = ai.context.ignoreList.filter(item => item !== muid); - seal.replyToSender(ctx, msg, '已从忽略名单中移除'); - AIManager.saveAI(sid); + let page = 1; + const kwargPage = cmdArgs.kwargs.find((kwarg) => kwarg.name === 'page' || kwarg.name === 'p'); + if (kwargPage && kwargPage.valueExists) { + page = parseInt(kwargPage.value); + if (isNaN(page)) { + seal.replyToSender(ctx, msg, '页码必须为数字'); return ret; } - case 'list': { - const s = ai.context.ignoreList.length === 0 ? '忽略名单为空' : `忽略名单如下:\n${ai.context.ignoreList.join('\n')}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - default: { - seal.replyToSender(ctx, msg, `帮助: -【.ai ign add @xxx】添加忽略名单 -【.ai ign rm @xxx】移除忽略名单 -【.ai ign lst】列出忽略名单 - -忽略名单中的对象仍能正常对话,但无法被选中QQ号`); + if (page < 1) { + seal.replyToSender(ctx, msg, '页码必须大于等于1'); return ret; } } - } - case 'token': { - const val2 = cmdArgs.getArgN(2); - switch (aliasToCmd(val2)) { - case 'list': { - const s = Object.keys(AIManager.usageMap).join('\n'); - seal.replyToSender(ctx, msg, `有使用记录的模型:\n${s}`); - return ret; - } - case 'sum': { - const usage = { - prompt_tokens: 0, - completion_tokens: 0 - }; - - for (const model in AIManager.usageMap) { - const modelUsage = AIManager.getModelUsage(model); - usage.prompt_tokens += modelUsage.prompt_tokens; - usage.completion_tokens += modelUsage.completion_tokens; - } - - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - const s = `输入token:${usage.prompt_tokens} -输出token:${usage.completion_tokens} -总token:${usage.prompt_tokens + usage.completion_tokens}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - case 'all': { - const s = Object.keys(AIManager.usageMap).map((model, index) => { - const usage = AIManager.getModelUsage(model); - - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return `${index + 1}. ${model}: 没有使用记录`; - } - - return `${index + 1}. ${model}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - if (!s) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - seal.replyToSender(ctx, msg, `全部使用记录如下:\n${s}`); - return ret; - } - case 'year': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentYM = currentYear * 12 + currentMonth; - for (const model in AIManager.usageMap) { - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, _] = key.split('-').map(v => parseInt(v)); - const ym = year * 12 + month; - - if (ym >= currentYM - 11 && ym <= currentYM) { - const key = `${year}-${month}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - } - - const val3 = cmdArgs.getArgN(3); - if (val3 === 'chart') { - get_chart_url('year', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 + monthA) - (yearB * 12 + monthB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - if (!s) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); - return ret; - } - case 'month': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentDay = now.getDate(); - const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; - for (const model in AIManager.usageMap) { - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, day] = key.split('-').map(v => parseInt(v)); - const ymd = year * 12 * 31 + month * 31 + day; - - if (ymd >= currentYMD - 30 && ymd <= currentYMD) { - const key = `${year}-${month}-${day}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - } - - const val3 = cmdArgs.getArgN(3); - if (val3 === 'chart') { - get_chart_url('month', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); - return ret; - } - case 'clear': { - const val3 = cmdArgs.getArgN(3); - if (!val3) { - AIManager.clearUsageMap(); - seal.replyToSender(ctx, msg, '已清除token使用记录'); - AIManager.saveUsageMap(); - return ret; - } - - if (!AIManager.usageMap.hasOwnProperty(val3)) { - seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); - return ret; - } - - delete AIManager.usageMap[val3]; - seal.replyToSender(ctx, msg, `已清除 ${val3} 的token使用记录`); - AIManager.saveUsageMap(); - return ret; - } - case '': - case 'help': { - seal.replyToSender(ctx, msg, `帮助: -【.ai tk lst】查看所有模型 -【.ai tk sum】查看所有模型的token使用记录总和 -【.ai tk all】查看所有模型的token使用记录 -【.ai tk [y/m] (chart)】查看所有模型今年/这个月的token使用记录 -【.ai tk <模型名称>】查看模型的token使用记录 -【.ai tk <模型名称> [y/m] (chart)】查看模型今年/这个月的token使用记录 -【.ai tk clr】清除token使用记录 -【.ai tk clr <模型名称>】清除token使用记录`); - return ret; - } - default: { - if (!AIManager.usageMap.hasOwnProperty(val2)) { - seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); - return ret; - } - - const val3 = cmdArgs.getArgN(3); - switch (aliasToCmd(val3)) { - case 'year': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentYM = currentYear * 12 + currentMonth; - const model = val2; - - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, _] = key.split('-').map(v => parseInt(v)); - const ym = year * 12 + month; - - if (ym >= currentYM - 11 && ym <= currentYM) { - const key = `${year}-${month}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - - const val4 = cmdArgs.getArgN(4); - if (val4 === 'chart') { - get_chart_url('year', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 + monthA) - (yearB * 12 + monthB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - if (!s) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); - return ret; - } - case 'month': { - const obj: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } - } = {}; - const now = new Date(); - const currentYear = now.getFullYear(); - const currentMonth = now.getMonth() + 1; - const currentDay = now.getDate(); - const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; - const model = val2; - - const modelUsage = AIManager.usageMap[model]; - for (const key in modelUsage) { - const usage = modelUsage[key]; - const [year, month, day] = key.split('-').map(v => parseInt(v)); - const ymd = year * 12 * 31 + month * 31 + day; - - if (ymd >= currentYMD - 30 && ymd <= currentYMD) { - const key = `${year}-${month}-${day}`; - if (!obj.hasOwnProperty(key)) { - obj[key] = { - prompt_tokens: 0, - completion_tokens: 0 - }; - } - - obj[key].prompt_tokens += usage.prompt_tokens; - obj[key].completion_tokens += usage.completion_tokens; - } - } - - const val4 = cmdArgs.getArgN(4); - if (val4 === 'chart') { - get_chart_url('month', obj) - .then(url => seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败')); - return ret; - } - - const keys = Object.keys(obj).sort((a, b) => { - const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); - const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); - return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); - }); - - const s = keys.map(key => { - const usage = obj[key]; - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - return ``; - } - - return `${key}: - 输入token:${usage.prompt_tokens} - 输出token:${usage.completion_tokens} - 总token:${usage.prompt_tokens + usage.completion_tokens}`; - }).join('\n'); - - seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); - return ret; - } - default: { - const usage = AIManager.getModelUsage(val2); - - if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { - seal.replyToSender(ctx, msg, `没有使用记录`); - return ret; - } - - const s = `输入token:${usage.prompt_tokens} -输出token:${usage.completion_tokens} -总token:${usage.prompt_tokens + usage.completion_tokens}`; - seal.replyToSender(ctx, msg, s); - return ret; - } - } - } - } - } - case 'shut': { - if (ai.stream.id === '') { - seal.replyToSender(ctx, msg, '当前没有正在进行的对话'); + const ai = AIManager.getAI(sid); + const { success, exist } = PrivilegeManager.checkPriv(ctx, cmdArgs, ai); + if (!success) { + seal.replyToSender(ctx, msg, exist ? '权限不足' : '命令不存在'); return ret; } - ai.stopCurrentChatStream() - .then(() => seal.replyToSender(ctx, msg, '已停止当前对话')); - return ret; - } - default: { + return SubCmd.map[subCmd].solve({ ctx, msg, cmdArgs, epId, uid, gid, sid, ai, page, ret }); + } else { ret.showHelp = true; return ret; } + } catch (e) { + logger.error(`指令.ai执行失败:${e.message}`); + seal.replyToSender(ctx, msg, `指令.ai执行失败:${e.message}`); + return seal.ext.newCmdExecuteResult(true); } - } catch (e) { - logger.error(`指令.ai执行失败:${e.message}`); - seal.replyToSender(ctx, msg, `指令.ai执行失败:${e.message}`); - return seal.ext.newCmdExecuteResult(true); } + + ConfigManager.ext.cmdMap['AI'] = cmd; + ConfigManager.ext.cmdMap['ai'] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/ctxn.ts b/src/cmd/sub_cmd/ctxn.ts new file mode 100644 index 0000000..c6fdc08 --- /dev/null +++ b/src/cmd/sub_cmd/ctxn.ts @@ -0,0 +1,68 @@ +import { aliasToCmd } from "../../utils/utils"; +import { I, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdCtxn() { + const cmd = new SubCmd('ctxn'); + cmd.desc = '上下文里的名字相关'; + cmd.help = `帮助: +【.ai ctxn status】查看上下文里的名字和自动修改状态 +【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片 +【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) +0: 不自动修改 +1: 自动修改为昵称 +2: 自动修改为群名片`; + cmd.priv = { + priv: U, args: { + status: { priv: U }, + set: { priv: I }, + mod: { priv: I } + } + }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, epId, gid, ai, ret } = scc; + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'status': { + seal.replyToSender(ctx, msg, `自动修改上下文里的名字状态:${ai.context.autoNameMod} +上下文里的名字有:\n${ai.context.userInfoList.map(ui => `${ui.name}(${ui.id})`).join('\n')}`); + return ret; + } + case 'set': { + const val3 = cmdArgs.getArgN(3); + const mod = aliasToCmd(val3); + if (mod !== 'nickname' && mod !== 'card') { + seal.replyToSender(ctx, msg, `帮助: +【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片`); + return ret; + } + const promises = ai.context.userInfoList.map(ui => ai.context.setName(epId, gid, ui.id, mod)); + Promise.all(promises).then(() => { + seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.userInfoList.map(uni => `${uni.name}(${uni.id})`).join('\n')}`); + }); + return ret; + } + case 'mod': { + const val3 = cmdArgs.getArgN(3); + const mod = parseInt(val3); + if (isNaN(mod) || mod < 0 || mod > 2) { + seal.replyToSender(ctx, msg, `帮助: +【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改) +0: 不自动修改 +1: 自动修改为昵称 +2: 自动修改为群名片`); + return ret; + } + ai.context.autoNameMod = mod; + seal.replyToSender(ctx, msg, `设置成功,将自动修改上下文里的名字为${mod === 1 ? '昵称' : '群名片'}`); + return ret; + } + default: { + seal.replyToSender(ctx, msg, cmd.help); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} diff --git a/src/cmd/sub_cmd/forget.ts b/src/cmd/sub_cmd/forget.ts new file mode 100644 index 0000000..5d3cb0b --- /dev/null +++ b/src/cmd/sub_cmd/forget.ts @@ -0,0 +1,45 @@ +import { AIManager } from "../../AI/AI"; +import { aliasToCmd } from "../../utils/utils"; +import { I, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdForget() { + const cmd = new SubCmd('forget'); + cmd.desc = '遗忘上下文'; + cmd.help = ''; + cmd.priv = { + priv: I, args: { + assistant: { priv: U }, + user: { priv: U } + } + }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, ai, ret } = scc; + + ai.resetState(); + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'assistant': { + ai.context.clearMessages('assistant', 'tool'); + seal.replyToSender(ctx, msg, 'ai上下文已清除'); + AIManager.saveAI(sid); + return ret; + } + case 'user': { + ai.context.clearMessages('user'); + seal.replyToSender(ctx, msg, '用户上下文已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + ai.context.clearMessages(); + seal.replyToSender(ctx, msg, '上下文已清除'); + AIManager.saveAI(sid); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/ignore.ts b/src/cmd/sub_cmd/ignore.ts new file mode 100644 index 0000000..1b0a925 --- /dev/null +++ b/src/cmd/sub_cmd/ignore.ts @@ -0,0 +1,76 @@ +import { AIManager } from "../../AI/AI"; +import { aliasToCmd } from "../../utils/utils"; +import { U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdIgnore() { + const cmd = new SubCmd('ignore'); + cmd.desc = '忽略名单相关操作'; + cmd.help = ''; + cmd.priv = { + priv: U, args: { + add: { priv: U }, + remove: { priv: U }, + list: { priv: U } + } + }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, epId, sid, ai, ret } = scc; + + if (ctx.isPrivate) { + seal.replyToSender(ctx, msg, '忽略名单仅在群聊可用'); + return ret; + } + + const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); + const muid = cmdArgs.amIBeMentionedFirst ? epId : mctx.player.userId; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'add': { + if (cmdArgs.at.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai ign add @xxx】添加忽略名单'); + return ret; + } + if (ai.context.ignoreList.includes(muid)) { + seal.replyToSender(ctx, msg, '已经在忽略名单中'); + return ret; + } + ai.context.ignoreList.push(muid); + seal.replyToSender(ctx, msg, '已添加到忽略名单'); + AIManager.saveAI(sid); + return ret; + } + case 'remove': { + if (cmdArgs.at.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai ign rm @xxx】移除忽略名单'); + return ret; + } + if (!ai.context.ignoreList.includes(muid)) { + seal.replyToSender(ctx, msg, '不在忽略名单中'); + return ret; + } + ai.context.ignoreList = ai.context.ignoreList.filter(item => item !== muid); + seal.replyToSender(ctx, msg, '已从忽略名单中移除'); + AIManager.saveAI(sid); + return ret; + } + case 'list': { + const s = ai.context.ignoreList.length === 0 ? '忽略名单为空' : `忽略名单如下:\n${ai.context.ignoreList.join('\n')}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: + 【.ai ign add @xxx】添加忽略名单 + 【.ai ign rm @xxx】移除忽略名单 + 【.ai ign lst】列出忽略名单 + + 忽略名单中的对象仍能正常对话,但无法被选中QQ号`); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts new file mode 100644 index 0000000..05e39cc --- /dev/null +++ b/src/cmd/sub_cmd/image.ts @@ -0,0 +1,116 @@ +import { AIManager } from "../../AI/AI"; +import { ImageManager } from "../../AI/image"; +import { aliasToCmd } from "../../utils/utils"; +import { transformArrayToContent, transformTextToArray } from "../../utils/utils_string"; +import { I, M, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdImage() { + const cmd = new SubCmd('image'); + cmd.desc = '图片相关操作'; + cmd.help = ''; + cmd.priv = { + priv: U, args: { + list: { + priv: U, args: { + steal: { priv: U }, + local: { priv: M } + } + }, + steal: { + priv: I, args: { + on: { priv: U }, + off: { priv: U }, + forget: { priv: U }, + } + }, + itt: { priv: M }, + find: { priv: I } + } + }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, ai, page, ret } = scc; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'list': { + const type = cmdArgs.getArgN(3); + switch (aliasToCmd(type)) { + case 'steal': { + seal.replyToSender(ctx, msg, ai.imageManager.getStolenImageListText(page) || '暂无偷取图片'); + return ret; + } + case 'local': { + seal.replyToSender(ctx, msg, ImageManager.getLocalImageListText(page) || '暂无本地图片'); + return ret; + } + default: { + seal.replyToSender(ctx, msg, '【.ai img list [stl/lcl]】展示偷取的图片/本地图片'); + return ret; + } + } + } + case 'steal': { + const op = cmdArgs.getArgN(3); + switch (aliasToCmd(op)) { + case 'on': { + ai.imageManager.stealStatus = true; + seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.imageManager.stolenImages.length}`); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + ai.imageManager.stealStatus = false; + seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.imageManager.stolenImages.length}`); + AIManager.saveAI(sid); + return ret; + } + case 'forget': { + ai.imageManager.stolenImages = []; + seal.replyToSender(ctx, msg, '偷取图片已遗忘'); + AIManager.saveAI(sid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `图片偷取状态:${ai.imageManager.stealStatus},当前偷取数量:${ai.imageManager.stolenImages.length}`); + return ret; + } + } + } + case 'itt': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + seal.replyToSender(ctx, msg, '【.ai img itt [图片] (附加提示词)】图片转文字'); + return ret; + } + const messageArray = transformTextToArray(val3); + const { images } = await transformArrayToContent(ctx, ai, messageArray); + if (images.length === 0) seal.replyToSender(ctx, msg, '请附带图片'); + const img = images[0]; + await img.imageToText(cmdArgs.getRestArgsFrom(4)) + seal.replyToSender(ctx, msg, img.CQCode + `\n` + img.content); + return ret; + } + case 'find': { + const id = cmdArgs.getArgN(3); + if (!id) { + seal.replyToSender(ctx, msg, '【.ai img find <图片ID>】查找图片'); + return ret; + } + const img = await ai.context.findImage(ctx, id); + seal.replyToSender(ctx, msg, img ? img.CQCode : '未找到该图片'); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: + 【.ai img list [stl/lcl]】展示偷取的图片/本地图片 + 【.ai img stl [on/off/f]】偷图 开启/关闭/遗忘 + 【.ai img itt [图片] (附加提示词)】图片转文字 + 【.ai img find <图片ID>】查找图片`); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/memory.ts b/src/cmd/sub_cmd/memory.ts new file mode 100644 index 0000000..848083f --- /dev/null +++ b/src/cmd/sub_cmd/memory.ts @@ -0,0 +1,283 @@ +import { AIManager } from "../../AI/AI"; +import { ConfigManager } from "../../config/configManager"; +import { aliasToCmd } from "../../utils/utils"; +import { I, S, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdMemory() { + const cmd = new SubCmd('memory'); + cmd.desc = '记忆相关操作'; + cmd.help = ''; + cmd.priv = { + priv: U, args: { + status: { priv: U }, + private: { + priv: U, args: { + set: { + priv: U, args: { + clear: { priv: U }, + "*": { priv: U } + } + }, + delete: { priv: U }, + list: { priv: U }, + clear: { priv: U } + } + }, + group: { + priv: I, args: { + set: { + priv: U, args: { + clear: { priv: U }, + "*": { priv: U } + } + }, + delete: { priv: U }, + list: { priv: U }, + clear: { priv: U } + } + }, + short: { + priv: S, args: { + list: { priv: U }, + clear: { priv: U }, + on: { priv: U }, + off: { priv: U } + } + }, + sum: { priv: U } + } + }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, epId, sid, ai, page, ret } = scc; + + const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); + const muid = mctx.player.userId; + + const ai2 = AIManager.getAI(muid); + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'status': { + let ai3 = ai; + if (cmdArgs.at.length > 0 && (cmdArgs.at.length !== 1 || cmdArgs.at[0].userId !== epId)) { + ai3 = ai2; + } + const { isMemory, isShortMemory } = ConfigManager.memory; + seal.replyToSender(ctx, msg, `${ai3.id} + 长期记忆开启状态: ${isMemory ? '是' : '否'} + 长期记忆条数: ${ai3.memory.memoryIds.length} + 关键词库: ${ai3.memory.keywords.join('、') || '无'} + 短期记忆开启状态: ${(isShortMemory && ai3.memory.useShortMemory) ? '是' : '否'} + 短期记忆条数: ${ai3.memory.shortMemoryList.length}`); + return ret; + } + case 'private': { + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'set': { + const s = cmdArgs.getRestArgsFrom(4); + switch (aliasToCmd(s)) { + case '': { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p st <内容>】设置个人设定,【.ai memo p st clr】清除个人设定'); + return ret; + } + case 'clear': { + ai2.memory.persona = '无'; + seal.replyToSender(ctx, msg, '设定已清除'); + AIManager.saveAI(muid); + return ret; + } + default: { + if (s.length > 20) { + seal.replyToSender(ctx, msg, '设定过长,请控制在20字以内'); + return ret; + } + ai2.memory.persona = s; + seal.replyToSender(ctx, msg, '设定已修改'); + AIManager.saveAI(muid); + return ret; + } + } + } + case 'delete': { + const idList = cmdArgs.args.slice(3); + const kw = cmdArgs.kwargs.map(item => item.name); + if (idList.length === 0 && kw.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p del --关键词1 --关键词2】删除个人记忆'); + return ret; + } + ai2.memory.deleteMemory(idList, kw); + seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ + isPrivate: true, + id: mctx.player.userId, + name: mctx.player.name + }, page) || '记忆已全部清除'); + AIManager.saveAI(muid); + return ret; + } + case 'list': { + seal.replyToSender(ctx, msg, ai2.memory.getLatestMemoryListText({ + isPrivate: true, + id: mctx.player.userId, + name: mctx.player.name + }, page) || '无记忆'); + return ret; + } + case 'clear': { + ai2.memory.clearMemory(); + seal.replyToSender(ctx, msg, '个人记忆已清除'); + AIManager.saveAI(muid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `参数缺失: + 【.ai memo p st <内容>】设置个人设定 + 【.ai memo p st clr】清除个人设定 + 【.ai memo p del --关键词1 --关键词2】删除个人记忆 + 【.ai memo p list】展示个人记忆 + 【.ai memo p clr】清除个人记忆`); + return ret; + } + } + } + case 'group': { + if (ctx.isPrivate) { + seal.replyToSender(ctx, msg, '群聊记忆仅在群聊可用'); + return ret; + } + + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'set': { + const s = cmdArgs.getRestArgsFrom(4); + switch (aliasToCmd(s)) { + case '': { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g st <内容>】设置群聊设定,【.ai memo g st clr】清除群聊设定'); + return ret; + } + case 'clear': { + ai.memory.persona = '无'; + seal.replyToSender(ctx, msg, '设定已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + if (s.length > 30) { + seal.replyToSender(ctx, msg, '设定过长,请控制在30字以内'); + return ret; + } + ai.memory.persona = s; + seal.replyToSender(ctx, msg, '设定已修改'); + AIManager.saveAI(sid); + return ret; + } + } + } + case 'delete': { + const idList = cmdArgs.args.slice(3); + const kw = cmdArgs.kwargs.map(item => item.name); + if (idList.length === 0 && kw.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g del 】删除群聊记忆'); + return ret; + } + ai.memory.deleteMemory(idList, kw); + seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, page) || '记忆已全部清除'); + AIManager.saveAI(sid); + return ret; + } + case 'list': { + seal.replyToSender(ctx, msg, ai.memory.getLatestMemoryListText({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, page) || '无记忆'); + return ret; + } + case 'clear': { + ai.memory.clearMemory(); + seal.replyToSender(ctx, msg, '群聊记忆已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `参数缺失: + 【.ai memo g st <内容>】设置群聊设定 + 【.ai memo g st clr】清除群聊设定 + 【.ai memo g del --关键词1 --关键词2】删除群聊记忆 + 【.ai memo g list】展示群聊记忆 + 【.ai memo g clr】清除群聊记忆`); + return ret; + } + } + } + case 'short': { + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'on': { + ai.memory.useShortMemory = true; + seal.replyToSender(ctx, msg, '短期记忆已开启'); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + ai.memory.useShortMemory = false; + seal.replyToSender(ctx, msg, '短期记忆已关闭'); + AIManager.saveAI(sid); + return ret; + } + case 'list': { + if (ai.memory.shortMemoryList.length === 0) { + seal.replyToSender(ctx, msg, '短期记忆为空'); + return ret; + } + seal.replyToSender(ctx, msg, ai.memory.shortMemoryList + .map((item, index) => `${index + 1}. ${item}`) + .slice((page - 1) * 10, page * 10) + .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); + return ret; + } + case 'clear': { + ai.memory.clearShortMemory(); + seal.replyToSender(ctx, msg, '短期记忆已清除'); + AIManager.saveAI(sid); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `参数缺失 + 【.ai memo short list】展示短期记忆 + 【.ai memo short clr】清除短期记忆 + 【.ai memo short [on/off]】开启/关闭短期记忆`); + return ret; + } + } + } + case 'sum': { + ai.context.summaryCounter = 0; + await ai.memory.updateShortMemory(ctx, msg, ai) + seal.replyToSender(ctx, msg, ai.memory.shortMemoryList + .map((item, index) => `${index + 1}. ${item}`) + .slice((page - 1) * 10, page * 10) + .join('\n') + `\n当前页码: ${page}/${Math.ceil(ai.memory.shortMemoryList.length / 10)}`); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: + 【.ai memo status (@xxx)】查看记忆状态,@为查看个人记忆状态 + 【.ai memo [p/g] st <内容>】设置个人/群聊设定 + 【.ai memo [p/g] st clr】清除个人/群聊设定 + 【.ai memo [p/g] del --关键词1 --关键词2】删除个人/群聊记忆 + 【.ai memo [p/g/short] list】展示个人/群聊/短期记忆 + 【.ai memo [p/g/short] clr】清除个人/群聊/短期记忆 + 【.ai memo short [on/off]】开启/关闭短期记忆 + 【.ai memo sum】立即总结一次短期记忆`); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/off.ts b/src/cmd/sub_cmd/off.ts new file mode 100644 index 0000000..bd36f35 --- /dev/null +++ b/src/cmd/sub_cmd/off.ts @@ -0,0 +1,82 @@ +import { AIManager } from "../../AI/AI"; +import { TimerManager } from "../../timer"; +import { I } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdOff() { + const cmd = new SubCmd('off'); + cmd.desc = '关闭AI,此时仍能用正则匹配触发'; + cmd.help = ''; + cmd.priv = { priv: I }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, ai, ret } = scc; + + const setting = ai.setting; + + const kwargs = cmdArgs.kwargs; + if (kwargs.length == 0) { + ai.resetState(); + TimerManager.removeTimers(sid, '', ['activeTime'], []); + + setting.counter = -1; + setting.timer = -1; + setting.prob = -1; + setting.standby = false; + setting.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } + + seal.replyToSender(ctx, msg, 'AI已关闭'); + AIManager.saveAI(sid); + return ret; + } + + let text = `AI已关闭:`; + kwargs.forEach(kwarg => { + const name = kwarg.name; + + switch (name) { + case 'c': + case 'counter': { + ai.context.counter = 0; + setting.counter = -1; + text += `\n计数器模式`; + break; + } + case 't': + case 'timer': { + clearTimeout(ai.context.timer); + ai.context.timer = null; + setting.timer = -1; + text += `\n计时器模式`; + break; + } + case 'p': + case 'prob': { + setting.prob = -1; + text += `\n概率模式`; + break; + } + case 'a': + case 'active': { + TimerManager.removeTimers(sid, '', ['activeTime'], []); + setting.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } + text += `\n活跃时间段`; + break; + } + } + }); + + seal.replyToSender(ctx, msg, text); + AIManager.saveAI(sid); + return ret; + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/on.ts b/src/cmd/sub_cmd/on.ts new file mode 100644 index 0000000..94002da --- /dev/null +++ b/src/cmd/sub_cmd/on.ts @@ -0,0 +1,127 @@ +import { AIManager } from "../../AI/AI"; +import { TimerManager } from "../../timer"; +import { S } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdOn() { + const cmd = new SubCmd('on'); + cmd.desc = '开启AI'; + cmd.help = `帮助: +【.ai on --<参数>=<数字>】 + +<参数>: +【c】计数器模式,接收消息数达到后触发 +单位/条,默认10条 +【t】计时器模式,最后一条消息后达到时限触发 +单位/秒,默认60秒 +【p】概率模式,每条消息按概率触发 +单位/%,默认10% +【a】活跃时间段和活跃次数 +格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5") + +【.ai on --t --p=42】使用示例`; + cmd.priv = { priv: S }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, ai, ret } = scc; + + const setting = ai.setting; + + const kwargs = cmdArgs.kwargs; + if (kwargs.length == 0) { + seal.replyToSender(ctx, msg, cmd.help); + return ret; + } + + let text = `AI已开启:`; + for (const kwarg of kwargs) { + const name = kwarg.name; + const exist = kwarg.valueExists; + const valInt = parseInt(kwarg.value); + const valFloat = parseFloat(kwarg.value); + const valStr = kwarg.value.trim(); + + switch (name) { + case 'c': + case 'counter': { + ai.context.counter = 0; + setting.counter = exist && !isNaN(valInt) ? valInt : 10; + text += `\n计数器模式:${setting.counter}条`; + break; + } + case 't': + case 'timer': { + clearTimeout(ai.context.timer); + ai.context.timer = null; + setting.timer = exist && !isNaN(valFloat) ? valFloat : 60; + text += `\n计时器模式:${setting.timer}秒`; + break; + } + case 'p': + case 'prob': { + setting.prob = exist && !isNaN(valFloat) ? valFloat : 10; + text += `\n概率模式:${setting.prob}%`; + break; + } + case 'a': + case 'active': { + if (!exist) { + seal.replyToSender(ctx, msg, '请输入活跃时间段'); + return ret; + } + + const arr = valStr.split('-').map((item, index) => { + const parts = item.split(/[::,,]+/).map(Number).map(i => isNaN(i) ? 0 : i); + if (index < 2) { + return Math.ceil((parts[0] * 60 + (parts[1] || 0)) % (24 * 60)); + } else { + return parts[0]; + } + }) + + const [start = 0, end = 0, segs = 1] = arr; + + if (start === end) { + seal.replyToSender(ctx, msg, '活跃时间段开始时间和结束时间不能相同'); + return ret; + } + + if (!Number.isInteger(segs)) { + seal.replyToSender(ctx, msg, '活跃次数必须为整数'); + return ret; + } + + const endReal = end >= start ? end : end + 24 * 60; + if (segs > endReal - start) { + seal.replyToSender(ctx, msg, '活跃次数不能大于活跃时间段分钟数'); + return ret; + } + + TimerManager.removeTimers(sid, '', ['activeTime'], []); + setting.activeTimeInfo = { + start, + end, + segs, + } + + text += `\n活跃时间段:${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}`; + text += `\n活跃次数:${segs}`; + + const curSegIndex = ai.curActiveTimeSegIndex; + const nextTimePoint = ai.getNextTimePoint(curSegIndex); + if (nextTimePoint !== -1) { + TimerManager.addActiveTimeTimer(ctx, ai, nextTimePoint); + } + break; + } + } + }; + + setting.standby = true; + + seal.replyToSender(ctx, msg, text); + AIManager.saveAI(sid); + return ret; + } + + SubCmd.map[cmd.name] = cmd; +} diff --git a/src/cmd/sub_cmd/privilege.ts b/src/cmd/sub_cmd/privilege.ts index 4ecff19..6ec255e 100644 --- a/src/cmd/sub_cmd/privilege.ts +++ b/src/cmd/sub_cmd/privilege.ts @@ -4,8 +4,9 @@ import { aliasToCmd } from "../../utils/utils"; import { M, PrivilegeManager, U } from "../privilege"; import { SubCmd, SubCmdContext } from "../root"; -export function getCmdPrivilege(): SubCmd { +export function registerCmdPrivilege() { const cmd = new SubCmd('privilege'); + cmd.desc = '权限相关'; cmd.help = `帮助: 【.ai priv ses st <会话权限>】修改会话权限 【.ai priv ses ck 】检查会话权限 @@ -152,5 +153,5 @@ ${HELPMAP["指令"]}`); } } - return cmd; + SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/prompt.ts b/src/cmd/sub_cmd/prompt.ts index 6d34565..3f6e168 100644 --- a/src/cmd/sub_cmd/prompt.ts +++ b/src/cmd/sub_cmd/prompt.ts @@ -3,8 +3,9 @@ import { buildSystemMessage } from "../../utils/utils_message"; import { M } from "../privilege"; import { SubCmd, SubCmdContext } from "../root"; -export function getCmdPrompt(): SubCmd { +export function registerCmdPrompt() { const cmd = new SubCmd('prompt'); + cmd.desc = '查看system prompt'; cmd.help = ''; cmd.priv = { priv: M }; cmd.solve = async (scc: SubCmdContext) => { @@ -15,5 +16,5 @@ export function getCmdPrompt(): SubCmd { return ret; } - return cmd; + SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/role.ts b/src/cmd/sub_cmd/role.ts new file mode 100644 index 0000000..ad8d30b --- /dev/null +++ b/src/cmd/sub_cmd/role.ts @@ -0,0 +1,35 @@ +import { ConfigManager } from "../../config/configManager"; +import { getRoleSetting } from "../../utils/utils_message"; +import { I } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdRole() { + const cmd = new SubCmd('role'); + cmd.desc = '切换角色设定'; + cmd.help = ''; + cmd.priv = { priv: I }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, ret } = scc; + + const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; + const { roleName } = getRoleSetting(ctx); + const val2 = cmdArgs.getArgN(2); + if (!val2) { + seal.replyToSender(ctx, msg, `当前角色设定名称为[${roleName}],名称有:\n${roleSettingNames.join('、')}`); + return ret; + } + if (!roleSettingNames.includes(val2)) { + seal.replyToSender(ctx, msg, `【.ai role <名称>】切换角色设定\n角色设定名称错误,名称有:\n${roleSettingNames.join('、')}`); + return ret; + } + const roleSettingIndex = roleSettingNames.indexOf(val2); + if (roleSettingIndex < 0 || roleSettingIndex >= roleSettingTemplate.length) { + seal.replyToSender(ctx, msg, `角色设定名称[${val2}]没有对应的角色设定`); + } + seal.vars.strSet(ctx, "$gSYSPROMPT", val2); + seal.replyToSender(ctx, msg, `角色设定已切换到[${val2}]`); + return ret; + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/sample.ts b/src/cmd/sub_cmd/sample.ts new file mode 100644 index 0000000..3e87e7e --- /dev/null +++ b/src/cmd/sub_cmd/sample.ts @@ -0,0 +1,15 @@ +import { U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdSample() { + const cmd = new SubCmd('sample'); + cmd.help = ''; + cmd.priv = { priv: U }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, epId, uid, gid, sid, ai, page, ret } = scc; + ctx; msg; cmdArgs; epId; uid; gid; sid; ai; page; ret; + return ret; + } + + SubCmd.map[cmd.name] = cmd; +} diff --git a/src/cmd/sub_cmd/shut.ts b/src/cmd/sub_cmd/shut.ts new file mode 100644 index 0000000..f152dfd --- /dev/null +++ b/src/cmd/sub_cmd/shut.ts @@ -0,0 +1,23 @@ +import { U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdShut() { + const cmd = new SubCmd('shut'); + cmd.desc = '打断当前对话'; + cmd.help = ''; + cmd.priv = { priv: U }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, ai, ret } = scc; + + if (ai.stream.id === '') { + seal.replyToSender(ctx, msg, '当前没有正在进行的对话'); + return ret; + } + + await ai.stopCurrentChatStream() + seal.replyToSender(ctx, msg, '已停止当前对话'); + return ret; + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/standby.ts b/src/cmd/sub_cmd/standby.ts new file mode 100644 index 0000000..612b91d --- /dev/null +++ b/src/cmd/sub_cmd/standby.ts @@ -0,0 +1,35 @@ +import { AIManager } from "../../AI/AI"; +import { TimerManager } from "../../timer"; +import { I } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdStandby() { + const cmd = new SubCmd('standby'); + cmd.desc = '开启待机模式,此时AI将记录聊天内容'; + cmd.help = ''; + cmd.priv = { priv: I }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, sid, ai, ret } = scc; + + const setting = ai.setting; + + ai.resetState(); + TimerManager.removeTimers(sid, '', ['activeTime'], []); + + setting.counter = -1; + setting.timer = -1; + setting.prob = -1; + setting.standby = true; + setting.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } + + seal.replyToSender(ctx, msg, 'AI已开启待机模式'); + AIManager.saveAI(sid); + return ret; + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/status.ts b/src/cmd/sub_cmd/status.ts index 5aa663c..05ed7aa 100644 --- a/src/cmd/sub_cmd/status.ts +++ b/src/cmd/sub_cmd/status.ts @@ -1,11 +1,12 @@ import { U } from "../privilege"; import { SubCmd, SubCmdContext } from "../root"; -export function getCmdStatus(): SubCmd { +export function registerCmdStatus() { const cmd = new SubCmd('status'); + cmd.desc = '查看当前AI状态'; cmd.help = ''; cmd.priv = { priv: U }; - cmd.solve = async (scc: SubCmdContext) => { + cmd.solve = (scc: SubCmdContext) => { const { ctx, msg, sid, ai, ret } = scc; const setting = ai.setting; const { start, end, segs } = setting.activeTimeInfo; @@ -22,5 +23,5 @@ export function getCmdStatus(): SubCmd { return ret; } - return cmd; + SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/timer.ts b/src/cmd/sub_cmd/timer.ts new file mode 100644 index 0000000..a7ba7ae --- /dev/null +++ b/src/cmd/sub_cmd/timer.ts @@ -0,0 +1,40 @@ +import { TimerManager } from "../../timer"; +import { aliasToCmd } from "../../utils/utils"; +import { I, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdTimer() { + const cmd = new SubCmd('timer'); + cmd.desc = '定时器相关'; + cmd.help = `帮助: +【.ai timer lst】查看当前聊天定时器 +【.ai timer clr】清除当前聊天定时器`; + cmd.priv = { + priv: U, args: { + list: { priv: U }, + clear: { priv: I } + } + }; + cmd.solve = (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, page, ret } = scc; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'list': { + seal.replyToSender(ctx, msg, TimerManager.getTimerListText(sid, page) || '当前对话没有定时器'); + return ret; + } + case 'clear': { + TimerManager.removeTimers(sid, '', [], []); + seal.replyToSender(ctx, msg, '所有定时器已清除'); + return ret; + } + default: { + seal.replyToSender(ctx, msg, cmd.help); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} diff --git a/src/cmd/sub_cmd/token.ts b/src/cmd/sub_cmd/token.ts new file mode 100644 index 0000000..bcf9752 --- /dev/null +++ b/src/cmd/sub_cmd/token.ts @@ -0,0 +1,402 @@ +import { AIManager } from "../../AI/AI"; +import { get_chart_url } from "../../service"; +import { aliasToCmd } from "../../utils/utils"; +import { S, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdToken() { + const cmd = new SubCmd('token'); + cmd.desc = 'token相关操作'; + cmd.help = ''; + cmd.priv = { + priv: S, args: { + list: { priv: U }, + sum: { priv: U }, + all: { priv: U }, + year: { + priv: U, args: { + chart: { priv: U } + } + }, + month: { + priv: U, args: { + chart: { priv: U } + } + }, + clear: { priv: U }, + help: { priv: U }, + "*": { priv: U } + } + }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, ret } = scc; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'list': { + const s = Object.keys(AIManager.usageMap).join('\n'); + seal.replyToSender(ctx, msg, `有使用记录的模型:\n${s}`); + return ret; + } + case 'sum': { + const usage = { + prompt_tokens: 0, + completion_tokens: 0 + }; + + for (const model in AIManager.usageMap) { + const modelUsage = AIManager.getModelUsage(model); + usage.prompt_tokens += modelUsage.prompt_tokens; + usage.completion_tokens += modelUsage.completion_tokens; + } + + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + const s = `输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + case 'all': { + const s = Object.keys(AIManager.usageMap).map((model, index) => { + const usage = AIManager.getModelUsage(model); + + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return `${index + 1}. ${model}: 没有使用记录`; + } + + return `${index + 1}. ${model}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + if (!s) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + seal.replyToSender(ctx, msg, `全部使用记录如下:\n${s}`); + return ret; + } + case 'year': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentYM = currentYear * 12 + currentMonth; + for (const model in AIManager.usageMap) { + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, _] = key.split('-').map(v => parseInt(v)); + const ym = year * 12 + month; + + if (ym >= currentYM - 11 && ym <= currentYM) { + const key = `${year}-${month}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + } + + const val3 = cmdArgs.getArgN(3); + if (val3 === 'chart') { + const url = await get_chart_url('year', obj); + seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败'); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 + monthA) - (yearB * 12 + monthB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + if (!s) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); + return ret; + } + case 'month': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentDay = now.getDate(); + const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; + for (const model in AIManager.usageMap) { + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, day] = key.split('-').map(v => parseInt(v)); + const ymd = year * 12 * 31 + month * 31 + day; + + if (ymd >= currentYMD - 30 && ymd <= currentYMD) { + const key = `${year}-${month}-${day}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + } + + const val3 = cmdArgs.getArgN(3); + if (val3 === 'chart') { + const url = await get_chart_url('month', obj); + seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败'); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); + return ret; + } + case 'clear': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + AIManager.clearUsageMap(); + seal.replyToSender(ctx, msg, '已清除token使用记录'); + AIManager.saveUsageMap(); + return ret; + } + + if (!AIManager.usageMap.hasOwnProperty(val3)) { + seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); + return ret; + } + + delete AIManager.usageMap[val3]; + seal.replyToSender(ctx, msg, `已清除 ${val3} 的token使用记录`); + AIManager.saveUsageMap(); + return ret; + } + case '': + case 'help': { + seal.replyToSender(ctx, msg, `帮助: + 【.ai tk lst】查看所有模型 + 【.ai tk sum】查看所有模型的token使用记录总和 + 【.ai tk all】查看所有模型的token使用记录 + 【.ai tk [y/m] (chart)】查看所有模型今年/这个月的token使用记录 + 【.ai tk <模型名称>】查看模型的token使用记录 + 【.ai tk <模型名称> [y/m] (chart)】查看模型今年/这个月的token使用记录 + 【.ai tk clr】清除token使用记录 + 【.ai tk clr <模型名称>】清除token使用记录`); + return ret; + } + default: { + if (!AIManager.usageMap.hasOwnProperty(val2)) { + seal.replyToSender(ctx, msg, '没有这个模型,请使用【.ai tk lst】查看所有模型'); + return ret; + } + + const val3 = cmdArgs.getArgN(3); + switch (aliasToCmd(val3)) { + case 'year': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentYM = currentYear * 12 + currentMonth; + const model = val2; + + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, _] = key.split('-').map(v => parseInt(v)); + const ym = year * 12 + month; + + if (ym >= currentYM - 11 && ym <= currentYM) { + const key = `${year}-${month}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + + const val4 = cmdArgs.getArgN(4); + if (val4 === 'chart') { + const url = await get_chart_url('year', obj); + seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败'); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 + monthA) - (yearB * 12 + monthB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + if (!s) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + seal.replyToSender(ctx, msg, `最近12个月使用记录如下:\n${s}`); + return ret; + } + case 'month': { + const obj: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } + } = {}; + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth() + 1; + const currentDay = now.getDate(); + const currentYMD = currentYear * 12 * 31 + currentMonth * 31 + currentDay; + const model = val2; + + const modelUsage = AIManager.usageMap[model]; + for (const key in modelUsage) { + const usage = modelUsage[key]; + const [year, month, day] = key.split('-').map(v => parseInt(v)); + const ymd = year * 12 * 31 + month * 31 + day; + + if (ymd >= currentYMD - 30 && ymd <= currentYMD) { + const key = `${year}-${month}-${day}`; + if (!obj.hasOwnProperty(key)) { + obj[key] = { + prompt_tokens: 0, + completion_tokens: 0 + }; + } + + obj[key].prompt_tokens += usage.prompt_tokens; + obj[key].completion_tokens += usage.completion_tokens; + } + } + + const val4 = cmdArgs.getArgN(4); + if (val4 === 'chart') { + const url = await get_chart_url('month', obj); + seal.replyToSender(ctx, msg, url ? `[CQ:image,file=${url}]` : '图表生成失败'); + return ret; + } + + const keys = Object.keys(obj).sort((a, b) => { + const [yearA, monthA, dayA] = a.split('-').map(v => parseInt(v)); + const [yearB, monthB, dayB] = b.split('-').map(v => parseInt(v)); + return (yearA * 12 * 31 + monthA * 31 + dayA) - (yearB * 12 * 31 + monthB * 31 + dayB); + }); + + const s = keys.map(key => { + const usage = obj[key]; + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + return ``; + } + + return `${key}: + 输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + }).join('\n'); + + seal.replyToSender(ctx, msg, `最近31天使用记录如下:\n${s}`); + return ret; + } + default: { + const usage = AIManager.getModelUsage(val2); + + if (usage.prompt_tokens === 0 && usage.completion_tokens === 0) { + seal.replyToSender(ctx, msg, `没有使用记录`); + return ret; + } + + const s = `输入token:${usage.prompt_tokens} + 输出token:${usage.completion_tokens} + 总token:${usage.prompt_tokens + usage.completion_tokens}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + } + } + } + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/cmd/sub_cmd/tool.ts b/src/cmd/sub_cmd/tool.ts new file mode 100644 index 0000000..586e657 --- /dev/null +++ b/src/cmd/sub_cmd/tool.ts @@ -0,0 +1,156 @@ +import { AIManager } from "../../AI/AI"; +import { ConfigManager } from "../../config/configManager"; +import { logger } from "../../logger"; +import { ToolManager } from "../../tool/tool"; +import { aliasToCmd } from "../../utils/utils"; +import { I, M, U } from "../privilege"; +import { SubCmd, SubCmdContext } from "../root"; + +export function registerCmdTool() { + const cmd = new SubCmd('tool'); + cmd.desc = '工具相关操作'; + cmd.help = ''; + cmd.priv = { + priv: U, args: { + on: { priv: I }, + off: { priv: I }, + help: { priv: U }, + call: { priv: M }, + "*": { priv: U } + } + }; + cmd.solve = async (scc: SubCmdContext) => { + const { ctx, msg, cmdArgs, sid, ai, ret } = scc; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'on': { + const val3 = cmdArgs.getArgN(3); + if (val3) { + const toolsNotAllow = ConfigManager.tool.toolsNotAllow; + if (toolsNotAllow.includes(val3)) { + seal.replyToSender(ctx, msg, `工具函数 ${val3} 不被允许开启`); + return ret; + } + + ai.tool.toolStatus[val3] = true; + seal.replyToSender(ctx, msg, `已开启工具函数 ${val3}`); + AIManager.saveAI(sid); + return ret; + } + const toolsNotAllow = ConfigManager.tool.toolsNotAllow; + for (const key in ai.tool.toolStatus) { + ai.tool.toolStatus[key] = toolsNotAllow.includes(key) ? false : true; + } + seal.replyToSender(ctx, msg, '已开启全部工具函数'); + AIManager.saveAI(sid); + return ret; + } + case 'off': { + const val3 = cmdArgs.getArgN(3); + if (val3) { + ai.tool.toolStatus[val3] = false; + seal.replyToSender(ctx, msg, `已关闭工具函数 ${val3}`); + AIManager.saveAI(sid); + return ret; + } + for (const key in ai.tool.toolStatus) { + ai.tool.toolStatus[key] = false; + } + seal.replyToSender(ctx, msg, '已关闭全部工具函数'); + AIManager.saveAI(sid); + return ret; + } + case 'help': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + seal.replyToSender(ctx, msg, `帮助: + 【.ai tool】列出所有工具 + 【.ai tool [on/off] <函数名>】开启或关闭工具函数 + 【.ai tool help <函数名>】查看工具详情 + 【.ai tool call <函数名> --参数名=具体参数】试用工具函数`); + return ret; + } + + if (!ToolManager.toolMap.hasOwnProperty(val3)) { + seal.replyToSender(ctx, msg, '没有这个工具函数'); + return ret; + } + + const tool = ToolManager.toolMap[val3]; + const s = `${tool.info.function.name} + 描述:${tool.info.function.description} + + 参数信息: + ${JSON.stringify(tool.info.function.parameters.properties, null, 2)} + + 必需参数:${tool.info.function.parameters.required.join(',')}`; + + seal.replyToSender(ctx, msg, s); + return ret; + } + case 'call': { + const val3 = cmdArgs.getArgN(3); + if (!val3) { + seal.replyToSender(ctx, msg, `调用函数缺少工具函数名`); + return ret; + } + if (!ToolManager.toolMap.hasOwnProperty(val3)) { + seal.replyToSender(ctx, msg, `调用函数失败:未注册的函数:${val3}`); + return ret; + } + const tool = ToolManager.toolMap[val3]; + if (tool.cmdInfo.ext !== '' && ToolManager.cmdArgs == null) { + seal.replyToSender(ctx, msg, `暂时无法调用函数,请先使用 .r 指令`); + return ret; + } + + try { + const args = cmdArgs.kwargs.reduce((acc, kwarg) => { + const valueString = kwarg.value; + try { + acc[kwarg.name] = JSON.parse(`[${valueString}]`)[0]; + } catch (e) { + acc[kwarg.name] = valueString; + } + return acc; + }, {}); + + for (const key of tool.info.function.parameters.required) { + if (!args.hasOwnProperty(key)) { + logger.warning(`调用函数失败:缺少必需参数 ${key}`); + seal.replyToSender(ctx, msg, `调用函数失败:缺少必需参数 ${key}`); + return ret; + } + } + + const { content, images } = await tool.solve(ctx, msg, ai, args); + seal.replyToSender(ctx, msg, `返回内容: + ${content} + 返回图片: + ${images.map(img => img.CQCode).join('\n')}`); + return ret; + } catch (e) { + const s = `调用函数 (${val3}) 失败:${e.message}`; + seal.replyToSender(ctx, msg, s); + return ret; + } + } + default: { + const toolStatus = ai.tool.toolStatus; + + let i = 1; + let s = '工具函数如下:'; + Object.keys(toolStatus).forEach(key => { + const status = toolStatus[key] ? '开' : '关'; + s += `\n${i++}. ${key}[${status}]`; + }); + + seal.replyToSender(ctx, msg, s); + return ret; + } + } + } + + SubCmd.map[cmd.name] = cmd; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2e1087a..eb0a53f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,21 +10,19 @@ import { createMsg } from "./utils/utils_seal"; import { PrivilegeManager } from "./cmd/privilege"; import { knowledgeMM } from "./AI/memory"; import { CQTYPESALLOW } from "./config/config"; -import { cmd } from "./cmd/root"; +import { registerCmd } from "./cmd/root"; function main() { ConfigManager.registerConfig(); checkUpdate(); ToolManager.registerTool(); TimerManager.init(); - PrivilegeManager.reviveCmdPriv(); knowledgeMM.init(); const ext = ConfigManager.ext; - // 将命令注册到扩展中 - ext.cmdMap['AI'] = cmd; - ext.cmdMap['ai'] = cmd; + registerCmd(); + PrivilegeManager.reviveCmdPriv(); ext.onPoke = (ctx, event) => { const msg = createMsg(event.isPrivate ? 'private' : 'group', event.senderId, event.groupId); From 6133a195d5e0fc6bad2d391bd8ebf22dc35ac591 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 4 Mar 2026 19:25:09 +0800 Subject: [PATCH 4/4] =?UTF-8?q?Thanks=E2=99=AA(=EF=BD=A5=CF=89=EF=BD=A5)?= =?UTF-8?q?=EF=BE=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmd/root.ts | 2 ++ src/cmd/sub_cmd/ctxn.ts | 2 -- src/cmd/sub_cmd/forget.ts | 2 -- src/cmd/sub_cmd/ignore.ts | 2 -- src/cmd/sub_cmd/image.ts | 2 -- src/cmd/sub_cmd/memory.ts | 2 -- src/cmd/sub_cmd/off.ts | 2 -- src/cmd/sub_cmd/on.ts | 2 -- src/cmd/sub_cmd/privilege.ts | 2 -- src/cmd/sub_cmd/prompt.ts | 2 -- src/cmd/sub_cmd/role.ts | 2 -- src/cmd/sub_cmd/sample.ts | 2 -- src/cmd/sub_cmd/shut.ts | 2 -- src/cmd/sub_cmd/standby.ts | 2 -- src/cmd/sub_cmd/status.ts | 2 -- src/cmd/sub_cmd/timer.ts | 2 -- src/cmd/sub_cmd/token.ts | 2 -- src/cmd/sub_cmd/tool.ts | 2 -- src/config/sample.ts | 17 +++++++++++++++++ src/tool/sample.ts | 26 ++++++++++++++++++++++++++ 20 files changed, 45 insertions(+), 34 deletions(-) create mode 100644 src/config/sample.ts create mode 100644 src/tool/sample.ts diff --git a/src/cmd/root.ts b/src/cmd/root.ts index 763a15e..b326d28 100644 --- a/src/cmd/root.ts +++ b/src/cmd/root.ts @@ -46,6 +46,8 @@ export class SubCmd { this.help = ''; this.priv = { priv: U }; this.solve = async () => { return seal.ext.newCmdExecuteResult(false); }; + + SubCmd.map[name] = this; } static map: { [key: string]: SubCmd } = {}; diff --git a/src/cmd/sub_cmd/ctxn.ts b/src/cmd/sub_cmd/ctxn.ts index c6fdc08..352b18d 100644 --- a/src/cmd/sub_cmd/ctxn.ts +++ b/src/cmd/sub_cmd/ctxn.ts @@ -63,6 +63,4 @@ export function registerCmdCtxn() { } } } - - SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/forget.ts b/src/cmd/sub_cmd/forget.ts index 5d3cb0b..59b7c52 100644 --- a/src/cmd/sub_cmd/forget.ts +++ b/src/cmd/sub_cmd/forget.ts @@ -40,6 +40,4 @@ export function registerCmdForget() { } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/ignore.ts b/src/cmd/sub_cmd/ignore.ts index 1b0a925..728213b 100644 --- a/src/cmd/sub_cmd/ignore.ts +++ b/src/cmd/sub_cmd/ignore.ts @@ -71,6 +71,4 @@ export function registerCmdIgnore() { } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index 05e39cc..f03ae33 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -111,6 +111,4 @@ export function registerCmdImage() { } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/memory.ts b/src/cmd/sub_cmd/memory.ts index 848083f..7b59807 100644 --- a/src/cmd/sub_cmd/memory.ts +++ b/src/cmd/sub_cmd/memory.ts @@ -278,6 +278,4 @@ export function registerCmdMemory() { } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/off.ts b/src/cmd/sub_cmd/off.ts index bd36f35..e52452d 100644 --- a/src/cmd/sub_cmd/off.ts +++ b/src/cmd/sub_cmd/off.ts @@ -77,6 +77,4 @@ export function registerCmdOff() { AIManager.saveAI(sid); return ret; } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/on.ts b/src/cmd/sub_cmd/on.ts index 94002da..68e791c 100644 --- a/src/cmd/sub_cmd/on.ts +++ b/src/cmd/sub_cmd/on.ts @@ -122,6 +122,4 @@ export function registerCmdOn() { AIManager.saveAI(sid); return ret; } - - SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/privilege.ts b/src/cmd/sub_cmd/privilege.ts index 6ec255e..8646b58 100644 --- a/src/cmd/sub_cmd/privilege.ts +++ b/src/cmd/sub_cmd/privilege.ts @@ -152,6 +152,4 @@ ${HELPMAP["指令"]}`); } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/prompt.ts b/src/cmd/sub_cmd/prompt.ts index 3f6e168..1b10b29 100644 --- a/src/cmd/sub_cmd/prompt.ts +++ b/src/cmd/sub_cmd/prompt.ts @@ -15,6 +15,4 @@ export function registerCmdPrompt() { seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); return ret; } - - SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/role.ts b/src/cmd/sub_cmd/role.ts index ad8d30b..6550f6a 100644 --- a/src/cmd/sub_cmd/role.ts +++ b/src/cmd/sub_cmd/role.ts @@ -30,6 +30,4 @@ export function registerCmdRole() { seal.replyToSender(ctx, msg, `角色设定已切换到[${val2}]`); return ret; } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/sample.ts b/src/cmd/sub_cmd/sample.ts index 3e87e7e..1bf6927 100644 --- a/src/cmd/sub_cmd/sample.ts +++ b/src/cmd/sub_cmd/sample.ts @@ -10,6 +10,4 @@ export function registerCmdSample() { ctx; msg; cmdArgs; epId; uid; gid; sid; ai; page; ret; return ret; } - - SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/shut.ts b/src/cmd/sub_cmd/shut.ts index f152dfd..6e6a1b0 100644 --- a/src/cmd/sub_cmd/shut.ts +++ b/src/cmd/sub_cmd/shut.ts @@ -18,6 +18,4 @@ export function registerCmdShut() { seal.replyToSender(ctx, msg, '已停止当前对话'); return ret; } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/standby.ts b/src/cmd/sub_cmd/standby.ts index 612b91d..9d16024 100644 --- a/src/cmd/sub_cmd/standby.ts +++ b/src/cmd/sub_cmd/standby.ts @@ -30,6 +30,4 @@ export function registerCmdStandby() { AIManager.saveAI(sid); return ret; } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/status.ts b/src/cmd/sub_cmd/status.ts index 05ed7aa..0ff4ec2 100644 --- a/src/cmd/sub_cmd/status.ts +++ b/src/cmd/sub_cmd/status.ts @@ -22,6 +22,4 @@ export function registerCmdStatus() { 待机模式: ${setting.standby ? '开启' : '关闭'}`); return ret; } - - SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/timer.ts b/src/cmd/sub_cmd/timer.ts index a7ba7ae..9ff7f77 100644 --- a/src/cmd/sub_cmd/timer.ts +++ b/src/cmd/sub_cmd/timer.ts @@ -35,6 +35,4 @@ export function registerCmdTimer() { } } } - - SubCmd.map[cmd.name] = cmd; } diff --git a/src/cmd/sub_cmd/token.ts b/src/cmd/sub_cmd/token.ts index bcf9752..6587e98 100644 --- a/src/cmd/sub_cmd/token.ts +++ b/src/cmd/sub_cmd/token.ts @@ -397,6 +397,4 @@ export function registerCmdToken() { } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/tool.ts b/src/cmd/sub_cmd/tool.ts index 586e657..fcf50e0 100644 --- a/src/cmd/sub_cmd/tool.ts +++ b/src/cmd/sub_cmd/tool.ts @@ -151,6 +151,4 @@ export function registerCmdTool() { } } } - - SubCmd.map[cmd.name] = cmd; } \ No newline at end of file diff --git a/src/config/sample.ts b/src/config/sample.ts new file mode 100644 index 0000000..272b9c5 --- /dev/null +++ b/src/config/sample.ts @@ -0,0 +1,17 @@ +import { ConfigManager } from "./configManager"; + +export class SampleConfig { + static ext: seal.ExtInfo; + + static register() { + SampleConfig.ext = ConfigManager.getExt('aiplugin4_0:示例'); + + seal.ext.registerBoolConfig(SampleConfig.ext, "是否启用", true, ''); + } + + static get() { + return { + enabled: seal.ext.getBoolConfig(SampleConfig.ext, "是否启用"), + } + } +} \ No newline at end of file diff --git a/src/tool/sample.ts b/src/tool/sample.ts new file mode 100644 index 0000000..1863d3c --- /dev/null +++ b/src/tool/sample.ts @@ -0,0 +1,26 @@ +import { Tool } from "./tool"; + +export function registerSample() { + const tool = new Tool({ + type: "function", + function: { + name: "sample", + description: `示例工具`, + parameters: { + type: "object", + properties: { + arg: { + type: 'string', + description: '参数' + } + }, + required: ["arg"] + } + } + }); + tool.solve = async (ctx, msg, ai, args) => { + const { arg } = args; + arg; ctx; msg; ai; + return { content: "调用成功", images: [] }; + } +} \ No newline at end of file