Spaces:
Sleeping
Sleeping
| import { NextRequest, NextResponse } from 'next/server'; | |
| /** | |
| * Try-On API Route | |
| * | |
| * Two-step AI pipeline: | |
| * 1. NVIDIA Vision (llama-3.2-11b-vision) analyzes the user's photo and | |
| * creates a detailed text description of their appearance | |
| * 2. Pollinations.ai FLUX generates a photorealistic image from a combined | |
| * prompt (user description + product info) | |
| * | |
| * No API keys needed for Pollinations — it's a free public API. | |
| * NVIDIA API key is already set as an env var. | |
| */ | |
| const NVIDIA_API_URL = 'https://integrate.api.nvidia.com/v1/chat/completions'; | |
| const NVIDIA_API_KEY = process.env.NVIDIA_API_KEY || ''; | |
| const VISION_MODEL = 'meta/llama-3.2-11b-vision-instruct'; | |
| export async function POST(request: NextRequest) { | |
| try { | |
| const body = await request.json(); | |
| const { userImage, productName, productCategory, productPrompt } = body; | |
| if (!userImage) { | |
| return NextResponse.json({ success: false, error: 'User image is required' }, { status: 400 }); | |
| } | |
| if (!productName && !productPrompt) { | |
| return NextResponse.json({ success: false, error: 'Product info is required' }, { status: 400 }); | |
| } | |
| // ---- Step 1: Use NVIDIA Vision to describe the user's appearance ---- | |
| let userDescription = ''; | |
| if (NVIDIA_API_KEY) { | |
| try { | |
| const visionResp = await fetch(NVIDIA_API_URL, { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${NVIDIA_API_KEY}`, | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| model: VISION_MODEL, | |
| messages: [ | |
| { | |
| role: 'user', | |
| content: [ | |
| { | |
| type: 'text', | |
| text: 'Describe this person\'s physical appearance in detail for an AI image generation prompt. Include: gender, approximate age, skin tone, hair style and color, face shape, body type, and what they are currently wearing. Be concise but specific. Format: "A [age]-year-old [gender] with [skin tone] skin, [hair description], [body type]..."', | |
| }, | |
| { type: 'image_url', image_url: { url: userImage } }, | |
| ], | |
| }, | |
| ], | |
| max_tokens: 200, | |
| temperature: 0.5, | |
| }), | |
| }); | |
| if (visionResp.ok) { | |
| const visionData = await visionResp.json(); | |
| userDescription = visionData.choices?.[0]?.message?.content || ''; | |
| // Clean up the description | |
| userDescription = userDescription.replace(/^(Here is|Description:|The person)/i, '').trim(); | |
| } | |
| } catch (e) { | |
| console.error('Vision analysis failed, proceeding without:', e); | |
| } | |
| } | |
| // ---- Step 2: Build the generation prompt ---- | |
| let prompt: string; | |
| const cat = (productCategory || '').toLowerCase(); | |
| if (productPrompt) { | |
| prompt = productPrompt; | |
| } else if (userDescription) { | |
| // Combine user description with product | |
| if (cat.includes('fashion') || cat.includes('clothing')) { | |
| prompt = `${userDescription}, now wearing ${productName}, photorealistic commercial fashion photography, full body, studio lighting, natural pose, high quality fashion editorial`; | |
| } else if (cat.includes('beauty') || cat.includes('cosmetic')) { | |
| prompt = `${userDescription}, applying ${productName}, photorealistic beauty editorial photography, close-up face, studio lighting, natural makeup look`; | |
| } else if (cat.includes('accessor') || cat.includes('watch') || cat.includes('jewelry') || cat.includes('bag')) { | |
| prompt = `${userDescription}, wearing ${productName}, photorealistic commercial product photography, showcasing the product naturally, studio lighting`; | |
| } else if (cat.includes('shoe') || cat.includes('sneaker')) { | |
| prompt = `${userDescription}, wearing ${productName} on their feet, photorealistic, full body shot showing the shoes, studio lighting`; | |
| } else { | |
| prompt = `${userDescription}, holding ${productName}, photorealistic commercial photography, natural pose, studio lighting`; | |
| } | |
| } else { | |
| // Fallback without user description | |
| prompt = `A person wearing ${productName}, ${productCategory || ''}, photorealistic, commercial photography, studio lighting, high quality`; | |
| } | |
| // ---- Step 3: Generate image via Pollinations.ai (FLUX, free, no key) ---- | |
| const encodedPrompt = encodeURIComponent(prompt); | |
| const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=768&height=1344&model=flux&nologo=true&seed=${Math.floor(Math.random() * 1000000)}`; | |
| // Fetch the generated image | |
| const imageResp = await fetch(imageUrl, { | |
| method: 'GET', | |
| headers: { 'Accept': 'image/png, image/jpeg' }, | |
| }); | |
| if (!imageResp.ok) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: `Image generation failed: HTTP ${imageResp.status}`, | |
| }, { status: 500 }); | |
| } | |
| // Convert to base64 | |
| const imageBuffer = await imageResp.arrayBuffer(); | |
| const base64 = Buffer.from(imageBuffer).toString('base64'); | |
| const contentType = imageResp.headers.get('content-type') || 'image/png'; | |
| return NextResponse.json({ | |
| success: true, | |
| image: `data:${contentType};base64,${base64}`, | |
| description: userDescription || undefined, | |
| }); | |
| } catch (error) { | |
| console.error('Try-on API error:', error); | |
| return NextResponse.json({ | |
| success: false, | |
| error: error instanceof Error ? error.message : 'Unknown error', | |
| }, { status: 500 }); | |
| } | |
| } | |