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.
@@ -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 allCaves = new Map((await this.ctx.database.get("cave", { status: "active" })).map((c) => [c.id, c]));
1198
- const duplicatePairs = [];
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.push(...results.filter(Boolean));
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
- const combinedText = cave.elements.filter((el) => el.type === "text" && el.content).map((el) => el.content).join("\n");
1274
- const imageElements = await Promise.all(
1275
- cave.elements.filter((el) => el.type === "image" && el.file).map(async (el) => {
1276
- try {
1277
- const buffer = mediaMap?.get(el.file) ?? await this.fileManager.readFile(el.file);
1278
- const mimeType = path3.extname(el.file).toLowerCase() === ".png" ? "image/png" : "image/jpeg";
1279
- return {
1280
- type: "image_url",
1281
- image_url: { url: `data:${mimeType};base64,${buffer.toString("base64")}` }
1282
- };
1283
- } catch (error) {
1284
- this.logger.warn(`读取文件(${el.file})失败:`, error);
1285
- return null;
1286
- }
1287
- })
1288
- );
1289
- const images = imageElements.filter(Boolean);
1290
- if (!combinedText.trim() && images.length === 0) return null;
1291
- const contentForAI = [];
1292
- if (combinedText.trim()) contentForAI.push({ type: "text", text: `请分析以下内容:
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
- contentForAI.push(...images);
1296
- const userMessage = { role: "user", content: contentForAI };
1297
- const response = await this.requestAI([userMessage], this.ANALYSIS_SYSTEM_PROMPT);
1298
- if (response) return {
1299
- cave: cave.id,
1300
- keywords: response.keywords || [],
1301
- description: response.description || "",
1302
- rating: Math.max(0, Math.min(100, response.rating || 0))
1303
- };
1304
- return null;
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
- try {
1368
- const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
1369
- const content = response?.choices?.[0]?.message?.content;
1370
- if (!content?.trim()) throw new Error();
1371
- const candidates = [];
1372
- const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
1373
- if (jsonBlockMatch && jsonBlockMatch[1]) candidates.push(jsonBlockMatch[1]);
1374
- candidates.push(content);
1375
- const firstBrace = content.indexOf("{");
1376
- const lastBrace = content.lastIndexOf("}");
1377
- if (firstBrace !== -1 && lastBrace > firstBrace) candidates.push(content.substring(firstBrace, lastBrace + 1));
1378
- for (const candidate of [...new Set(candidates)]) {
1379
- try {
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.27",
4
+ "version": "2.7.29",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],
8
- "homepage": "https://github.com/YisRime/koishi-plugin-best-cave",
8
+ "homepage": "https://github.com//Koishi-Plugin/best-cave",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/YisRime/koishi-plugin-best-cave.git"
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",