mioku-plugin-chat 2.9.9 → 2.9.11
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/core/chat-engine.ts +12 -6
- package/core/media/gif-extractor.ts +3 -6
- package/core/media/image-analyzer.ts +16 -11
- package/core/media/image-compress.ts +3 -1
- package/package.json +1 -1
- package/utils/message.ts +2 -11
package/core/chat-engine.ts
CHANGED
|
@@ -68,12 +68,7 @@ export async function runChat(
|
|
|
68
68
|
return isSkillAllowedForRole(skill, toolCtx.triggerSkillRole);
|
|
69
69
|
},
|
|
70
70
|
);
|
|
71
|
-
const emotionState =
|
|
72
|
-
sessionId: toolCtx.sessionId,
|
|
73
|
-
botNickname: promptCtx.botNickname,
|
|
74
|
-
chatHistory: history,
|
|
75
|
-
targetMessage,
|
|
76
|
-
});
|
|
71
|
+
const emotionState = humanize.emotionAgent.getCurrent(toolCtx.sessionId);
|
|
77
72
|
|
|
78
73
|
const staticCtx: StaticPromptContext = {
|
|
79
74
|
config: promptCtx.config,
|
|
@@ -249,6 +244,17 @@ export async function runChat(
|
|
|
249
244
|
? await ai.withUsageContext(usageContext, runComplete)
|
|
250
245
|
: await runComplete();
|
|
251
246
|
|
|
247
|
+
humanize.emotionAgent
|
|
248
|
+
.refreshIfNeeded({
|
|
249
|
+
sessionId: toolCtx.sessionId,
|
|
250
|
+
botNickname: promptCtx.botNickname,
|
|
251
|
+
chatHistory: history,
|
|
252
|
+
targetMessage,
|
|
253
|
+
})
|
|
254
|
+
.catch((err) =>
|
|
255
|
+
logger.warn(`[chat-engine] background emotion refresh failed: ${err}`),
|
|
256
|
+
);
|
|
257
|
+
|
|
252
258
|
if (streamEnabled) {
|
|
253
259
|
streamBuffer += streamThinkFilter.push("", true);
|
|
254
260
|
await flushStreamBuffer(true);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from "mioki";
|
|
2
2
|
import sharp from "sharp";
|
|
3
|
+
import { QQ_IMAGE_FETCH_HEADERS } from "./image-compress";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* GIF 帧提取结果
|
|
@@ -19,7 +20,7 @@ export async function extractGifFrames(
|
|
|
19
20
|
): Promise<GifFramesResult | null> {
|
|
20
21
|
try {
|
|
21
22
|
// 下载 GIF
|
|
22
|
-
const response = await fetch(gifUrl);
|
|
23
|
+
const response = await fetch(gifUrl, { headers: QQ_IMAGE_FETCH_HEADERS });
|
|
23
24
|
if (!response.ok) {
|
|
24
25
|
logger.error(
|
|
25
26
|
`[gif-extractor] Failed to fetch GIF: ${response.statusText}`,
|
|
@@ -102,11 +103,7 @@ export async function isGifUrl(url: string): Promise<boolean> {
|
|
|
102
103
|
try {
|
|
103
104
|
const response = await fetch(url, {
|
|
104
105
|
method: "GET",
|
|
105
|
-
headers:
|
|
106
|
-
"User-Agent":
|
|
107
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
108
|
-
Referer: "https://qq.com/",
|
|
109
|
-
},
|
|
106
|
+
headers: QQ_IMAGE_FETCH_HEADERS,
|
|
110
107
|
});
|
|
111
108
|
const contentType = response.headers.get("content-type");
|
|
112
109
|
if (contentType && contentType.includes("image/gif")) {
|
|
@@ -3,7 +3,10 @@ import type { AIInstance } from "mioku";
|
|
|
3
3
|
import { logger } from "mioki";
|
|
4
4
|
import type { ChatDatabase } from "../../db";
|
|
5
5
|
import type { ImageRecord } from "../../types";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
prepareImageUrlsForModel,
|
|
8
|
+
QQ_IMAGE_FETCH_HEADERS,
|
|
9
|
+
} from "./image-compress";
|
|
7
10
|
|
|
8
11
|
export interface ImageAnalysisResult {
|
|
9
12
|
success: boolean;
|
|
@@ -17,7 +20,7 @@ export interface ImageAnalysisOptions {
|
|
|
17
20
|
|
|
18
21
|
export async function calculateImageHash(url: string): Promise<string | null> {
|
|
19
22
|
try {
|
|
20
|
-
const response = await fetch(url);
|
|
23
|
+
const response = await fetch(url, { headers: QQ_IMAGE_FETCH_HEADERS });
|
|
21
24
|
if (!response.ok) {
|
|
22
25
|
logger.warn(
|
|
23
26
|
`[image-analyzer] Failed to download image for hashing: ${response.status} ${response.statusText}`,
|
|
@@ -25,6 +28,14 @@ export async function calculateImageHash(url: string): Promise<string | null> {
|
|
|
25
28
|
return null;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
const contentType = response.headers.get("content-type");
|
|
32
|
+
if (contentType && contentType.startsWith("text/")) {
|
|
33
|
+
logger.warn(
|
|
34
|
+
`[image-analyzer] Non-image content for hashing (${contentType}): ${url}`,
|
|
35
|
+
);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
29
40
|
return crypto.createHash("md5").update(buffer).digest("hex");
|
|
30
41
|
} catch (err) {
|
|
@@ -146,7 +157,7 @@ export async function processImage(
|
|
|
146
157
|
|
|
147
158
|
const record: ImageRecord = {
|
|
148
159
|
hash,
|
|
149
|
-
url:
|
|
160
|
+
url: imageUrl,
|
|
150
161
|
type: "image",
|
|
151
162
|
description: analysis.description || "未知",
|
|
152
163
|
createdAt: Date.now(),
|
|
@@ -166,17 +177,11 @@ export async function getImageTag(
|
|
|
166
177
|
const exact = db.getImageByUrl(imageUrl);
|
|
167
178
|
if (exact) return `[image:${exact.description}]`;
|
|
168
179
|
|
|
169
|
-
const normalized = normalizeImageUrl(imageUrl);
|
|
170
|
-
if (normalized && normalized !== imageUrl) {
|
|
171
|
-
const normalizedHit = db.getImageByUrl(normalized);
|
|
172
|
-
if (normalizedHit) return `[image:${normalizedHit.description}]`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
180
|
const hash = await calculateImageHash(imageUrl);
|
|
176
181
|
if (hash) {
|
|
177
182
|
const byHash = db.getImageByHash(hash);
|
|
178
183
|
if (byHash) {
|
|
179
|
-
db.saveImage(asImageRecord(byHash,
|
|
184
|
+
db.saveImage(asImageRecord(byHash, imageUrl));
|
|
180
185
|
return `[image:${byHash.description}]`;
|
|
181
186
|
}
|
|
182
187
|
}
|
|
@@ -187,7 +192,7 @@ export async function getImageTag(
|
|
|
187
192
|
function asImageRecord(record: ImageRecord, url: string): ImageRecord {
|
|
188
193
|
return {
|
|
189
194
|
...record,
|
|
190
|
-
url
|
|
195
|
+
url,
|
|
191
196
|
type: "image",
|
|
192
197
|
};
|
|
193
198
|
}
|
|
@@ -5,12 +5,14 @@ const IMAGE_MAX_BYTES = 1 * 1024 * 1024;
|
|
|
5
5
|
const COMPRESS_MAX_WIDTH = 1280;
|
|
6
6
|
const COMPRESS_JPEG_QUALITY = 80;
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
export const QQ_IMAGE_FETCH_HEADERS = {
|
|
9
9
|
"User-Agent":
|
|
10
10
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
11
11
|
Referer: "https://qq.com/",
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
const FETCH_HEADERS = QQ_IMAGE_FETCH_HEADERS;
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* 准备发给模型的图片 URL:体积超过 1MB 时压缩为 JPEG data URL,否则原样返回。
|
|
16
18
|
* data URL(如 GIF 抽帧结果)与非 http(s) 链接直接放行。
|
package/package.json
CHANGED
package/utils/message.ts
CHANGED
|
@@ -14,10 +14,7 @@ import {
|
|
|
14
14
|
type HistoryMediaProcessingOptions,
|
|
15
15
|
type MediaMessageSegment,
|
|
16
16
|
} from "../core/media/history-media";
|
|
17
|
-
import {
|
|
18
|
-
calculateImageHash,
|
|
19
|
-
normalizeImageUrl,
|
|
20
|
-
} from "../core/media/image-analyzer";
|
|
17
|
+
import { calculateImageHash } from "../core/media/image-analyzer";
|
|
21
18
|
|
|
22
19
|
const HISTORY_MEDIA_CONCURRENCY = 8;
|
|
23
20
|
|
|
@@ -310,12 +307,6 @@ async function getImageTagWithHashCache(
|
|
|
310
307
|
const exact = db.getImageByUrl?.(imageUrl);
|
|
311
308
|
if (exact) return `[image:${exact.description}]`;
|
|
312
309
|
|
|
313
|
-
const normalized = normalizeImageUrl(imageUrl);
|
|
314
|
-
if (normalized && normalized !== imageUrl) {
|
|
315
|
-
const hit = db.getImageByUrl?.(normalized);
|
|
316
|
-
if (hit) return `[image:${hit.description}]`;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
310
|
let hash = ctx.hashLookupCache.get(`hash:${imageUrl}`);
|
|
320
311
|
if (hash === undefined) {
|
|
321
312
|
hash = (await calculateImageHash(imageUrl)) || null;
|
|
@@ -328,7 +319,7 @@ async function getImageTagWithHashCache(
|
|
|
328
319
|
try {
|
|
329
320
|
db.saveImage?.({
|
|
330
321
|
hash: byHash.hash,
|
|
331
|
-
url:
|
|
322
|
+
url: imageUrl,
|
|
332
323
|
type: "image",
|
|
333
324
|
description: byHash.description,
|
|
334
325
|
createdAt: byHash.createdAt,
|