koishi-plugin-aka-ai-generator 0.2.16 → 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 +4 -1
- package/lib/index.js +159 -2
- package/lib/providers/gemini.d.ts +11 -0
- package/lib/providers/index.d.ts +5 -1
- package/package.json +1 -1
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;
|
|
@@ -36,6 +36,9 @@ export interface Config {
|
|
|
36
36
|
yunwuModelId: string;
|
|
37
37
|
gptgodApiKey: string;
|
|
38
38
|
gptgodModelId: string;
|
|
39
|
+
geminiApiKey: string;
|
|
40
|
+
geminiModelId: string;
|
|
41
|
+
geminiApiBase: string;
|
|
39
42
|
modelMappings?: ModelMappingConfig[];
|
|
40
43
|
apiTimeout: number;
|
|
41
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
|
}
|
|
@@ -614,17 +763,22 @@ var Config = import_koishi.Schema.intersect([
|
|
|
614
763
|
import_koishi.Schema.object({
|
|
615
764
|
provider: import_koishi.Schema.union([
|
|
616
765
|
import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
|
|
617
|
-
import_koishi.Schema.const("gptgod").description("GPTGod 服务")
|
|
766
|
+
import_koishi.Schema.const("gptgod").description("GPTGod 服务"),
|
|
767
|
+
import_koishi.Schema.const("gemini").description("Google Gemini 原生")
|
|
618
768
|
]).default("yunwu").description("图像生成供应商"),
|
|
619
769
|
yunwuApiKey: import_koishi.Schema.string().description("云雾API密钥").role("secret").required(),
|
|
620
770
|
yunwuModelId: import_koishi.Schema.string().default("gemini-2.5-flash-image").description("云雾图像生成模型ID"),
|
|
621
771
|
gptgodApiKey: import_koishi.Schema.string().description("GPTGod API 密钥").role("secret").default(""),
|
|
622
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 基础地址"),
|
|
623
776
|
modelMappings: import_koishi.Schema.array(import_koishi.Schema.object({
|
|
624
777
|
suffix: import_koishi.Schema.string().required().description("指令后缀(例如 4K,对应输入 -4K)"),
|
|
625
778
|
provider: import_koishi.Schema.union([
|
|
626
779
|
import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
|
|
627
|
-
import_koishi.Schema.const("gptgod").description("GPTGod 服务")
|
|
780
|
+
import_koishi.Schema.const("gptgod").description("GPTGod 服务"),
|
|
781
|
+
import_koishi.Schema.const("gemini").description("Google Gemini 原生")
|
|
628
782
|
]).description("可选:覆盖供应商"),
|
|
629
783
|
modelId: import_koishi.Schema.string().required().description("触发该后缀时使用的模型 ID")
|
|
630
784
|
})).role("table").default([]).description("根据 -后缀切换模型/供应商"),
|
|
@@ -680,6 +834,9 @@ function apply(ctx, config) {
|
|
|
680
834
|
yunwuModelId: providerType === "yunwu" ? modelId || config.yunwuModelId : config.yunwuModelId,
|
|
681
835
|
gptgodApiKey: config.gptgodApiKey,
|
|
682
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,
|
|
683
840
|
apiTimeout: config.apiTimeout,
|
|
684
841
|
logLevel: config.logLevel,
|
|
685
842
|
logger,
|
|
@@ -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
|
+
}
|
package/lib/providers/index.d.ts
CHANGED
|
@@ -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';
|