import { NextRequest, NextResponse } from "next/server"; import { getZine, saveZine, getPageImagePath, readFileAsBase64, savePageImage } from "@/lib/storage"; export async function POST(request: NextRequest) { try { const body = await request.json(); const { zineId, pageNumber, maskBase64, newText, style, tone } = body; // Validate required fields if (!zineId || !pageNumber || !maskBase64 || !newText) { return NextResponse.json( { error: "Missing required fields: zineId, pageNumber, maskBase64, newText" }, { status: 400 } ); } // Validate Fal.ai API key const falKey = process.env.FAL_KEY; if (!falKey) { return NextResponse.json( { error: "FAL_KEY not configured" }, { status: 500 } ); } // Verify zine exists const zine = await getZine(zineId); if (!zine) { return NextResponse.json( { error: "Zine not found" }, { status: 404 } ); } // Get existing page image const existingImagePath = await getPageImagePath(zineId, pageNumber); if (!existingImagePath) { return NextResponse.json( { error: "Page image not found" }, { status: 404 } ); } const existingImageBase64 = await readFileAsBase64(existingImagePath); // Build the text inpainting prompt const textPrompt = buildTextPrompt(newText, style, tone); console.log(`Inpainting text on page ${pageNumber}: "${newText.slice(0, 50)}..."`); // Call Fal.ai FLUX Pro Fill for inpainting const newImageBase64 = await inpaintWithFluxFill( existingImageBase64, maskBase64, textPrompt, falKey ); // Save the inpainted image await savePageImage(zineId, pageNumber, newImageBase64); // Update zine metadata zine.updatedAt = new Date().toISOString(); await saveZine(zine); // Return success with cache-busted image URL const imageUrl = `/api/zine/${zineId}?image=p${pageNumber}&t=${Date.now()}`; return NextResponse.json({ pageNumber, imageUrl, success: true, }); } catch (error) { console.error("Text inpainting error:", error); return NextResponse.json( { error: error instanceof Error ? error.message : "Failed to inpaint text" }, { status: 500 } ); } } function buildTextPrompt(newText: string, style: string, tone: string): string { // Build a contextual prompt for text generation const styleDescriptions: Record = { "punk-zine": "punk zine aesthetic, bold hand-drawn lettering, screen-printed style", "collage": "collage art style, cut-out letters, mixed media typography", "minimal": "clean minimal design, modern sans-serif typography", "vintage": "vintage retro style, distressed typography, aged paper texture", "psychedelic": "psychedelic art style, flowing organic letterforms, vibrant colors", }; const toneDescriptions: Record = { "rebellious": "bold, confrontational, high-contrast", "playful": "fun, whimsical, energetic", "thoughtful": "contemplative, balanced, readable", "informative": "clear, educational, professional", "poetic": "artistic, expressive, lyrical", }; const styleDesc = styleDescriptions[style] || "creative zine typography"; const toneDesc = toneDescriptions[tone] || "expressive"; return `${styleDesc}. The text clearly reads: "${newText}". ${toneDesc} aesthetic. Bold, clear lettering that integrates seamlessly with the surrounding design. High contrast for readability.`; } async function inpaintWithFluxFill( imageBase64: string, maskBase64: string, prompt: string, falKey: string ): Promise { console.log("Calling Fal.ai FLUX Pro Fill for inpainting..."); // Use fal.run for synchronous execution const response = await fetch("https://fal.run/fal-ai/flux-pro/v1/fill", { method: "POST", headers: { "Authorization": `Key ${falKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ image_url: `data:image/png;base64,${imageBase64}`, mask_url: `data:image/png;base64,${maskBase64}`, prompt: prompt, num_inference_steps: 40, guidance_scale: 7.0, output_format: "png", safety_tolerance: 3, }), }); if (!response.ok) { const errorText = await response.text(); console.error("Fal.ai error:", response.status, errorText); throw new Error(`Fal.ai API error: ${response.status} - ${errorText}`); } const result = await response.json(); console.log("Fal.ai inpainting response received"); // Extract the image URL from response if (result.images && result.images.length > 0) { const imageUrl = result.images[0].url; console.log("Downloading inpainted image..."); return await fetchImageAsBase64(imageUrl); } console.error("Fal.ai response:", JSON.stringify(result).slice(0, 500)); throw new Error("No image in Fal.ai response"); } async function fetchImageAsBase64(url: string): Promise { const response = await fetch(url); if (!response.ok) { throw new Error("Failed to fetch generated image"); } const buffer = await response.arrayBuffer(); return Buffer.from(buffer).toString("base64"); }