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.
- package/lib/index.js +184 -89
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -432,100 +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
|
-
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
|
-
|
|
1216
|
+
let userPromptParts = [];
|
|
1153
1217
|
if (modifiers.customAdditions?.length) {
|
|
1154
|
-
|
|
1218
|
+
userPromptParts.push(...modifiers.customAdditions);
|
|
1155
1219
|
}
|
|
1156
1220
|
if (modifiers.customPromptSuffix) {
|
|
1157
|
-
|
|
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
|
|
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
|
}
|