koishi-plugin-aka-ai-generator 0.2.14 → 0.2.17

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
@@ -1,6 +1,6 @@
1
1
  import { Context, Schema } from 'koishi';
2
2
  export declare const name = "aka-ai-generator";
3
- export type ImageProvider = 'yunwu' | 'gptgod';
3
+ export type ImageProvider = 'yunwu' | 'gptgod' | 'gemini';
4
4
  export interface ModelMappingConfig {
5
5
  suffix: string;
6
6
  modelId: string;
@@ -9,6 +9,7 @@ export interface ModelMappingConfig {
9
9
  export interface StyleConfig {
10
10
  commandName: string;
11
11
  prompt: string;
12
+ mode?: 'single' | 'multiple';
12
13
  }
13
14
  export interface StyleGroupConfig {
14
15
  prompts: StyleConfig[];
@@ -35,6 +36,9 @@ export interface Config {
35
36
  yunwuModelId: string;
36
37
  gptgodApiKey: string;
37
38
  gptgodModelId: string;
39
+ geminiApiKey: string;
40
+ geminiModelId: string;
41
+ geminiApiBase: string;
38
42
  modelMappings?: ModelMappingConfig[];
39
43
  apiTimeout: number;
40
44
  commandTimeout: number;
package/lib/index.js CHANGED
@@ -561,6 +561,145 @@ var GptGodProvider = class {
561
561
  }
562
562
  };
563
563
 
564
+ // src/providers/gemini.ts
565
+ async function downloadImageAsBase643(ctx, url, timeout, logger) {
566
+ try {
567
+ const response = await ctx.http.get(url, {
568
+ responseType: "arraybuffer",
569
+ timeout: timeout * 1e3
570
+ });
571
+ const buffer = Buffer.from(response);
572
+ const base64 = buffer.toString("base64");
573
+ let mimeType = "image/jpeg";
574
+ const urlLower = url.toLowerCase();
575
+ if (urlLower.endsWith(".png")) {
576
+ mimeType = "image/png";
577
+ } else if (urlLower.endsWith(".webp")) {
578
+ mimeType = "image/webp";
579
+ } else if (urlLower.endsWith(".gif")) {
580
+ mimeType = "image/gif";
581
+ }
582
+ logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
583
+ return { data: base64, mimeType };
584
+ } catch (error) {
585
+ logger.error("下载图片失败", { url, error });
586
+ throw new Error("下载图片失败,请检查图片链接是否有效");
587
+ }
588
+ }
589
+ __name(downloadImageAsBase643, "downloadImageAsBase64");
590
+ function parseGeminiResponse(response) {
591
+ try {
592
+ const images = [];
593
+ if (response.candidates && response.candidates.length > 0) {
594
+ for (const candidate of response.candidates) {
595
+ if (candidate.content && candidate.content.parts) {
596
+ for (const part of candidate.content.parts) {
597
+ if (part.inlineData && part.inlineData.data) {
598
+ const base64Data = part.inlineData.data;
599
+ const mimeType = part.inlineData.mimeType || "image/jpeg";
600
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
601
+ images.push(dataUrl);
602
+ } else if (part.inline_data && part.inline_data.data) {
603
+ const base64Data = part.inline_data.data;
604
+ const mimeType = part.inline_data.mime_type || "image/jpeg";
605
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
606
+ images.push(dataUrl);
607
+ } else if (part.fileData && part.fileData.fileUri) {
608
+ images.push(part.fileData.fileUri);
609
+ }
610
+ }
611
+ }
612
+ }
613
+ }
614
+ return images;
615
+ } catch (error) {
616
+ return [];
617
+ }
618
+ }
619
+ __name(parseGeminiResponse, "parseGeminiResponse");
620
+ var GeminiProvider = class {
621
+ static {
622
+ __name(this, "GeminiProvider");
623
+ }
624
+ config;
625
+ constructor(config) {
626
+ this.config = config;
627
+ }
628
+ async generateImages(prompt, imageUrls, numImages) {
629
+ const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
630
+ const logger = this.config.logger;
631
+ const ctx = this.config.ctx;
632
+ logger.debug("开始下载图片并转换为Base64", { urls });
633
+ const imageParts = [];
634
+ for (const url of urls) {
635
+ const { data, mimeType } = await downloadImageAsBase643(
636
+ ctx,
637
+ url,
638
+ this.config.apiTimeout,
639
+ logger
640
+ );
641
+ imageParts.push({
642
+ inline_data: {
643
+ mime_type: mimeType,
644
+ data
645
+ }
646
+ });
647
+ }
648
+ const apiBase = this.config.apiBase?.replace(/\/$/, "") || "https://generativelanguage.googleapis.com";
649
+ const endpoint = `${apiBase}/v1beta/models/${this.config.modelId}:generateContent`;
650
+ const allImages = [];
651
+ for (let i = 0; i < numImages; i++) {
652
+ const requestData = {
653
+ contents: [
654
+ {
655
+ role: "user",
656
+ parts: [
657
+ { text: prompt },
658
+ ...imageParts
659
+ ]
660
+ }
661
+ ],
662
+ generationConfig: {
663
+ responseModalities: ["IMAGE"]
664
+ }
665
+ };
666
+ logger.debug("调用 Gemini API", { prompt, imageCount: urls.length, numImages, current: i + 1, endpoint });
667
+ try {
668
+ const response = await ctx.http.post(
669
+ endpoint,
670
+ requestData,
671
+ {
672
+ headers: {
673
+ "Content-Type": "application/json"
674
+ },
675
+ params: {
676
+ key: this.config.apiKey
677
+ },
678
+ timeout: this.config.apiTimeout * 1e3
679
+ }
680
+ );
681
+ const images = parseGeminiResponse(response);
682
+ allImages.push(...images);
683
+ logger.success("Gemini API 调用成功", { current: i + 1, total: numImages });
684
+ } catch (error) {
685
+ logger.error("Gemini API 调用失败", {
686
+ message: error?.message || "未知错误",
687
+ code: error?.code,
688
+ status: error?.response?.status,
689
+ current: i + 1,
690
+ total: numImages
691
+ });
692
+ if (allImages.length > 0) {
693
+ logger.warn("部分图片生成失败,返回已生成的图片", { generated: allImages.length, requested: numImages });
694
+ break;
695
+ }
696
+ throw new Error(`图像处理API调用失败: ${error?.message || "未知错误"}`);
697
+ }
698
+ }
699
+ return allImages;
700
+ }
701
+ };
702
+
564
703
  // src/providers/index.ts
565
704
  function createImageProvider(config) {
566
705
  switch (config.provider) {
@@ -582,6 +721,16 @@ function createImageProvider(config) {
582
721
  logger: config.logger,
583
722
  ctx: config.ctx
584
723
  });
724
+ case "gemini":
725
+ return new GeminiProvider({
726
+ apiKey: config.geminiApiKey,
727
+ modelId: config.geminiModelId,
728
+ apiBase: config.geminiApiBase,
729
+ apiTimeout: config.apiTimeout,
730
+ logLevel: config.logLevel,
731
+ logger: config.logger,
732
+ ctx: config.ctx
733
+ });
585
734
  default:
586
735
  throw new Error(`不支持的供应商类型: ${config.provider}`);
587
736
  }
@@ -604,23 +753,32 @@ var COMMANDS = {
604
753
  };
605
754
  var StyleItemSchema = import_koishi.Schema.object({
606
755
  commandName: import_koishi.Schema.string().required().description("命令名称(不含前缀斜杠)"),
607
- prompt: import_koishi.Schema.string().role("textarea", { rows: 4 }).required().description("生成 prompt")
756
+ prompt: import_koishi.Schema.string().role("textarea", { rows: 4 }).required().description("生成 prompt"),
757
+ mode: import_koishi.Schema.union([
758
+ import_koishi.Schema.const("single").description("单图模式"),
759
+ import_koishi.Schema.const("multiple").description("多图模式")
760
+ ]).default("single").description("图片输入模式")
608
761
  });
609
762
  var Config = import_koishi.Schema.intersect([
610
763
  import_koishi.Schema.object({
611
764
  provider: import_koishi.Schema.union([
612
765
  import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
613
- import_koishi.Schema.const("gptgod").description("GPTGod 服务")
766
+ import_koishi.Schema.const("gptgod").description("GPTGod 服务"),
767
+ import_koishi.Schema.const("gemini").description("Google Gemini 原生")
614
768
  ]).default("yunwu").description("图像生成供应商"),
615
769
  yunwuApiKey: import_koishi.Schema.string().description("云雾API密钥").role("secret").required(),
616
770
  yunwuModelId: import_koishi.Schema.string().default("gemini-2.5-flash-image").description("云雾图像生成模型ID"),
617
771
  gptgodApiKey: import_koishi.Schema.string().description("GPTGod API 密钥").role("secret").default(""),
618
772
  gptgodModelId: import_koishi.Schema.string().default("nano-banana").description("GPTGod 模型ID"),
773
+ geminiApiKey: import_koishi.Schema.string().description("Gemini API 密钥").role("secret").default(""),
774
+ geminiModelId: import_koishi.Schema.string().default("gemini-2.5-flash").description("Gemini 模型ID"),
775
+ geminiApiBase: import_koishi.Schema.string().default("https://generativelanguage.googleapis.com").description("Gemini API 基础地址"),
619
776
  modelMappings: import_koishi.Schema.array(import_koishi.Schema.object({
620
777
  suffix: import_koishi.Schema.string().required().description("指令后缀(例如 4K,对应输入 -4K)"),
621
778
  provider: import_koishi.Schema.union([
622
779
  import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
623
- import_koishi.Schema.const("gptgod").description("GPTGod 服务")
780
+ import_koishi.Schema.const("gptgod").description("GPTGod 服务"),
781
+ import_koishi.Schema.const("gemini").description("Google Gemini 原生")
624
782
  ]).description("可选:覆盖供应商"),
625
783
  modelId: import_koishi.Schema.string().required().description("触发该后缀时使用的模型 ID")
626
784
  })).role("table").default([]).description("根据 -后缀切换模型/供应商"),
@@ -646,11 +804,13 @@ var Config = import_koishi.Schema.intersect([
646
804
  styles: import_koishi.Schema.array(StyleItemSchema).role("table").default([
647
805
  {
648
806
  commandName: "变手办",
649
- prompt: "将这张照片变成手办模型。在它后面放置一个印有图像主体的盒子,桌子上有一台电脑显示Blender建模过程。在盒子前面添加一个圆形塑料底座,角色手办站在上面。如果可能的话,将场景设置在室内"
807
+ prompt: "将这张照片变成手办模型。在它后面放置一个印有图像主体的盒子,桌子上有一台电脑显示Blender建模过程。在盒子前面添加一个圆形塑料底座,角色手办站在上面。如果可能的话,将场景设置在室内",
808
+ mode: "single"
650
809
  },
651
810
  {
652
811
  commandName: "变写实",
653
- prompt: "请根据用户提供的图片,在严格保持主体身份、外观特征与姿态不变的前提下,生成一张照片级真实感的超写实摄影作品。要求:1. 采用专业相机拍摄(如佳能EOS R5),使用85mm f/1.4人像镜头,呈现柯达Portra 400胶片质感,8K超高清画质,HDR高动态范围,电影级打光效果;2. 画面应具有照片级真实感、超现实主义风格和高细节表现,确保光影、皮肤质感、服饰纹理与背景环境都贴近真实世界;3. 使用自然光影营造真实氛围,呈现raw and natural的原始自然感,具有authentic film snapshot的真实胶片质感;4. 整体需具备tactile feel触感质感和simulated texture模拟纹理细节,可以适度优化噪点与瑕疵,但不要改变主体特征或添加额外元素;5. 整体效果需像专业摄影棚拍摄的真实照片,具有电影级画质;6. 如果主体是人物脸部,脸部生成效果应参考欧美混血白人精致美丽帅气英俊的外观特征进行生成,保持精致立体的五官轮廓、健康光泽的肌肤质感、优雅的气质和自然的表情,确保面部特征协调美观。"
812
+ prompt: "请根据用户提供的图片,在严格保持主体身份、外观特征与姿态不变的前提下,生成一张照片级真实感的超写实摄影作品。要求:1. 采用专业相机拍摄(如佳能EOS R5),使用85mm f/1.4人像镜头,呈现柯达Portra 400胶片质感,8K超高清画质,HDR高动态范围,电影级打光效果;2. 画面应具有照片级真实感、超现实主义风格和高细节表现,确保光影、皮肤质感、服饰纹理与背景环境都贴近真实世界;3. 使用自然光影营造真实氛围,呈现raw and natural的原始自然感,具有authentic film snapshot的真实胶片质感;4. 整体需具备tactile feel触感质感和simulated texture模拟纹理细节,可以适度优化噪点与瑕疵,但不要改变主体特征或添加额外元素;5. 整体效果需像专业摄影棚拍摄的真实照片,具有电影级画质;6. 如果主体是人物脸部,脸部生成效果应参考欧美混血白人精致美丽帅气英俊的外观特征进行生成,保持精致立体的五官轮廓、健康光泽的肌肤质感、优雅的气质和自然的表情,确保面部特征协调美观。",
813
+ mode: "single"
654
814
  }
655
815
  ]).description("自定义风格命令配置")
656
816
  }),
@@ -674,6 +834,9 @@ function apply(ctx, config) {
674
834
  yunwuModelId: providerType === "yunwu" ? modelId || config.yunwuModelId : config.yunwuModelId,
675
835
  gptgodApiKey: config.gptgodApiKey,
676
836
  gptgodModelId: providerType === "gptgod" ? modelId || config.gptgodModelId : config.gptgodModelId,
837
+ geminiApiKey: config.geminiApiKey,
838
+ geminiModelId: providerType === "gemini" ? modelId || config.geminiModelId : config.geminiModelId,
839
+ geminiApiBase: config.geminiApiBase,
677
840
  apiTimeout: config.apiTimeout,
678
841
  logLevel: config.logLevel,
679
842
  logger,
@@ -1111,55 +1274,72 @@ function apply(ctx, config) {
1111
1274
  });
1112
1275
  }
1113
1276
  __name(recordUserUsage, "recordUserUsage");
1114
- async function getImageUrl(img, session) {
1115
- let url = null;
1116
- if (img) {
1117
- url = img.attrs?.src || null;
1118
- if (url) {
1119
- if (config.logLevel === "debug") {
1120
- logger.debug("从命令参数获取图片", { url });
1277
+ async function getInputData(session, imgParam, mode) {
1278
+ const collectedImages = [];
1279
+ let collectedText = "";
1280
+ if (imgParam) {
1281
+ if (typeof imgParam === "object" && imgParam.attrs?.src) {
1282
+ collectedImages.push(imgParam.attrs.src);
1283
+ } else if (typeof imgParam === "string") {
1284
+ if (imgParam.startsWith("http") || imgParam.startsWith("data:")) {
1285
+ collectedImages.push(imgParam);
1121
1286
  }
1122
- return url;
1123
1287
  }
1124
1288
  }
1125
- let elements = session.quote?.elements;
1126
- if (elements) {
1127
- const images2 = import_koishi.h.select(elements, "img");
1128
- if (images2.length > 0) {
1129
- if (images2.length > 1) {
1130
- await session.send('本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令');
1131
- return null;
1289
+ if (session.quote?.elements) {
1290
+ const quoteImages = import_koishi.h.select(session.quote.elements, "img");
1291
+ for (const img of quoteImages) {
1292
+ if (img.attrs.src) collectedImages.push(img.attrs.src);
1293
+ }
1294
+ }
1295
+ if (collectedImages.length > 0) {
1296
+ if (mode === "single") {
1297
+ if (collectedImages.length > 1) {
1298
+ return { error: '本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令' };
1299
+ }
1300
+ return { images: collectedImages };
1301
+ }
1302
+ return { images: collectedImages };
1303
+ }
1304
+ const promptMsg = mode === "single" ? "请在30秒内发送一张图片" : "请发送图片(发送纯文字结束,至少需要2张)";
1305
+ await session.send(promptMsg);
1306
+ while (true) {
1307
+ const msg = await session.prompt(mode === "multiple" ? 6e4 : 3e4);
1308
+ if (!msg) return { error: "等待超时" };
1309
+ const elements = import_koishi.h.parse(msg);
1310
+ const images = import_koishi.h.select(elements, "img");
1311
+ const textElements = import_koishi.h.select(elements, "text");
1312
+ const text = textElements.map((el) => el.attrs.content).join(" ").trim();
1313
+ if (images.length > 0) {
1314
+ for (const img of images) {
1315
+ collectedImages.push(img.attrs.src);
1132
1316
  }
1133
- url = images2[0].attrs.src;
1134
- if (config.logLevel === "debug") {
1135
- logger.debug("从引用消息获取图片", { url });
1317
+ if (mode === "single") {
1318
+ if (collectedImages.length > 1) {
1319
+ return { error: "本功能仅支持处理一张图片,检测到多张图片" };
1320
+ }
1321
+ if (text) collectedText = text;
1322
+ break;
1323
+ }
1324
+ if (text) {
1325
+ collectedText = text;
1326
+ break;
1136
1327
  }
1137
- return url;
1328
+ await session.send(`已收到 ${collectedImages.length} 张图片,继续发送或输入文字结束`);
1329
+ continue;
1330
+ }
1331
+ if (text) {
1332
+ if (collectedImages.length === 0) {
1333
+ await session.send("未检测到图片,请先发送图片");
1334
+ continue;
1335
+ }
1336
+ collectedText = text;
1337
+ break;
1138
1338
  }
1139
1339
  }
1140
- await session.send("请在30秒内发送一张图片");
1141
- const msg = await session.prompt(3e4);
1142
- if (!msg) {
1143
- await session.send("等待超时");
1144
- return null;
1145
- }
1146
- elements = import_koishi.h.parse(msg);
1147
- const images = import_koishi.h.select(elements, "img");
1148
- if (images.length === 0) {
1149
- await session.send("未检测到图片,请重试");
1150
- return null;
1151
- }
1152
- if (images.length > 1) {
1153
- await session.send('本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令');
1154
- return null;
1155
- }
1156
- url = images[0].attrs.src;
1157
- if (config.logLevel === "debug") {
1158
- logger.debug("从用户输入获取图片", { url });
1159
- }
1160
- return url;
1340
+ return { images: collectedImages, text: collectedText };
1161
1341
  }
1162
- __name(getImageUrl, "getImageUrl");
1342
+ __name(getInputData, "getInputData");
1163
1343
  async function requestProviderImages(prompt, imageUrls, numImages, requestContext) {
1164
1344
  const providerType = requestContext?.provider || config.provider;
1165
1345
  const targetModelId = requestContext?.modelId;
@@ -1174,9 +1354,9 @@ function apply(ctx, config) {
1174
1354
  return await providerInstance.generateImages(prompt, imageUrls, numImages);
1175
1355
  }
1176
1356
  __name(requestProviderImages, "requestProviderImages");
1177
- async function processImageWithTimeout(session, img, prompt, styleName, requestContext, displayInfo) {
1357
+ async function processImageWithTimeout(session, img, prompt, styleName, requestContext, displayInfo, mode = "single") {
1178
1358
  return Promise.race([
1179
- processImage(session, img, prompt, styleName, requestContext, displayInfo),
1359
+ processImage(session, img, prompt, styleName, requestContext, displayInfo, mode),
1180
1360
  new Promise(
1181
1361
  (_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1182
1362
  )
@@ -1184,11 +1364,11 @@ function apply(ctx, config) {
1184
1364
  const userId = session.userId;
1185
1365
  if (userId) activeTasks.delete(userId);
1186
1366
  logger.error("图像处理超时或失败", { userId, error });
1187
- return error.message === "命令执行超时" ? "图像处理超时,请重试" : "图像处理失败,请稍后重试";
1367
+ return error.message === "命令执行超时" ? "图像处理超时,请重试" : `图像处理失败:${error.message}`;
1188
1368
  });
1189
1369
  }
1190
1370
  __name(processImageWithTimeout, "processImageWithTimeout");
1191
- async function processImage(session, img, prompt, styleName, requestContext, displayInfo) {
1371
+ async function processImage(session, img, prompt, styleName, requestContext, displayInfo, mode = "single") {
1192
1372
  const userId = session.userId;
1193
1373
  if (activeTasks.has(userId)) {
1194
1374
  return "您有一个图像处理任务正在进行中,请等待完成";
@@ -1197,17 +1377,22 @@ function apply(ctx, config) {
1197
1377
  if (imageCount < 1 || imageCount > 4) {
1198
1378
  return "生成数量必须在 1-4 之间";
1199
1379
  }
1200
- const imageUrl = await getImageUrl(img, session);
1201
- if (!imageUrl) {
1202
- return;
1380
+ const inputResult = await getInputData(session, img, mode);
1381
+ if ("error" in inputResult) {
1382
+ return inputResult.error;
1383
+ }
1384
+ const { images: imageUrls, text: extraText } = inputResult;
1385
+ let finalPrompt = prompt;
1386
+ if (extraText) {
1387
+ finalPrompt += " " + extraText;
1203
1388
  }
1204
1389
  const providerType = requestContext?.provider || config.provider;
1205
1390
  const providerModelId = requestContext?.modelId || (providerType === "yunwu" ? config.yunwuModelId : config.gptgodModelId);
1206
1391
  logger.info("开始图像处理", {
1207
1392
  userId,
1208
- imageUrl,
1393
+ imageUrls,
1209
1394
  styleName,
1210
- prompt,
1395
+ prompt: finalPrompt,
1211
1396
  numImages: imageCount,
1212
1397
  provider: providerType,
1213
1398
  modelId: providerModelId
@@ -1229,7 +1414,7 @@ ${infoParts.join("\n")}`;
1229
1414
  await session.send(statusMessage);
1230
1415
  try {
1231
1416
  activeTasks.set(userId, "processing");
1232
- const images = await requestProviderImages(prompt, imageUrl, imageCount, requestContext);
1417
+ const images = await requestProviderImages(finalPrompt, imageUrls, imageCount, requestContext);
1233
1418
  if (images.length === 0) {
1234
1419
  activeTasks.delete(userId);
1235
1420
  return "图像处理失败:未能生成图片";
@@ -1246,8 +1431,8 @@ ${infoParts.join("\n")}`;
1246
1431
  } catch (error) {
1247
1432
  activeTasks.delete(userId);
1248
1433
  logger.error("图像处理失败", { userId, error });
1249
- if (error?.message && (error.message.includes("内容被安全策略拦截") || error.message.includes("生成失败") || error.message.includes("处理失败"))) {
1250
- return error.message;
1434
+ if (error?.message) {
1435
+ return `图像处理失败:${error.message}`;
1251
1436
  }
1252
1437
  return "图像处理失败,请稍后重试";
1253
1438
  }
@@ -1307,7 +1492,8 @@ ${infoParts.join("\n")}`;
1307
1492
  displayInfo.modelId = modifiers.modelMapping.modelId;
1308
1493
  displayInfo.modelDescription = modifiers.modelMapping.suffix || modifiers.modelMapping.modelId;
1309
1494
  }
1310
- return processImageWithTimeout(session, img, mergedPrompt, style.commandName, requestContext, displayInfo);
1495
+ const mode = style.mode || "single";
1496
+ return processImageWithTimeout(session, img, mergedPrompt, style.commandName, requestContext, displayInfo, mode);
1311
1497
  });
1312
1498
  logger.info(`已注册命令: ${style.commandName}`);
1313
1499
  }
@@ -1412,8 +1598,8 @@ Prompt: ${prompt}`);
1412
1598
  } catch (error) {
1413
1599
  activeTasks.delete(userId);
1414
1600
  logger.error("自定义图像处理失败", { userId, error });
1415
- if (error?.message && (error.message.includes("内容被安全策略拦截") || error.message.includes("生成失败") || error.message.includes("处理失败"))) {
1416
- return error.message;
1601
+ if (error?.message) {
1602
+ return `图像处理失败:${error.message}`;
1417
1603
  }
1418
1604
  return "图像处理失败,请稍后重试";
1419
1605
  }
@@ -1425,7 +1611,7 @@ Prompt: ${prompt}`);
1425
1611
  const userId = session.userId;
1426
1612
  if (userId) activeTasks.delete(userId);
1427
1613
  logger.error("自定义图像处理超时或失败", { userId, error });
1428
- return error.message === "命令执行超时" ? "图像处理超时,请重试" : "图像处理失败,请稍后重试";
1614
+ return error.message === "命令执行超时" ? "图像处理超时,请重试" : `图像处理失败:${error.message}`;
1429
1615
  });
1430
1616
  });
1431
1617
  ctx.command(COMMANDS.COMPOSE_IMAGE, "合成多张图片,使用自定义prompt控制合成效果").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }) => {
@@ -1518,8 +1704,8 @@ Prompt: ${prompt}`);
1518
1704
  } catch (error) {
1519
1705
  activeTasks.delete(userId);
1520
1706
  logger.error("图片合成失败", { userId, error });
1521
- if (error?.message && (error.message.includes("内容被安全策略拦截") || error.message.includes("生成失败") || error.message.includes("处理失败"))) {
1522
- return error.message;
1707
+ if (error?.message) {
1708
+ return `图片合成失败:${error.message}`;
1523
1709
  }
1524
1710
  return "图片合成失败,请稍后重试";
1525
1711
  }
@@ -1531,7 +1717,7 @@ Prompt: ${prompt}`);
1531
1717
  const userId = session.userId;
1532
1718
  if (userId) activeTasks.delete(userId);
1533
1719
  logger.error("图片合成超时或失败", { userId, error });
1534
- return error.message === "命令执行超时" ? "图片合成超时,请重试" : "图片合成失败,请稍后重试";
1720
+ return error.message === "命令执行超时" ? "图片合成超时,请重试" : `图片合成失败:${error.message}`;
1535
1721
  });
1536
1722
  });
1537
1723
  ctx.command(`${COMMANDS.RECHARGE} [content:text]`, "为用户充值次数(仅管理员)").action(async ({ session }, content) => {
@@ -0,0 +1,11 @@
1
+ import { ImageProvider, ProviderConfig } from './types';
2
+ export interface GeminiConfig extends ProviderConfig {
3
+ apiKey: string;
4
+ modelId: string;
5
+ apiBase?: string;
6
+ }
7
+ export declare class GeminiProvider implements ImageProvider {
8
+ private config;
9
+ constructor(config: GeminiConfig);
10
+ generateImages(prompt: string, imageUrls: string | string[], numImages: number): Promise<string[]>;
11
+ }
@@ -1,11 +1,14 @@
1
1
  import { ImageProvider } from './types';
2
- export type ProviderType = 'yunwu' | 'gptgod';
2
+ export type ProviderType = 'yunwu' | 'gptgod' | 'gemini';
3
3
  export interface ProviderFactoryConfig {
4
4
  provider: ProviderType;
5
5
  yunwuApiKey: string;
6
6
  yunwuModelId: string;
7
7
  gptgodApiKey: string;
8
8
  gptgodModelId: string;
9
+ geminiApiKey: string;
10
+ geminiModelId: string;
11
+ geminiApiBase: string;
9
12
  apiTimeout: number;
10
13
  logLevel: 'info' | 'debug';
11
14
  logger: any;
@@ -18,3 +21,4 @@ export declare function createImageProvider(config: ProviderFactoryConfig): Imag
18
21
  export { ImageProvider } from './types';
19
22
  export { YunwuProvider } from './yunwu';
20
23
  export { GptGodProvider } from './gptgod';
24
+ export { GeminiProvider } from './gemini';
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.2.14",
4
+ "version": "0.2.17",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [