koishi-plugin-best-cave 2.7.27 → 2.7.29
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 +0 -1
- package/lib/index.js +58 -59
- package/package.json +3 -3
package/lib/AIManager.d.ts
CHANGED
|
@@ -69,7 +69,6 @@ export declare class AIManager {
|
|
|
69
69
|
* @param {CaveObject} caveA - 第一个回声洞对象。
|
|
70
70
|
* @param {CaveObject} caveB - 第二个回声洞对象。
|
|
71
71
|
* @returns {Promise<boolean>} 如果内容被 AI 判断为重复,则返回 true,否则返回 false。
|
|
72
|
-
* @throws {Error} 当 AI 请求失败时抛出。
|
|
73
72
|
* @private
|
|
74
73
|
*/
|
|
75
74
|
private isContentDuplicateAI;
|
package/lib/index.js
CHANGED
|
@@ -1155,7 +1155,6 @@ var AIManager = class {
|
|
|
1155
1155
|
await session.send(`开始分析 ${cavesToAnalyze.length} 个回声洞...`);
|
|
1156
1156
|
let successCount = 0;
|
|
1157
1157
|
let failedCount = 0;
|
|
1158
|
-
let consecutiveFailures = 0;
|
|
1159
1158
|
for (let i = 0; i < cavesToAnalyze.length; i += 25) {
|
|
1160
1159
|
const batch = cavesToAnalyze.slice(i, i + 25);
|
|
1161
1160
|
this.logger.info(`[${i + 1}/${cavesToAnalyze.length}] 正在分析 ${batch.length} 个回声洞...`);
|
|
@@ -1167,10 +1166,8 @@ var AIManager = class {
|
|
|
1167
1166
|
const cave2 = batch[j];
|
|
1168
1167
|
if (result.status === "fulfilled" && result.value.length > 0) {
|
|
1169
1168
|
successfulAnalyses.push(result.value[0]);
|
|
1170
|
-
consecutiveFailures = 0;
|
|
1171
1169
|
} else {
|
|
1172
1170
|
failedCount++;
|
|
1173
|
-
consecutiveFailures++;
|
|
1174
1171
|
if (result.status === "rejected") this.logger.error(`分析回声洞(${cave2.id})失败:`, result.reason);
|
|
1175
1172
|
}
|
|
1176
1173
|
}
|
|
@@ -1178,7 +1175,6 @@ var AIManager = class {
|
|
|
1178
1175
|
await this.ctx.database.upsert("cave_meta", successfulAnalyses);
|
|
1179
1176
|
successCount += successfulAnalyses.length;
|
|
1180
1177
|
}
|
|
1181
|
-
if (consecutiveFailures >= 3) break;
|
|
1182
1178
|
}
|
|
1183
1179
|
return `已分析 ${successCount} 个回声洞(失败 ${failedCount} 个)`;
|
|
1184
1180
|
} catch (error) {
|
|
@@ -1194,8 +1190,12 @@ var AIManager = class {
|
|
|
1194
1190
|
if (allMeta.length < 2) return "无可比较数据";
|
|
1195
1191
|
const candidatePairs = generateFromLSH(allMeta, (meta) => ({ id: meta.cave, keys: meta.keywords }));
|
|
1196
1192
|
if (candidatePairs.size === 0) return "未发现相似内容";
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1193
|
+
const idsToCompare = /* @__PURE__ */ new Set();
|
|
1194
|
+
candidatePairs.forEach((pairKey) => {
|
|
1195
|
+
pairKey.split("-").map(Number).forEach((id) => idsToCompare.add(id));
|
|
1196
|
+
});
|
|
1197
|
+
const caveData = await this.ctx.database.get("cave", { id: { $in: Array.from(idsToCompare) }, status: "active" });
|
|
1198
|
+
const allCaves = new Map(caveData.map((c) => [c.id, c]));
|
|
1199
1199
|
const comparisonPromises = Array.from(candidatePairs).map(async (pairKey) => {
|
|
1200
1200
|
const [id1, id2] = pairKey.split("-").map(Number);
|
|
1201
1201
|
const cave1 = allCaves.get(id1);
|
|
@@ -1204,7 +1204,7 @@ var AIManager = class {
|
|
|
1204
1204
|
return null;
|
|
1205
1205
|
});
|
|
1206
1206
|
const results = await Promise.all(comparisonPromises);
|
|
1207
|
-
duplicatePairs
|
|
1207
|
+
const duplicatePairs = results.filter(Boolean);
|
|
1208
1208
|
if (duplicatePairs.length === 0) return "未发现高重复性的内容";
|
|
1209
1209
|
const dsu = new DSU();
|
|
1210
1210
|
const allIds = /* @__PURE__ */ new Set();
|
|
@@ -1270,38 +1270,45 @@ var AIManager = class {
|
|
|
1270
1270
|
async analyze(caves, mediaBuffers) {
|
|
1271
1271
|
const mediaMap = mediaBuffers ? new Map(mediaBuffers.map((m) => [m.fileName, m.buffer])) : void 0;
|
|
1272
1272
|
const analysisPromises = caves.map(async (cave) => {
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1273
|
+
try {
|
|
1274
|
+
const combinedText = cave.elements.filter((el) => el.type === "text" && el.content).map((el) => el.content).join("\n");
|
|
1275
|
+
const imageElements = await Promise.all(
|
|
1276
|
+
cave.elements.filter((el) => el.type === "image" && el.file).map(async (el) => {
|
|
1277
|
+
try {
|
|
1278
|
+
const buffer = mediaMap?.get(el.file) ?? await this.fileManager.readFile(el.file);
|
|
1279
|
+
const mimeType = path3.extname(el.file).toLowerCase() === ".png" ? "image/png" : "image/jpeg";
|
|
1280
|
+
return {
|
|
1281
|
+
type: "image_url",
|
|
1282
|
+
image_url: { url: `data:${mimeType};base64,${buffer.toString("base64")}` }
|
|
1283
|
+
};
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
this.logger.warn(`读取文件(${el.file})失败:`, error);
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
})
|
|
1289
|
+
);
|
|
1290
|
+
const images = imageElements.filter(Boolean);
|
|
1291
|
+
if (!combinedText.trim() && images.length === 0) return null;
|
|
1292
|
+
const contentForAI = [];
|
|
1293
|
+
if (combinedText.trim()) contentForAI.push({ type: "text", text: `请分析以下内容:
|
|
1293
1294
|
|
|
1294
1295
|
${combinedText}` });
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1296
|
+
contentForAI.push(...images);
|
|
1297
|
+
const userMessage = { role: "user", content: contentForAI };
|
|
1298
|
+
const response = await this.requestAI([userMessage], this.ANALYSIS_SYSTEM_PROMPT);
|
|
1299
|
+
if (response) {
|
|
1300
|
+
return {
|
|
1301
|
+
cave: cave.id,
|
|
1302
|
+
keywords: response.keywords || [],
|
|
1303
|
+
description: response.description || "",
|
|
1304
|
+
rating: Math.max(0, Math.min(100, response.rating || 0))
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
return null;
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
this.logger.error(`分析回声洞(${cave.id})失败:`, error);
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1305
1312
|
});
|
|
1306
1313
|
const results = await Promise.all(analysisPromises);
|
|
1307
1314
|
return results.filter((result) => !!result);
|
|
@@ -1311,7 +1318,6 @@ ${combinedText}` });
|
|
|
1311
1318
|
* @param {CaveObject} caveA - 第一个回声洞对象。
|
|
1312
1319
|
* @param {CaveObject} caveB - 第二个回声洞对象。
|
|
1313
1320
|
* @returns {Promise<boolean>} 如果内容被 AI 判断为重复,则返回 true,否则返回 false。
|
|
1314
|
-
* @throws {Error} 当 AI 请求失败时抛出。
|
|
1315
1321
|
* @private
|
|
1316
1322
|
*/
|
|
1317
1323
|
async isContentDuplicateAI(caveA, caveB) {
|
|
@@ -1364,28 +1370,21 @@ ${combinedText}` });
|
|
|
1364
1370
|
"Content-Type": "application/json",
|
|
1365
1371
|
"Authorization": `Bearer ${this.config.aiApiKey}`
|
|
1366
1372
|
};
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
return JSON.parse(candidate);
|
|
1381
|
-
} catch (parseError) {
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
this.logger.error("解析失败", "原始响应:", JSON.stringify(response, null, 2));
|
|
1385
|
-
throw new Error();
|
|
1386
|
-
} catch (e) {
|
|
1387
|
-
throw e;
|
|
1373
|
+
const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
|
|
1374
|
+
const content = response?.choices?.[0]?.message?.content;
|
|
1375
|
+
if (!content?.trim()) throw new Error();
|
|
1376
|
+
const candidates = [];
|
|
1377
|
+
const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
|
|
1378
|
+
if (jsonBlockMatch && jsonBlockMatch[1]) candidates.push(jsonBlockMatch[1]);
|
|
1379
|
+
candidates.push(content);
|
|
1380
|
+
const firstBrace = content.indexOf("{");
|
|
1381
|
+
const lastBrace = content.lastIndexOf("}");
|
|
1382
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) candidates.push(content.substring(firstBrace, lastBrace + 1));
|
|
1383
|
+
for (const candidate of [...new Set(candidates)]) try {
|
|
1384
|
+
return JSON.parse(candidate);
|
|
1385
|
+
} catch (parseError) {
|
|
1388
1386
|
}
|
|
1387
|
+
this.logger.error("原始响应:", JSON.stringify(response, null, 2));
|
|
1389
1388
|
}
|
|
1390
1389
|
};
|
|
1391
1390
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-best-cave",
|
|
3
3
|
"description": "功能强大、高度可定制的回声洞插件。支持丰富的媒体类型、内容查重、AI分析、人工审核、用户昵称、数据迁移以及本地/S3 双重文件存储后端。",
|
|
4
|
-
"version": "2.7.
|
|
4
|
+
"version": "2.7.29",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Yis_Rime <yis_rime@outlook.com>"
|
|
7
7
|
],
|
|
8
|
-
"homepage": "https://github.com/
|
|
8
|
+
"homepage": "https://github.com//Koishi-Plugin/best-cave",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
11
|
+
"url": "git+https://github.com//Koishi-Plugin/best-cave.git"
|
|
12
12
|
},
|
|
13
13
|
"main": "lib/index.js",
|
|
14
14
|
"typings": "lib/index.d.ts",
|