koishi-plugin-aka-ai-generator 0.3.8 → 0.4.0

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.d.ts CHANGED
@@ -53,7 +53,7 @@ export interface Config {
53
53
  export interface RechargeRecord {
54
54
  id: string;
55
55
  timestamp: string;
56
- type: 'single' | 'batch';
56
+ type: 'single' | 'batch' | 'all';
57
57
  operator: {
58
58
  userId: string;
59
59
  userName: string;
package/lib/index.js CHANGED
@@ -587,11 +587,31 @@ 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
+ }
593
601
  if (response.candidates && response.candidates.length > 0) {
594
602
  for (const candidate of response.candidates) {
603
+ if (candidate.finishReason && candidate.finishReason !== "STOP") {
604
+ logger?.warn("Gemini 响应 finishReason 异常", {
605
+ finishReason: candidate.finishReason,
606
+ safetyRatings: candidate.safetyRatings
607
+ });
608
+ if (candidate.finishReason === "SAFETY" || candidate.finishReason === "RECITATION") {
609
+ throw new Error(`内容被阻止: ${candidate.finishReason},可能包含不安全的内容`);
610
+ }
611
+ if (candidate.finishReason !== "MAX_TOKENS") {
612
+ logger?.warn("Gemini 响应可能不完整", { finishReason: candidate.finishReason });
613
+ }
614
+ }
595
615
  if (candidate.content && candidate.content.parts) {
596
616
  for (const part of candidate.content.parts) {
597
617
  if (part.inlineData && part.inlineData.data) {
@@ -599,21 +619,39 @@ function parseGeminiResponse(response) {
599
619
  const mimeType = part.inlineData.mimeType || "image/jpeg";
600
620
  const dataUrl = `data:${mimeType};base64,${base64Data}`;
601
621
  images.push(dataUrl);
622
+ logger?.debug("从响应中提取到图片 (inlineData)", { mimeType, dataLength: base64Data.length });
602
623
  } else if (part.inline_data && part.inline_data.data) {
603
624
  const base64Data = part.inline_data.data;
604
625
  const mimeType = part.inline_data.mime_type || "image/jpeg";
605
626
  const dataUrl = `data:${mimeType};base64,${base64Data}`;
606
627
  images.push(dataUrl);
628
+ logger?.debug("从响应中提取到图片 (inline_data)", { mimeType, dataLength: base64Data.length });
607
629
  } else if (part.fileData && part.fileData.fileUri) {
608
630
  images.push(part.fileData.fileUri);
631
+ logger?.debug("从响应中提取到图片 (fileData)", { fileUri: part.fileData.fileUri });
632
+ } else if (part.text) {
633
+ logger?.warn("响应中包含文本而非图片", { text: part.text.substring(0, 100) });
609
634
  }
610
635
  }
636
+ } else {
637
+ logger?.warn("候选响应中没有 content.parts", { candidate: JSON.stringify(candidate).substring(0, 200) });
611
638
  }
612
639
  }
640
+ } else {
641
+ logger?.warn("Gemini API 响应中没有 candidates", { response: JSON.stringify(response).substring(0, 500) });
642
+ }
643
+ if (images.length === 0) {
644
+ logger?.error("未能从 Gemini API 响应中提取到任何图片", {
645
+ hasCandidates: !!response.candidates,
646
+ candidatesCount: response.candidates?.length || 0,
647
+ responseKeys: Object.keys(response),
648
+ firstCandidate: response.candidates?.[0] ? JSON.stringify(response.candidates[0]).substring(0, 300) : null
649
+ });
613
650
  }
614
651
  return images;
615
652
  } catch (error) {
616
- return [];
653
+ logger?.error("解析 Gemini 响应时出错", { error: error.message, stack: error.stack });
654
+ throw error;
617
655
  }
618
656
  }
619
657
  __name(parseGeminiResponse, "parseGeminiResponse");
@@ -626,12 +664,18 @@ var GeminiProvider = class {
626
664
  this.config = config;
627
665
  }
628
666
  async generateImages(prompt, imageUrls, numImages) {
629
- const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
667
+ let urls = [];
668
+ if (Array.isArray(imageUrls)) {
669
+ urls = imageUrls.filter((url) => url && typeof url === "string" && url.trim());
670
+ } else if (imageUrls && typeof imageUrls === "string" && imageUrls.trim()) {
671
+ urls = [imageUrls];
672
+ }
630
673
  const logger = this.config.logger;
631
674
  const ctx = this.config.ctx;
632
- logger.debug("开始下载图片并转换为Base64", { urls });
675
+ logger.debug("开始处理图片输入", { urls, promptLength: prompt.length, isTextToImage: urls.length === 0 });
633
676
  const imageParts = [];
634
677
  for (const url of urls) {
678
+ if (!url || !url.trim()) continue;
635
679
  const { data, mimeType } = await downloadImageAsBase643(
636
680
  ctx,
637
681
  url,
@@ -678,14 +722,24 @@ var GeminiProvider = class {
678
722
  timeout: this.config.apiTimeout * 1e3
679
723
  }
680
724
  );
681
- const images = parseGeminiResponse(response);
725
+ const images = parseGeminiResponse(response, logger);
726
+ if (images.length === 0) {
727
+ logger.warn("Gemini API 调用成功但未解析到图片", {
728
+ current: i + 1,
729
+ total: numImages,
730
+ responseHasCandidates: !!response.candidates,
731
+ responseKeys: Object.keys(response)
732
+ });
733
+ } else {
734
+ logger.success("Gemini API 调用成功", { current: i + 1, total: numImages, imagesCount: images.length });
735
+ }
682
736
  allImages.push(...images);
683
- logger.success("Gemini API 调用成功", { current: i + 1, total: numImages });
684
737
  } catch (error) {
685
738
  logger.error("Gemini API 调用失败", {
686
739
  message: error?.message || "未知错误",
687
740
  code: error?.code,
688
741
  status: error?.response?.status,
742
+ responseData: error?.response?.data ? JSON.stringify(error.response.data).substring(0, 500) : void 0,
689
743
  current: i + 1,
690
744
  total: numImages
691
745
  });
@@ -696,6 +750,10 @@ var GeminiProvider = class {
696
750
  throw new Error(`图像处理API调用失败: ${error?.message || "未知错误"}`);
697
751
  }
698
752
  }
753
+ if (allImages.length === 0) {
754
+ logger.error("所有 Gemini API 调用都未生成图片", { numImages });
755
+ throw new Error("未能从 Gemini API 生成图片,请检查 prompt 和模型配置");
756
+ }
699
757
  return allImages;
700
758
  }
701
759
  };
