koishi-plugin-aka-ai-generator 0.3.9 → 0.4.1

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 +112 -6
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -587,11 +587,64 @@ async function downloadImageAsBase643(ctx, url, timeout, logger) {
587
587
  }
588
588
  }
589
589
  __name(downloadImageAsBase643, "downloadImageAsBase64");
590
- function parseGeminiResponse(response) {
590
+ function parseGeminiResponse(response, logger) {
591
591
  try {
592
592
  const images = [];
593
+ if (!response) {
594
+ logger?.error("Gemini API 响应为空");
595
+ return [];
596
+ }
597
+ if (response.error) {
598
+ logger?.error("Gemini API 返回错误", { error: response.error });
599
+ throw new Error(`Gemini API 错误: ${response.error.message || JSON.stringify(response.error)}`);
600
+ }
601
+ if (response.promptFeedback) {
602
+ const blockReason = response.promptFeedback.blockReason;
603
+ const safetyRatings = response.promptFeedback.safetyRatings;
604
+ if (blockReason) {
605
+ logger?.error("Gemini API 请求被阻止", {
606
+ blockReason,
607
+ safetyRatings,
608
+ blockReasonMessage: response.promptFeedback.blockReasonMessage
609
+ });
610
+ let errorMessage = "请求被 Gemini API 阻止";
611
+ switch (blockReason) {
612
+ case "SAFETY":
613
+ errorMessage = "内容被安全策略阻止,可能包含不安全的内容";
614
+ break;
615
+ case "OTHER":
616
+ errorMessage = "请求被阻止(原因:OTHER),可能是内容不符合使用政策或模型无法处理";
617
+ if (response.promptFeedback.blockReasonMessage) {
618
+ errorMessage += `:${response.promptFeedback.blockReasonMessage}`;
619
+ }
620
+ break;
621
+ case "RECITATION":
622
+ errorMessage = "内容包含受版权保护的内容";
623
+ break;
624
+ default:
625
+ errorMessage = `请求被阻止(原因:${blockReason})`;
626
+ }
627
+ if (safetyRatings && Array.isArray(safetyRatings) && safetyRatings.length > 0) {
628
+ const ratings = safetyRatings.map((r) => `${r.category}:${r.probability}`).join(", ");
629
+ errorMessage += ` [安全评分: ${ratings}]`;
630
+ }
631
+ throw new Error(errorMessage);
632
+ }
633
+ }
593
634
  if (response.candidates && response.candidates.length > 0) {
594
635
  for (const candidate of response.candidates) {
636
+ if (candidate.finishReason && candidate.finishReason !== "STOP") {
637
+ logger?.warn("Gemini 响应 finishReason 异常", {
638
+ finishReason: candidate.finishReason,
639
+ safetyRatings: candidate.safetyRatings
640
+ });
641
+ if (candidate.finishReason === "SAFETY" || candidate.finishReason === "RECITATION") {
642
+ throw new Error(`内容被阻止: ${candidate.finishReason},可能包含不安全的内容`);
643
+ }
644
+ if (candidate.finishReason !== "MAX_TOKENS") {
645
+ logger?.warn("Gemini 响应可能不完整", { finishReason: candidate.finishReason });
646
+ }
647
+ }
595
648
  if (candidate.content && candidate.content.parts) {
596
649
  for (const part of candidate.content.parts) {
597
650
  if (part.inlineData && part.inlineData.data) {
@@ -599,21 +652,54 @@ function parseGeminiResponse(response) {
599
652
  const mimeType = part.inlineData.mimeType || "image/jpeg";
600
653
  const dataUrl = `data:${mimeType};base64,${base64Data}`;
601
654
  images.push(dataUrl);
655
+ logger?.debug("从响应中提取到图片 (inlineData)", { mimeType, dataLength: base64Data.length });
602
656
  } else if (part.inline_data && part.inline_data.data) {
603
657
  const base64Data = part.inline_data.data;
604
658
  const mimeType = part.inline_data.mime_type || "image/jpeg";
605
659
  const dataUrl = `data:${mimeType};base64,${base64Data}`;
606
660
  images.push(dataUrl);
661
+ logger?.debug("从响应中提取到图片 (inline_data)", { mimeType, dataLength: base64Data.length });
607
662
  } else if (part.fileData && part.fileData.fileUri) {
608
663
  images.push(part.fileData.fileUri);
664
+ logger?.debug("从响应中提取到图片 (fileData)", { fileUri: part.fileData.fileUri });
665
+ } else if (part.text) {
666
+ logger?.warn("响应中包含文本而非图片", { text: part.text.substring(0, 100) });
609
667
  }
610
668
  }
669
+ } else {
670
+ logger?.warn("候选响应中没有 content.parts", { candidate: JSON.stringify(candidate).substring(0, 200) });
611
671
  }
612
672
  }
673
+ } else {
674
+ const hasPromptFeedback = !!response.promptFeedback;
675
+ const responseKeys = Object.keys(response);
676
+ logger?.error("Gemini API 响应中没有 candidates", {
677
+ response: JSON.stringify(response).substring(0, 500),
678
+ hasPromptFeedback,
679
+ responseKeys
680
+ });
681
+ if (!hasPromptFeedback) {
682
+ throw new Error("Gemini API 响应格式异常:既没有生成内容也没有反馈信息");
683
+ }
684
+ if (hasPromptFeedback && !response.promptFeedback.blockReason) {
685
+ logger?.warn("有 promptFeedback 但没有 blockReason,也没有 candidates", {
686
+ promptFeedback: response.promptFeedback
687
+ });
688
+ }
689
+ }
690
+ if (images.length === 0) {
691
+ logger?.error("未能从 Gemini API 响应中提取到任何图片", {
692
+ hasCandidates: !!response.candidates,
693
+ candidatesCount: response.candidates?.length || 0,
694
+ responseKeys: Object.keys(response),
695
+ firstCandidate: response.candidates?.[0] ? JSON.stringify(response.candidates[0]).substring(0, 300) : null,
696
+ promptFeedback: response.promptFeedback ? JSON.stringify(response.promptFeedback) : null
697
+ });
613
698
  }
614
699
  return images;
615
700
  } catch (error) {
616
- return [];
701
+ logger?.error("解析 Gemini 响应时出错", { error: error.message, stack: error.stack });
702
+ throw error;
617
703
  }
618
704
  }
619
705
  __name(parseGeminiResponse, "parseGeminiResponse");
@@ -626,12 +712,18 @@ var GeminiProvider = class {
626
712
  this.config = config;
627
713
  }
628
714
  async generateImages(prompt, imageUrls, numImages) {
629
- const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
715
+ let urls = [];
716
+ if (Array.isArray(imageUrls)) {
717
+ urls = imageUrls.filter((url) => url && typeof url === "string" && url.trim());
718
+ } else if (imageUrls && typeof imageUrls === "string" && imageUrls.trim()) {
719
+ urls = [imageUrls];
720
+ }
630
721
  const logger = this.config.logger;
631
722
  const ctx = this.config.ctx;
632
- logger.debug("开始下载图片并转换为Base64", { urls });
723
+ logger.debug("开始处理图片输入", { urls, promptLength: prompt.length, isTextToImage: urls.length === 0 });
633
724
  const imageParts = [];
634
725
  for (const url of urls) {
726
+ if (!url || !url.trim()) continue;
635
727
  const { data, mimeType } = await downloadImageAsBase643(
636
728
  ctx,
637
729
  url,
@@ -678,14 +770,24 @@ var GeminiProvider = class {
678
770
  timeout: this.config.apiTimeout * 1e3
679
771
  }
680
772
  );
681
- const images = parseGeminiResponse(response);
773
+ const images = parseGeminiResponse(response, logger);
774
+ if (images.length === 0) {
775
+ logger.warn("Gemini API 调用成功但未解析到图片", {
776
+ current: i + 1,
777
+ total: numImages,
778
+ responseHasCandidates: !!response.candidates,
779
+ responseKeys: Object.keys(response)
780
+ });
781
+ } else {
782
+ logger.success("Gemini API 调用成功", { current: i + 1, total: numImages, imagesCount: images.length });
783
+ }
682
784
  allImages.push(...images);
683
- logger.success("Gemini API 调用成功", { current: i + 1, total: numImages });
684
785
  } catch (error) {
685
786
  logger.error("Gemini API 调用失败", {
686
787
  message: error?.message || "未知错误",
687
788
  code: error?.code,
688
789
  status: error?.response?.status,
790
+ responseData: error?.response?.data ? JSON.stringify(error.response.data).substring(0, 500) : void 0,
689
791
  current: i + 1,
690
792
  total: numImages
691
793
  });
@@ -696,6 +798,10 @@ var GeminiProvider = class {
696
798
  throw new Error(`图像处理API调用失败: ${error?.message || "未知错误"}`);
697
799
  }
698
800
  }
801
+ if (allImages.length === 0) {
802
+ logger.error("所有 Gemini API 调用都未生成图片", { numImages });
803
+ throw new Error("未能从 Gemini API 生成图片,请检查 prompt 和模型配置");
804
+ }
699
805
  return allImages;
700
806
  }
701
807
  };
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.3.9",
4
+ "version": "0.4.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [