|
import { GoogleGenerativeAI } from '@google/generative-ai'; |
|
|
|
|
|
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); |
|
}; |
|
|
|
|
|
export const config = { |
|
api: { |
|
bodyParser: { |
|
sizeLimit: '10mb' |
|
} |
|
} |
|
}; |
|
|
|
export default async function handler(req, res) { |
|
|
|
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' }); |
|
} |
|
|
|
|
|
const base64Data = image.split(',')[1]; |
|
if (!base64Data) { |
|
return res.status(400).json({ error: 'Invalid image format' }); |
|
} |
|
|
|
|
|
const apiKey = customApiKey || process.env.GEMINI_API_KEY; |
|
const genAI = new GoogleGenerativeAI(apiKey); |
|
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' }); |
|
|
|
|
|
const imageParts = [ |
|
{ |
|
inlineData: { |
|
data: base64Data, |
|
mimeType: "image/jpeg" |
|
} |
|
} |
|
]; |
|
|
|
|
|
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...'); |
|
|
|
|
|
const result = await model.generateContent([prompt, ...imageParts]); |
|
const response = result.response; |
|
const responseText = response.text(); |
|
|
|
console.log('Received response from Gemini'); |
|
|
|
|
|
try { |
|
|
|
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 { |
|
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); |
|
} |
|
|
|
|
|
const lines = responseText.split('\n'); |
|
let suggestedName = 'Custom Material'; |
|
|
|
|
|
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(); |
|
|
|
suggestedName = suggestedName.replace(/^["'](.*)["']$/, '$1'); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return res.status(200).json({ |
|
prompt: responseText, |
|
suggestedName |
|
}); |
|
} |
|
} catch (error) { |
|
console.error('Error in analyze-image:', error); |
|
|
|
|
|
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 |
|
}); |
|
} |
|
} |