Your Name 3a20c5a36d
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
新增RUXA生图服务商并打通聊天/画布链路
2026-02-07 20:09:31 +08:00

193 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }
);
}
}