koishi-plugin-aka-ai-generator 0.0.2 → 0.0.5

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
@@ -37,6 +37,7 @@ export interface Config {
37
37
  rateLimitMax: number;
38
38
  adminUsers: string[];
39
39
  styles: StyleConfig[];
40
+ logLevel: 'info' | 'debug';
40
41
  }
41
42
  export interface RechargeRecord {
42
43
  id: string;
package/lib/index.js CHANGED
@@ -195,7 +195,9 @@ async function downloadImageAsBase642(ctx, url, timeout, logger) {
195
195
  mimeType = "image/gif";
196
196
  }
197
197
  }
198
- logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
198
+ if (logger) {
199
+ logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
200
+ }
199
201
  return { data: base64, mimeType };
200
202
  } catch (error) {
201
203
  logger.error("下载图片失败", { url, error });
@@ -203,14 +205,23 @@ async function downloadImageAsBase642(ctx, url, timeout, logger) {
203
205
  }
204
206
  }
205
207
  __name(downloadImageAsBase642, "downloadImageAsBase64");
206
- function parseGptGodResponse(response) {
208
+ function parseGptGodResponse(response, logger) {
207
209
  try {
208
210
  const images = [];
209
211
  if (Array.isArray(response.images)) {
212
+ if (logger) {
213
+ logger.debug("从 response.images 数组提取图片", { count: response.images.length });
214
+ }
210
215
  return response.images;
211
216
  }
212
- if (response.image && typeof response.image === "string" && response.image.startsWith("data:")) {
213
- images.push(response.image);
217
+ if (response.image && typeof response.image === "string") {
218
+ if (response.image.startsWith("data:") || response.image.startsWith("http")) {
219
+ if (logger) {
220
+ logger.debug("从 response.image 提取图片");
221
+ }
222
+ images.push(response.image);
223
+ return images;
224
+ }
214
225
  }
215
226
  if (response?.choices?.length > 0) {
216
227
  const firstChoice = response.choices[0];
@@ -219,33 +230,123 @@ function parseGptGodResponse(response) {
219
230
  if (typeof messageContent === "string") {
220
231
  contentText = messageContent;
221
232
  } else if (Array.isArray(messageContent)) {
222
- contentText = messageContent.map((part) => part?.text || "").join("\n");
233
+ for (const part of messageContent) {
234
+ if (part?.type === "image_url" && part?.image_url?.url) {
235
+ if (logger) {
236
+ logger.debug("从 content 数组的 image_url 提取图片");
237
+ }
238
+ images.push(part.image_url.url);
239
+ } else if (part?.type === "text" && part?.text) {
240
+ contentText += part.text + "\n";
241
+ } else if (part?.text) {
242
+ contentText += part.text + "\n";
243
+ }
244
+ }
223
245
  } else if (messageContent?.text) {
224
246
  contentText = messageContent.text;
225
247
  }
226
- const mdImageRegex = /!\[.*?\]\((https?:\/\/[^\)]+)\)/g;
227
- let match;
228
- while ((match = mdImageRegex.exec(contentText)) !== null) {
229
- images.push(match[1]);
248
+ if (images.length > 0) {
249
+ return images;
230
250
  }
231
- if (images.length === 0) {
232
- const urlRegex = /(https?:\/\/[^\s"')]+\.(?:png|jpg|jpeg|webp|gif))/gi;
233
- let urlMatch;
234
- while ((urlMatch = urlRegex.exec(contentText)) !== null) {
235
- images.push(urlMatch[1]);
251
+ if (contentText) {
252
+ const mdImageRegex = /!\[.*?\]\((https?:\/\/[^\)]+)\)/g;
253
+ let match;
254
+ while ((match = mdImageRegex.exec(contentText)) !== null) {
255
+ images.push(match[1]);
256
+ }
257
+ if (images.length === 0) {
258
+ const urlRegex = /(https?:\/\/[^\s"')<>]+\.(?:png|jpg|jpeg|webp|gif|bmp))/gi;
259
+ let urlMatch;
260
+ while ((urlMatch = urlRegex.exec(contentText)) !== null) {
261
+ images.push(urlMatch[1]);
262
+ }
263
+ }
264
+ if (images.length === 0 && contentText.trim().startsWith("http")) {
265
+ const trimmedUrl = contentText.trim().split(/\s/)[0];
266
+ if (trimmedUrl.match(/^https?:\/\//)) {
267
+ images.push(trimmedUrl);
268
+ }
269
+ }
270
+ const dataUrlRegex = /(data:image\/[^;]+;base64,[^\s"')<>]+)/gi;
271
+ let dataUrlMatch;
272
+ while ((dataUrlMatch = dataUrlRegex.exec(contentText)) !== null) {
273
+ images.push(dataUrlMatch[1]);
274
+ }
275
+ }
276
+ if (images.length === 0 && firstChoice.message) {
277
+ if (firstChoice.message.image_url) {
278
+ if (logger) {
279
+ logger.debug("从 message.image_url 提取图片");
280
+ }
281
+ images.push(firstChoice.message.image_url);
282
+ }
283
+ if (Array.isArray(firstChoice.message.images)) {
284
+ if (logger) {
285
+ logger.debug("从 message.images 数组提取图片", { count: firstChoice.message.images.length });
286
+ }
287
+ return firstChoice.message.images;
288
+ }
289
+ }
290
+ }
291
+ if (images.length === 0) {
292
+ if (response.data) {
293
+ if (Array.isArray(response.data)) {
294
+ const dataImages = response.data.filter(
295
+ (item) => item?.url || item?.image_url || typeof item === "string" && (item.startsWith("http") || item.startsWith("data:"))
296
+ ).map((item) => item?.url || item?.image_url || item);
297
+ if (dataImages.length > 0) {
298
+ if (logger) {
299
+ logger.debug("从 response.data 数组提取图片", { count: dataImages.length });
300
+ }
301
+ return dataImages;
302
+ }
303
+ } else if (response.data.url || response.data.image_url) {
304
+ if (logger) {
305
+ logger.debug("从 response.data 提取图片");
306
+ }
307
+ images.push(response.data.url || response.data.image_url);
308
+ }
309
+ }
310
+ if (response.result) {
311
+ if (Array.isArray(response.result)) {
312
+ const resultImages = response.result.filter(
313
+ (item) => item?.url || item?.image_url || typeof item === "string" && (item.startsWith("http") || item.startsWith("data:"))
314
+ ).map((item) => item?.url || item?.image_url || item);
315
+ if (resultImages.length > 0) {
316
+ if (logger) {
317
+ logger.debug("从 response.result 数组提取图片", { count: resultImages.length });
318
+ }
319
+ return resultImages;
320
+ }
321
+ } else if (typeof response.result === "string" && (response.result.startsWith("http") || response.result.startsWith("data:"))) {
322
+ if (logger) {
323
+ logger.debug("从 response.result 提取图片");
324
+ }
325
+ images.push(response.result);
236
326
  }
237
327
  }
238
- if (images.length === 0 && contentText.trim().startsWith("http")) {
239
- images.push(contentText.trim());
328
+ }
329
+ if (images.length > 0) {
330
+ if (logger) {
331
+ logger.debug("成功提取图片", { count: images.length });
240
332
  }
241
- const dataUrlRegex = /(data:image\/[^;]+;base64,[^\s"')]+)/gi;
242
- let dataUrlMatch;
243
- while ((dataUrlMatch = dataUrlRegex.exec(contentText)) !== null) {
244
- images.push(dataUrlMatch[1]);
333
+ } else {
334
+ if (logger) {
335
+ logger.warn("未能从响应中提取图片", {
336
+ responseStructure: {
337
+ hasChoices: !!response?.choices,
338
+ hasImages: !!response?.images,
339
+ hasImage: !!response?.image,
340
+ hasData: !!response?.data,
341
+ hasResult: !!response?.result,
342
+ keys: Object.keys(response || {})
343
+ }
344
+ });
245
345
  }
246
346
  }
247
347
  return images;
248
348
  } catch (error) {
349
+ logger?.error("解析响应时出错", { error });
249
350
  return [];
250
351
  }
251
352
  }
@@ -265,7 +366,9 @@ var GptGodProvider = class {
265
366
  if (!this.config.apiKey) {
266
367
  throw new Error("GPTGod 配置不完整,请检查 API Key");
267
368
  }
268
- logger.debug("调用 GPTGod 图像编辑 API", { prompt, imageCount: urls.length, numImages });
369
+ if (this.config.logLevel === "debug") {
370
+ logger.debug("调用 GPTGod 图像编辑 API", { prompt, imageCount: urls.length, numImages });
371
+ }
269
372
  const contentParts = [
270
373
  {
271
374
  type: "text",
@@ -312,9 +415,62 @@ var GptGodProvider = class {
312
415
  }
313
416
  );
314
417
  logger.success("GPTGod 图像编辑 API 调用成功");
315
- const images = parseGptGodResponse(response);
418
+ if (response?.choices?.length > 0) {
419
+ const firstChoice = response.choices[0];
420
+ const messageContent = firstChoice.message?.content;
421
+ let errorMessage = "";
422
+ if (typeof messageContent === "string") {
423
+ errorMessage = messageContent;
424
+ } else if (Array.isArray(messageContent)) {
425
+ const textParts = messageContent.filter((part) => part?.type === "text" && part?.text).map((part) => part.text).join(" ");
426
+ errorMessage = textParts;
427
+ } else if (messageContent?.text) {
428
+ errorMessage = messageContent.text;
429
+ }
430
+ 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"))) {
431
+ logger.error("内容被 Google Gemini 政策拦截", {
432
+ errorMessage: errorMessage.substring(0, 200),
433
+ finishReason: firstChoice.finish_reason
434
+ });
435
+ throw new Error("内容被安全策略拦截");
436
+ }
437
+ if (errorMessage && (errorMessage.toLowerCase().includes("error") || errorMessage.toLowerCase().includes("failed") || errorMessage.toLowerCase().includes("blocked")) && !errorMessage.match(/https?:\/\//)) {
438
+ logger.error("API 返回错误消息", {
439
+ errorMessage: errorMessage.substring(0, 200),
440
+ finishReason: firstChoice.finish_reason
441
+ });
442
+ const shortError = errorMessage.length > 50 ? errorMessage.substring(0, 50) + "..." : errorMessage;
443
+ throw new Error(`处理失败:${shortError}`);
444
+ }
445
+ }
446
+ if (this.config.logLevel === "debug") {
447
+ logger.debug("GPTGod API 响应结构", {
448
+ hasChoices: !!response?.choices,
449
+ choicesLength: response?.choices?.length,
450
+ hasImages: !!response?.images,
451
+ hasImage: !!response?.image,
452
+ responseKeys: Object.keys(response || {}),
453
+ 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"
454
+ });
455
+ }
456
+ const images = parseGptGodResponse(response, this.config.logLevel === "debug" ? logger : null);
316
457
  if (images.length < numImages) {
317
- logger.warn("生成的图片数量不足", { requested: numImages, received: images.length });
458
+ const warnData = {
459
+ requested: numImages,
460
+ received: images.length
461
+ };
462
+ if (this.config.logLevel === "debug") {
463
+ warnData.responsePreview = JSON.stringify(response).substring(0, 500);
464
+ }
465
+ logger.warn("生成的图片数量不足", warnData);
466
+ if (images.length === 0 && response?.choices?.[0]?.message?.content) {
467
+ const content = response.choices[0].message.content;
468
+ const contentText = typeof content === "string" ? content : Array.isArray(content) ? content.map((p) => p?.text || "").join(" ") : "";
469
+ if (contentText && !contentText.match(/https?:\/\//)) {
470
+ const shortError = contentText.length > 50 ? contentText.substring(0, 50) + "..." : contentText;
471
+ throw new Error(`生成失败:${shortError}`);
472
+ }
473
+ }
318
474
  }
319
475
  return images;
320
476
  } catch (error) {
@@ -337,6 +493,7 @@ function createImageProvider(config) {
337
493
  apiKey: config.yunwuApiKey,
338
494
  modelId: config.yunwuModelId,
339
495
  apiTimeout: config.apiTimeout,
496
+ logLevel: config.logLevel,
340
497
  logger: config.logger,
341
498
  ctx: config.ctx
342
499
  });
@@ -345,6 +502,7 @@ function createImageProvider(config) {
345
502
  apiKey: config.gptgodApiKey,
346
503
  modelId: config.gptgodModelId,
347
504
  apiTimeout: config.apiTimeout,
505
+ logLevel: config.logLevel,
348
506
  logger: config.logger,
349
507
  ctx: config.ctx
350
508
  });
@@ -384,7 +542,12 @@ var Config = import_koishi.Schema.intersect([
384
542
  rateLimitWindow: import_koishi.Schema.number().default(300).min(60).max(3600).description("限流时间窗口(秒)"),
385
543
  rateLimitMax: import_koishi.Schema.number().default(3).min(1).max(20).description("限流窗口内最大调用次数"),
386
544
  // 管理员设置
387
- adminUsers: import_koishi.Schema.array(import_koishi.Schema.string()).default([]).description("管理员用户ID列表(不受每日使用限制)")
545
+ adminUsers: import_koishi.Schema.array(import_koishi.Schema.string()).default([]).description("管理员用户ID列表(不受每日使用限制)"),
546
+ // 日志级别设置
547
+ logLevel: import_koishi.Schema.union([
548
+ import_koishi.Schema.const("info").description("普通信息"),
549
+ import_koishi.Schema.const("debug").description("完整的debug信息")
550
+ ]).default("info").description("日志输出详细程度")
388
551
  }),
389
552
  // 自定义风格命令配置
390
553
  import_koishi.Schema.object({
@@ -432,6 +595,7 @@ function apply(ctx, config) {
432
595
  gptgodApiKey: config.gptgodApiKey,
433
596
  gptgodModelId: config.gptgodModelId,
434
597
  apiTimeout: config.apiTimeout,
598
+ logLevel: config.logLevel,
435
599
  logger,
436
600
  ctx
437
601
  });
@@ -688,7 +852,9 @@ function apply(ctx, config) {
688
852
  if (img) {
689
853
  url = img.attrs?.src || null;
690
854
  if (url) {
691
- logger.debug("从命令参数获取图片", { url });
855
+ if (config.logLevel === "debug") {
856
+ logger.debug("从命令参数获取图片", { url });
857
+ }
692
858
  return url;
693
859
  }
694
860
  }
@@ -701,7 +867,9 @@ function apply(ctx, config) {
701
867
  return null;
702
868
  }
703
869
  url = images2[0].attrs.src;
704
- logger.debug("从引用消息获取图片", { url });
870
+ if (config.logLevel === "debug") {
871
+ logger.debug("从引用消息获取图片", { url });
872
+ }
705
873
  return url;
706
874
  }
707
875
  }
@@ -722,7 +890,9 @@ function apply(ctx, config) {
722
890
  return null;
723
891
  }
724
892
  url = images[0].attrs.src;
725
- logger.debug("从用户输入获取图片", { url });
893
+ if (config.logLevel === "debug") {
894
+ logger.debug("从用户输入获取图片", { url });
895
+ }
726
896
  return url;
727
897
  }
728
898
  __name(getImageUrl, "getImageUrl");
@@ -816,7 +986,7 @@ function apply(ctx, config) {
816
986
  if (activeTasks.has(userId)) {
817
987
  return "您有一个图像处理任务正在进行中,请等待完成";
818
988
  }
819
- await session.send('请发送一张图片和prompt,支持两种方式:\n1. 同时发送:[图片] + prompt描述\n2. 分步发送:先发送一张图片,再发送prompt文字\n\n例如:[图片] 让这张图片变成油画风格\n\n注意:本功能仅支持处理一张图片,多张图片请使用"合成图像"命令');
989
+ await session.send('图片+描述\n\n多张图片使用"合成图像"指令');
820
990
  const collectedImages = [];
821
991
  let prompt = "";
822
992
  while (true) {
@@ -921,7 +1091,7 @@ Prompt: ${prompt}`);
921
1091
  if (activeTasks.has(userId)) {
922
1092
  return "您有一个图像处理任务正在进行中,请等待完成";
923
1093
  }
924
- await session.send("请发送多张图片和prompt,支持两种方式:\n1. 同时发送:[图片1] [图片2]... + prompt描述\n2. 分步发送:先发送多张图片,再发送prompt文字\n\n例如:[图片1] [图片2] 将这两张图片合成一张");
1094
+ await session.send("多张图片+描述");
925
1095
  const collectedImages = [];
926
1096
  let prompt = "";
927
1097
  while (true) {
@@ -7,6 +7,7 @@ export interface ProviderFactoryConfig {
7
7
  gptgodApiKey: string;
8
8
  gptgodModelId: string;
9
9
  apiTimeout: number;
10
+ logLevel: 'info' | 'debug';
10
11
  logger: any;
11
12
  ctx: any;
12
13
  }
@@ -10,6 +10,7 @@ export interface ImageProvider {
10
10
  }
11
11
  export interface ProviderConfig {
12
12
  apiTimeout: number;
13
+ logLevel: 'info' | 'debug';
13
14
  logger: any;
14
15
  ctx: any;
15
16
  }
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.0.2",
4
+ "version": "0.0.5",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [