mioku-plugin-chat 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/configs/base.ts +12 -0
- package/configs/personalization.ts +84 -0
- package/configs/settings.ts +54 -0
- package/context.ts +98 -0
- package/core/base.ts +936 -0
- package/core/chat-engine.ts +779 -0
- package/core/external-skills.ts +94 -0
- package/core/feature-prompts.ts +47 -0
- package/core/media/audio.ts +73 -0
- package/core/media/gif-extractor.ts +118 -0
- package/core/media/history-media.ts +751 -0
- package/core/media/image-analyzer.ts +447 -0
- package/core/media/markdown-message.ts +190 -0
- package/core/multimodal.ts +180 -0
- package/core/prompt.ts +827 -0
- package/core/tools.ts +862 -0
- package/core/web/searxng.ts +184 -0
- package/core/web/web-reader.ts +792 -0
- package/db.ts +849 -0
- package/humanize/emoji-agent.ts +397 -0
- package/humanize/expression.ts +152 -0
- package/humanize/index.ts +40 -0
- package/humanize/memory.ts +131 -0
- package/humanize/planner.ts +249 -0
- package/humanize/topic.ts +205 -0
- package/humanize/utils.ts +28 -0
- package/index.ts +1830 -0
- package/manage/cooldown.ts +445 -0
- package/manage/group-structured-history.ts +195 -0
- package/manage/idle-check.ts +186 -0
- package/manage/queue-processor.ts +228 -0
- package/manage/rate-limiter.ts +312 -0
- package/manage/session.ts +96 -0
- package/manage/skill-session.ts +173 -0
- package/manage/types.ts +138 -0
- package/package.json +51 -0
- package/tsconfig.json +7 -0
- package/types.ts +381 -0
- package/utils/index.ts +9 -0
- package/utils/message.ts +496 -0
- package/utils/queue.ts +186 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { AIInstance } from "mioku";
|
|
2
|
+
import type { MiokiContext } from "mioki";
|
|
3
|
+
import { logger } from "mioki";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 多模态内容管理器
|
|
7
|
+
* 负责处理图片、视频等多模态内容的描述和分析
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 图片描述结果
|
|
12
|
+
*/
|
|
13
|
+
export interface ImageDescriptionResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
description?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 使用多模态工作模型描述图片
|
|
21
|
+
* @param ai AI 实例
|
|
22
|
+
* @param imageUrl 图片 URL
|
|
23
|
+
* @param model 多模态工作模型名称
|
|
24
|
+
* @param context 可选的上下文信息(例如用户的问题)
|
|
25
|
+
* @returns 图片描述结果
|
|
26
|
+
*/
|
|
27
|
+
export async function describeImage(
|
|
28
|
+
ai: AIInstance,
|
|
29
|
+
imageUrl: string,
|
|
30
|
+
model: string,
|
|
31
|
+
context?: string,
|
|
32
|
+
): Promise<ImageDescriptionResult> {
|
|
33
|
+
try {
|
|
34
|
+
// 检查是否为 GIF,如果是则提取三帧
|
|
35
|
+
const { isGifUrl, extractGifFrames } =
|
|
36
|
+
await import("./media/gif-extractor");
|
|
37
|
+
let imageUrls: string[] = [imageUrl];
|
|
38
|
+
|
|
39
|
+
if (await isGifUrl(imageUrl)) {
|
|
40
|
+
const result = await extractGifFrames(imageUrl);
|
|
41
|
+
if (result && result.frames.length > 0) {
|
|
42
|
+
imageUrls = result.frames;
|
|
43
|
+
} else {
|
|
44
|
+
logger.warn(
|
|
45
|
+
`[multimodal] Failed to extract GIF frames, using original URL`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 构建提示词
|
|
51
|
+
const systemPrompt = `You are an image analysis assistant. Your task is to provide a detailed, accurate description of the image.
|
|
52
|
+
|
|
53
|
+
Instructions:
|
|
54
|
+
- Describe what you see in the image clearly and objectively
|
|
55
|
+
- Include important details like objects, people, text, colors, composition, and atmosphere
|
|
56
|
+
- If there is text in the image, transcribe it accurately
|
|
57
|
+
- Keep your description concise but comprehensive (2-4 sentences)
|
|
58
|
+
- Use natural language that can be easily understood
|
|
59
|
+
${imageUrls.length > 1 ? "\n- Note: You are viewing multiple frames from an animated image (GIF). Describe the overall motion and changes across frames." : ""}
|
|
60
|
+
${context ? `\nUser context: ${context}` : ""}`;
|
|
61
|
+
|
|
62
|
+
const userPrompt = context
|
|
63
|
+
? `Please describe this image in detail, paying special attention to anything relevant to: "${context}"`
|
|
64
|
+
: imageUrls.length > 1
|
|
65
|
+
? `Please describe these ${imageUrls.length} frames from an animated image in detail.`
|
|
66
|
+
: "Please describe this image in detail.";
|
|
67
|
+
|
|
68
|
+
// 构建消息内容
|
|
69
|
+
const contentParts: any[] = [{ type: "text", text: userPrompt }];
|
|
70
|
+
for (const url of imageUrls) {
|
|
71
|
+
contentParts.push({
|
|
72
|
+
type: "image_url",
|
|
73
|
+
image_url: { url, detail: "auto" },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 调用多模态模型
|
|
78
|
+
const response = await ai.complete({
|
|
79
|
+
model,
|
|
80
|
+
messages: [
|
|
81
|
+
{ role: "system", content: systemPrompt },
|
|
82
|
+
{
|
|
83
|
+
role: "user",
|
|
84
|
+
content: contentParts,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
temperature: 0.3, // 使用较低的温度以获得更准确的描述
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!response.content) {
|
|
91
|
+
logger.warn("[multimodal] No description returned from model");
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: "Model returned empty response",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logger.info(`[multimodal] image: ${response.content}`);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
description: response.content,
|
|
103
|
+
};
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.error(`[multimodal] Failed to describe image: ${err}`);
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: String(err),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 获取指定消息 ID 中的图片 URL
|
|
115
|
+
* @param ctx Mioki 上下文
|
|
116
|
+
* @param messageId 消息 ID
|
|
117
|
+
* @param e
|
|
118
|
+
* @returns 图片 URL 或 null
|
|
119
|
+
*/
|
|
120
|
+
export async function getImageUrlByMessageId(
|
|
121
|
+
ctx: MiokiContext,
|
|
122
|
+
messageId: number,
|
|
123
|
+
e: any,
|
|
124
|
+
): Promise<string | null> {
|
|
125
|
+
try {
|
|
126
|
+
// 通过 message_id 获取消息详情
|
|
127
|
+
const msg = await ctx.pickBot(e.self_id).getMsg(messageId);
|
|
128
|
+
if (!msg || !msg.message) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 查找图片段
|
|
133
|
+
const imageSeg = msg.message.find((s: any) => s.type === "image");
|
|
134
|
+
if (!imageSeg) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 提取图片 URL
|
|
139
|
+
return (imageSeg as any).url || (imageSeg as any).data?.url || null;
|
|
140
|
+
} catch (err) {
|
|
141
|
+
logger.error(
|
|
142
|
+
`[multimodal] Failed to get image URL from message ${messageId}: ${err}`,
|
|
143
|
+
);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 获取引用消息中的图片 URL
|
|
150
|
+
* @param ctx Mioki 上下文
|
|
151
|
+
* @param event 消息事件
|
|
152
|
+
* @returns 图片 URL 或 null
|
|
153
|
+
*/
|
|
154
|
+
export async function getQuoteImageUrl(
|
|
155
|
+
ctx: MiokiContext,
|
|
156
|
+
event: any,
|
|
157
|
+
): Promise<string | null> {
|
|
158
|
+
if (!event.quote_id) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const quotedMsg = await ctx.getQuoteMsg(event);
|
|
164
|
+
if (!quotedMsg || !quotedMsg.message) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 查找图片段
|
|
169
|
+
const imageSeg = quotedMsg.message.find((s: any) => s.type === "image");
|
|
170
|
+
if (!imageSeg) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 提取图片 URL
|
|
175
|
+
return (imageSeg as any).url || (imageSeg as any).data?.url || null;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.error(`[multimodal] Failed to get quote image URL: ${err}`);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|