app/api/image/route.ts (195 lines of code) (raw):

import { NextRequest, NextResponse } from "next/server"; import { GoogleGenAI } from "@google/genai"; import { HistoryItem, HistoryPart } from "@/lib/types"; // Initialize the Google Gen AI client with your API key const GEMINI_API_KEY = process.env.GEMINI_API_KEY || ""; const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY }); // Define the model ID for Gemini 2.0 Flash experimental const MODEL_ID = "gemini-2.0-flash-exp-image-generation"; // Define interface for the formatted history item interface FormattedHistoryItem { role: "user" | "model"; parts: Array<{ text?: string; inlineData?: { data: string; mimeType: string }; }>; } export async function POST(req: NextRequest) { try { // Make sure we have an API key configured if (!GEMINI_API_KEY) { console.error("GEMINI_API_KEY is not configured"); return NextResponse.json( { success: false, error: "GEMINI_API_KEY is not configured" }, { status: 500 } ); } // Parse JSON request const requestData = await req.json().catch((err) => { console.error("Failed to parse JSON body:", err); return null; }); if (!requestData) { return NextResponse.json( { success: false, error: "Invalid JSON in request body" }, { status: 400 } ); } const { prompt, image: inputImage, history } = requestData; if (!prompt) { return NextResponse.json( { success: false, error: "Prompt is required" }, { status: 400 } ); } let response; // Validate the image if provided if (inputImage) { if (typeof inputImage !== "string" || !inputImage.startsWith("data:")) { console.error("Invalid image data URL format", { inputImage }); return NextResponse.json( { success: false, error: "Invalid image data URL format" }, { status: 400 } ); } const imageParts = inputImage.split(","); if (imageParts.length < 2) { console.error("Malformed image data URL", { inputImage }); return NextResponse.json( { success: false, error: "Malformed image data URL" }, { status: 400 } ); } const base64Image = imageParts[1]; // Check for non-empty and valid base64 (basic check) if (!base64Image || !/^([A-Za-z0-9+/=]+)$/.test(base64Image.replace(/\s/g, ""))) { console.error("Image data is empty or not valid base64", { base64Image }); return NextResponse.json( { success: false, error: "Image data is empty or not valid base64" }, { status: 400 } ); } } try { // Convert history to the format expected by Gemini API const formattedHistory = history && history.length > 0 ? history .map((item: HistoryItem) => { return { role: item.role, parts: item.parts .map((part: HistoryPart) => { if (part.text) { return { text: part.text }; } if (part.image && item.role === "user") { const imgParts = part.image.split(","); if (imgParts.length > 1) { return { inlineData: { data: imgParts[1], mimeType: part.image.includes("image/png") ? "image/png" : "image/jpeg", }, }; } } return { text: "" }; }) .filter((part) => Object.keys(part).length > 0), // Remove empty parts }; }) .filter((item: FormattedHistoryItem) => item.parts.length > 0) // Remove items with no parts : []; // Prepare the current message parts const messageParts = []; // Add the text prompt messageParts.push({ text: prompt }); // Add the image if provided if (inputImage) { // For image editing console.log("Processing image edit request"); // Check if the image is a valid data URL if (!inputImage.startsWith("data:")) { throw new Error("Invalid image data URL format"); } const imageParts = inputImage.split(","); if (imageParts.length < 2) { throw new Error("Invalid image data URL format"); } const base64Image = imageParts[1]; const mimeType = inputImage.includes("image/png") ? "image/png" : "image/jpeg"; console.log( "Base64 image length:", base64Image.length, "MIME type:", mimeType ); // Add the image to message parts messageParts.push({ inlineData: { data: base64Image, mimeType: mimeType, }, }); } // Add the message parts to the history formattedHistory.push(messageParts); // Generate the content response = await ai.models.generateContent({ model: MODEL_ID, contents: formattedHistory, config: { temperature: 1, topP: 0.95, topK: 40, responseModalities: ["Text", "Image"], }, }); } catch (error) { console.error("Error in chat.sendMessage:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error in AI processing"; return NextResponse.json( { success: false, error: "Gemini API error", details: errorMessage }, { status: 500 } ); } let textResponse = null; let imageData = null; let mimeType = "image/png"; // Process the response if (response.candidates && response.candidates.length > 0) { const parts = response.candidates[0].content.parts; console.log("Number of parts in response:", parts.length); for (const part of parts) { if ("inlineData" in part && part.inlineData) { // Get the image data imageData = part.inlineData.data; mimeType = part.inlineData.mimeType || "image/png"; console.log( "Image data received, length:", imageData?.length || 0, "MIME type:", mimeType ); } else if ("text" in part && part.text) { // Store the text textResponse = part.text; console.log( "Text response received:", textResponse.substring(0, 50) + "..." ); } } } else { console.error("No response from Gemini API", { result }); return NextResponse.json( { success: false, error: "No response from Gemini API" }, { status: 500 } ); } if (!imageData) { console.error("No image data in Gemini response", { response }); return NextResponse.json( { success: false, error: "No image data in Gemini response" }, { status: 500 } ); } // Return the base64 image and description as JSON return NextResponse.json({ success: true, image: `data:${mimeType};base64,${imageData}`, description: textResponse || null }); } catch (error) { console.error("Error generating image:", error); return NextResponse.json( { success: false, error: "Failed to generate image", details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } }