File size: 6,230 Bytes
899a91f
5f07a23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aba9d46
 
 
 
 
 
 
 
5f07a23
 
 
 
 
 
 
 
 
 
 
899a91f
 
5f07a23
 
aba9d46
 
 
 
 
 
899a91f
 
aba9d46
 
 
 
 
 
 
 
 
 
5f07a23
aba9d46
 
5f07a23
 
 
 
 
 
 
 
 
 
 
 
 
aba9d46
899a91f
aba9d46
 
 
 
 
 
 
 
 
 
 
 
 
899a91f
 
aba9d46
 
 
 
 
 
 
 
 
 
 
 
899a91f
aba9d46
 
5f07a23
 
aba9d46
 
899a91f
aba9d46
5f07a23
aba9d46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899a91f
aba9d46
 
 
 
 
 
 
 
 
5f07a23
 
899a91f
aba9d46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899a91f
 
aba9d46
 
 
5f07a23
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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
    });
  }
}