koishi-plugin-best-cave 2.7.29 → 2.7.30

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.
@@ -84,6 +84,7 @@ export declare class AIManager {
84
84
  /**
85
85
  * @description 封装了向 OpenAI 兼容的 API 发送请求的底层逻辑。
86
86
  * @template T - 期望从 AI 响应的 JSON 中解析出的数据类型。
87
+ * @param {string} modelIdentifier - 模型的完整标识符,格式为 `端点名称/模型名称`。
87
88
  * @param {any[]} messages - 发送给 AI 的消息数组,通常包含用户消息。
88
89
  * @param {string} systemPrompt - 指导 AI 行为的系统级指令。
89
90
  * @returns {Promise<T>} 一个 Promise,解析为从 AI 响应中提取并解析的 JSON 对象。
package/lib/index.d.ts CHANGED
@@ -58,9 +58,13 @@ export interface Config {
58
58
  bucket?: string;
59
59
  publicUrl?: string;
60
60
  enableAI: boolean;
61
- aiEndpoint?: string;
62
- aiApiKey?: string;
63
- aiModel?: string;
61
+ endpoints?: {
62
+ name: string;
63
+ url: string;
64
+ key: string;
65
+ }[];
66
+ analysisModel?: string;
67
+ duplicateCheckModel?: string;
64
68
  }
65
69
  export declare const Config: Schema<Config>;
66
70
  export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -1295,7 +1295,7 @@ var AIManager = class {
1295
1295
  ${combinedText}` });
1296
1296
  contentForAI.push(...images);
1297
1297
  const userMessage = { role: "user", content: contentForAI };
1298
- const response = await this.requestAI([userMessage], this.ANALYSIS_SYSTEM_PROMPT);
1298
+ const response = await this.requestAI(this.config.analysisModel, [userMessage], this.ANALYSIS_SYSTEM_PROMPT);
1299
1299
  if (response) {
1300
1300
  return {
1301
1301
  cave: cave.id,
@@ -1328,7 +1328,7 @@ ${combinedText}` });
1328
1328
  content_b: { id: caveB.id, text: formatContent(caveB.elements) }
1329
1329
  };
1330
1330
  const userMessage = { role: "user", content: JSON.stringify(userMessageContent) };
1331
- const response = await this.requestAI([userMessage], this.DUPLICATE_CHECK_SYSTEM_PROMPT);
1331
+ const response = await this.requestAI(this.config.duplicateCheckModel, [userMessage], this.DUPLICATE_CHECK_SYSTEM_PROMPT);
1332
1332
  return response?.duplicate || false;
1333
1333
  } catch (error) {
1334
1334
  this.logger.error(`比较回声洞(${caveA.id})与(${caveB.id})失败:`, error);
@@ -1354,25 +1354,37 @@ ${combinedText}` });
1354
1354
  /**
1355
1355
  * @description 封装了向 OpenAI 兼容的 API 发送请求的底层逻辑。
1356
1356
  * @template T - 期望从 AI 响应的 JSON 中解析出的数据类型。
1357
+ * @param {string} modelIdentifier - 模型的完整标识符,格式为 `端点名称/模型名称`。
1357
1358
  * @param {any[]} messages - 发送给 AI 的消息数组,通常包含用户消息。
1358
1359
  * @param {string} systemPrompt - 指导 AI 行为的系统级指令。
1359
1360
  * @returns {Promise<T>} 一个 Promise,解析为从 AI 响应中提取并解析的 JSON 对象。
1360
1361
  * @throws {Error} 当网络请求失败、AI 未返回有效内容或 JSON 解析失败时抛出。
1361
1362
  * @private
1362
1363
  */
1363
- async requestAI(messages, systemPrompt) {
1364
+ async requestAI(modelIdentifier, messages, systemPrompt) {
1365
+ if (!modelIdentifier || !modelIdentifier.includes("/")) {
1366
+ throw new Error(`无效的模型标识符: ${modelIdentifier}。格式应为 '端点名称/模型名称'`);
1367
+ }
1368
+ const [name2, modelName] = modelIdentifier.split("/");
1369
+ if (!name2 || !modelName) {
1370
+ throw new Error(`无效的模型标识符: ${modelIdentifier}。格式应为 '端点名称/模型名称'`);
1371
+ }
1372
+ const endpointConfig = this.config.endpoints?.find((e) => e.name === name2);
1373
+ if (!endpointConfig) {
1374
+ throw new Error(`未在配置中找到名为 '${name2}' 的端点`);
1375
+ }
1364
1376
  const payload = {
1365
- model: this.config.aiModel,
1377
+ model: modelName,
1366
1378
  messages: [{ role: "system", content: systemPrompt }, ...messages]
1367
1379
  };
1368
- const fullUrl = `${this.config.aiEndpoint.replace(/\/$/, "")}/chat/completions`;
1380
+ const fullUrl = `${endpointConfig.url.replace(/\/$/, "")}/chat/completions`;
1369
1381
  const headers = {
1370
1382
  "Content-Type": "application/json",
1371
- "Authorization": `Bearer ${this.config.aiApiKey}`
1383
+ "Authorization": `Bearer ${endpointConfig.key}`
1372
1384
  };
1373
1385
  const response = await this.http.post(fullUrl, payload, { headers, timeout: 6e5 });
1374
1386
  const content = response?.choices?.[0]?.message?.content;
1375
- if (!content?.trim()) throw new Error();
1387
+ if (!content?.trim()) throw new Error("AI 响应内容为空");
1376
1388
  const candidates = [];
1377
1389
  const jsonBlockMatch = content.match(/```json\s*([\s\S]*?)\s*```/i);
1378
1390
  if (jsonBlockMatch && jsonBlockMatch[1]) candidates.push(jsonBlockMatch[1]);
@@ -1380,11 +1392,14 @@ ${combinedText}` });
1380
1392
  const firstBrace = content.indexOf("{");
1381
1393
  const lastBrace = content.lastIndexOf("}");
1382
1394
  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) {
1395
+ for (const candidate of [...new Set(candidates)]) {
1396
+ try {
1397
+ return JSON.parse(candidate);
1398
+ } catch (parseError) {
1399
+ }
1386
1400
  }
1387
1401
  this.logger.error("原始响应:", JSON.stringify(response, null, 2));
1402
+ throw new Error("无法从 AI 响应中解析出有效的 JSON");
1388
1403
  }
1389
1404
  };
1390
1405
 
@@ -1420,9 +1435,13 @@ var Config = import_koishi3.Schema.intersect([
1420
1435
  }).description("复核配置"),
1421
1436
  import_koishi3.Schema.object({
1422
1437
  enableAI: import_koishi3.Schema.boolean().default(false).description("启用 AI"),
1423
- aiEndpoint: import_koishi3.Schema.string().description("端点 (Endpoint)").role("link").default("https://api.siliconflow.cn/v1"),
1424
- aiApiKey: import_koishi3.Schema.string().description("密钥 (Key)").role("secret"),
1425
- aiModel: import_koishi3.Schema.string().description("模型 (Model)").default("THUDM/GLM-4.1V-9B-Thinking")
1438
+ endpoints: import_koishi3.Schema.array(import_koishi3.Schema.object({
1439
+ name: import_koishi3.Schema.string().description("名称").required(),
1440
+ url: import_koishi3.Schema.string().description("端点 (Endpoint)").role("link").required(),
1441
+ key: import_koishi3.Schema.string().description("密钥 (API Key)").role("secret").required()
1442
+ })).description("端点列表"),
1443
+ analysisModel: import_koishi3.Schema.string().description("分析模型"),
1444
+ duplicateCheckModel: import_koishi3.Schema.string().description("查重模型")
1426
1445
  }).description("模型配置"),
1427
1446
  import_koishi3.Schema.object({
1428
1447
  localPath: import_koishi3.Schema.string().description("文件映射路径"),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "功能强大、高度可定制的回声洞插件。支持丰富的媒体类型、内容查重、AI分析、人工审核、用户昵称、数据迁移以及本地/S3 双重文件存储后端。",
4
- "version": "2.7.29",
4
+ "version": "2.7.30",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],