import { NextResponse } from 'next/server'; import { recordEvent } from '@/lib/auditDb'; import { buildCharacterSpec, cleanupCharacterAnchor } from '@/lib/packGenerator'; import { detectProvider } from '@/lib/providers'; import { loadSession, saveSession } from '@/lib/storage'; import type { LockCharacterFromUploadRequest, LockCharacterResponse } from '@/lib/types'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; export async function POST(req: Request) { const { sessionId, subjectImageId, userHint, force = false } = (await req.json()) as LockCharacterFromUploadRequest; if (!sessionId || !subjectImageId) { return NextResponse.json({ error: 'sessionId and subjectImageId required' }, { status: 400 }); } const session = await loadSession(sessionId); if (!session) return NextResponse.json({ error: 'session not found' }, { status: 404 }); const sourceImage = session.images.find(image => image.id === subjectImageId || image.meta?.uploadedImageId === subjectImageId ); if (!sourceImage) return NextResponse.json({ error: 'subject image not found' }, { status: 404 }); if (!force && session.characterSpec?.sourceImageId === sourceImage.id && session.characterSpec.cleanReferenceImageUrl) { recordEvent({ action: 'character.lock_from_upload_cached', sessionId, targetType: 'upload', targetId: subjectImageId, status: 'ok', provider: detectProvider() }); return NextResponse.json({ characterSpec: session.characterSpec, provider: detectProvider(), } satisfies LockCharacterResponse); } try { recordEvent({ action: 'character.lock_from_upload_started', sessionId, targetType: 'upload', targetId: subjectImageId, status: 'started', provider: detectProvider(), metadata: { force, userHint: Boolean(userHint?.trim()) } }); if (userHint?.trim()) session.prompt = userHint.trim(); const characterSpec = await buildCharacterSpec(session, sourceImage); const cleaned = await cleanupCharacterAnchor({ session, sourceImage, characterSpec, force: true, preserveLevel: 'strict', }); session.characterSpec = cleaned.characterSpec; await saveSession(session); recordEvent({ action: 'character.lock_from_upload_completed', sessionId, targetType: 'upload', targetId: subjectImageId, status: 'ok', provider: cleaned.provider, metadata: { name: cleaned.characterSpec.name, cleanReferenceImageUrl: cleaned.cleanReferenceImageUrl }, }); return NextResponse.json({ characterSpec: cleaned.characterSpec, provider: cleaned.provider, } satisfies LockCharacterResponse); } catch (error) { recordEvent({ action: 'character.lock_from_upload_failed', sessionId, targetType: 'upload', targetId: subjectImageId, status: 'error', provider: detectProvider(), message: String(error) }); return NextResponse.json({ error: String(error) }, { status: 500 }); } }