diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 66585c0..f1add92 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -24,7 +24,7 @@ export class AI { context: Context; tool: ToolManager; memory: Memory; - image: ImageManager; + imageManager: ImageManager; privilege: Privilege; // 下面是临时变量,用于处理消息 @@ -45,7 +45,7 @@ export class AI { this.context = new Context(); this.tool = new ToolManager(); this.memory = new Memory(); - this.image = new ImageManager(); + this.imageManager = new ImageManager(); this.privilege = { limit: 100, counter: -1, @@ -66,7 +66,7 @@ export class AI { static reviver(value: any, id: string): AI { const ai = new AI(id); - const validKeys = ['version', 'context', 'tool', 'memory', 'image', 'privilege']; + const validKeys = ['version', 'context', 'tool', 'memory', 'imageManager', 'privilege']; for (const k of validKeys) { if (value.hasOwnProperty(k)) { @@ -132,7 +132,7 @@ export class AI { //获取处理后的回复 const raw_reply = await sendChatRequest(ctx, msg, this, messages, "auto"); - result = await handleReply(ctx, msg, raw_reply, this.context); + result = await handleReply(ctx, msg, this, raw_reply); if (!checkRepeat(this.context, result.contextArray.join('')) || result.replyArray.join('').trim() === '') { break; @@ -160,7 +160,8 @@ export class AI { //发送偷来的图片 const { p } = ConfigManager.image; if (Math.random() * 100 <= p) { - const file = await this.image.drawImageFile(); + const file = await this.imageManager.drawImageFile(); + if (file) { seal.replyToSender(ctx, msg, `[CQ:image,file=${file}]`); } @@ -211,7 +212,7 @@ export class AI { // 对于function_call前面的内容,发送并添加到上下文中 const match = raw_reply.match(/([\s\S]*)/); if (match && match[1].trim()) { - const { contextArray, replyArray, images } = await handleReply(ctx, msg, match[1], this.context); + const { contextArray, replyArray, images } = await handleReply(ctx, msg, this, match[1]); if (this.stream.id !== id) { return; @@ -267,7 +268,7 @@ export class AI { } } - const { contextArray, replyArray, images } = await handleReply(ctx, msg, raw_reply, this.context); + const { contextArray, replyArray, images } = await handleReply(ctx, msg, this, raw_reply); if (this.stream.id !== id) { return; @@ -345,7 +346,7 @@ export class AIManager { if (key === "memory") { return Memory.reviver(value); } - if (key === "image") { + if (key === "imageManager") { return ImageManager.reviver(value); } diff --git a/src/AI/context.ts b/src/AI/context.ts index 59a01c2..dc70092 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -1,6 +1,6 @@ import { ToolCall } from "../tool/tool"; import { ConfigManager } from "../config/config"; -import { Image } from "./image"; +import { Image, ImageManager } from "./image"; import { createCtx, createMsg } from "../utils/utils_seal"; import { levenshteinDistance } from "../utils/utils_string"; import { AI, AIManager } from "./AI"; @@ -374,7 +374,7 @@ export class Context { return names; } - findImage(id: string): Image { + findImage(id: string, im: ImageManager): Image | null { if (/^[0-9a-z]{6}$/.test(id.trim())) { const messages = this.messages; for (let i = messages.length - 1; i >= 0; i--) { @@ -407,6 +407,13 @@ export class Context { return new Image(localImages[id]); } + const savedImage = im.savedImages.find(img => img.id === id); + if (savedImage) { + const filePath = seal.base64ToImage(savedImage.base64); + savedImage.file = filePath; + return savedImage; + } + return null; } } diff --git a/src/AI/image.ts b/src/AI/image.ts index a689f77..07b0ab6 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -7,28 +7,36 @@ export class Image { id: string; isUrl: boolean; file: string; + scenes: string[]; + base64: string; content: string; + weight: number; constructor(file: string) { this.id = generateId(); this.isUrl = file.startsWith('http'); this.file = file; + this.scenes = []; + this.base64 = ''; this.content = ''; + this.weight = 1; } } export class ImageManager { - imageList: Image[]; + stolenImages: Image[]; + savedImages: Image[]; stealStatus: boolean; constructor() { - this.imageList = []; + this.stolenImages = []; + this.savedImages = []; this.stealStatus = false; } static reviver(value: any): ImageManager { const im = new ImageManager(); - const validKeys = ['imageList', 'stealStatus']; + const validKeys = ['stolenImages', 'savedImages', 'stealStatus']; for (const k of validKeys) { if (value.hasOwnProperty(k)) { @@ -39,9 +47,24 @@ export class ImageManager { return im; } - updateImageList(images: Image[]) { - const { maxImageNum } = ConfigManager.image; - this.imageList = this.imageList.concat(images.filter(item => item.isUrl)).slice(-maxImageNum); + updateStolenImages(images: Image[]) { + const { maxStolenImageNum } = ConfigManager.image; + this.stolenImages = this.stolenImages.concat(images.filter(item => item.isUrl)).slice(-maxStolenImageNum); + } + + updateSavedImages(images: Image[]) { + const { maxSavedImageNum } = ConfigManager.image; + this.savedImages = this.savedImages.concat(images.filter(item => item.isUrl)); + + if (this.savedImages.length > maxSavedImageNum) { + this.savedImages = this.savedImages + .sort((a, b) => b.weight - a.weight) + .slice(0, maxSavedImageNum); + } + } + + delSavedImage(nameList: string[]) { + this.savedImages = this.savedImages.filter(img => !nameList.includes(img.id)); } drawLocalImageFile(): string { @@ -72,12 +95,12 @@ export class ImageManager { } async drawStolenImageFile(): Promise { - if (this.imageList.length == 0) { + if (this.stolenImages.length === 0) { return ''; } - const index = Math.floor(Math.random() * this.imageList.length); - const image = this.imageList.splice(index, 1)[0]; + const index = Math.floor(Math.random() * this.stolenImages.length); + const image = this.stolenImages.splice(index, 1)[0]; const url = image.file; if (!await ImageManager.checkImageUrl(url)) { @@ -88,6 +111,13 @@ export class ImageManager { return url; } + drawSavedImageFile(): string { + if (this.savedImages.length === 0) return null; + const index = Math.floor(Math.random() * this.savedImages.length); + const image = this.savedImages[index]; + return seal.base64ToImage(image.base64); + } + async drawImageFile(): Promise { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { @@ -108,24 +138,18 @@ export class ImageManager { }, {}); const values = Object.values(localImages); - if (this.imageList.length == 0 && values.length == 0) { + if (this.stolenImages.length == 0 && values.length == 0 && this.savedImages.length == 0) { return ''; } - const index = Math.floor(Math.random() * (values.length + this.imageList.length)); + const index = Math.floor(Math.random() * (values.length + this.stolenImages.length + this.savedImages.length)); if (index < values.length) { return values[index]; + } else if (index < values.length + this.stolenImages.length) { + return await this.drawStolenImageFile(); } else { - const image = this.imageList.splice(index - values.length, 1)[0]; - const url = image.file; - - if (!await ImageManager.checkImageUrl(url)) { - await new Promise(resolve => setTimeout(resolve, 500)); - return await this.drawImageFile(); - } - - return url; + return this.drawSavedImageFile(); } } diff --git a/src/AI/update.ts b/src/AI/update.ts index 3aa6bab..67fe2b3 100644 --- a/src/AI/update.ts +++ b/src/AI/update.ts @@ -1,6 +1,7 @@ // 版本更新日志,格式为 "版本号": "更新内容",版本号格式为 "x.y.z",按照时间顺序从新到旧排列。 export const updateInfo = { - "4.10.1": `- 修复了构建ctx时,isPrivate始终为0的问题`, + "4.10.1": `- 修复了构建ctx时,isPrivate始终为0的问题 +- 新增保存图片功能`, "4.10.0": `- 新增了全局待机模式配置项 - 修改了部分正则和部分默认配置项 - 修复了无法调用内置指令 diff --git a/src/config/config_image.ts b/src/config/config_image.ts index 10fef2b..1c19dcc 100644 --- a/src/config/config_image.ts +++ b/src/config/config_image.ts @@ -22,6 +22,7 @@ export class ImageConfig { seal.ext.registerOptionConfig(ImageConfig.ext, "识别图片时将url转换为base64", "永不", ["永不", "自动", "总是"], "解决大模型无法正常获取QQ图床图片的问题"); seal.ext.registerIntConfig(ImageConfig.ext, "图片最大回复字符数", 500); seal.ext.registerIntConfig(ImageConfig.ext, "偷取图片存储上限", 50, "每个群聊或私聊单独储存"); + seal.ext.registerIntConfig(ImageConfig.ext, "保存图片存储上限", 50, "每个群聊或私聊单独储存"); } static get() { @@ -36,7 +37,8 @@ export class ImageConfig { defaultPrompt: seal.ext.getStringConfig(ImageConfig.ext, "图片识别默认prompt"), urlToBase64: seal.ext.getOptionConfig(ImageConfig.ext, "识别图片时将url转换为base64"), maxChars: seal.ext.getIntConfig(ImageConfig.ext, "图片最大回复字符数"), - maxImageNum: seal.ext.getIntConfig(ImageConfig.ext, "偷取图片存储上限") + maxStolenImageNum: seal.ext.getIntConfig(ImageConfig.ext, "偷取图片存储上限"), + maxSavedImageNum: seal.ext.getIntConfig(ImageConfig.ext, "保存图片存储上限") } } } \ No newline at end of file diff --git a/src/config/config_message.ts b/src/config/config_message.ts index 5dc3d7f..bb460af 100644 --- a/src/config/config_message.ts +++ b/src/config/config_message.ts @@ -97,13 +97,13 @@ export class MessageConfig { - <|img:xxxxxx|>为图片,其中xxxxxx为6位的图片id,如果要发送出现过的图片请使用<|img:xxxxxx|>的格式 {{/if}} {{else}} -{{#if 本地图片不为空}} +{{#if 可发送图片不为空}} ## 图片相关 {{/if}} {{/if}} -{{#if 本地图片不为空}} - - 可使用<|img:图片名称|>发送表情包,表情名称有:{{{本地图片名称}}} +{{#if 可发送图片不为空}} + - 可使用<|img:图片名称|>发送表情包,表情名称有:{{{可发送图片列表}}} {{/if}} {{#if 开启长期记忆}} diff --git a/src/index.ts b/src/index.ts index 598967a..baf3875 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1172,10 +1172,12 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const cmdImage = seal.ext.newCmdItemInfo(); cmdImage.name = 'img'; // 指令名字,可用中文 cmdImage.help = `盗图指南: -【img draw [stl/lcl/all]】随机抽取偷的图片/本地图片/全部 -【img stl (on/off)】偷图 开启/关闭 -【img f】遗忘 -【img itt [图片/ran] (附加提示词)】图片转文字`; +【.img draw [stl/lcl/save/all]】随机抽取偷的图片/本地图片/保存的图片/全部 +【.img stl (on/off)】偷图 开启/关闭 +【.img f [stl/save/all]】遗忘偷的图片/保存的图片/全部 +【.img itt [图片/ran] (附加提示词)】图片转文字 +【.img list [show/send]】展示保存的图片列表/展示并发送所有保存的图片 +【.img del <图片名称1> <图片名称2> ...】删除指定名称的保存图片`; cmdImage.solve = (ctx, msg, cmdArgs) => { try { const val = cmdArgs.getArgN(1); @@ -1192,28 +1194,36 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { switch (type) { case 'lcl': case 'local': { - const image = ai.image.drawLocalImageFile(); - if (!image) { + const file = ai.imageManager.drawLocalImageFile(); + if (!file) { seal.replyToSender(ctx, msg, '暂无本地图片'); return ret; } - seal.replyToSender(ctx, msg, `[CQ:image,file=${image}]`); + seal.replyToSender(ctx, msg, `[CQ:image,file=${file}]`); return ret; } case 'stl': case 'stolen': { - ai.image.drawStolenImageFile() - .then(image => { - if (!image) { + ai.imageManager.drawStolenImageFile() + .then(file => { + if (!file) { seal.replyToSender(ctx, msg, '暂无偷取图片'); } else { - seal.replyToSender(ctx, msg, `[CQ:image,file=${image}]`); + seal.replyToSender(ctx, msg, `[CQ:image,file=${file}]`); } }); return ret; } + case 'save': { + const file = ai.imageManager.drawSavedImageFile(); + if (!file) { + seal.replyToSender(ctx, msg, '暂无保存的表情包图片'); + } + seal.replyToSender(ctx, msg, `[CQ:image,file=${file}]`); + return ret; + } case 'all': { - ai.image.drawImageFile() + ai.imageManager.drawImageFile() .then(image => { if (!image) { seal.replyToSender(ctx, msg, '暂无图片'); @@ -1234,19 +1244,19 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const op = cmdArgs.getArgN(2); switch (op) { case 'on': { - ai.image.stealStatus = true; - seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.image.imageList.length}`); + ai.imageManager.stealStatus = true; + seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.imageManager.stolenImages.filter(img => img.isUrl).length}`); AIManager.saveAI(id); return ret; } case 'off': { - ai.image.stealStatus = false; - seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.image.imageList.length}`); + ai.imageManager.stealStatus = false; + seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.imageManager.stolenImages.filter(img => img.isUrl).length}`); AIManager.saveAI(id); return ret; } default: { - seal.replyToSender(ctx, msg, `图片偷取状态:${ai.image.stealStatus},当前偷取数量:${ai.image.imageList.length}`); + seal.replyToSender(ctx, msg, `图片偷取状态:${ai.imageManager.stealStatus},当前偷取数量:${ai.imageManager.stolenImages.filter(img => img.isUrl).length}`); return ret; } } @@ -1254,20 +1264,43 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { case 'f': case 'fgt': case 'forget': { - ai.image.imageList = []; - seal.replyToSender(ctx, msg, '图片已遗忘'); - AIManager.saveAI(id); - return ret; + const type = cmdArgs.getArgN(2); + switch (type) { + case 'stl': + case 'stolen': { + ai.imageManager.stolenImages = []; + seal.replyToSender(ctx, msg, '偷取图片已遗忘'); + AIManager.saveAI(id); + return ret; + } + case 'save': { + ai.imageManager.savedImages = []; + seal.replyToSender(ctx, msg, '保存图片已遗忘'); + AIManager.saveAI(id); + return ret; + } + case 'all': { + ai.imageManager.stolenImages = []; + ai.imageManager.savedImages = []; + seal.replyToSender(ctx, msg, '所有图片已遗忘'); + AIManager.saveAI(id); + return ret; + } + default: { + ret.showHelp = true; + return ret; + } + } } case 'itt': { const val2 = cmdArgs.getArgN(2); if (!val2) { - seal.replyToSender(ctx, msg, '【img itt [图片/ran] (附加提示词)】图片转文字'); + seal.replyToSender(ctx, msg, '【.img itt [图片/ran] (附加提示词)】图片转文字'); return ret; } if (val2 == 'ran') { - ai.image.drawStolenImageFile() + ai.imageManager.drawStolenImageFile() .then(url => { if (!url) { seal.replyToSender(ctx, msg, '图片偷取为空'); @@ -1294,6 +1327,55 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { } return ret; } + case 'list': { + const type = cmdArgs.getArgN(2); + switch (type) { + case 'show': { + if (ai.imageManager.savedImages.length === 0) { + seal.replyToSender(ctx, msg, '暂无保存的图片'); + return ret; + } + + const imageList = ai.imageManager.savedImages.map((img, index) => `${index + 1}. 名称: ${img.id} +应用场景: ${img.scenes.join('、') || '无'} +权重: ${img.weight}`).join('\n'); + + seal.replyToSender(ctx, msg, `保存的图片列表:\n${imageList}`); + return ret; + } + case 'send': { + if (ai.imageManager.savedImages.length === 0) { + seal.replyToSender(ctx, msg, '暂无保存的图片'); + return ret; + } + + const imageList = ai.imageManager.savedImages.map((img, index) => { + return `${index + 1}. 名称: ${img.id} +应用场景: ${img.scenes.join('、') || '无'} +权重: ${img.weight} +[CQ:image,file=${seal.base64ToImage(img.base64)}]`; + }).join('\n\n'); + + seal.replyToSender(ctx, msg, `保存的图片列表:\n${imageList}`); + return ret; + } + default: { + seal.replyToSender(ctx, msg, '参数缺失,【.img list show】展示保存的图片列表,【.img list send】展示并发送所有保存的图片'); + return ret; + } + } + } + case 'del': { + const nameList = cmdArgs.args.slice(1); + if (nameList.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.img del <图片名称1> <图片名称2> ...】删除指定名称的保存图片'); + return ret; + } + + ai.imageManager.delSavedImage(nameList); + seal.replyToSender(ctx, msg, `已删除图片`); + return ret; + } default: { ret.showHelp = true; return ret; @@ -1367,8 +1449,8 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const result = await ImageManager.handleImageMessage(ctx, message); message = result.message; images = result.images; - if (ai.image.stealStatus) { - ai.image.updateImageList(images); + if (ai.imageManager.stealStatus) { + ai.imageManager.updateStolenImages(images); } } @@ -1398,8 +1480,8 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const result = await ImageManager.handleImageMessage(ctx, message); message = result.message; images = result.images; - if (ai.image.stealStatus) { - ai.image.updateImageList(images); + if (ai.imageManager.stealStatus) { + ai.imageManager.updateStolenImages(images); } } @@ -1422,8 +1504,8 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const result = await ImageManager.handleImageMessage(ctx, message); message = result.message; images = result.images; - if (ai.image.stealStatus) { - ai.image.updateImageList(images); + if (ai.imageManager.stealStatus) { + ai.imageManager.updateStolenImages(images); } } @@ -1492,8 +1574,8 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const result = await ImageManager.handleImageMessage(ctx, message); message = result.message; images = result.images; - if (ai.image.stealStatus) { - ai.image.updateImageList(images); + if (ai.imageManager.stealStatus) { + ai.imageManager.updateStolenImages(images); } } @@ -1536,8 +1618,8 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const result = await ImageManager.handleImageMessage(ctx, message); message = result.message; images = result.images; - if (ai.image.stealStatus) { - ai.image.updateImageList(images); + if (ai.imageManager.stealStatus) { + ai.imageManager.updateStolenImages(images); } } diff --git a/src/tool/tool.ts b/src/tool/tool.ts index caa7f03..b69c17d 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -4,7 +4,7 @@ import { ConfigManager } from "../config/config" import { registerAttrGet, registerAttrSet, registerAttrShow } from "./tool_attr" import { registerBan, registerGetBanList, registerWholeBan } from "./tool_ban" import { registerDrawDeck } from "./tool_deck" -import { registerCheckAvatar, registerImageToText, registerTextToImage } from "./tool_image" +import { registerCheckAvatar, registerImageToText, registerTextToImage, registerSaveImage, registerDelImage } from "./tool_image" import { registerJrrp } from "./tool_jrrp" import { registerAddMemory, registerDelMemory, registerShowMemory } from "./tool_memory" import { registerModuRoll, registerModuSearch } from "./tool_modu" @@ -34,9 +34,7 @@ export interface ToolInfo { [key: string]: { type: string, description: string, - items?: { - type: string - }, + items?: object, enum?: string[] } }, @@ -199,6 +197,8 @@ export class ToolManager { registerImageToText(); registerCheckAvatar(); registerTextToImage(); + registerSaveImage(); + registerDelImage(); registerGroupSign(); registerGetPersonInfo(); registerSendMsg(); diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 5c2483e..1b66757 100644 --- a/src/tool/tool_image.ts +++ b/src/tool/tool_image.ts @@ -1,4 +1,4 @@ -import { ImageManager } from "../AI/image"; +import { Image, ImageManager } from "../AI/image"; import { logger } from "../AI/logger"; import { ConfigManager } from "../config/config"; import { Tool, ToolInfo, ToolManager } from "./tool"; @@ -30,12 +30,11 @@ export function registerImageToText() { tool.solve = async (_, __, ai, args) => { const { id, content } = args; - const image = ai.context.findImage(id); - const text = content ? `请帮我用简短的语言概括这张图片中出现的:${content}` : ``; - + const image = ai.context.findImage(id, ai.imageManager); if (!image) { return `未找到图片${id}`; } + const text = content ? `请帮我用简短的语言概括这张图片中出现的:${content}` : ``; if (image.isUrl) { const reply = await ImageManager.imageToText(image.file, text); @@ -159,5 +158,131 @@ export function registerTextToImage() { } }; + ToolManager.toolMap[info.function.name] = tool; +} + +export function registerSaveImage() { + const info: ToolInfo = { + type: "function", + function: { + name: "save_image", + description: "将图片保存为表情包", + parameters: { + type: "object", + properties: { + images: { + type: "array", + description: "要保存的图片信息数组", + items: { + type: "object", + properties: { + id: { + type: "string", + description: `图片的id,六位字符` + }, + name: { + type: "string", + description: `图片命名` + }, + scenes: { + type: "array", + description: `表情包的应用场景`, + items: { + type: "string" + } + } + } + } + } + }, + required: ["images"] + } + } + } + + const tool = new Tool(info); + tool.solve = async (_, __, ai, args) => { + const { images } = args; + + const savedImages: Image[] = []; + for (const ii of images) { + const { id, name, scenes } = ii; + + const image = ai.context.findImage(id, ai.imageManager); + if (!image) { + return `未找到图片${id}`; + } + + if (image.isUrl) { + const { base64 } = await ImageManager.imageUrlToBase64(image.file); + if (!base64) { + logger.error(`图片${id}转换为base64失败`); + return `图片转换为base64失败`; + } + + const newImage = new Image(image.file); + + let acc = 0; + do { + newImage.id = name + (acc++ ? `_${acc}` : ''); + } while (ai.context.findImage(newImage.id, ai.imageManager)); + + newImage.scenes = scenes; + newImage.base64 = base64; + newImage.content = image.content; + + savedImages.push(newImage); + } else { + return '本地图片不用再次储存'; + } + } + + + try { + ai.imageManager.updateSavedImages(savedImages); + return `图片已保存`; + } catch (e) { + return `图片保存失败:${e.message}` + } + } + + ToolManager.toolMap[info.function.name] = tool; +} + +export function registerDelImage() { + const info: ToolInfo = { + type: "function", + function: { + name: "del_image", + description: "删除保存的表情包图片", + parameters: { + type: "object", + properties: { + names: { + type: "array", + description: `要删除的图片名称数组` + } + }, + required: ["names"] + } + } + } + + const tool = new Tool(info); + tool.solve = async (_, __, ai, args) => { + const { names } = args; + + for (const name of names) { + const imageIndex = ai.imageManager.savedImages.findIndex(img => img.id === name); + if (imageIndex === -1) { + return `未找到名称为"${name}"的保存图片`; + } + + ai.imageManager.savedImages.splice(imageIndex, 1); + } + + return `已删除${names.length}个图片`; + } + ToolManager.toolMap[info.function.name] = tool; } \ No newline at end of file diff --git a/src/tool/tool_message.ts b/src/tool/tool_message.ts index 12b0bc5..ac96a94 100644 --- a/src/tool/tool_message.ts +++ b/src/tool/tool_message.ts @@ -57,7 +57,7 @@ export function registerSendMsg() { if (match) { for (let i = 0; i < match.length; i++) { const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1].trim().slice(0, 6); - const image = ai.context.findImage(id); + const image = ai.context.findImage(id, ai.imageManager); if (image) { originalImages.push(image); @@ -102,7 +102,7 @@ export function registerSendMsg() { await ai.context.addSystemUserMessage("来自其他对话的消息发送提示", `${source}: 原因: ${reason || '无'}`, originalImages); - const { contextArray, replyArray, images } = await handleReply(ctx, msg, content, ai.context); + const { contextArray, replyArray, images } = await handleReply(ctx, msg, ai, content); try { for (let i = 0; i < contextArray.length; i++) { @@ -174,8 +174,8 @@ export function registerGetMsg() { const result = await ImageManager.handleImageMessage(ctx, message); message = result.message; images = result.images; - if (ai.image.stealStatus) { - ai.image.updateImageList(images); + if (ai.imageManager.stealStatus) { + ai.imageManager.updateStolenImages(images); } } diff --git a/src/tool/tool_time.ts b/src/tool/tool_time.ts index b47d921..761b02a 100644 --- a/src/tool/tool_time.ts +++ b/src/tool/tool_time.ts @@ -138,11 +138,11 @@ export function registerCancelTimer() { type: 'object', properties: { index_list: { - type: 'array', - items: { - type: 'integer' - }, - description: '要取消的定时器序号列表,序号从1开始' + type: 'array', + items: { + type: 'integer' + }, + description: '要取消的定时器序号列表,序号从1开始' } }, required: ['index_list'] @@ -170,7 +170,7 @@ export function registerCancelTimer() { const i = timerQueue.indexOf(timers[index - 1]); if (i === -1) { - return `出错了:找不到序号${index}的定时器`; + return `出错了:找不到序号${index}的定时器`; } timerQueue.splice(i, 1); diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index b865cee..7638fbe 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -10,22 +10,27 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { const { isTool, usePromptEngineering } = ConfigManager.tool; const { localImagePaths, receiveImage, condition } = ConfigManager.image; const { isMemory, isShortMemory } = ConfigManager.memory; - const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { - if (path.trim() === '') { - return acc; - } - try { - const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); - if (!name) { - throw new Error(`本地图片路径格式错误:${path}`); + const sandableImagesPrompt: string = localImagePaths + .map(path => { + if (path.trim() === '') { + return null; } - - acc[name] = path; - } catch (e) { - logger.error(e); - } - return acc; - }, {}); + try { + const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); + if (!name) { + throw new Error(`本地图片路径格式错误:${path}`); + } + + return name; + } catch (e) { + logger.error(e); + } + return null; + }) + .filter(Boolean) + .concat(ai.imageManager.savedImages.map(img => `${img.id}\n应用场景: ${img.scenes.join('、')}`)) + .map((prompt, index) => `${index + 1}. ${prompt}`) + .join('\n'); let [roleSettingIndex, _] = seal.vars.intGet(ctx, "$gSYSPROMPT"); if (roleSettingIndex < 0 || roleSettingIndex >= roleSettingTemplate.length) { @@ -63,8 +68,8 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { "展示消息ID": showMsgId, "接收图片": receiveImage, "图片条件不为零": condition !== '0', - "本地图片不为空": Object.keys(localImages).length !== 0, - "本地图片名称": Object.keys(localImages).join("、"), + "可发送图片不为空": sandableImagesPrompt, + "可发送图片列表": sandableImagesPrompt, "开启长期记忆": isMemory && memoryPrompt, "记忆信息": memoryPrompt, "开启短期记忆": isShortMemory && shortMemoryPrompt, diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 5289b68..a4f9a84 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -4,6 +4,7 @@ import { Image, ImageManager } from "../AI/image"; import { logger } from "../AI/logger"; import { ConfigManager } from "../config/config"; import { transformMsgIdBack } from "./utils"; +import { AI } from "../AI/AI"; export function transformTextToArray(s: string): { type: string, data: { [key: string]: string } }[] { const segments = s.split(/(\[CQ:.*?\])/).filter(segment => segment); @@ -76,7 +77,7 @@ export function transformArrayToText(messageArray: { type: string, data: { [key: return s; } -export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, s: string, context: Context): Promise<{ contextArray: string[], replyArray: string[], images: Image[] }> { +export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, ai: AI, s: string): Promise<{ contextArray: string[], replyArray: string[], images: Image[] }> { const { replymsg, isTrim } = ConfigManager.reply; // 分离AI臆想出来的多轮对话 @@ -92,7 +93,7 @@ export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, s: st const segment = segments[i]; const match = segment.match(/[<<][\|│|]from[::]?\s?(.+?)(?:[\|│|][>>]|[\|│|>>])/); if (match) { - const uid = await context.findUserId(ctx, match[1]); + const uid = await ai.context.findUserId(ctx, match[1]); if (uid === ctx.endPoint.userId && i < segments.length - 1) { s += segments[i + 1]; // 如果臆想对象是自己,那么将下一条消息添加到s中 } @@ -120,10 +121,10 @@ export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, s: st // 处理回复消息 for (let i = 0; i < replyArray.length; i++) { let reply = replyArray[i]; - reply = await replaceMentions(ctx, context, reply); - reply = await replacePoke(ctx, context, reply); + reply = await replaceMentions(ctx, ai.context, reply); + reply = await replacePoke(ctx, ai.context, reply); reply = await replaceQuote(reply); - const { result, images: replyImages } = await replaceImages(context, reply); + const { result, images: replyImages } = await replaceImages(ai.context, ai.imageManager, reply); reply = isTrim ? result.trim() : result; const prefix = (replymsg && msg.rawId && !/^\[CQ:reply,id=-?\d+\]/.test(reply)) ? `[CQ:reply,id=${msg.rawId}]` : ``; @@ -349,10 +350,11 @@ async function replaceQuote(reply: string) { /** * 替换图片占位符为CQ码 * @param context + * @param im 图片管理器 * @param reply * @returns */ -async function replaceImages(context: Context, reply: string) { +async function replaceImages(context: Context, im: ImageManager, reply: string) { let result = reply; const images = []; @@ -360,14 +362,16 @@ async function replaceImages(context: Context, reply: string) { if (match) { for (let i = 0; i < match.length; i++) { const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1]; - const image = context.findImage(id); + const image = context.findImage(id, im); if (image) { - const file = image.file; images.push(image); - if (!image.isUrl || (image.isUrl && await ImageManager.checkImageUrl(file))) { - result = result.replace(match[i], `[CQ:image,file=${file}]`); + if (!image.isUrl || (image.isUrl && await ImageManager.checkImageUrl(image.file))) { + if (image.base64) { + image.weight += 1; + } + result = result.replace(match[i], `[CQ:image,file=${image.file}]`); continue; } }