diff --git a/app/api/scene-fusion/route.ts b/app/api/scene-fusion/route.ts index 6fc654931..05fbe061e 100644 --- a/app/api/scene-fusion/route.ts +++ b/app/api/scene-fusion/route.ts @@ -2,7 +2,7 @@ * 场景裂变 API * * 将一张产品图批量融合到多个场景图中 - * 使用阿里云千问图片编辑模型 + * 支持多种生图模型:阿里生图、Gemini、既梦 */ import { NextResponse } from "next/server"; @@ -13,6 +13,9 @@ import { generatedImage } from "@/lib/db/schema"; import { getEnabledAiModelsWithProvider, getSystemSetting } from "@/lib/db/queries"; import { uploadFile } from "@/lib/storage/service"; import sharp from "sharp"; +import { createAlibabaImage } from "@/lib/ai/providers/alibaba-image"; +import { createGeminiImage } from "@/lib/ai/providers/gemini-image"; +import { createVolcanoVision } from "@/lib/ai/providers/volcano-vision"; // 请求体 Schema const sceneFusionRequestSchema = z.object({ @@ -336,7 +339,200 @@ function buildPrompt( } /** - * 处理单个场景 + * 通用场景处理函数 - 根据 provider 类型调用不同的实现 + */ +async function processSceneWithProvider( + productImageUrl: string, + sceneImageUrl: string, + productName: string | undefined, + targetName: string | undefined, + customPrompt: string | undefined, + defaultPrompt: string, + provider: { type: string; baseUrl: string; apiKey: string }, + modelId: string, + aspectRatio?: string, + imageSize?: string +): Promise<{ thumbnail: string; original: string; prompt: string }> { + // 构建智能提示词 + const prompt = buildPrompt(defaultPrompt, productName, targetName, customPrompt); + + console.log(`[SceneFusion] Built prompt: ${prompt}`); + console.log(`[SceneFusion] Provider type: ${provider.type}`); + + // 根据 provider 类型选择不同的处理逻辑 + switch (provider.type) { + case "alibaba-image": + return processWithAlibabaImage(productImageUrl, sceneImageUrl, prompt, provider, modelId, aspectRatio, imageSize); + + case "gemini": + return processWithGemini(productImageUrl, sceneImageUrl, prompt, provider, modelId, aspectRatio, imageSize); + + case "volcano-vision": + return processWithVolcanoVision(productImageUrl, sceneImageUrl, prompt, provider, modelId, aspectRatio, imageSize); + + default: + throw new Error(`不支持的模型类型: ${provider.type}`); + } +} + +/** + * 使用阿里生图模型处理 + */ +async function processWithAlibabaImage( + productImageUrl: string, + sceneImageUrl: string, + prompt: string, + provider: { baseUrl: string; apiKey: string }, + modelId: string, + aspectRatio?: string, + imageSize?: string +): Promise<{ thumbnail: string; original: string; prompt: string }> { + // 计算尺寸 + const size = calculateSize(aspectRatio || "auto", imageSize || "2K"); + + // 调用阿里生图 API + const generatedImageUrl = await callAlibabaImageAPI( + provider.baseUrl, + provider.apiKey, + modelId, + productImageUrl, + sceneImageUrl, + prompt, + size + ); + + // 下载并上传到 S3 + const uploaded = await downloadAndUploadToS3(generatedImageUrl, "scene-fusion-alibaba"); + + return { ...uploaded, prompt }; +} + +/** + * 使用 Gemini 模型处理 + */ +async function processWithGemini( + productImageUrl: string, + sceneImageUrl: string, + prompt: string, + provider: { baseUrl: string; apiKey: string }, + modelId: string, + aspectRatio?: string, + imageSize?: string +): Promise<{ thumbnail: string; original: string; prompt: string }> { + console.log(`[SceneFusion][Gemini] Processing with model: ${modelId}`); + + // 创建 Gemini provider + const geminiProvider = createGeminiImage({ + baseURL: provider.baseUrl, + apiKey: provider.apiKey, + }); + + // 构建消息格式(Gemini 使用 OpenAI 兼容格式) + const messages = [ + { + role: "user", + content: [ + { type: "image_url", image_url: { url: productImageUrl } }, + { type: "image_url", image_url: { url: sceneImageUrl } }, + { type: "text", text: prompt }, + ], + }, + ]; + + // 调用 Gemini API + const result = await geminiProvider.chat(modelId).doGenerate({ + messages, + imageParams: { + aspectRatio: aspectRatio || "auto", + imageSize: imageSize || "2K", + imageCount: 1, + }, + }); + + // 解析返回的 Markdown 格式图片 + const { thumbnail, original } = parseImageFromMarkdown(result.text); + + return { thumbnail, original, prompt }; +} + +/** + * 使用既梦/火山模型处理 + */ +async function processWithVolcanoVision( + productImageUrl: string, + sceneImageUrl: string, + prompt: string, + provider: { baseUrl: string; apiKey: string }, + modelId: string, + aspectRatio?: string, + imageSize?: string +): Promise<{ thumbnail: string; original: string; prompt: string }> { + console.log(`[SceneFusion][Volcano] Processing with model: ${modelId}`); + + // 创建 Volcano Vision provider + const volcanoProvider = createVolcanoVision({ + baseURL: provider.baseUrl, + apiKey: provider.apiKey, + }); + + // 构建消息格式 + const messages = [ + { + role: "user", + content: [ + { type: "image_url", image_url: { url: productImageUrl } }, + { type: "image_url", image_url: { url: sceneImageUrl } }, + { type: "text", text: prompt }, + ], + }, + ]; + + // 调用 Volcano Vision API + const result = await volcanoProvider.chat(modelId).doGenerate({ + messages, + imageParams: { + aspectRatio: aspectRatio || "auto", + imageSize: imageSize || "2K", + imageCount: 1, + }, + }); + + // 解析返回的 Markdown 格式图片 + const { thumbnail, original } = parseImageFromMarkdown(result.text); + + return { thumbnail, original, prompt }; +} + +/** + * 从 Markdown 格式解析图片 URL + * 格式: ![thumb|||originalUrl|||size](thumbnailUrl) + */ +function parseImageFromMarkdown(markdown: string): { thumbnail: string; original: string } { + console.log(`[SceneFusion] Parsing markdown: ${markdown.substring(0, 200)}...`); + + // 匹配 ![thumb|||originalUrl|||size](thumbnailUrl) 格式 + const match = markdown.match(/!\[thumb\|\|\|([^|]+)\|\|\|[^\]]*\]\(([^)]+)\)/); + if (match) { + return { + original: match[1], + thumbnail: match[2], + }; + } + + // 尝试匹配普通图片格式 ![alt](url) + const simpleMatch = markdown.match(/!\[[^\]]*\]\(([^)]+)\)/); + if (simpleMatch) { + return { + original: simpleMatch[1], + thumbnail: simpleMatch[1], + }; + } + + throw new Error("无法从响应中解析图片 URL"); +} + +/** + * 处理单个场景 (保留旧函数用于兼容) */ async function processScene( productImageUrl: string, @@ -491,6 +687,7 @@ export async function POST(request: Request) { console.log(`[SceneFusion] Scene Image URL: ${scene.imageUrl.substring(0, 80)}...`); console.log(`[SceneFusion] Target Name: ${scene.targetName || '(not specified)'}`); console.log(`[SceneFusion] Custom Prompt: ${scene.customPrompt || '(none)'}`); + console.log(`[SceneFusion] Provider Type: ${provider.type}`); // 如果不是第一张图,等待5秒 if (i > 0) { @@ -499,17 +696,18 @@ export async function POST(request: Request) { } try { - const result = await processScene( + // 使用新的通用处理函数,支持多种模型类型 + const result = await processSceneWithProvider( productImageUrl, scene.imageUrl, productName, scene.targetName, scene.customPrompt, config.defaultPrompt, - provider.baseUrl, - provider.apiKey, + { type: provider.type, baseUrl: provider.baseUrl, apiKey: provider.apiKey }, model.modelId, - size + aspectRatio, + imageSize ); // 将分辨率等级用于数据库存储