koishi-plugin-aka-ai-generator 0.2.4 → 0.2.6
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/index.js +118 -75
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -432,92 +432,135 @@ var GptGodProvider = class {
|
|
|
432
432
|
}
|
|
433
433
|
]
|
|
434
434
|
};
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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 (
|
|
461
|
-
logger.
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
473
|
-
|
|
529
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
530
|
+
continue;
|
|
474
531
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
496
|
-
|
|
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
|
-
code: error?.code,
|
|
513
|
-
status: error?.response?.status,
|
|
514
|
-
data: error?.response?.data
|
|
515
|
-
});
|
|
516
|
-
if (error?.message?.includes("fetch") && error?.message?.includes(GPTGOD_DEFAULT_API_URL)) {
|
|
517
|
-
throw new Error("图像处理失败:无法连接 GPTGod API 服务器,请稍后重试");
|
|
518
|
-
}
|
|
519
|
-
throw new Error("图像处理API调用失败");
|
|
520
562
|
}
|
|
563
|
+
throw lastError;
|
|
521
564
|
}
|
|
522
565
|
};
|
|
523
566
|
|