Spaces:
Running
Running
| import { GoogleGenerativeAI } from '@google/generative-ai'; | |
| // Initialize Gemini API with error handling | |
| const initializeGeminiAI = () => { | |
| const apiKey = process.env.GEMINI_API_KEY; | |
| if (!apiKey) { | |
| throw new Error('GEMINI_API_KEY is not set in environment variables'); | |
| } | |
| return new GoogleGenerativeAI(apiKey); | |
| }; | |
| // Configure API route options | |
| export const config = { | |
| api: { | |
| bodyParser: { | |
| sizeLimit: '10mb' // Increase the body size limit to 10MB for larger images | |
| } | |
| } | |
| }; | |
| export default async function handler(req, res) { | |
| // Only allow POST requests | |
| if (req.method !== 'POST') { | |
| res.setHeader('Allow', ['POST']); | |
| return res.status(405).json({ error: `Method ${req.method} Not Allowed` }); | |
| } | |
| try { | |
| const { image, customApiKey } = req.body; | |
| if (!image) { | |
| return res.status(400).json({ error: 'Image is required' }); | |
| } | |
| // Extract base64 data | |
| const base64Data = image.split(',')[1]; | |
| if (!base64Data) { | |
| return res.status(400).json({ error: 'Invalid image format' }); | |
| } | |
| // Initialize Gemini with the appropriate API key | |
| const apiKey = customApiKey || process.env.GEMINI_API_KEY; | |
| const genAI = new GoogleGenerativeAI(apiKey); | |
| const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' }); | |
| // Prepare the image parts | |
| const imageParts = [ | |
| { | |
| inlineData: { | |
| data: base64Data, | |
| mimeType: "image/jpeg" | |
| } | |
| } | |
| ]; | |
| // Create the prompt - Updated for clarity and better results | |
| const prompt = `Analyze this reference image/moodboard and create a detailed material prompt for a 3D rendering that captures its essence and visual qualities. | |
| The prompt should follow this format and style, but be unique and creative: | |
| Example 1: "Recreate this doodle as a physical chrome sculpture made of chromium metal tubes or pipes in a professional studio setting. If it is typography, render it accordingly, but always have a black background and studio lighting. Render it in Cinema 4D with Octane, using studio lighting against a pure black background. Make it look like a high-end product rendering of a sculptural piece." | |
| Example 2: "Convert this drawing / text into a soft body physics render. Render it as if made of a soft, jelly-like material that responds to gravity and motion. Add realistic deformation, bounce, and squash effects typical of soft body dynamics. Use dramatic lighting against a black background to emphasize the material's translucency and surface properties. Make it look like a high-end 3D animation frame" | |
| Create a new, unique prompt based on the visual qualities of the uploaded image that follows a similar style but is completely different from the examples. | |
| Focus on: | |
| 1. Material properties (metallic, glass, fabric, liquid, etc.) | |
| 2. Lighting and environment (studio, dramatic, moody, etc.) | |
| 3. Visual style (high-end, realistic, stylized, etc.) | |
| 4. Rendering technique (Cinema 4D, Octane, etc.) | |
| Also suggest a short, memorable name for this material style (1-2 words) based on the key visual characteristics. | |
| Format the response as JSON with 'prompt' and 'suggestedName' fields.`; | |
| console.log('Calling Gemini Vision API for image analysis...'); | |
| // Generate content | |
| const result = await model.generateContent([prompt, ...imageParts]); | |
| const response = result.response; | |
| const responseText = response.text(); | |
| console.log('Received response from Gemini'); | |
| // Try to parse as JSON, fallback to text if needed | |
| try { | |
| // First try direct parse | |
| const jsonResponse = JSON.parse(responseText); | |
| return res.status(200).json(jsonResponse); | |
| } catch (e) { | |
| console.log('Response not in JSON format, attempting to extract JSON'); | |
| // Try to extract JSON from text response | |
| try { | |
| const jsonMatch = responseText.match(/\{[\s\S]*\}/); | |
| if (jsonMatch) { | |
| const extractedJson = JSON.parse(jsonMatch[0]); | |
| return res.status(200).json(extractedJson); | |
| } | |
| } catch (extractError) { | |
| console.error('Failed to extract JSON from response:', extractError); | |
| } | |
| // If all parsing attempts fail, create structured response from text | |
| const lines = responseText.split('\n'); | |
| let suggestedName = 'Custom Material'; | |
| // Try to find a name in the response | |
| for (const line of lines) { | |
| if (line.toLowerCase().includes('name:') || line.toLowerCase().includes('suggested name:')) { | |
| const namePart = line.split(':')[1]; | |
| if (namePart && namePart.trim()) { | |
| suggestedName = namePart.trim(); | |
| // Remove quotes if present | |
| suggestedName = suggestedName.replace(/^["'](.*)["']$/, '$1'); | |
| break; | |
| } | |
| } | |
| } | |
| return res.status(200).json({ | |
| prompt: responseText, | |
| suggestedName | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Error in analyze-image:', error); | |
| // Check for quota exceeded errors | |
| 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.' | |
| }); | |
| } | |
| return res.status(500).json({ | |
| error: 'Failed to analyze image', | |
| details: error.message | |
| }); | |
| } | |
| } |