66 lines
2.9 KiB
TypeScript
66 lines
2.9 KiB
TypeScript
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 });
|
|
}
|
|
}
|