@@ -748,6 +806,7 @@ var COMMANDS = {
748
806
  PIXELATE: "变像素",
749
807
  QUERY_QUOTA: "图像额度",
750
808
  RECHARGE: "图像充值",
809
+ RECHARGE_ALL: "活动充值",
751
810
  RECHARGE_HISTORY: "图像充值记录",
752
811
  FUNCTION_LIST: "图像功能",
753
812
  IMAGE_COMMANDS: "图像指令"
@@ -979,6 +1038,7 @@ function apply(ctx, config) {
979
1038
  // 管理员指令
980
1039
  adminCommands: [
981
1040
  { name: COMMANDS.RECHARGE, description: "为用户充值次数(仅管理员)" },
1041
+ { name: COMMANDS.RECHARGE_ALL, description: "为所有用户充值次数(活动派发,仅管理员)" },
982
1042
  { name: COMMANDS.RECHARGE_HISTORY, description: "查看充值历史记录(仅管理员)" }
983
1043
  ]
984
1044
  };
@@ -1622,102 +1682,19 @@ Prompt: ${prompt}`);
1622
1682
  return error.message === "命令执行超时" ? "图片合成超时,请重试" : `图片合成失败:${error.message}`;
1623
1683
  });
1624
1684
  });
1625
- ctx.command(`${COMMANDS.RECHARGE} [content:text]`, "为用户充值次数(仅管理员)").option("all", "-all 对所有使用过插件的用户充值").action(async ({ session, options }, content) => {
1685
+ ctx.command(`${COMMANDS.RECHARGE} [content:text]`, "为用户充值次数(仅管理员)").action(async ({ session }, content) => {
1626
1686
  if (!session?.userId) return "会话无效";
1627
1687
  if (!isAdmin(session.userId)) {
1628
1688
  return "权限不足,仅管理员可操作";
1629
1689
  }
1630
- let useAll = options?.all;
1631
- let inputContent = content;
1632
- if (!useAll && inputContent) {
1633
- const contentStr = typeof inputContent === "string" ? inputContent : inputContent ? import_koishi.h.select(import_koishi.h.parse(inputContent), "text").map((e) => e.attrs.content).join(" ") : "";
1634
- if (contentStr && /-all\b/i.test(contentStr)) {
1635
- useAll = true;
1636
- inputContent = contentStr.replace(/-all\s*/gi, "").trim();
1637
- }
1638
- }
1639
- if (useAll) {
1640
- if (!inputContent || typeof inputContent === "string" && !inputContent.trim()) {
1641
- inputContent = await getPromptInput(session, "请输入充值次数 [备注],例如:20 或 20 活动奖励");
1642
- }
1643
- if (!inputContent) return "输入超时或无效";
1644
- let text2 = "";
1645
- if (typeof inputContent === "string") {
1646
- text2 = inputContent.trim();
1647
- } else {
1648
- const elements2 = import_koishi.h.parse(inputContent);
1649
- const textElements2 = import_koishi.h.select(elements2, "text");
1650
- text2 = textElements2.map((el) => el.attrs.content).join(" ").trim();
1651
- }
1652
- const parts2 = text2.split(/\s+/).filter((p) => p);
1653
- if (parts2.length === 0) {
1654
- return "请输入充值次数,例如:图像充值 -all 20";
1655
- }
1656
- const amount2 = parseInt(parts2[0]);
1657
- const note2 = parts2.slice(1).join(" ") || "全员充值";
1658
- if (!amount2 || amount2 <= 0) {
1659
- return "充值次数必须大于0";
1660
- }
1661
- try {
1662
- const usersData = await loadUsersData();
1663
- const rechargeHistory = await loadRechargeHistory();
1664
- const now = (/* @__PURE__ */ new Date()).toISOString();
1665
- const recordId = `recharge_${now.replace(/[-:T.]/g, "").slice(0, 14)}_${Math.random().toString(36).substr(2, 3)}`;
1666
- const targets = [];
1667
- const allUserIds = Object.keys(usersData);
1668
- if (allUserIds.length === 0) {
1669
- return "当前没有使用过插件的用户";
1670
- }
1671
- for (const userId of allUserIds) {
1672
- if (!userId) continue;
1673
- const userData = usersData[userId];
1674
- const userName = userData.userName || userId;
1675
- const beforeBalance = userData.remainingPurchasedCount;
1676
- userData.purchasedCount += amount2;
1677
- userData.remainingPurchasedCount += amount2;
1678
- targets.push({
1679
- userId,
1680
- userName,
1681
- amount: amount2,
1682
- beforeBalance,
1683
- afterBalance: userData.remainingPurchasedCount
1684
- });
1685
- }
1686
- await saveUsersData(usersData);
1687
- const record = {
1688
- id: recordId,
1689
- timestamp: now,
1690
- type: "batch",
1691
- operator: {
1692
- userId: session.userId,
1693
- userName: session.username || session.userId
1694
- },
1695
- targets,
1696
- totalAmount: amount2 * allUserIds.length,
1697
- note: note2 || "全员充值",
1698
- metadata: { all: true }
1699
- };
1700
- rechargeHistory.records.push(record);
1701
- await saveRechargeHistory(rechargeHistory);
1702
- return `✅ 全员充值成功
1703
- 目标用户数:${allUserIds.length}人
1704
- 充值次数:${amount2}次/人
1705
- 总充值:${record.totalAmount}次
1706
- 操作员:${record.operator.userName}
1707
- 备注:${record.note}`;
1708
- } catch (error) {
1709
- logger.error("全员充值操作失败", error);
1710
- return "充值失败,请稍后重试";
1711
- }
1712
- }
1713
- const rechargeContent = content || await getPromptInput(session, "请输入充值信息,格式:\n@用户1 @用户2 充值次数 [备注]");
1714
- if (!rechargeContent) return "输入超时或无效";
1715
- const elements = import_koishi.h.parse(rechargeContent);
1690
+ const inputContent = content || await getPromptInput(session, "请输入充值信息,格式:\n@用户1 @用户2 充值次数 [备注]");
1691
+ if (!inputContent) return "输入超时或无效";
1692
+ const elements = import_koishi.h.parse(inputContent);
1716
1693
  const atElements = import_koishi.h.select(elements, "at");
1717
1694
  const textElements = import_koishi.h.select(elements, "text");
1718
1695
  const text = textElements.map((el) => el.attrs.content).join(" ").trim();
1719
1696
  if (atElements.length === 0) {
1720
- return "未找到@用户,请使用@用户的方式,或使用 -all 参数进行全员充值";
1697
+ return "未找到@用户,请使用@用户的方式";
1721
1698
  }
1722
1699
  const parts = text.split(/\s+/).filter((p) => p);
1723
1700
  if (parts.length === 0) {
@@ -1798,6 +1775,77 @@ Prompt: ${prompt}`);
1798
1775
  return "充值失败,请稍后重试";
1799
1776
  }
1800
1777
  });
1778
+ ctx.command(`${COMMANDS.RECHARGE_ALL} [content:text]`, "为所有用户充值次数(活动派发,仅管理员)").action(async ({ session }, content) => {
1779
+ if (!session?.userId) return "会话无效";
1780
+ if (!isAdmin(session.userId)) {
1781
+ return "权限不足,仅管理员可操作";
1782
+ }
1783
+ const inputContent = content || await getPromptInput(session, "请输入活动充值信息,格式:\n充值次数 [备注]\n例如:20 或 20 春节活动奖励");
1784
+ if (!inputContent) return "输入超时或无效";
1785
+ const elements = import_koishi.h.parse(inputContent);
1786
+ const textElements = import_koishi.h.select(elements, "text");
1787
+ const text = textElements.map((el) => el.attrs.content).join(" ").trim();
1788
+ const parts = text.split(/\s+/).filter((p) => p);
1789
+ if (parts.length === 0) {
1790
+ return "请输入充值次数,例如:图像活动充值 20 或 图像活动充值 20 活动名称";
1791
+ }
1792
+ const amount = parseInt(parts[0]);
1793
+ const note = parts.slice(1).join(" ") || "活动充值";
1794
+ if (!amount || amount <= 0) {
1795
+ return "充值次数必须大于0";
1796
+ }
1797
+ try {
1798
+ const usersData = await loadUsersData();
1799
+ const rechargeHistory = await loadRechargeHistory();
1800
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1801
+ const recordId = `recharge_all_${now.replace(/[-:T.]/g, "").slice(0, 14)}_${Math.random().toString(36).substr(2, 3)}`;
1802
+ const allUserIds = Object.keys(usersData).filter((userId) => userId && usersData[userId]);
1803
+ if (allUserIds.length === 0) {
1804
+ return "当前没有使用过插件的用户,无法进行活动充值";
1805
+ }
1806
+ const targets = [];
1807
+ for (const userId of allUserIds) {
1808
+ if (!userId) continue;
1809
+ const userData = usersData[userId];
1810
+ const userName = userData.userName || userId;
1811
+ const beforeBalance = userData.remainingPurchasedCount;
1812
+ userData.purchasedCount += amount;
1813
+ userData.remainingPurchasedCount += amount;
1814
+ targets.push({
1815
+ userId,
1816
+ userName,
1817
+ amount,
1818
+ beforeBalance,
1819
+ afterBalance: userData.remainingPurchasedCount
1820
+ });
1821
+ }
1822
+ await saveUsersData(usersData);
1823
+ const record = {
1824
+ id: recordId,
1825
+ timestamp: now,
1826
+ type: "all",
1827
+ operator: {
1828
+ userId: session.userId,
1829
+ userName: session.username || session.userId
1830
+ },
1831
+ targets,
1832
+ totalAmount: amount * allUserIds.length,
1833
+ note: note || "活动充值",
1834
+ metadata: { all: true }
1835
+ };
1836
+ rechargeHistory.records.push(record);
1837
+ await saveRechargeHistory(rechargeHistory);
1838
+ return `✅ 活动充值成功
1839
+ 目标用户数:${allUserIds.length}人
1840
+ 充值次数:${amount}次/人
1841
+ 总充值:${record.totalAmount}次
1842
+ 操作员:${record.operator.userName}
1843
+ 备注:${record.note}`;
1844
+ } catch (error) {
1845
+ logger.error("活动充值操作失败", error);
1846
+ return "活动充值失败,请稍后重试";
1847
+ }
1848
+ });
1801
1849
  ctx.command(`${COMMANDS.QUERY_QUOTA} [target:text]`, "查询用户额度信息").action(async ({ session }, target) => {
1802
1850
  if (!session?.userId) return "会话无效";
1803
1851
  const userIsAdmin = isAdmin(session.userId);
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.8",
4
+ "version": "0.4.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [