import * as fs from "node:fs"; import * as path from "node:path"; import { GoogleGenAI } from "@google/genai"; export const NANO_BANANA_MODELS = [ { id: "gemini-nano-banana-2.1", label: "Nano Banana 2.1 (Default High-Efficiency)", description: "gemini-nano-banana-2.1 — Fast, high-efficiency native image generation and conversational editing" }, { id: "gemini-3-pro-image", label: "Nano Banana Pro (Studio Quality)", description: "gemini-3-pro-image — High-detail studio mockup generation and multi-turn editing" }, { id: "gemini-3.1-flash-lite-image", label: "Nano Banana 2 Lite (Ultra-Fast)", description: "gemini-3.1-flash-lite-image — Rapid concept sketching and quick iterations" } ]; export const SURFACE_PRESETS = { "tshirt-front": { label: "👕 T-Shirt (Full Front Print)", buildPrompt: ({ prompt, garmentColorName, garmentHex }) => `Photorealistic studio flat-lay apparel mockup of a heavyweight ${garmentColorName || "vintage cream"} (${garmentHex || "#F5E8D3"}) crewneck t-shirt. Centered full-front screenprint graphic design: ${prompt}. Crisp fabric texture, authentic screenprint ink detail, clean studio lighting.` }, "tshirt-left-chest": { label: "👕 T-Shirt (5-inch Left-Chest Emblem)", buildPrompt: ({ prompt, garmentColorName, garmentHex }) => `Photorealistic studio closeup & flat-lay apparel mockup of a heavyweight ${garmentColorName || "vintage cream"} (${garmentHex || "#F5E8D3"}) crewneck t-shirt featuring a compact 5-inch left-chest pocket-zone screenprinted emblem: ${prompt}. Clean vector-style spot-color ink on fabric, studio lighting.` }, "isolated-graphic": { label: "🎨 Isolated Screenprint Artwork (Vector Style)", buildPrompt: ({ prompt, garmentColorName }) => `Isolated flat vector screenprint t-shirt graphic emblem on a clean solid ${garmentColorName || "cream"} background, no shirt folds, bold spot-color illustration and outlined vintage typography: ${prompt}` }, "sticker": { label: "🏷️ Die-Cut Vinyl Sticker / Patch", buildPrompt: ({ prompt }) => `Photorealistic die-cut vinyl sticker with a crisp white contour border resting on a dark slate studio surface. Bold vector illustration and typography: ${prompt}` }, "poster": { label: "🖼️ Framed Studio Art Poster", buildPrompt: ({ prompt }) => `Gallery-framed vertical screenprint poster mockup in a minimalist studio interior. Bold editorial illustration and typography: ${prompt}` }, "app-ui": { label: "💻 Product / App Interface Concept", buildPrompt: ({ prompt }) => `High-craft modern product design and UI concept presentation mockup on a sleek dark desktop studio display: ${prompt}` } }; export function buildNanoBananaPrompt({ rawPrompt, ideaTitle, elevatorPitch, template = "tshirt-front", garmentColorName = "Vintage Cream", garmentHex = "#F5E8D3", whackCard = null, editInstruction = null }) { if (editInstruction && editInstruction.trim()) { let editText = editInstruction.trim(); if (whackCard) { editText += ` Incorporate creative twist from Card #${whackCard.number} (${whackCard.title}): ${whackCard.promptQuestion}`; } return editText; } const baseConcept = (rawPrompt && rawPrompt.trim()) ? rawPrompt.trim() : `${ideaTitle}${elevatorPitch ? ` — ${elevatorPitch}` : ""}`; const surface = SURFACE_PRESETS[template] || SURFACE_PRESETS["tshirt-front"]; let finalPrompt = surface.buildPrompt({ prompt: baseConcept, garmentColorName, garmentHex }); if (whackCard) { finalPrompt += ` Creative direction inspired by "${whackCard.title}": ${whackCard.promptQuestion}`; } return finalPrompt; } /** * Generates or edits an image using Google Gemini Nano Banana (`gemini-nano-banana-2.1`) * via the official `@google/genai` Interactions API (`ai.interactions.create`). */ export async function generateWithNanoBanana({ apiKey, model = "gemini-nano-banana-2.1", prompt, referenceImagePath = null, referenceImageBase64 = null, referenceMimeType = "image/png", outputDir }) { const resolvedKey = apiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY; if (!resolvedKey) { const err = new Error( "No Gemini API key configured. Click '⚙️ Nano Banana API Key' in the Mockup Studio (or set GEMINI_API_KEY) to generate live images with Nano Banana 2.1, or use the Interactive T-Shirt Canvas Compositor." ); err.code = "MISSING_API_KEY"; throw err; } const validModel = NANO_BANANA_MODELS.some((m) => m.id === model) ? model : "gemini-nano-banana-2.1"; const ai = new GoogleGenAI({ apiKey: resolvedKey }); let inputPayload = prompt; // Check if we are performing conversational image-to-image editing let base64Data = referenceImageBase64; let mimeType = referenceMimeType || "image/png"; if (!base64Data && referenceImagePath && fs.existsSync(referenceImagePath)) { const bytes = fs.readFileSync(referenceImagePath); base64Data = bytes.toString("base64"); if (referenceImagePath.endsWith(".jpg") || referenceImagePath.endsWith(".jpeg")) { mimeType = "image/jpeg"; } } if (base64Data) { // Strip data URL prefix if present const cleanedBase64 = base64Data.replace(/^data:image\/[a-zA-Z0-9+.-]+;base64,/, ""); inputPayload = [ { type: "text", text: prompt }, { type: "image", mime_type: mimeType, data: cleanedBase64 } ]; } const interaction = await ai.interactions.create({ model: validModel, input: inputPayload }); const generatedImage = interaction.output_image; if (!generatedImage || !generatedImage.data) { throw new Error( interaction.output_text || "Nano Banana completed the interaction without returning an image block. Try refining your visual prompt." ); } const ext = (generatedImage.mime_type || "").includes("jpeg") ? "jpg" : "png"; const filename = `nb-${Date.now()}-${Math.random().toString(36).slice(2, 7)}.${ext}`; const fullPath = path.join(outputDir, filename); fs.mkdirSync(outputDir, { recursive: true }); fs.writeFileSync(fullPath, Buffer.from(generatedImage.data, "base64")); return { filename, imageUrl: `/mockups/${filename}`, mimeType: generatedImage.mime_type || `image/${ext}`, modelUsed: validModel, interactionId: interaction.id || null, textNotes: interaction.output_text || null }; }