koishi-plugin-aka-ai-generator 0.2.5 → 0.2.7

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.
Files changed (2) hide show
  1. package/lib/index.js +184 -89
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -432,100 +432,135 @@ var GptGodProvider = class {
432
432
  }
433
433
  ]
434
434
  };
435
- try {
436
- const response = await ctx.http.post(
437
- GPTGOD_DEFAULT_API_URL,
438
- requestData,
439
- {
440
- headers: {
441
- "Content-Type": "application/json",
442
- "Authorization": `Bearer ${this.config.apiKey}`
443
- },
444
- timeout: this.config.apiTimeout * 1e3
435
+ const maxRetries = 3;
436
+ let lastError = null;
437
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
438
+ try {
439
+ const requestBodySize = JSON.stringify(requestData).length;
440
+ if (this.config.logLevel === "debug") {
441
+ logger.debug(`GPTGod API 请求 (尝试 ${attempt}/${maxRetries})`, {
442
+ requestBodySize: `${(requestBodySize / 1024).toFixed(2)} KB`,
443
+ imageCount: urls.length
444
+ });
445
445
  }
446
- );
447
- logger.success("GPTGod 图像编辑 API 调用成功");
448
- if (response?.choices?.length > 0) {
449
- const firstChoice = response.choices[0];
450
- const messageContent = firstChoice.message?.content;
451
- let errorMessage = "";
452
- if (typeof messageContent === "string") {
453
- errorMessage = messageContent;
454
- } else if (Array.isArray(messageContent)) {
455
- const textParts = messageContent.filter((part) => part?.type === "text" && part?.text).map((part) => part.text).join(" ");
456
- errorMessage = textParts;
457
- } else if (messageContent?.text) {
458
- errorMessage = messageContent.text;
446
+ const response = await ctx.http.post(
447
+ GPTGOD_DEFAULT_API_URL,
448
+ requestData,
449
+ {
450
+ headers: {
451
+ "Content-Type": "application/json",
452
+ "Authorization": `Bearer ${this.config.apiKey}`
453
+ },
454
+ timeout: this.config.apiTimeout * 1e3
455
+ }
456
+ );
457
+ logger.success("GPTGod 图像编辑 API 调用成功");
458
+ if (response?.choices?.length > 0) {
459
+ const firstChoice = response.choices[0];
460
+ const messageContent = firstChoice.message?.content;
461
+ let errorMessage = "";
462
+ if (typeof messageContent === "string") {
463
+ errorMessage = messageContent;
464
+ } else if (Array.isArray(messageContent)) {
465
+ const textParts = messageContent.filter((part) => part?.type === "text" && part?.text).map((part) => part.text).join(" ");
466
+ errorMessage = textParts;
467
+ } else if (messageContent?.text) {
468
+ errorMessage = messageContent.text;
469
+ }
470
+ if (errorMessage && (errorMessage.includes("PROHIBITED_CONTENT") || errorMessage.includes("blocked by Google Gemini") || errorMessage.includes("prohibited under official usage policies") || errorMessage.toLowerCase().includes("content is prohibited"))) {
471
+ logger.error("内容被 Google Gemini 政策拦截", {
472
+ errorMessage: errorMessage.substring(0, 200),
473
+ finishReason: firstChoice.finish_reason
474
+ });
475
+ throw new Error("内容被安全策略拦截");
476
+ }
477
+ if (errorMessage && (errorMessage.toLowerCase().includes("error") || errorMessage.toLowerCase().includes("failed") || errorMessage.toLowerCase().includes("blocked")) && !errorMessage.match(/https?:\/\//)) {
478
+ logger.error("API 返回错误消息", {
479
+ errorMessage: errorMessage.substring(0, 200),
480
+ finishReason: firstChoice.finish_reason
481
+ });
482
+ const shortError = errorMessage.length > 50 ? errorMessage.substring(0, 50) + "..." : errorMessage;
483
+ throw new Error(`处理失败:${shortError}`);
484
+ }
459
485
  }
460
- if (errorMessage && (errorMessage.includes("PROHIBITED_CONTENT") || errorMessage.includes("blocked by Google Gemini") || errorMessage.includes("prohibited under official usage policies") || errorMessage.toLowerCase().includes("content is prohibited"))) {
461
- logger.error("内容被 Google Gemini 政策拦截", {
462
- errorMessage: errorMessage.substring(0, 200),
463
- finishReason: firstChoice.finish_reason
486
+ if (this.config.logLevel === "debug") {
487
+ logger.debug("GPTGod API 响应结构", {
488
+ hasChoices: !!response?.choices,
489
+ choicesLength: response?.choices?.length,
490
+ hasImages: !!response?.images,
491
+ hasImage: !!response?.image,
492
+ responseKeys: Object.keys(response || {}),
493
+ firstChoiceContent: response?.choices?.[0]?.message?.content ? typeof response.choices[0].message.content === "string" ? response.choices[0].message.content.substring(0, 200) : JSON.stringify(response.choices[0].message.content).substring(0, 200) : "none"
464
494
  });
465
- throw new Error("内容被安全策略拦截");
466
495
  }
467
- if (errorMessage && (errorMessage.toLowerCase().includes("error") || errorMessage.toLowerCase().includes("failed") || errorMessage.toLowerCase().includes("blocked")) && !errorMessage.match(/https?:\/\//)) {
468
- logger.error("API 返回错误消息", {
469
- errorMessage: errorMessage.substring(0, 200),
470
- finishReason: firstChoice.finish_reason
496
+ const images = parseGptGodResponse(response, this.config.logLevel === "debug" ? logger : null);
497
+ if (images.length < numImages) {
498
+ const warnData = {
499
+ requested: numImages,
500
+ received: images.length
501
+ };
502
+ if (this.config.logLevel === "debug") {
503
+ warnData.responsePreview = JSON.stringify(response).substring(0, 500);
504
+ }
505
+ logger.warn("生成的图片数量不足", warnData);
506
+ if (images.length === 0 && response?.choices?.[0]?.message?.content) {
507
+ const content = response.choices[0].message.content;
508
+ const contentText = typeof content === "string" ? content : Array.isArray(content) ? content.map((p) => p?.text || "").join(" ") : "";
509
+ if (contentText && !contentText.match(/https?:\/\//)) {
510
+ const shortError = contentText.length > 50 ? contentText.substring(0, 50) + "..." : contentText;
511
+ throw new Error(`生成失败:${shortError}`);
512
+ }
513
+ }
514
+ }
515
+ return images;
516
+ } catch (error) {
517
+ lastError = error;
518
+ if (error?.message && (error.message.includes("内容被安全策略拦截") || error.message.includes("生成失败") || error.message.includes("处理失败"))) {
519
+ throw error;
520
+ }
521
+ const isRetryableError = error?.cause?.code === "UND_ERR_SOCKET" || // Socket 错误
522
+ error?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed") || error?.message?.includes("fetch failed") || error?.message?.includes("ECONNRESET") || error?.message?.includes("ETIMEDOUT") || error?.response?.status >= 500 && error?.response?.status < 600;
523
+ if (isRetryableError && attempt < maxRetries) {
524
+ const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 5e3);
525
+ logger.warn(`GPTGod API 调用失败,将在 ${delay}ms 后重试 (${attempt}/${maxRetries})`, {
526
+ error: error?.message || error?.cause?.message || "连接错误",
527
+ code: error?.code || error?.cause?.code
471
528
  });
472
- const shortError = errorMessage.length > 50 ? errorMessage.substring(0, 50) + "..." : errorMessage;
473
- throw new Error(`处理失败:${shortError}`);
529
+ await new Promise((resolve) => setTimeout(resolve, delay));
530
+ continue;
474
531
  }
475
- }
476
- if (this.config.logLevel === "debug") {
477
- logger.debug("GPTGod API 响应结构", {
478
- hasChoices: !!response?.choices,
479
- choicesLength: response?.choices?.length,
480
- hasImages: !!response?.images,
481
- hasImage: !!response?.image,
482
- responseKeys: Object.keys(response || {}),
483
- firstChoiceContent: response?.choices?.[0]?.message?.content ? typeof response.choices[0].message.content === "string" ? response.choices[0].message.content.substring(0, 200) : JSON.stringify(response.choices[0].message.content).substring(0, 200) : "none"
532
+ logger.error("GPTGod 图像编辑 API 调用失败", {
533
+ message: error?.message || "未知错误",
534
+ name: error?.name,
535
+ code: error?.code,
536
+ status: error?.response?.status,
537
+ statusText: error?.response?.statusText,
538
+ data: error?.response?.data,
539
+ stack: error?.stack,
540
+ cause: error?.cause,
541
+ attempt,
542
+ maxRetries,
543
+ // 如果是 axios 错误,通常会有 config 和 request 信息
544
+ url: error?.config?.url,
545
+ method: error?.config?.method,
546
+ headers: error?.config?.headers
484
547
  });
485
- }
486
- const images = parseGptGodResponse(response, this.config.logLevel === "debug" ? logger : null);
487
- if (images.length < numImages) {
488
- const warnData = {
489
- requested: numImages,
490
- received: images.length
491
- };
492
- if (this.config.logLevel === "debug") {
493
- warnData.responsePreview = JSON.stringify(response).substring(0, 500);
548
+ if (error?.cause?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed")) {
549
+ throw new Error("图像处理失败:服务器连接中断,可能是服务器负载过高或网络不稳定,请稍后重试");
494
550
  }
495
- logger.warn("生成的图片数量不足", warnData);
496
- if (images.length === 0 && response?.choices?.[0]?.message?.content) {
497
- const content = response.choices[0].message.content;
498
- const contentText = typeof content === "string" ? content : Array.isArray(content) ? content.map((p) => p?.text || "").join(" ") : "";
499
- if (contentText && !contentText.match(/https?:\/\//)) {
500
- const shortError = contentText.length > 50 ? contentText.substring(0, 50) + "..." : contentText;
501
- throw new Error(`生成失败:${shortError}`);
502
- }
551
+ if (error?.message?.includes("fetch") && error?.message?.includes(GPTGOD_DEFAULT_API_URL)) {
552
+ throw new Error("图像处理失败:无法连接 GPTGod API 服务器,请检查网络连接或稍后重试");
503
553
  }
554
+ if (error?.response?.status === 413) {
555
+ throw new Error("图像处理失败:请求体过大,请尝试使用较小的图片");
556
+ }
557
+ if (error?.response?.status === 429) {
558
+ throw new Error("图像处理失败:请求过于频繁,请稍后重试");
559
+ }
560
+ throw new Error("图像处理API调用失败");
504
561
  }
505
- return images;
506
- } catch (error) {
507
- if (error?.message && (error.message.includes("内容被安全策略拦截") || error.message.includes("生成失败") || error.message.includes("处理失败"))) {
508
- throw error;
509
- }
510
- logger.error("GPTGod 图像编辑 API 调用失败", {
511
- message: error?.message || "未知错误",
512
- name: error?.name,
513
- code: error?.code,
514
- status: error?.response?.status,
515
- statusText: error?.response?.statusText,
516
- data: error?.response?.data,
517
- stack: error?.stack,
518
- cause: error?.cause,
519
- // 如果是 axios 错误,通常会有 config 和 request 信息
520
- url: error?.config?.url,
521
- method: error?.config?.method,
522
- headers: error?.config?.headers
523
- });
524
- if (error?.message?.includes("fetch") && error?.message?.includes(GPTGOD_DEFAULT_API_URL)) {
525
- throw new Error("图像处理失败:无法连接 GPTGod API 服务器,请稍后重试");
526
- }
527
- throw new Error("图像处理API调用失败");
528
562
  }
563
+ throw lastError;
529
564
  }
530
565
  };
531
566
 
@@ -666,6 +701,35 @@ function apply(ctx, config) {
666
701
  return value?.replace(/^\-+/, "").trim().toLowerCase();
667
702
  }
668
703
  __name(normalizeSuffix, "normalizeSuffix");
704
+ function parseNumImagesFromPrompt(prompt) {
705
+ if (!prompt || typeof prompt !== "string") {
706
+ return { numImages: void 0, cleanedPrompt: prompt };
707
+ }
708
+ const patterns = [
709
+ /生成\s*([1-4])\s*张(?:图片)?/i,
710
+ /([1-4])\s*张(?:图片)?/,
711
+ /生成\s*([1-4])\s*个(?:图片)?/i,
712
+ /([1-4])\s*个(?:图片)?/,
713
+ /num[:\s]*([1-4])/i,
714
+ /数量[:\s]*([1-4])/i
715
+ ];
716
+ let numImages = void 0;
717
+ let cleanedPrompt = prompt;
718
+ for (const pattern of patterns) {
719
+ const match = prompt.match(pattern);
720
+ if (match) {
721
+ const num = parseInt(match[1], 10);
722
+ if (num >= 1 && num <= 4) {
723
+ numImages = num;
724
+ cleanedPrompt = prompt.replace(pattern, "").trim();
725
+ cleanedPrompt = cleanedPrompt.replace(/\s+/g, " ").replace(/[,,]\s*$/, "").trim();
726
+ break;
727
+ }
728
+ }
729
+ }
730
+ return { numImages, cleanedPrompt };
731
+ }
732
+ __name(parseNumImagesFromPrompt, "parseNumImagesFromPrompt");
669
733
  function buildModelMappingIndex(mappings) {
670
734
  const map = /* @__PURE__ */ new Map();
671
735
  if (!Array.isArray(mappings)) return map;
@@ -1149,16 +1213,33 @@ ${infoParts.join("\n")}`;
1149
1213
  return limitCheck.message;
1150
1214
  }
1151
1215
  const modifiers = parseStyleCommandModifiers(argv, img);
1152
- const promptSegments = [style.prompt];
1216
+ let userPromptParts = [];
1153
1217
  if (modifiers.customAdditions?.length) {
1154
- promptSegments.push(...modifiers.customAdditions);
1218
+ userPromptParts.push(...modifiers.customAdditions);
1155
1219
  }
1156
1220
  if (modifiers.customPromptSuffix) {
1157
- promptSegments.push(modifiers.customPromptSuffix);
1221
+ userPromptParts.push(modifiers.customPromptSuffix);
1222
+ }
1223
+ const userPromptText = userPromptParts.join(" - ");
1224
+ let promptNumImages = void 0;
1225
+ let cleanedUserPrompt = userPromptText;
1226
+ if (userPromptText) {
1227
+ const parsed = parseNumImagesFromPrompt(userPromptText);
1228
+ if (parsed.numImages) {
1229
+ promptNumImages = parsed.numImages;
1230
+ cleanedUserPrompt = parsed.cleanedPrompt;
1231
+ if (config.logLevel === "debug") {
1232
+ logger.debug("从 prompt 中解析到生成数量", { numImages: promptNumImages, cleanedPrompt: cleanedUserPrompt });
1233
+ }
1234
+ }
1235
+ }
1236
+ const promptSegments = [style.prompt];
1237
+ if (cleanedUserPrompt) {
1238
+ promptSegments.push(cleanedUserPrompt);
1158
1239
  }
1159
1240
  const mergedPrompt = promptSegments.filter(Boolean).join(" - ");
1160
1241
  const requestContext = {
1161
- numImages: options?.num
1242
+ numImages: options?.num || promptNumImages
1162
1243
  };
1163
1244
  if (modifiers.modelMapping?.provider) {
1164
1245
  requestContext.provider = modifiers.modelMapping.provider;
@@ -1240,8 +1321,15 @@ ${infoParts.join("\n")}`;
1240
1321
  if (!prompt) {
1241
1322
  return "未检测到prompt描述,请重新发送";
1242
1323
  }
1324
+ const { numImages: promptNumImages, cleanedPrompt } = parseNumImagesFromPrompt(prompt);
1325
+ if (promptNumImages) {
1326
+ prompt = cleanedPrompt;
1327
+ if (config.logLevel === "debug") {
1328
+ logger.debug("从 prompt 中解析到生成数量", { numImages: promptNumImages, cleanedPrompt });
1329
+ }
1330
+ }
1243
1331
  const imageUrl = collectedImages[0];
1244
- const imageCount = options?.num || config.defaultNumImages;
1332
+ const imageCount = options?.num || promptNumImages || config.defaultNumImages;
1245
1333
  if (imageCount < 1 || imageCount > 4) {
1246
1334
  return "生成数量必须在 1-4 之间";
1247
1335
  }
@@ -1339,7 +1427,14 @@ Prompt: ${prompt}`);
1339
1427
  if (!prompt) {
1340
1428
  return "未检测到prompt描述,请重新发送";
1341
1429
  }
1342
- const imageCount = options?.num || config.defaultNumImages;
1430
+ const { numImages: promptNumImages, cleanedPrompt } = parseNumImagesFromPrompt(prompt);
1431
+ if (promptNumImages) {
1432
+ prompt = cleanedPrompt;
1433
+ if (config.logLevel === "debug") {
1434
+ logger.debug("从 prompt 中解析到生成数量", { numImages: promptNumImages, cleanedPrompt });
1435
+ }
1436
+ }
1437
+ const imageCount = options?.num || promptNumImages || config.defaultNumImages;
1343
1438
  if (imageCount < 1 || imageCount > 4) {
1344
1439
  return "生成数量必须在 1-4 之间";
1345
1440
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-aka-ai-generator",
3
3
  "description": "自用AI生成插件(GPTGod & Yunwu)",
4
- "version": "0.2.5",
4
+ "version": "0.2.7",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [