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 +1 -1
- package/lib/index.js +142 -94
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
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
|
-
|
|
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
|
-
|
|
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("
|
|
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]`, "为用户充值次数(仅管理员)").
|
|
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
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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 "
|
|
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);
|