koishi-plugin-best-cave 2.7.31 → 2.7.33
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/lib/AIManager.d.ts +4 -2
- package/lib/index.d.ts +3 -0
- package/lib/index.js +127 -55
- package/package.json +1 -1
package/lib/AIManager.d.ts
CHANGED
|
@@ -26,8 +26,10 @@ export declare class AIManager {
|
|
|
26
26
|
private fileManager;
|
|
27
27
|
private http;
|
|
28
28
|
private endpointIndex;
|
|
29
|
+
private failureCount;
|
|
30
|
+
private retryTime;
|
|
29
31
|
/**
|
|
30
|
-
* @description AI
|
|
32
|
+
* @description 用于分析的 AI 系统提示词。
|
|
31
33
|
*/
|
|
32
34
|
private readonly ANALYSIS_SYSTEM_PROMPT;
|
|
33
35
|
/**
|
|
@@ -95,7 +97,7 @@ export declare class AIManager {
|
|
|
95
97
|
* @param {any[]} messages - 发送给 AI 的消息数组。
|
|
96
98
|
* @param {string} systemPrompt - 系统提示词。
|
|
97
99
|
* @returns {Promise<T>} - 一个 Promise,解析为从 AI 响应中解析出的 JSON 对象。
|
|
98
|
-
* @throws {Error} - 如果 AI
|
|
100
|
+
* @throws {Error} - 如果 AI 服务持续失败,则抛出错误。
|
|
99
101
|
*/
|
|
100
102
|
private requestAI;
|
|
101
103
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -63,6 +63,9 @@ export interface Config {
|
|
|
63
63
|
key: string;
|
|
64
64
|
model: string;
|
|
65
65
|
}[];
|
|
66
|
+
enableApprove: boolean;
|
|
67
|
+
approveThreshold: number;
|
|
68
|
+
systemPrompt: string;
|
|
66
69
|
}
|
|
67
70
|
export declare const Config: Schema<Config>;
|
|
68
71
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -1112,37 +1112,17 @@ var AIManager = class {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
http;
|
|
1114
1114
|
endpointIndex = 0;
|
|
1115
|
+
failureCount = 0;
|
|
1116
|
+
retryTime = 0;
|
|
1115
1117
|
/**
|
|
1116
|
-
* @description AI
|
|
1118
|
+
* @description 用于分析的 AI 系统提示词。
|
|
1117
1119
|
*/
|
|
1118
1120
|
ANALYSIS_SYSTEM_PROMPT = `你需要分析给定的内容,并按照以下规则进行评分、分类和提取内容中的关键词。
|
|
1119
|
-
你的回复必须且只能是一个JSON对象,禁止含有任何其他内容,例如{"rating": 88,"type": "Game","keywords": ["Minecraft", "Nether"]}
|
|
1120
|
-
1."rating" (整数, 0-100): 对内容进行严格且有区分度的评分,以下为评分标准:
|
|
1121
|
-
- 基础分: 50
|
|
1122
|
-
+10至+20: 高原创性、创意或艺术性。
|
|
1123
|
-
+10至+20: 非常搞笑、幽默或有很强的笑点。
|
|
1124
|
-
+10至+20: 引人深思、感人或有强烈的共鸣。
|
|
1125
|
-
+5至+15: 玩梗巧妙或二创质量高,能识别出具体梗/文化背景。
|
|
1126
|
-
-10至-20: 内容质量低下(如图片模糊、有压缩痕迹、文字错别字)。
|
|
1127
|
-
-10至-20: 内容意义不明或非常无聊。
|
|
1128
|
-
-5至-15: 简单或低创意的烂梗、过时流行语。
|
|
1129
|
-
-20至-30: 几乎没有信息量的内容。
|
|
1130
|
-
2."type" (字符串): 对内容进行严格且标准的分类,以下为分类标准:
|
|
1131
|
-
- Game: 与电子游戏直接相关或源自于电子游戏的内容。
|
|
1132
|
-
- ACG: 与动漫、漫画及广义二次元文化紧密相关的内容。
|
|
1133
|
-
- Internet: 源于互联网的通用流行文化、迷因(Meme)或社群现象。
|
|
1134
|
-
- Reality: 取材于现实世界的日常经验和场景的内容。
|
|
1135
|
-
- Creative: 具有独特的原创性、艺术性或巧妙构思的内容。
|
|
1136
|
-
- Other: 不适合归入以上任何一类的无关内容或小众内容。
|
|
1137
|
-
3."keywords" (字符串数组): 从内容中提取全面且细分的关键词,以下为提取准则:
|
|
1138
|
-
- 直接提取: 优先从文字内容中直接提取核心词汇,而不是进行归纳或总结。提取图片中可辨识的对象、场景或文字。
|
|
1139
|
-
- 简洁规范: 关键词必须简短且为规范化、普遍使用的词语。例如,使用“明日方舟”而非“粥”,使用“梗”而非“梗图”。
|
|
1140
|
-
- 全面细分: 提取多个不同维度的关键词,包括但不限于:人物/对象、场景/地点、事件/行为、特定梗/文化元素。
|
|
1141
|
-
- 避免宽泛: 确保关键词具体且相关,避免使用过于宽泛或模糊的术语,避免近似关键词,所有词应完整定义内容。`;
|
|
1121
|
+
你的回复必须且只能是一个JSON对象,禁止含有任何其他内容,例如{"rating": 88,"type": "Game","keywords": ["Minecraft", "Nether"]}。`;
|
|
1142
1122
|
/**
|
|
1143
1123
|
* @description 用于查重的 AI 系统提示词。
|
|
1144
1124
|
*/
|
|
1145
|
-
DUPLICATE_SYSTEM_PROMPT =
|
|
1125
|
+
DUPLICATE_SYSTEM_PROMPT = `你需要比较给定的“新内容”与“候选内容”,识别内容语义或核心思想重复的候选内容。
|
|
1146
1126
|
你的回复必须且只能是一个JSON数组,禁止含有任何其他内容,只包含重复项ID,例如[1, 2],若无重复,则返回[]。`;
|
|
1147
1127
|
/**
|
|
1148
1128
|
* @description 注册与 AI 功能相关的管理命令。
|
|
@@ -1265,7 +1245,7 @@ var AIManager = class {
|
|
|
1265
1245
|
return await this.IsDuplicate(dummyCave, potentialDuplicates);
|
|
1266
1246
|
} catch (error) {
|
|
1267
1247
|
this.logger.error("查重回声洞出错:", error);
|
|
1268
|
-
|
|
1248
|
+
throw error;
|
|
1269
1249
|
}
|
|
1270
1250
|
}
|
|
1271
1251
|
/**
|
|
@@ -1280,7 +1260,8 @@ var AIManager = class {
|
|
|
1280
1260
|
const contentForAI = await this.prepareContent(cave, mediaBuffers ? new Map(mediaBuffers.map((m) => [m.fileName, m.buffer])) : void 0);
|
|
1281
1261
|
if (!contentForAI) return null;
|
|
1282
1262
|
const userMessage = { role: "user", content: contentForAI };
|
|
1283
|
-
const response = await this.requestAI([userMessage], this.ANALYSIS_SYSTEM_PROMPT
|
|
1263
|
+
const response = await this.requestAI([userMessage], `${this.ANALYSIS_SYSTEM_PROMPT}
|
|
1264
|
+
${this.config.systemPrompt}`);
|
|
1284
1265
|
if (response) {
|
|
1285
1266
|
return {
|
|
1286
1267
|
cave: cave.id,
|
|
@@ -1292,7 +1273,7 @@ var AIManager = class {
|
|
|
1292
1273
|
return null;
|
|
1293
1274
|
} catch (error) {
|
|
1294
1275
|
this.logger.error(`分析回声洞(${cave.id})失败:`, error);
|
|
1295
|
-
|
|
1276
|
+
throw error;
|
|
1296
1277
|
}
|
|
1297
1278
|
});
|
|
1298
1279
|
const results = await Promise.all(analysisPromises);
|
|
@@ -1311,7 +1292,7 @@ var AIManager = class {
|
|
|
1311
1292
|
try {
|
|
1312
1293
|
const buffer = mediaMap?.get(el.file) ?? await this.fileManager.readFile(el.file);
|
|
1313
1294
|
const mimeType = path3.extname(el.file).toLowerCase() === ".png" ? "image/png" : "image/jpeg";
|
|
1314
|
-
return { type: "image_url",
|
|
1295
|
+
return { type: "image_url", image_url: { url: `data:${mimeType};base64,${buffer.toString("base64")}` } };
|
|
1315
1296
|
} catch (error) {
|
|
1316
1297
|
this.logger.warn(`读取文件(${el.file})失败:`, error);
|
|
1317
1298
|
return null;
|
|
@@ -1334,16 +1315,18 @@ var AIManager = class {
|
|
|
1334
1315
|
async IsDuplicate(mainCave, candidateCaves) {
|
|
1335
1316
|
try {
|
|
1336
1317
|
const formatContent = /* @__PURE__ */ __name((elements) => elements.filter((el) => el.type === "text" && el.content).map((el) => el.content).join(" "), "formatContent");
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1318
|
+
const newContentText = formatContent(mainCave.elements);
|
|
1319
|
+
const candidatesText = candidateCaves.map((cave) => `{"id": ${cave.id}, "text": "${formatContent(cave.elements).replace(/"/g, '\\"')}"}`).join("\n");
|
|
1320
|
+
const userMessageContent = `新内容:
|
|
1321
|
+
${newContentText}
|
|
1322
|
+
候选内容:
|
|
1323
|
+
${candidatesText}`;
|
|
1341
1324
|
const userMessage = { role: "user", content: JSON.stringify(userMessageContent) };
|
|
1342
1325
|
const response = await this.requestAI([userMessage], this.DUPLICATE_SYSTEM_PROMPT);
|
|
1343
1326
|
return response || [];
|
|
1344
1327
|
} catch (error) {
|
|
1345
1328
|
this.logger.error(`比较回声洞(${mainCave.id})失败:`, error);
|
|
1346
|
-
|
|
1329
|
+
throw error;
|
|
1347
1330
|
}
|
|
1348
1331
|
}
|
|
1349
1332
|
/**
|
|
@@ -1366,28 +1349,38 @@ var AIManager = class {
|
|
|
1366
1349
|
* @param {any[]} messages - 发送给 AI 的消息数组。
|
|
1367
1350
|
* @param {string} systemPrompt - 系统提示词。
|
|
1368
1351
|
* @returns {Promise<T>} - 一个 Promise,解析为从 AI 响应中解析出的 JSON 对象。
|
|
1369
|
-
* @throws {Error} - 如果 AI
|
|
1352
|
+
* @throws {Error} - 如果 AI 服务持续失败,则抛出错误。
|
|
1370
1353
|
*/
|
|
1371
1354
|
async requestAI(messages, systemPrompt) {
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${endpointConfig.key}` };
|
|
1377
|
-
const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
|
|
1378
|
-
const content = response?.choices?.[0]?.message?.content;
|
|
1379
|
-
if (!content?.trim()) throw new Error("响应为空");
|
|
1380
|
-
const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
|
|
1381
|
-
if (jsonBlockMatch && jsonBlockMatch[1]) try {
|
|
1382
|
-
return JSON.parse(jsonBlockMatch[1]);
|
|
1383
|
-
} catch (e) {
|
|
1355
|
+
const now = Date.now();
|
|
1356
|
+
if (now < this.retryTime) {
|
|
1357
|
+
const waitTime = this.retryTime - now;
|
|
1358
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
1384
1359
|
}
|
|
1385
1360
|
try {
|
|
1386
|
-
|
|
1387
|
-
|
|
1361
|
+
const endpointConfig = this.config.endpoints[this.endpointIndex];
|
|
1362
|
+
this.endpointIndex = (this.endpointIndex + 1) % this.config.endpoints.length;
|
|
1363
|
+
const payload = { model: endpointConfig.model, messages: [{ role: "system", content: systemPrompt }, ...messages] };
|
|
1364
|
+
const fullUrl = `${endpointConfig.url.replace(/\/$/, "")}/chat/completions`;
|
|
1365
|
+
const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${endpointConfig.key}` };
|
|
1366
|
+
const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
|
|
1367
|
+
const content = response?.choices?.[0]?.message?.content;
|
|
1368
|
+
if (!content?.trim()) throw new Error();
|
|
1369
|
+
try {
|
|
1370
|
+
const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
|
|
1371
|
+
const jsonString = jsonBlockMatch ? jsonBlockMatch[1] : content;
|
|
1372
|
+
this.failureCount = 0;
|
|
1373
|
+
return JSON.parse(jsonString);
|
|
1374
|
+
} catch (e) {
|
|
1375
|
+
this.logger.error("原始响应:", JSON.stringify(response, null, 2));
|
|
1376
|
+
throw new Error();
|
|
1377
|
+
}
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
this.failureCount++;
|
|
1380
|
+
this.logger.warn(`请求失败: ${error.message}`);
|
|
1381
|
+
if (this.failureCount >= 3) this.retryTime = Date.now() + 6e4;
|
|
1382
|
+
throw error;
|
|
1388
1383
|
}
|
|
1389
|
-
this.logger.error("原始响应:", JSON.stringify(response, null, 2));
|
|
1390
|
-
throw new Error();
|
|
1391
1384
|
}
|
|
1392
1385
|
};
|
|
1393
1386
|
|
|
@@ -1406,6 +1399,74 @@ var usage = `
|
|
|
1406
1399
|
<p>🐛 遇到问题?请通过 <strong>Issues</strong> 提交反馈,或加入 QQ 群 <a href="https://qm.qq.com/q/PdLMx9Jowq" style="color:#e0574a;text-decoration:none;"><strong>855571375</strong></a> 进行交流</p>
|
|
1407
1400
|
</div>
|
|
1408
1401
|
`;
|
|
1402
|
+
var DEFAULT_PROMPT = `1."rating" (整数): 对内容进行公正客观的评分,以下为评分标准:
|
|
1403
|
+
从以下维度分别评分,每项0-10分,总分为各项之和,最高100分。
|
|
1404
|
+
- 原创性: 评估内容的创意和独特性。
|
|
1405
|
+
- 10分: 完全原创的梗或高质量的二次创作,展现出独特的巧思。
|
|
1406
|
+
- 7-9分: 对现有梗或模板进行了巧妙的改造或融合,赋予了新的趣味。
|
|
1407
|
+
- 4-6分: 直接使用了较为流行的模板或创意,但结合了新的元素或语境。
|
|
1408
|
+
- 1-3分: 简单套用常见模板,缺乏个人想法,或是陈旧内容的再次利用。
|
|
1409
|
+
- 0分: 完全照搬或低质量的复制。
|
|
1410
|
+
- 内容价值: 评估内容所蕴含的幽默、情感或信息价值。
|
|
1411
|
+
- 10分: 能引发强烈共鸣或深度思考,具有极高的娱乐性或启发性。
|
|
1412
|
+
- 7-9分: 幽默感强,能让人会心一笑,或是在特定圈层中具有高度相关性。
|
|
1413
|
+
- 4-6分: 内容有趣,有一定记忆点,但可能受众较窄或深度不足。
|
|
1414
|
+
- 1-3分: 内容平淡,笑点模糊或表达主观,难以引起共鸣。
|
|
1415
|
+
- 0分: 内容空洞、无意义或令人不适。
|
|
1416
|
+
- 视觉呈现: 评估图像的质量和元素的协调性。
|
|
1417
|
+
- 10分: 构图、P图技术或截图时机堪称完美,视觉元素清晰且极具表现力。
|
|
1418
|
+
- 7-9分: 图像清晰,元素搭配得当,能有效服务于主题表达。
|
|
1419
|
+
- 4-6分: 图像基本清晰,但可能存在P图痕迹明显、元素杂乱或压缩痕迹等问题。
|
|
1420
|
+
- 1-3分: 图像模糊、分辨率低,或视觉元素(如文字、贴图)严重影响观感。
|
|
1421
|
+
- 0分: 图像完全无法辨认或引起生理不适。
|
|
1422
|
+
- 文本功底: 评估内容中的文字表达能力。
|
|
1423
|
+
- 10分: 文字精炼、幽默且一语中的,与图片配合天衣无缝。
|
|
1424
|
+
- 7-9分: 文字通顺,能准确表达核心笑点或信息。
|
|
1425
|
+
- 4-6分: 文字基本通顺,但可能存在错别字或表达略显啰嗦。
|
|
1426
|
+
- 1-3分: 文字表达不清,存在语病,或与图片关联性不强。
|
|
1427
|
+
- 5分(基准分): 内容中不包含任何文本元素时,此项计为5分。
|
|
1428
|
+
- 传播潜力: 评估内容被二次创作、分享和讨论的可能性。
|
|
1429
|
+
- 10分: “梗”感十足,极易引发模仿、分享和病毒式传播。
|
|
1430
|
+
- 7-9分: 具有成为热点的潜质,易于在社交圈内传播和讨论。
|
|
1431
|
+
- 4-6分: 内容有趣,可能会被小范围分享,但缺乏破圈传播的潜力。
|
|
1432
|
+
- 1-3分: 内容过于小众或个人化,难以被大众理解和传播。
|
|
1433
|
+
- 0分: 内容无法引发任何分享或讨论的意愿。
|
|
1434
|
+
- 娱乐效果: 评估内容的趣味性和吸引力。
|
|
1435
|
+
- 10分: 极度搞笑或有趣,能立刻吸引用户注意力并带来强烈的愉悦感。
|
|
1436
|
+
- 7-9分: 具有明显的笑点或趣味性,能有效调动观看者的情绪。
|
|
1437
|
+
- 4-6分: 内容有一定趣味,但可能需要特定背景知识才能理解笑点。
|
|
1438
|
+
- 1-3分: 趣味性较弱,难以引人发笑或产生兴趣。
|
|
1439
|
+
- 0分: 内容枯燥乏味或令人反感。
|
|
1440
|
+
- 逻辑清晰度: 评估内容的叙事或表达是否连贯易懂。
|
|
1441
|
+
- 10分: 无论是笑话、故事还是玩梗,逻辑都非常清晰,核心意图一目了然。
|
|
1442
|
+
- 7-9分: 内容主旨明确,大部分人都能轻松理解其意图。
|
|
1443
|
+
- 4-6分: 整体可以理解,但可能在某些细节上存在逻辑跳跃或模糊之处。
|
|
1444
|
+
- 1-3分: 逻辑混乱,表达不知所云,需要费力猜测其含义。
|
|
1445
|
+
- 0分: 完全没有逻辑可言。
|
|
1446
|
+
- 制作完成度: 评估内容的完整度和精良程度。
|
|
1447
|
+
- 10分: 无论是P图、对话截图还是漫画,细节处理到位,内容完整精致。
|
|
1448
|
+
- 7-9分: 内容完整,制作较为用心,没有明显的半成品痕迹。
|
|
1449
|
+
- 4-6分: 内容主体完整,但在细节上(如裁剪、打码)存在瑕疵。
|
|
1450
|
+
- 1-3分: 内容残缺不全,或制作粗糙,有明显的未完成感。
|
|
1451
|
+
- 0分: 内容严重残缺或制作质量极差。
|
|
1452
|
+
- 内容导向: 评估内容是否积极健康。
|
|
1453
|
+
- 10分: 内容积极向上,或为中性、善意的幽默,能带来正面情绪。
|
|
1454
|
+
- 5-9分: 内容中性,不包含明显的价值观偏向。
|
|
1455
|
+
- 1-4分: 可能包含一些有争议、冒犯性或易引战的元素。
|
|
1456
|
+
- 0分: 包含强烈的攻击性、歧视性或宣扬不良价值观的内容。
|
|
1457
|
+
- 内容合规性: 评估内容是否符合社区规范。
|
|
1458
|
+
- 10分: 内容完全合规。
|
|
1459
|
+
- 0分: 包含广告/引流/二维码、令人不适的图像、人身攻击或违反法律法规的内容,此项直接为0分。
|
|
1460
|
+
2."type" (字符串): 对内容进行准确且规范的分类,以下为分类规范:
|
|
1461
|
+
- Game: 与电子游戏直接相关或源自于电子游戏的内容。
|
|
1462
|
+
- ACG: 与动漫、漫画及广义二次元文化紧密相关的内容。
|
|
1463
|
+
- Internet: 源于互联网的流行文化、迷因或社群现象。
|
|
1464
|
+
- Reality: 取材于现实世界的日常经验和场景的内容。
|
|
1465
|
+
- Creative: 具有原创性、艺术性或巧妙构思的内容。
|
|
1466
|
+
- Other: 不适合归入以上任何一类的无关或小众内容。
|
|
1467
|
+
3."keywords" (字符串数组): 从内容中直接提取具体且全面的关键词,以下为提取准则:
|
|
1468
|
+
- 必须源自可直接识别的文字、对象、场景等元素,仅在无文字元素时才可使用适当的词汇进行描述。
|
|
1469
|
+
- 关键词必须规范且简短,通过多个不同维度关键词准确定义内容,禁止用模糊、笼统的概括性词汇。`;
|
|
1409
1470
|
var logger = new import_koishi3.Logger("best-cave");
|
|
1410
1471
|
var Config = import_koishi3.Schema.intersect([
|
|
1411
1472
|
import_koishi3.Schema.object({
|
|
@@ -1423,11 +1484,14 @@ var Config = import_koishi3.Schema.intersect([
|
|
|
1423
1484
|
}).description("复核配置"),
|
|
1424
1485
|
import_koishi3.Schema.object({
|
|
1425
1486
|
enableAI: import_koishi3.Schema.boolean().default(false).description("启用 AI"),
|
|
1487
|
+
enableApprove: import_koishi3.Schema.boolean().default(false).description("启用自动审核"),
|
|
1488
|
+
approveThreshold: import_koishi3.Schema.number().min(0).max(100).step(1).default(80).description("评分阈值"),
|
|
1426
1489
|
endpoints: import_koishi3.Schema.array(import_koishi3.Schema.object({
|
|
1427
1490
|
url: import_koishi3.Schema.string().description("端点 (Endpoint)").role("link").required(),
|
|
1428
|
-
key: import_koishi3.Schema.string().description("密钥 (API Key)").role("secret")
|
|
1491
|
+
key: import_koishi3.Schema.string().description("密钥 (API Key)").role("secret"),
|
|
1429
1492
|
model: import_koishi3.Schema.string().description("模型 (Model)").required()
|
|
1430
|
-
})).description("端点列表").role("table")
|
|
1493
|
+
})).description("端点列表").role("table"),
|
|
1494
|
+
systemPrompt: import_koishi3.Schema.string().role("textarea").default(DEFAULT_PROMPT).description("系统提示词")
|
|
1431
1495
|
}).description("模型配置"),
|
|
1432
1496
|
import_koishi3.Schema.object({
|
|
1433
1497
|
localPath: import_koishi3.Schema.string().description("文件映射路径"),
|
|
@@ -1553,15 +1617,23 @@ function apply(ctx, config) {
|
|
|
1553
1617
|
}
|
|
1554
1618
|
}
|
|
1555
1619
|
if (hasMedia) await Promise.all(downloadedMedia.map((item) => fileManager.saveFile(item.fileName, item.buffer)));
|
|
1620
|
+
let analysisResult;
|
|
1556
1621
|
if (aiManager) {
|
|
1557
1622
|
const analyses = await aiManager.analyze([newCave], downloadedMedia);
|
|
1558
|
-
if (analyses.length > 0)
|
|
1623
|
+
if (analyses.length > 0) {
|
|
1624
|
+
analysisResult = analyses[0];
|
|
1625
|
+
await ctx.database.upsert("cave_meta", analyses);
|
|
1626
|
+
}
|
|
1559
1627
|
}
|
|
1560
1628
|
if (hashManager) {
|
|
1561
1629
|
const allHashesToInsert = [...textHashesToStore, ...imageHashesToStore].map((h4) => ({ ...h4, cave: newCave.id }));
|
|
1562
1630
|
if (allHashesToInsert.length > 0) await ctx.database.upsert("cave_hash", allHashesToInsert);
|
|
1563
1631
|
}
|
|
1564
|
-
if (finalStatus === "pending" && reviewManager)
|
|
1632
|
+
if (finalStatus === "pending" && reviewManager) {
|
|
1633
|
+
if (analysisResult && config.enableApprove && analysisResult.rating >= config.approveThreshold) {
|
|
1634
|
+
await ctx.database.upsert("cave", [{ id: newCave.id, status: "active" }]);
|
|
1635
|
+
} else reviewManager.sendForPend(newCave);
|
|
1636
|
+
}
|
|
1565
1637
|
} catch (error) {
|
|
1566
1638
|
logger.error(`回声洞(${newId})处理失败:`, error);
|
|
1567
1639
|
await ctx.database.upsert("cave", [{ id: newId, status: "delete" }]);
|