193 lines
5.7 KiB
TypeScript
193 lines
5.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
||
import { auth } from "@/app/(auth)/auth";
|
||
import { getEnabledAiModelsWithProvider, getAiProviderById } from "@/lib/db/queries";
|
||
import { createGeminiImage } from "@/lib/ai/providers/gemini-image";
|
||
import { createAlibabaImage } from "@/lib/ai/providers/alibaba-image";
|
||
import { createRuxaImage } from "@/lib/ai/providers/ruxa-image";
|
||
|
||
export const maxDuration = 120;
|
||
|
||
interface GenerateImageRequest {
|
||
prompt: string;
|
||
negativePrompt?: string;
|
||
model?: string; // 模型 ID
|
||
aspectRatio?: string; // 宽高比 "1:1", "16:9" 等
|
||
imageCount?: number; // 生成数量
|
||
referenceImage?: string; // 参考图片(用于图生图)
|
||
}
|
||
|
||
interface ParsedImage {
|
||
thumbnailUrl: string;
|
||
originalUrl: string;
|
||
}
|
||
|
||
function parseImagesFromMarkdown(markdown: string): ParsedImage[] {
|
||
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
||
const matches = [...markdown.matchAll(imageRegex)];
|
||
const parsed: ParsedImage[] = [];
|
||
|
||
for (const match of matches) {
|
||
const altText = match[1] || "";
|
||
const thumbnailUrl = match[2];
|
||
let originalUrl = thumbnailUrl;
|
||
|
||
if (altText.includes("thumb|||")) {
|
||
const content = altText.substring(altText.indexOf("thumb|||") + 8);
|
||
const parts = content.split("|||");
|
||
originalUrl = parts[0] || thumbnailUrl;
|
||
} else if (altText.includes("thumb$$")) {
|
||
const content = altText.substring(altText.indexOf("thumb$$") + 7);
|
||
const parts = content.split("$$");
|
||
originalUrl = parts[0] || thumbnailUrl;
|
||
} else if (altText.includes("thumb|")) {
|
||
const content = altText.substring(altText.indexOf("thumb|") + 6);
|
||
const parts = content.split("|");
|
||
originalUrl = parts[0] || thumbnailUrl;
|
||
}
|
||
|
||
parsed.push({ thumbnailUrl, originalUrl });
|
||
}
|
||
|
||
return parsed;
|
||
}
|
||
|
||
export async function POST(request: Request) {
|
||
try {
|
||
const session = await auth();
|
||
if (!session?.user?.id) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "未登录" },
|
||
{ status: 401 }
|
||
);
|
||
}
|
||
|
||
const body: GenerateImageRequest = await request.json();
|
||
const { prompt, negativePrompt, model, aspectRatio = "1:1", imageCount = 1, referenceImage } = body;
|
||
|
||
if (!prompt) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "请输入提示词" },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 获取可用的图片生成模型(筛选 supportsImageGen 的模型)
|
||
const allModels = await getEnabledAiModelsWithProvider();
|
||
const imageModels = allModels.filter(m => m.model.supportsImageGen);
|
||
|
||
if (!imageModels || imageModels.length === 0) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "暂无可用的图片生成模型" },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 选择模型(如果指定了 model,使用指定的;否则使用默认的)
|
||
let selectedModel = model
|
||
? imageModels.find(m => m.model.modelId === model)
|
||
: imageModels.find(m => m.model.isDefault) || imageModels[0];
|
||
|
||
if (!selectedModel) {
|
||
selectedModel = imageModels[0];
|
||
}
|
||
|
||
const provider = await getAiProviderById({ id: selectedModel.model.providerId });
|
||
if (!provider) {
|
||
return NextResponse.json(
|
||
{ success: false, error: "模型提供商配置错误" },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
|
||
// 构建消息
|
||
const messages = [{
|
||
role: "user" as const,
|
||
content: referenceImage
|
||
? [
|
||
{ type: "text" as const, text: prompt },
|
||
{ type: "image_url" as const, image_url: { url: referenceImage } },
|
||
]
|
||
: prompt,
|
||
}];
|
||
|
||
// 图片参数
|
||
const imageParams = {
|
||
aspectRatio,
|
||
imageSize: "2K",
|
||
imageCount,
|
||
negativePrompt,
|
||
};
|
||
|
||
let imageMarkdown = "";
|
||
|
||
// 根据提供商类型调用不同的生成器
|
||
if (provider.type === "gemini") {
|
||
const geminiProvider = createGeminiImage({
|
||
apiKey: provider.apiKey,
|
||
baseURL: provider.baseUrl,
|
||
});
|
||
|
||
const model = geminiProvider.chat(selectedModel.model.modelId);
|
||
const result = await model.doGenerate({
|
||
messages,
|
||
imageParams,
|
||
});
|
||
imageMarkdown = result.text || "";
|
||
} else if (provider.type === "alibaba-image") {
|
||
const alibabaProvider = createAlibabaImage({
|
||
apiKey: provider.apiKey,
|
||
baseURL: provider.baseUrl,
|
||
});
|
||
|
||
const model = alibabaProvider.chat(selectedModel.model.modelId);
|
||
const result = await model.doGenerate({
|
||
messages,
|
||
imageParams,
|
||
});
|
||
imageMarkdown = result.text || "";
|
||
} else if (provider.type === "ruxa-image") {
|
||
const ruxaProvider = createRuxaImage({
|
||
apiKey: provider.apiKey,
|
||
baseURL: provider.baseUrl,
|
||
});
|
||
|
||
const model = ruxaProvider.chat(selectedModel.model.modelId);
|
||
const result = await model.doGenerate({
|
||
messages,
|
||
imageParams,
|
||
});
|
||
imageMarkdown = result.text || "";
|
||
} else {
|
||
return NextResponse.json(
|
||
{ success: false, error: "不支持的图片生成模型类型" },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
const parsedImages = parseImagesFromMarkdown(imageMarkdown);
|
||
|
||
if (parsedImages.length > 0) {
|
||
return NextResponse.json({
|
||
success: true,
|
||
data: {
|
||
images: parsedImages,
|
||
model: selectedModel.model.displayName,
|
||
prompt,
|
||
}
|
||
});
|
||
} else {
|
||
return NextResponse.json(
|
||
{ success: false, error: "图片生成失败" },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error("[Canvas Generate Image] Error:", error);
|
||
return NextResponse.json(
|
||
{ success: false, error: error instanceof Error ? error.message : "生成失败" },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|