feat: mockup generation API route using gpt-image-1
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { NextRequest } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { readFile } from 'fs/promises'
|
||||
import { getTemplateImagePath, listTemplates } from '@/lib/mockupTemplates'
|
||||
import type { MockupGenerateRequest } from '@/types/mockup'
|
||||
|
||||
function buildPrompt(req: MockupGenerateRequest): string {
|
||||
const familyContext: Record<string, string> = {
|
||||
'Menuiserie': 'door installed in a residential entrance or hallway, visible wall and floor context',
|
||||
'Baies vitrées': 'bay window or sliding door installed in a living room with garden view',
|
||||
'Carrelage': 'tiles covering the floor or wall of a modern room',
|
||||
}
|
||||
const context = familyContext[req.family] ?? 'product installed in an appropriate room setting'
|
||||
const dimClause = req.dimensions
|
||||
? ` The product dimensions are exactly ${req.dimensions} — preserve these exact proportions, do NOT resize or distort the product to fit the scene.`
|
||||
: ''
|
||||
const instructionClause = req.instruction ? ` Additional instruction: ${req.instruction}.` : ''
|
||||
|
||||
return `Replace the product in the reference scene with the provided product image (${req.productName}). Show the ${context}.${dimClause} Keep the product's exact design, texture, colors and proportions identical to the provided image. Maintain realistic perspective and lighting consistent with the scene.${instructionClause}`
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session) return Response.json({ error: 'Non authentifié' }, { status: 401 })
|
||||
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
if (!apiKey) return Response.json({ error: 'OPENAI_API_KEY manquant' }, { status: 500 })
|
||||
|
||||
const body = await req.json() as MockupGenerateRequest
|
||||
|
||||
const templates = await listTemplates()
|
||||
const template = templates.find(t => t.id === body.templateId)
|
||||
if (!template) return Response.json({ error: 'Template introuvable' }, { status: 404 })
|
||||
|
||||
const templateBuffer = await readFile(getTemplateImagePath(template.filename))
|
||||
const templateExt = template.filename.split('.').pop() ?? 'jpg'
|
||||
const templateMime = templateExt === 'png' ? 'image/png' : 'image/jpeg'
|
||||
|
||||
const productBuffer = Buffer.from(body.productImageBase64, 'base64')
|
||||
|
||||
const form = new FormData()
|
||||
form.append('model', 'gpt-image-1')
|
||||
form.append('prompt', buildPrompt(body))
|
||||
form.append('n', '1')
|
||||
form.append('size', '1024x1024')
|
||||
form.append('image[]', new Blob([templateBuffer], { type: templateMime }), `template.${templateExt}`)
|
||||
form.append('image[]', new Blob([productBuffer], { type: 'image/png' }), 'product.png')
|
||||
|
||||
const openaiRes = await fetch('https://api.openai.com/v1/images/edits', {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
body: form,
|
||||
})
|
||||
|
||||
if (!openaiRes.ok) {
|
||||
const err = await openaiRes.text()
|
||||
return Response.json({ error: `OpenAI error: ${err}` }, { status: 502 })
|
||||
}
|
||||
|
||||
const data = await openaiRes.json() as { data: Array<{ b64_json?: string }> }
|
||||
const b64 = data.data?.[0]?.b64_json
|
||||
if (!b64) return Response.json({ error: 'Pas de résultat OpenAI' }, { status: 502 })
|
||||
|
||||
return Response.json({ imageBase64: b64 })
|
||||
}
|
||||
Reference in New Issue
Block a user