Spaces:
Sleeping
Sleeping
File size: 5,615 Bytes
bea55e2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 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 });
}
}
|