From 273294a39f277c2d1a7050ee19a148cdb38f2e15 Mon Sep 17 00:00:00 2001 From: baiyu-yu <135424680+baiyu-yu@users.noreply.github.com> Date: Mon, 30 Jun 2025 01:26:52 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E5=8C=85=E9=95=BF=E4=B9=85=E8=AE=B0=E5=BF=86=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最后还是放弃了用cq码发base64 --- src/AI/AI.ts | 7 ++- src/AI/context.ts | 25 +++++++- src/AI/image.ts | 92 ++++++++++++++++++++++++---- src/config/config_image.ts | 4 +- src/config/config_message.ts | 7 +++ src/index.ts | 24 ++++++-- src/tool/tool.ts | 4 +- src/tool/tool_image.ts | 113 ++++++++++++++++++++++++++++++++++- src/tool/tool_message.ts | 4 +- src/utils/utils_message.ts | 12 ++++ src/utils/utils_string.ts | 15 ++--- 11 files changed, 275 insertions(+), 32 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 66585c0..9b12b66 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -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, raw_reply, this.context, this); if (!checkRepeat(this.context, result.contextArray.join('')) || result.replyArray.join('').trim() === '') { break; @@ -161,6 +161,7 @@ export class AI { const { p } = ConfigManager.image; if (Math.random() * 100 <= p) { const file = await this.image.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, match[1], this.context, this); 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, raw_reply, this.context, this); if (this.stream.id !== id) { return; diff --git a/src/AI/context.ts b/src/AI/context.ts index 59a01c2..828ff08 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -374,7 +374,7 @@ export class Context { return names; } - findImage(id: string): Image { + findImage(id: string, ai?: AI): Image | null { if (/^[0-9a-z]{6}$/.test(id.trim())) { const messages = this.messages; for (let i = messages.length - 1; i >= 0; i--) { @@ -385,6 +385,29 @@ export class Context { } } + if (ai && ai.image) { + const savedImage = ai.image.imageList.find(img => { + if (img.content) { + try { + const meta = JSON.parse(img.content); + return meta.name === id; + } catch { + return false; + } + } + return false; + }); + if (savedImage) { + + const filePath = seal.base64ToImage(savedImage.file); + const newImage = new Image(filePath); + newImage.id = savedImage.id; + newImage.content = savedImage.content; + newImage.isUrl = false; + return newImage; + } + } + const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { if (path.trim() === '') { diff --git a/src/AI/image.ts b/src/AI/image.ts index a689f77..5f1981f 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -44,6 +44,43 @@ export class ImageManager { this.imageList = this.imageList.concat(images.filter(item => item.isUrl)).slice(-maxImageNum); } + updateSavedImageList(name: string, scene: string, base64: string) { + const { maxSavedImageNum } = ConfigManager.image; + + const savedImages = this.imageList.filter(img => !img.isUrl && (() => { + try { + const meta = JSON.parse(img.content); + return meta && typeof meta.name === 'string'; + } catch { + return false; + } + })()); + + if (savedImages.length >= maxSavedImageNum) { + throw new Error(`保存图片已达到上限${maxSavedImageNum}张,无法继续保存`); + } + + let finalName = name; + let count = 1; + while (this.imageList.some(img => { + try { + const meta = JSON.parse(img.content); + return meta.name === finalName; + } catch { + return false; + } + })) { + finalName = `${name}_${count++}`; + } + + const img = new Image(`${base64}`); + img.content = JSON.stringify({ name: finalName, scene: scene || '' }); + + this.imageList.push(img); + return finalName; + } + + drawLocalImageFile(): string { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { @@ -72,12 +109,13 @@ export class ImageManager { } async drawStolenImageFile(): Promise { - if (this.imageList.length == 0) { + const stolenImages = this.imageList.filter(img => img.isUrl); + if (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() * stolenImages.length); + const image = stolenImages.splice(index, 1)[0]; const url = image.file; if (!await ImageManager.checkImageUrl(url)) { @@ -88,6 +126,34 @@ export class ImageManager { return url; } + async drawSaveImageFile(): Promise<{ file: string, name: string, scene: string } | null> { + const savedImages = this.imageList.filter(img => !img.isUrl && (() => { + try { + const meta = JSON.parse(img.content); + return meta && typeof meta.name === 'string'; + } catch { + return false; + } + })()); + if (savedImages.length === 0) { + return null; + } + const index = Math.floor(Math.random() * savedImages.length); + const image = savedImages.splice(index, 1)[0]; + const imagefile = seal.base64ToImage(image.file); + + try { + const meta = JSON.parse(image.content); + return { + file: imagefile, + name: meta.name, + scene: meta.scene || '' + }; + } catch { + return null; + } + } + async drawImageFile(): Promise { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { @@ -117,15 +183,21 @@ export class ImageManager { if (index < values.length) { return values[index]; } else { - const image = this.imageList.splice(index - values.length, 1)[0]; - const url = image.file; + const image = this.imageList[index - values.length]; + if (!image.isUrl) { + const file = seal.base64ToImage(image.file); + return file; + } else { + this.imageList.splice(index - values.length, 1); + const url = image.file; - if (!await ImageManager.checkImageUrl(url)) { - await new Promise(resolve => setTimeout(resolve, 500)); - return await this.drawImageFile(); - } + if (!await ImageManager.checkImageUrl(url)) { + await new Promise(resolve => setTimeout(resolve, 500)); + return await this.drawImageFile(); + } - return url; + return url; + } } } diff --git a/src/config/config_image.ts b/src/config/config_image.ts index 10fef2b..441df5d 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, "偷取图片存储上限") + maxImageNum: 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..cc24299 100644 --- a/src/config/config_message.ts +++ b/src/config/config_message.ts @@ -105,6 +105,13 @@ export class MessageConfig { {{#if 本地图片不为空}} - 可使用<|img:图片名称|>发送表情包,表情名称有:{{{本地图片名称}}} {{/if}} +{{#if 保存图片不为空}} + +## 图片相关 +{{/if}} +{{#if 保存图片不为空}} + - 可使用<|img:图片名称|>发送表情包,括号内为使用场景介绍,在发送时只使用括号前的名称,表情名称有:{{{保存图片名称}}} +{{/if}} {{#if 开启长期记忆}} ## 记忆 diff --git a/src/index.ts b/src/index.ts index 598967a..3da29db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1172,7 +1172,7 @@ ${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 draw [stl/lcl/save/all]】随机抽取偷的图片/本地图片/保存的图片全部 【img stl (on/off)】偷图 开启/关闭 【img f】遗忘 【img itt [图片/ran] (附加提示词)】图片转文字`; @@ -1212,6 +1212,22 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { }); return ret; } + case 'save': { + ai.image.drawSaveImageFile() + .then(image => { + if (!image) { + seal.replyToSender(ctx, msg, '暂无保存的表情包图片'); + } else { + // 发送图片+名称+场景 + let text = `[CQ:image,file=${image.file}]\n名称:${image.name}`; + if (image.scene) { + text += `\n场景:${image.scene}`; + } + seal.replyToSender(ctx, msg, text); + } + }); + return ret; + } case 'all': { ai.image.drawImageFile() .then(image => { @@ -1235,18 +1251,18 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { switch (op) { case 'on': { ai.image.stealStatus = true; - seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.image.imageList.length}`); + seal.replyToSender(ctx, msg, `图片偷取已开启,当前偷取数量:${ai.image.imageList.filter(img => img.isUrl).length}`); AIManager.saveAI(id); return ret; } case 'off': { ai.image.stealStatus = false; - seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.image.imageList.length}`); + seal.replyToSender(ctx, msg, `图片偷取已关闭,当前偷取数量:${ai.image.imageList.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.image.stealStatus},当前偷取数量:${ai.image.imageList.filter(img => img.isUrl).length}`); return ret; } } diff --git a/src/tool/tool.ts b/src/tool/tool.ts index caa7f03..6141577 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, registerDeleteSavedImage } from "./tool_image" import { registerJrrp } from "./tool_jrrp" import { registerAddMemory, registerDelMemory, registerShowMemory } from "./tool_memory" import { registerModuRoll, registerModuSearch } from "./tool_modu" @@ -199,6 +199,8 @@ export class ToolManager { registerImageToText(); registerCheckAvatar(); registerTextToImage(); + registerSaveImage(); + registerDeleteSavedImage(); registerGroupSign(); registerGetPersonInfo(); registerSendMsg(); diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 5c2483e..936e644 100644 --- a/src/tool/tool_image.ts +++ b/src/tool/tool_image.ts @@ -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); if (!image) { return `未找到图片${id}`; } + const text = content ? `请帮我用简短的语言概括这张图片中出现的:${content}` : ``; if (image.isUrl) { const reply = await ImageManager.imageToText(image.file, text); @@ -159,5 +158,113 @@ 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: { + id: { + type: "string", + description: `图片的id,六位字符` + }, + name: { + type: "string", + description: `图片命名` + }, + scene: { + type: "string", + description: `表情包的应用场景` + } + }, + required: ["id", "name"] + } + } + } + + const tool = new Tool(info); + tool.solve = async (_, __, ai, args) => { + const { id, name, scene } = args; + + const image = ai.context.findImage(id, ai); + if (!image) { + return `未找到图片${id}`; + } + + if (image.isUrl) { + const { base64 } = await ImageManager.imageUrlToBase64(image.file); + if (!base64) { + logger.warning(`转换为base64失败`); + return '转换为base64失败'; + } + + try { + const finalName = ai.image.updateSavedImageList(name, scene || '', base64); + return `图片已保存为表情包,名称:${finalName}`; + } catch (error) { + return error.message; + } + } else { + return '本地图片不用再次储存'; + } + } + + ToolManager.toolMap[info.function.name] = tool; +} + +export function registerDeleteSavedImage() { + const info: ToolInfo = { + type: "function", + function: { + name: "delete_saved_image", + description: "删除保存的表情包图片", + parameters: { + type: "object", + properties: { + name: { + type: "string", + description: `要删除的图片名称` + } + }, + required: ["name"] + } + } + } + + const tool = new Tool(info); + tool.solve = async (_, __, ai, args) => { + const { name } = args; + + const imageIndex = ai.image.imageList.findIndex(img => { + if (img.content) { + try { + const meta = JSON.parse(img.content); + return meta.name === name; + } catch { + return false; + } + } + return false; + }); + + if (imageIndex === -1) { + return `未找到名称为"${name}"的保存图片`; + } + + const deletedImage = ai.image.imageList.splice(imageIndex, 1)[0]; + try { + const meta = JSON.parse(deletedImage.content); + return `已删除保存的图片:${meta.name}${meta.scene ? `(${meta.scene})` : ''}`; + } catch { + return `已删除保存的图片:${name}`; + } + } + 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..d5ea6f3 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); 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, content, ai.context, ai); try { for (let i = 0; i < contextArray.length; i++) { diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index b865cee..4c2084c 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -27,6 +27,16 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { return acc; }, {}); + const savedImages = ai.image.imageList.filter(img => !img.isUrl); + const savedImageNames = savedImages.map(img => { + try { + const meta = JSON.parse(img.content); + return meta.scene ? `${meta.name}(${meta.scene})` : meta.name; + } catch { + return ''; + } + }).filter(Boolean).join("、"); + let [roleSettingIndex, _] = seal.vars.intGet(ctx, "$gSYSPROMPT"); if (roleSettingIndex < 0 || roleSettingIndex >= roleSettingTemplate.length) { roleSettingIndex = 0; @@ -65,6 +75,8 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { "图片条件不为零": condition !== '0', "本地图片不为空": Object.keys(localImages).length !== 0, "本地图片名称": Object.keys(localImages).join("、"), + "保存图片不为空": savedImages.length > 0, + "保存图片名称": savedImageNames, "开启长期记忆": isMemory && memoryPrompt, "记忆信息": memoryPrompt, "开启短期记忆": isShortMemory && shortMemoryPrompt, diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 5289b68..377bad1 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, s: string, context: Context, ai?: AI): Promise<{ contextArray: string[], replyArray: string[], images: Image[] }> { const { replymsg, isTrim } = ConfigManager.reply; // 分离AI臆想出来的多轮对话 @@ -123,7 +124,7 @@ export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, s: st reply = await replaceMentions(ctx, context, reply); reply = await replacePoke(ctx, context, reply); reply = await replaceQuote(reply); - const { result, images: replyImages } = await replaceImages(context, reply); + const { result, images: replyImages } = await replaceImages(context, reply, ai); reply = isTrim ? result.trim() : result; const prefix = (replymsg && msg.rawId && !/^\[CQ:reply,id=-?\d+\]/.test(reply)) ? `[CQ:reply,id=${msg.rawId}]` : ``; @@ -350,9 +351,10 @@ async function replaceQuote(reply: string) { * 替换图片占位符为CQ码 * @param context * @param reply + * @param ai * @returns */ -async function replaceImages(context: Context, reply: string) { +async function replaceImages(context: Context, reply: string, ai?: AI) { let result = reply; const images = []; @@ -360,14 +362,13 @@ 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, ai); 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))) { + result = result.replace(match[i], `[CQ:image,file=${image.file}]`); continue; } } From 2571347ac582efe93685238b8f1ed3af286d3cac Mon Sep 17 00:00:00 2001 From: baiyu-yu <135424680+baiyu-yu@users.noreply.github.com> Date: Tue, 1 Jul 2025 01:24:58 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E5=82=A8=E5=AD=98?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E8=B0=83=E6=95=B4=E5=88=B0ImageManager?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit imageList没改名,要改吗 --- src/AI/context.ts | 3 +- src/AI/image.ts | 76 +++++++++++++------------------------- src/index.ts | 36 ++++++++++++++---- src/tool/tool_image.ts | 4 +- src/utils/utils_message.ts | 2 +- 5 files changed, 59 insertions(+), 62 deletions(-) diff --git a/src/AI/context.ts b/src/AI/context.ts index 828ff08..03a5e78 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -386,7 +386,7 @@ export class Context { } if (ai && ai.image) { - const savedImage = ai.image.imageList.find(img => { + const savedImage = ai.image.savedImages.find(img => { if (img.content) { try { const meta = JSON.parse(img.content); @@ -398,7 +398,6 @@ export class Context { return false; }); if (savedImage) { - const filePath = seal.base64ToImage(savedImage.file); const newImage = new Image(filePath); newImage.id = savedImage.id; diff --git a/src/AI/image.ts b/src/AI/image.ts index 5f1981f..0da629d 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -19,16 +19,18 @@ export class Image { export class ImageManager { imageList: Image[]; + savedImages: Image[]; stealStatus: boolean; constructor() { this.imageList = []; + this.savedImages = []; this.stealStatus = false; } static reviver(value: any): ImageManager { const im = new ImageManager(); - const validKeys = ['imageList', 'stealStatus']; + const validKeys = ['imageList', 'savedImages', 'stealStatus']; for (const k of validKeys) { if (value.hasOwnProperty(k)) { @@ -46,23 +48,13 @@ export class ImageManager { updateSavedImageList(name: string, scene: string, base64: string) { const { maxSavedImageNum } = ConfigManager.image; - - const savedImages = this.imageList.filter(img => !img.isUrl && (() => { - try { - const meta = JSON.parse(img.content); - return meta && typeof meta.name === 'string'; - } catch { - return false; - } - })()); - - if (savedImages.length >= maxSavedImageNum) { + if (this.savedImages.length >= maxSavedImageNum) { throw new Error(`保存图片已达到上限${maxSavedImageNum}张,无法继续保存`); } let finalName = name; let count = 1; - while (this.imageList.some(img => { + while (this.savedImages.some(img => { try { const meta = JSON.parse(img.content); return meta.name === finalName; @@ -75,8 +67,8 @@ export class ImageManager { const img = new Image(`${base64}`); img.content = JSON.stringify({ name: finalName, scene: scene || '' }); - - this.imageList.push(img); + img.isUrl = false; + this.savedImages.push(img); return finalName; } @@ -109,13 +101,10 @@ export class ImageManager { } async drawStolenImageFile(): Promise { - const stolenImages = this.imageList.filter(img => img.isUrl); - if (stolenImages.length === 0) { - return ''; - } + if (this.imageList.length === 0) return ''; - const index = Math.floor(Math.random() * stolenImages.length); - const image = stolenImages.splice(index, 1)[0]; + const index = Math.floor(Math.random() * this.imageList.length); + const image = this.imageList.splice(index, 1)[0]; const url = image.file; if (!await ImageManager.checkImageUrl(url)) { @@ -127,21 +116,10 @@ export class ImageManager { } async drawSaveImageFile(): Promise<{ file: string, name: string, scene: string } | null> { - const savedImages = this.imageList.filter(img => !img.isUrl && (() => { - try { - const meta = JSON.parse(img.content); - return meta && typeof meta.name === 'string'; - } catch { - return false; - } - })()); - if (savedImages.length === 0) { - return null; - } - const index = Math.floor(Math.random() * savedImages.length); - const image = savedImages.splice(index, 1)[0]; + if (this.savedImages.length === 0) return null; + const index = Math.floor(Math.random() * this.savedImages.length); + const image = this.savedImages[index]; const imagefile = seal.base64ToImage(image.file); - try { const meta = JSON.parse(image.content); return { @@ -174,30 +152,28 @@ export class ImageManager { }, {}); const values = Object.values(localImages); - if (this.imageList.length == 0 && values.length == 0) { + if (this.imageList.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.imageList.length + this.savedImages.length)); if (index < values.length) { return values[index]; - } else { + } else if (index < values.length + this.imageList.length) { const image = this.imageList[index - values.length]; - if (!image.isUrl) { - const file = seal.base64ToImage(image.file); - return file; - } else { - this.imageList.splice(index - values.length, 1); - const url = image.file; - - if (!await ImageManager.checkImageUrl(url)) { - await new Promise(resolve => setTimeout(resolve, 500)); - return await this.drawImageFile(); - } + this.imageList.splice(index - values.length, 1); + const url = image.file; - return url; + if (!await ImageManager.checkImageUrl(url)) { + await new Promise(resolve => setTimeout(resolve, 500)); + return await this.drawImageFile(); } + + return url; + } else { + const image = this.savedImages[index - values.length - this.imageList.length]; + return seal.base64ToImage(image.file); } } diff --git a/src/index.ts b/src/index.ts index 3da29db..895cb56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1172,9 +1172,9 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const cmdImage = seal.ext.newCmdItemInfo(); cmdImage.name = 'img'; // 指令名字,可用中文 cmdImage.help = `盗图指南: -【img draw [stl/lcl/save/all]】随机抽取偷的图片/本地图片/保存的图片全部 +【img draw [stl/lcl/save/all]】随机抽取偷的图片/本地图片/保存的图片/全部 【img stl (on/off)】偷图 开启/关闭 -【img f】遗忘 +【img f [stl/save/all]】遗忘偷的图片/保存的图片/全部 【img itt [图片/ran] (附加提示词)】图片转文字`; cmdImage.solve = (ctx, msg, cmdArgs) => { try { @@ -1218,7 +1218,6 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { if (!image) { seal.replyToSender(ctx, msg, '暂无保存的表情包图片'); } else { - // 发送图片+名称+场景 let text = `[CQ:image,file=${image.file}]\n名称:${image.name}`; if (image.scene) { text += `\n场景:${image.scene}`; @@ -1270,10 +1269,33 @@ ${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.image.imageList = []; + seal.replyToSender(ctx, msg, '偷取图片已遗忘'); + AIManager.saveAI(id); + return ret; + } + case 'save': { + ai.image.savedImages = []; + seal.replyToSender(ctx, msg, '保存图片已遗忘'); + AIManager.saveAI(id); + return ret; + } + case 'all': { + ai.image.imageList = []; + ai.image.savedImages = []; + seal.replyToSender(ctx, msg, '所有图片已遗忘'); + AIManager.saveAI(id); + return ret; + } + default: { + ret.showHelp = true; + return ret; + } + } } case 'itt': { const val2 = cmdArgs.getArgN(2); diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 936e644..1498ed0 100644 --- a/src/tool/tool_image.ts +++ b/src/tool/tool_image.ts @@ -241,7 +241,7 @@ export function registerDeleteSavedImage() { tool.solve = async (_, __, ai, args) => { const { name } = args; - const imageIndex = ai.image.imageList.findIndex(img => { + const imageIndex = ai.image.savedImages.findIndex(img => { if (img.content) { try { const meta = JSON.parse(img.content); @@ -257,7 +257,7 @@ export function registerDeleteSavedImage() { return `未找到名称为"${name}"的保存图片`; } - const deletedImage = ai.image.imageList.splice(imageIndex, 1)[0]; + const deletedImage = ai.image.savedImages.splice(imageIndex, 1)[0]; try { const meta = JSON.parse(deletedImage.content); return `已删除保存的图片:${meta.name}${meta.scene ? `(${meta.scene})` : ''}`; diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 4c2084c..d6ff185 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -27,7 +27,7 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { return acc; }, {}); - const savedImages = ai.image.imageList.filter(img => !img.isUrl); + const savedImages = ai.image.savedImages; const savedImageNames = savedImages.map(img => { try { const meta = JSON.parse(img.content); From 5ab8b6a0c35e9df6544cc2e1ff5a2cf41e02c138 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 3 Jul 2025 16:16:03 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E9=83=A8=E5=88=86=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BF=AE=E6=94=B9=E3=80=81=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 注意到id和name进行区分并无意义 --- src/AI/AI.ts | 16 ++-- src/AI/context.ts | 35 +++------ src/AI/image.ts | 82 ++++++--------------- src/config/config_image.ts | 2 +- src/config/config_message.ts | 13 +--- src/index.ts | 73 +++++++++---------- src/tool/tool.ts | 8 +- src/tool/tool_image.ts | 137 ++++++++++++++++++++--------------- src/tool/tool_message.ts | 8 +- src/tool/tool_time.ts | 12 +-- src/utils/utils_message.ts | 51 ++++++------- src/utils/utils_string.ts | 16 ++-- 12 files changed, 199 insertions(+), 254 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 9b12b66..11b5b4c 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, this); + result = await handleReply(ctx, msg, this, raw_reply); if (!checkRepeat(this.context, result.contextArray.join('')) || result.replyArray.join('').trim() === '') { break; @@ -160,8 +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}]`); } @@ -212,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, this); + const { contextArray, replyArray, images } = await handleReply(ctx, msg, this, match[1]); if (this.stream.id !== id) { return; @@ -268,7 +268,7 @@ export class AI { } } - const { contextArray, replyArray, images } = await handleReply(ctx, msg, raw_reply, this.context, this); + const { contextArray, replyArray, images } = await handleReply(ctx, msg, this, raw_reply); if (this.stream.id !== id) { return; diff --git a/src/AI/context.ts b/src/AI/context.ts index 03a5e78..029d62c 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, ai?: AI): Image | null { + 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--) { @@ -385,28 +385,6 @@ export class Context { } } - if (ai && ai.image) { - const savedImage = ai.image.savedImages.find(img => { - if (img.content) { - try { - const meta = JSON.parse(img.content); - return meta.name === id; - } catch { - return false; - } - } - return false; - }); - if (savedImage) { - const filePath = seal.base64ToImage(savedImage.file); - const newImage = new Image(filePath); - newImage.id = savedImage.id; - newImage.content = savedImage.content; - newImage.isUrl = false; - return newImage; - } - } - const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { if (path.trim() === '') { @@ -429,6 +407,15 @@ 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); + const newImage = new Image(filePath); + newImage.id = savedImage.id; + newImage.content = savedImage.content; + return newImage; + } + return null; } } diff --git a/src/AI/image.ts b/src/AI/image.ts index 0da629d..c1045f6 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -7,23 +7,27 @@ export class Image { id: string; isUrl: boolean; file: string; + scenes: string[]; + base64: string; content: string; constructor(file: string) { this.id = generateId(); this.isUrl = file.startsWith('http'); this.file = file; + this.scenes = []; + this.base64 = ''; this.content = ''; } } export class ImageManager { - imageList: Image[]; + stolenImages: Image[]; savedImages: Image[]; stealStatus: boolean; constructor() { - this.imageList = []; + this.stolenImages = []; this.savedImages = []; this.stealStatus = false; } @@ -41,38 +45,16 @@ 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); } - updateSavedImageList(name: string, scene: string, base64: string) { + updateSavedImages(images: Image[]) { const { maxSavedImageNum } = ConfigManager.image; - if (this.savedImages.length >= maxSavedImageNum) { - throw new Error(`保存图片已达到上限${maxSavedImageNum}张,无法继续保存`); - } - - let finalName = name; - let count = 1; - while (this.savedImages.some(img => { - try { - const meta = JSON.parse(img.content); - return meta.name === finalName; - } catch { - return false; - } - })) { - finalName = `${name}_${count++}`; - } - - const img = new Image(`${base64}`); - img.content = JSON.stringify({ name: finalName, scene: scene || '' }); - img.isUrl = false; - this.savedImages.push(img); - return finalName; + this.savedImages = this.savedImages.concat(images.filter(item => item.isUrl)).slice(-maxSavedImageNum); } - drawLocalImageFile(): string { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { @@ -101,10 +83,12 @@ export class ImageManager { } async drawStolenImageFile(): Promise { - if (this.imageList.length === 0) return ''; + 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)) { @@ -115,21 +99,11 @@ export class ImageManager { return url; } - async drawSaveImageFile(): Promise<{ file: string, name: string, scene: string } | null> { + drawSavedImageFile(): string { if (this.savedImages.length === 0) return null; const index = Math.floor(Math.random() * this.savedImages.length); const image = this.savedImages[index]; - const imagefile = seal.base64ToImage(image.file); - try { - const meta = JSON.parse(image.content); - return { - file: imagefile, - name: meta.name, - scene: meta.scene || '' - }; - } catch { - return null; - } + return seal.base64ToImage(image.base64); } async drawImageFile(): Promise { @@ -152,28 +126,18 @@ export class ImageManager { }, {}); const values = Object.values(localImages); - if (this.imageList.length == 0 && values.length == 0 && this.savedImages.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 + this.savedImages.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.imageList.length) { - const image = this.imageList[index - values.length]; - this.imageList.splice(index - values.length, 1); - const url = image.file; - - if (!await ImageManager.checkImageUrl(url)) { - await new Promise(resolve => setTimeout(resolve, 500)); - return await this.drawImageFile(); - } - - return url; + } else if (index < values.length + this.stolenImages.length) { + return await this.drawStolenImageFile(); } else { - const image = this.savedImages[index - values.length - this.imageList.length]; - return seal.base64ToImage(image.file); + return this.drawSavedImageFile(); } } diff --git a/src/config/config_image.ts b/src/config/config_image.ts index 441df5d..1c19dcc 100644 --- a/src/config/config_image.ts +++ b/src/config/config_image.ts @@ -37,7 +37,7 @@ 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, "保存图片存储上限") } } diff --git a/src/config/config_message.ts b/src/config/config_message.ts index cc24299..bb460af 100644 --- a/src/config/config_message.ts +++ b/src/config/config_message.ts @@ -97,20 +97,13 @@ export class MessageConfig { - <|img:xxxxxx|>为图片,其中xxxxxx为6位的图片id,如果要发送出现过的图片请使用<|img:xxxxxx|>的格式 {{/if}} {{else}} -{{#if 本地图片不为空}} +{{#if 可发送图片不为空}} ## 图片相关 {{/if}} {{/if}} -{{#if 本地图片不为空}} - - 可使用<|img:图片名称|>发送表情包,表情名称有:{{{本地图片名称}}} -{{/if}} -{{#if 保存图片不为空}} - -## 图片相关 -{{/if}} -{{#if 保存图片不为空}} - - 可使用<|img:图片名称|>发送表情包,括号内为使用场景介绍,在发送时只使用括号前的名称,表情名称有:{{{保存图片名称}}} +{{#if 可发送图片不为空}} + - 可使用<|img:图片名称|>发送表情包,表情名称有:{{{可发送图片列表}}} {{/if}} {{#if 开启长期记忆}} diff --git a/src/index.ts b/src/index.ts index 895cb56..a0d051f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1192,43 +1192,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': { - ai.image.drawSaveImageFile() - .then(image => { - if (!image) { - seal.replyToSender(ctx, msg, '暂无保存的表情包图片'); - } else { - let text = `[CQ:image,file=${image.file}]\n名称:${image.name}`; - if (image.scene) { - text += `\n场景:${image.scene}`; - } - seal.replyToSender(ctx, msg, text); - } - }); + 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, '暂无图片'); @@ -1249,19 +1242,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.filter(img => img.isUrl).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.filter(img => img.isUrl).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.filter(img => img.isUrl).length}`); + seal.replyToSender(ctx, msg, `图片偷取状态:${ai.imageManager.stealStatus},当前偷取数量:${ai.imageManager.stolenImages.filter(img => img.isUrl).length}`); return ret; } } @@ -1273,20 +1266,20 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { switch (type) { case 'stl': case 'stolen': { - ai.image.imageList = []; + ai.imageManager.stolenImages = []; seal.replyToSender(ctx, msg, '偷取图片已遗忘'); AIManager.saveAI(id); return ret; } case 'save': { - ai.image.savedImages = []; + ai.imageManager.savedImages = []; seal.replyToSender(ctx, msg, '保存图片已遗忘'); AIManager.saveAI(id); return ret; } case 'all': { - ai.image.imageList = []; - ai.image.savedImages = []; + ai.imageManager.stolenImages = []; + ai.imageManager.savedImages = []; seal.replyToSender(ctx, msg, '所有图片已遗忘'); AIManager.saveAI(id); return ret; @@ -1305,7 +1298,7 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { } if (val2 == 'ran') { - ai.image.drawStolenImageFile() + ai.imageManager.drawStolenImageFile() .then(url => { if (!url) { seal.replyToSender(ctx, msg, '图片偷取为空'); @@ -1405,8 +1398,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); } } @@ -1436,8 +1429,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); } } @@ -1460,8 +1453,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); } } @@ -1530,8 +1523,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); } } @@ -1574,8 +1567,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 6141577..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, registerSaveImage, registerDeleteSavedImage } 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[] } }, @@ -200,7 +198,7 @@ export class ToolManager { registerCheckAvatar(); registerTextToImage(); registerSaveImage(); - registerDeleteSavedImage(); + registerDelImage(); registerGroupSign(); registerGetPersonInfo(); registerSendMsg(); diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 1498ed0..6513d99 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,7 +30,7 @@ export function registerImageToText() { tool.solve = async (_, __, ai, args) => { const { id, content } = args; - const image = ai.context.findImage(id, ai); + const image = ai.context.findImage(id, ai.imageManager); if (!image) { return `未找到图片${id}`; } @@ -170,101 +170,118 @@ export function registerSaveImage() { parameters: { type: "object", properties: { - id: { - type: "string", - description: `图片的id,六位字符` - }, - name: { - type: "string", - description: `图片命名` - }, - scene: { - type: "string", - description: `表情包的应用场景` + 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: ["id", "name"] + required: ["images"] } } } const tool = new Tool(info); tool.solve = async (_, __, ai, args) => { - const { id, name, scene } = args; + const { images } = args; - const image = ai.context.findImage(id, ai); - if (!image) { - return `未找到图片${id}`; - } + const savedImages: Image[] = []; + for (const ii of images) { + const { id, name, scenes } = ii; - if (image.isUrl) { - const { base64 } = await ImageManager.imageUrlToBase64(image.file); - if (!base64) { - logger.warning(`转换为base64失败`); - return '转换为base64失败'; + const image = ai.context.findImage(id, ai.imageManager); + if (!image) { + return `未找到图片${id}`; } - try { - const finalName = ai.image.updateSavedImageList(name, scene || '', base64); - return `图片已保存为表情包,名称:${finalName}`; - } catch (error) { - return error.message; + if (image.isUrl) { + const { base64 } = await ImageManager.imageUrlToBase64(image.file); + if (!base64) { + logger.warning(`图片${id}转换为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 '本地图片不用再次储存'; } - } else { - return '本地图片不用再次储存'; + } + + + try { + ai.imageManager.updateSavedImages(savedImages); + return `图片已保存`; + } catch (e) { + return `图片保存失败:${e.message}` } } - + ToolManager.toolMap[info.function.name] = tool; } -export function registerDeleteSavedImage() { +export function registerDelImage() { const info: ToolInfo = { type: "function", function: { - name: "delete_saved_image", + name: "del_image", description: "删除保存的表情包图片", parameters: { type: "object", properties: { - name: { - type: "string", - description: `要删除的图片名称` + names: { + type: "array", + description: `要删除的图片名称数组` } }, - required: ["name"] + required: ["names"] } } } const tool = new Tool(info); tool.solve = async (_, __, ai, args) => { - const { name } = args; - - const imageIndex = ai.image.savedImages.findIndex(img => { - if (img.content) { - try { - const meta = JSON.parse(img.content); - return meta.name === name; - } catch { - return false; - } + const { names } = args; + + for (const name of names) { + const imageIndex = ai.imageManager.savedImages.findIndex(img => img.id === name); + if (imageIndex === -1) { + return `未找到名称为"${name}"的保存图片`; } - return false; - }); - if (imageIndex === -1) { - return `未找到名称为"${name}"的保存图片`; + ai.imageManager.savedImages.splice(imageIndex, 1); } - const deletedImage = ai.image.savedImages.splice(imageIndex, 1)[0]; - try { - const meta = JSON.parse(deletedImage.content); - return `已删除保存的图片:${meta.name}${meta.scene ? `(${meta.scene})` : ''}`; - } catch { - return `已删除保存的图片:${name}`; - } + 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 d5ea6f3..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, ai); + 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, ai); + 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 d6ff185..7638fbe 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -10,32 +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; - }, {}); - - const savedImages = ai.image.savedImages; - const savedImageNames = savedImages.map(img => { - try { - const meta = JSON.parse(img.content); - return meta.scene ? `${meta.name}(${meta.scene})` : meta.name; - } catch { - return ''; - } - }).filter(Boolean).join("、"); + 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) { @@ -73,10 +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("、"), - "保存图片不为空": savedImages.length > 0, - "保存图片名称": savedImageNames, + "可发送图片不为空": sandableImagesPrompt, + "可发送图片列表": sandableImagesPrompt, "开启长期记忆": isMemory && memoryPrompt, "记忆信息": memoryPrompt, "开启短期记忆": isShortMemory && shortMemoryPrompt, diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 377bad1..6448d1c 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -77,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, ai?: AI): 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臆想出来的多轮对话 @@ -93,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中 } @@ -121,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, ai); + 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}]` : ``; @@ -350,11 +350,11 @@ async function replaceQuote(reply: string) { /** * 替换图片占位符为CQ码 * @param context + * @param im 图片管理器 * @param reply - * @param ai * @returns */ -async function replaceImages(context: Context, reply: string, ai?: AI) { +async function replaceImages(context: Context, im: ImageManager, reply: string) { let result = reply; const images = []; @@ -362,7 +362,7 @@ async function replaceImages(context: Context, reply: string, ai?: AI) { if (match) { for (let i = 0; i < match.length; i++) { const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1]; - const image = context.findImage(id, ai); + const image = context.findImage(id, im); if (image) { images.push(image); From be3cda7f22e14396a8083908c71180f11eee3f17 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 3 Jul 2025 16:17:10 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AI/update.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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": `- 新增了全局待机模式配置项 - 修改了部分正则和部分默认配置项 - 修复了无法调用内置指令 From b58d507671f43d55a8cc50b150fe329d8ece1293 Mon Sep 17 00:00:00 2001 From: baiyu-yu <135424680+baiyu-yu@users.noreply.github.com> Date: Sun, 13 Jul 2025 12:53:59 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AE=80=E6=98=93?= =?UTF-8?q?=E6=9D=83=E9=87=8D=E6=9C=BA=E5=88=B6=E5=92=8C=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=9B=BE=E7=89=87=E5=88=97=E8=A1=A8=E3=80=81?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=BF=9D=E5=AD=98=E5=9B=BE=E7=89=87=E6=8C=87?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 太简易了,简易到不知道有什么用 --- src/AI/AI.ts | 2 +- src/AI/context.ts | 1 + src/AI/image.ts | 46 +++++++++++++++++++++++++++++++++++++-- src/index.ts | 34 ++++++++++++++++++++++++++++- src/utils/utils_string.ts | 4 ++++ 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 11b5b4c..f1add92 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -346,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 029d62c..e5aa92b 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -413,6 +413,7 @@ export class Context { const newImage = new Image(filePath); newImage.id = savedImage.id; newImage.content = savedImage.content; + newImage.weight = savedImage.weight; return newImage; } diff --git a/src/AI/image.ts b/src/AI/image.ts index c1045f6..6f796a6 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -10,6 +10,7 @@ export class Image { scenes: string[]; base64: string; content: string; + weight: number; constructor(file: string) { this.id = generateId(); @@ -18,6 +19,7 @@ export class Image { this.scenes = []; this.base64 = ''; this.content = ''; + this.weight = 1; } } @@ -34,7 +36,7 @@ export class ImageManager { static reviver(value: any): ImageManager { const im = new ImageManager(); - const validKeys = ['imageList', 'savedImages', 'stealStatus']; + const validKeys = ['stolenImages', 'savedImages', 'stealStatus']; for (const k of validKeys) { if (value.hasOwnProperty(k)) { @@ -52,7 +54,12 @@ export class ImageManager { updateSavedImages(images: Image[]) { const { maxSavedImageNum } = ConfigManager.image; - this.savedImages = this.savedImages.concat(images.filter(item => item.isUrl)).slice(-maxSavedImageNum); + this.savedImages = this.savedImages.concat(images.filter(item => item.isUrl)); + + if (this.savedImages.length > maxSavedImageNum) { + this.savedImages.sort((a, b) => a.weight - b.weight); + this.savedImages = this.savedImages.slice(-maxSavedImageNum); + } } drawLocalImageFile(): string { @@ -106,6 +113,41 @@ export class ImageManager { return seal.base64ToImage(image.base64); } + getSavedImagesInfo(): string { + if (this.savedImages.length === 0) { + return '暂无保存的图片'; + } + + const imageList = this.savedImages.map((img, index) => + `${index + 1}. 名称: ${img.id}\n 应用场景: ${img.scenes.join('、') || '无'}\n 权重: ${img.weight}` + ).join('\n'); + + return `保存的图片列表:\n${imageList}`; + } + + getSavedImagesInfoWithCQ(): string { + if (this.savedImages.length === 0) { + return '暂无保存的图片'; + } + + const imageList = this.savedImages.map((img, index) => { + const filePath = seal.base64ToImage(img.base64); + return `${index + 1}. 名称: ${img.id}\n 应用场景: ${img.scenes.join('、') || '无'}\n 权重: ${img.weight}\n [CQ:image,file=${filePath}]`; + }).join('\n\n'); + + return `保存的图片列表:\n${imageList}`; + } + + deleteSavedImageByName(imageName: string): string { + const imageIndex = this.savedImages.findIndex(img => img.id === imageName); + if (imageIndex === -1) { + return `未找到名称为"${imageName}"的保存图片`; + } + + const deletedImage = this.savedImages.splice(imageIndex, 1)[0]; + return `已删除图片"${deletedImage.id}"`; + } + async drawImageFile(): Promise { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { diff --git a/src/index.ts b/src/index.ts index a0d051f..debe647 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1175,7 +1175,9 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { 【img draw [stl/lcl/save/all]】随机抽取偷的图片/本地图片/保存的图片/全部 【img stl (on/off)】偷图 开启/关闭 【img f [stl/save/all]】遗忘偷的图片/保存的图片/全部 -【img itt [图片/ran] (附加提示词)】图片转文字`; +【img itt [图片/ran] (附加提示词)】图片转文字 +【img list [show/send]】展示保存的图片列表/展示并发送所有保存的图片 +【img del <图片名称>】删除指定名称的保存图片`; cmdImage.solve = (ctx, msg, cmdArgs) => { try { const val = cmdArgs.getArgN(1); @@ -1325,6 +1327,36 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { } return ret; } + case 'list': { + const type = cmdArgs.getArgN(2); + switch (type) { + case 'show': { + const info = ai.imageManager.getSavedImagesInfo(); + seal.replyToSender(ctx, msg, info); + return ret; + } + case 'send': { + const info = ai.imageManager.getSavedImagesInfoWithCQ(); + seal.replyToSender(ctx, msg, info); + return ret; + } + default: { + seal.replyToSender(ctx, msg, '参数缺失,【img list show】展示保存的图片列表,【img list send】展示并发送所有保存的图片'); + return ret; + } + } + } + case 'del': { + const imageName = cmdArgs.getArgN(2); + if (!imageName) { + seal.replyToSender(ctx, msg, '参数缺失,【img del <图片名称>】删除指定名称的保存图片'); + return ret; + } + + const result = ai.imageManager.deleteSavedImageByName(imageName); + seal.replyToSender(ctx, msg, result); + return ret; + } default: { ret.showHelp = true; return ret; diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 6448d1c..228025b 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -368,6 +368,10 @@ async function replaceImages(context: Context, im: ImageManager, reply: string) images.push(image); if (!image.isUrl || (image.isUrl && await ImageManager.checkImageUrl(image.file))) { + const savedImage = im.savedImages.find(img => img.id === id); + if (savedImage) { + savedImage.weight += 1; + } result = result.replace(match[i], `[CQ:image,file=${image.file}]`); continue; } From fcf4695b8b74f97c3ef6ecb6a715ac1c358a1032 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 15 Jul 2025 14:05:02 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BA=86=E9=83=A8?= =?UTF-8?q?=E5=88=86=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AI/context.ts | 7 ++---- src/AI/image.ts | 46 ++++++--------------------------- src/index.ts | 53 ++++++++++++++++++++++++++------------- src/tool/tool_image.ts | 3 ++- src/utils/utils_string.ts | 5 ++-- 5 files changed, 50 insertions(+), 64 deletions(-) diff --git a/src/AI/context.ts b/src/AI/context.ts index e5aa92b..dc70092 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -410,11 +410,8 @@ export class Context { const savedImage = im.savedImages.find(img => img.id === id); if (savedImage) { const filePath = seal.base64ToImage(savedImage.base64); - const newImage = new Image(filePath); - newImage.id = savedImage.id; - newImage.content = savedImage.content; - newImage.weight = savedImage.weight; - return newImage; + savedImage.file = filePath; + return savedImage; } return null; diff --git a/src/AI/image.ts b/src/AI/image.ts index 6f796a6..07b0ab6 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -55,13 +55,18 @@ export class ImageManager { updateSavedImages(images: Image[]) { const { maxSavedImageNum } = ConfigManager.image; this.savedImages = this.savedImages.concat(images.filter(item => item.isUrl)); - + if (this.savedImages.length > maxSavedImageNum) { - this.savedImages.sort((a, b) => a.weight - b.weight); - this.savedImages = this.savedImages.slice(-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 { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { @@ -113,41 +118,6 @@ export class ImageManager { return seal.base64ToImage(image.base64); } - getSavedImagesInfo(): string { - if (this.savedImages.length === 0) { - return '暂无保存的图片'; - } - - const imageList = this.savedImages.map((img, index) => - `${index + 1}. 名称: ${img.id}\n 应用场景: ${img.scenes.join('、') || '无'}\n 权重: ${img.weight}` - ).join('\n'); - - return `保存的图片列表:\n${imageList}`; - } - - getSavedImagesInfoWithCQ(): string { - if (this.savedImages.length === 0) { - return '暂无保存的图片'; - } - - const imageList = this.savedImages.map((img, index) => { - const filePath = seal.base64ToImage(img.base64); - return `${index + 1}. 名称: ${img.id}\n 应用场景: ${img.scenes.join('、') || '无'}\n 权重: ${img.weight}\n [CQ:image,file=${filePath}]`; - }).join('\n\n'); - - return `保存的图片列表:\n${imageList}`; - } - - deleteSavedImageByName(imageName: string): string { - const imageIndex = this.savedImages.findIndex(img => img.id === imageName); - if (imageIndex === -1) { - return `未找到名称为"${imageName}"的保存图片`; - } - - const deletedImage = this.savedImages.splice(imageIndex, 1)[0]; - return `已删除图片"${deletedImage.id}"`; - } - async drawImageFile(): Promise { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { diff --git a/src/index.ts b/src/index.ts index debe647..baf3875 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1172,12 +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/save/all]】随机抽取偷的图片/本地图片/保存的图片/全部 -【img stl (on/off)】偷图 开启/关闭 -【img f [stl/save/all]】遗忘偷的图片/保存的图片/全部 -【img itt [图片/ran] (附加提示词)】图片转文字 -【img list [show/send]】展示保存的图片列表/展示并发送所有保存的图片 -【img del <图片名称>】删除指定名称的保存图片`; +【.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); @@ -1295,7 +1295,7 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { 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; } @@ -1331,30 +1331,49 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { const type = cmdArgs.getArgN(2); switch (type) { case 'show': { - const info = ai.imageManager.getSavedImagesInfo(); - seal.replyToSender(ctx, msg, info); + 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': { - const info = ai.imageManager.getSavedImagesInfoWithCQ(); - seal.replyToSender(ctx, msg, info); + 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】展示并发送所有保存的图片'); + seal.replyToSender(ctx, msg, '参数缺失,【.img list show】展示保存的图片列表,【.img list send】展示并发送所有保存的图片'); return ret; } } } case 'del': { - const imageName = cmdArgs.getArgN(2); - if (!imageName) { - seal.replyToSender(ctx, msg, '参数缺失,【img del <图片名称>】删除指定名称的保存图片'); + const nameList = cmdArgs.args.slice(1); + if (nameList.length === 0) { + seal.replyToSender(ctx, msg, '参数缺失,【.img del <图片名称1> <图片名称2> ...】删除指定名称的保存图片'); return ret; } - const result = ai.imageManager.deleteSavedImageByName(imageName); - seal.replyToSender(ctx, msg, result); + ai.imageManager.delSavedImage(nameList); + seal.replyToSender(ctx, msg, `已删除图片`); return ret; } default: { diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 6513d99..1b66757 100644 --- a/src/tool/tool_image.ts +++ b/src/tool/tool_image.ts @@ -216,7 +216,8 @@ export function registerSaveImage() { if (image.isUrl) { const { base64 } = await ImageManager.imageUrlToBase64(image.file); if (!base64) { - logger.warning(`图片${id}转换为base64失败`); + logger.error(`图片${id}转换为base64失败`); + return `图片转换为base64失败`; } const newImage = new Image(image.file); diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 228025b..a4f9a84 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -368,9 +368,8 @@ async function replaceImages(context: Context, im: ImageManager, reply: string) images.push(image); if (!image.isUrl || (image.isUrl && await ImageManager.checkImageUrl(image.file))) { - const savedImage = im.savedImages.find(img => img.id === id); - if (savedImage) { - savedImage.weight += 1; + if (image.base64) { + image.weight += 1; } result = result.replace(match[i], `[CQ:image,file=${image.file}]`); continue;