import { GoogleGenerativeAI } from "@google/generative-ai"; // Configure API route options export const config = { api: { bodyParser: { sizeLimit: '10mb' // Increase the body size limit to 10MB } } }; 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 { prompt, drawingData, customApiKey, generateTextOnly } = req.body; // Validate prompt if (!prompt) { console.error('Missing prompt in request'); return res.status(400).json({ error: 'Prompt is required' }); } // Log the API request details (excluding the full drawing data for brevity) console.log('API Request:', { prompt: prompt.substring(0, 100) + '...', hasDrawingData: !!drawingData, hasCustomApiKey: !!customApiKey, generateTextOnly: !!generateTextOnly, retryCount }); // Check 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 with API key (attempt ${retryCount + 1}/${MAX_RETRIES + 1})`); const genAI = new GoogleGenerativeAI(apiKey); // Configure the model with settings based on the request type 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: generateTextOnly ? ["text"] : ["image", "text"] } }); // Handle text-only generation if (generateTextOnly) { console.log('Generating text-only response'); // Make text-only API call const result = await model.generateContent(prompt); const response = await result.response; // Extract text from response const textResponse = response.text(); console.log('Generated text response:', textResponse.substring(0, 100) + '...'); return res.status(200).json({ success: true, textResponse: textResponse }); } // For image generation, proceed as normal // Prepare the generation content let generationContent; if (drawingData) { console.log('Including drawing data in generation request'); // If we have drawing data, include it in the request generationContent = [ { inlineData: { data: drawingData, mimeType: "image/png" } }, { text: prompt } ]; } else { console.log('Using text-only prompt for generation'); // Text-only prompt if no drawing generationContent = [{ text: prompt }]; } console.log(`Calling Gemini API for image generation (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 imageData = 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) { imageData = part.inlineData.data; console.log('Found image data in response'); break; } } if (!imageData) { console.error('No image data in response parts:', response.candidates[0].content.parts); throw new Error('No image data found in response parts'); } console.log('Successfully generated image'); return res.status(200).json({ success: true, imageData: imageData }); // If we reach here, we succeeded, so break out of the retry loop break; } 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('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/generate:', 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 generation.', details: error.stack, retries: retryCount }); } }