import { GoogleGenerativeAI } from "@google/generative-ai"; import { NextResponse } from 'next/server'; // Configuration for the API route export const config = { api: { bodyParser: { sizeLimit: '10mb', // Increase limit to 10MB (adjust if needed) }, }, }; export default async function handler(req, res) { // Only allow POST requests if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } // Add retry configuration const MAX_RETRIES = 2; const RETRY_DELAY = 1000; // 1 second let retryCount = 0; // Function to wait for a delay const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); try { const { imageData, customApiKey } = req.body; // Validate inputs if (!imageData) { return res.status(400).json({ error: 'Image data is required' }); } // Set up the API key const apiKey = customApiKey || process.env.GEMINI_API_KEY; if (!apiKey) { console.error('Missing Gemini API key'); return res.status(500).json({ error: 'API key is not configured' }); } // Retry loop for handling transient errors while (true) { try { console.log(`Initializing Gemini AI for doodle conversion (attempt ${retryCount + 1}/${MAX_RETRIES + 1})`); // Initialize the Gemini API const genAI = new GoogleGenerativeAI(apiKey); console.log('Configuring Gemini model'); const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp-image-generation", generationConfig: { temperature: 1, topP: 0.95, topK: 40, maxOutputTokens: 8192, responseModalities: ["image", "text"] } }); // Create the prompt for doodle conversion const prompt = `Could you please convert this image into a black and white doodle. Requirements: - Use ONLY pure black lines on a pure white background - No gray tones or shading - Maintain the key shapes and outlines - Follow the original content but simplify if needed - IMPORTANT: If this image contains any text, logo, or wordmark: * Simply convert it to black and white, and pass it through * Preserve ALL text exactly as it appears in the original * Maintain the exact spelling, letterspacing, and arrangement of letters * Keep text legible and clear with high contrast * Do not simplify or omit any text elements * Text should remain readable in the final doodle, and true to the original :))`; // Prepare the generation content console.log('Including image data in generation request'); const generationContent = [ { inlineData: { data: imageData, mimeType: "image/png" } }, { text: prompt } ]; // Generate content console.log(`Calling Gemini API for doodle conversion (attempt ${retryCount + 1}/${MAX_RETRIES + 1})...`); const result = await model.generateContent(generationContent); console.log('Gemini API response received'); const response = await result.response; // Process the response to extract image data let convertedImageData = null; if (!response?.candidates?.[0]?.content?.parts) { console.error('Invalid response structure:', response); throw new Error('Invalid response structure from Gemini API'); } for (const part of response.candidates[0].content.parts) { if (part.inlineData) { convertedImageData = part.inlineData.data; console.log('Found image data in response'); break; } } if (!convertedImageData) { console.error('No image data in response parts:', response.candidates[0].content.parts); throw new Error('No image data found in response parts'); } // Return the converted image data console.log('Successfully generated doodle'); return res.status(200).json({ success: true, imageData: convertedImageData }); } catch (attemptError) { // Only retry certain types of errors that might be transient const isRetryableError = attemptError.message?.includes('timeout') || attemptError.message?.includes('network') || attemptError.message?.includes('ECONNRESET') || attemptError.message?.includes('rate limit') || attemptError.message?.includes('503') || attemptError.message?.includes('500') || attemptError.message?.includes('overloaded') || attemptError.message?.includes('connection'); // Check if we should retry if (retryCount < MAX_RETRIES && isRetryableError) { console.log(`Retryable error encountered (${retryCount + 1}/${MAX_RETRIES}):`, attemptError.message); retryCount++; // Wait before retrying await wait(RETRY_DELAY * retryCount); continue; } // If we've exhausted retries or it's not a retryable error, rethrow throw attemptError; } } } catch (error) { console.error('Error in /api/convert-to-doodle:', error); // Check for specific error types if (error.message?.includes('quota') || error.message?.includes('Resource has been exhausted')) { return res.status(429).json({ error: 'API quota exceeded. Please try again later or use your own API key.' }); } if (error.message?.includes('API key')) { return res.status(500).json({ error: 'Invalid or missing API key. Please check your configuration.' }); } if (error.message?.includes('safety') || error.message?.includes('blocked') || error.message?.includes('harmful')) { return res.status(400).json({ error: 'Content was blocked due to safety filters. Try a different prompt.' }); } return res.status(500).json({ error: error.message || 'An error occurred during conversion.', details: error.stack, retries: retryCount }); } }