import { Router } from 'express'; import { PluginContext } from '../../../backend/src/core/types'; import OpenAI from 'openai'; import fs from 'fs'; import path from 'path'; import https from 'https'; import { v4 as uuidv4 } from 'uuid'; export function createRoutes(ctx: PluginContext): Router { const router = Router(); const uploadDir = process.env.UPLOAD_DIR || path.join(process.cwd(), 'uploads'); router.get('/', (req, res) => { res.json({ message: 'Nanobana plugin active', timestamp: new Date().toISOString(), service: 'Clube67 System' }); }); router.post('/generate-image', async (req, res) => { try { const { type, prompt, partnerName, city, description, partnerId } = req.body; if (!partnerId) { return res.status(400).json({ error: 'Partner ID is required' }); } // Check Usage Limit (1 per type) // FUTURE UPDATE: Add monetization logic here to allow more generations // Check for Unlimited User (ruibto@gmail.com) const user = await ctx.db('users').where({ partner_id: partnerId }).first(); const isUnlimited = user && (user.email === 'ruibto@gmail.com'); if (!isUnlimited) { const usageCount = await ctx.db('nanobana_usage') .where({ partner_id: partnerId, type }) .count('id as count') .first(); const count = usageCount ? Number(usageCount.count) : 0; if (count >= 1) { return res.status(403).json({ error: `Limite gratuito atingido para ${type === 'logo' ? 'Logo' : 'Banner'}. (Máximo: 1)` }); } } // Get Config const nanobanaConfig = ctx.config.get('nanobana'); const apiKey = nanobanaConfig?.apiKey; if (!apiKey) { return res.status(400).json({ error: 'OpenAI API Key não configurada no plugin Nanobana.' }); } const openai = new OpenAI({ apiKey }); // Vision Analysis (if enabled) let visionDescription = ''; if (req.body.useImageRef && req.body.referenceImageUrl) { try { const refUrl = req.body.referenceImageUrl; let imagePath = ''; // Resolve local path if (refUrl.includes('/uploads/')) { const filename = path.basename(refUrl); imagePath = path.join(uploadDir, filename); } if (imagePath && fs.existsSync(imagePath)) { const imageBuffer = fs.readFileSync(imagePath); const base64Image = imageBuffer.toString('base64'); const mimeType = imagePath.endsWith('.png') ? 'image/png' : 'image/jpeg'; const dataUrl = `data:${mimeType};base64,${base64Image}`; const visionResponse = await openai.chat.completions.create({ model: "gpt-4o", messages: [ { role: "user", content: [ { type: "text", text: "Describe this image in detail. Focus on the main subject, colors, style, artistic technique, and composition. The description will be used to recreate a similar image." }, { type: "image_url", image_url: { url: dataUrl } } ], }, ], max_tokens: 300, }); visionDescription = visionResponse.choices[0].message.content || ''; console.log('[Nanobana] Vision Description:', visionDescription); } else { console.warn('[Nanobana] Reference image not found locally:', refUrl); } } catch (visionErr) { console.error('[Nanobana] Vision Error:', visionErr); // Continue without vision } } // Construct Prompt let finalPrompt = ''; if (visionDescription) { finalPrompt = `Create an image based on this visual description: "${visionDescription}".\n\nCONTEXT/MODIFICATIONS REQUESTED: ${prompt || 'Keep the style similar.'}.\n\nIdentity: ${partnerName} (${city}).`; } else if (prompt) { finalPrompt = prompt; } else { if (type === 'logo') { finalPrompt = `A professional, minimalist, and modern logo for a company named "${partnerName}". Context: ${description}. Location style: ${city}. High quality, vector style, white background.`; } else { finalPrompt = `A stunning, high-quality banner image for a company named "${partnerName}". Context: ${description}. The image should be wide (landscape), professional photography style, inviting, and related to the business topic.`; } } // Generate Image const response = await openai.images.generate({ model: "dall-e-3", prompt: finalPrompt, n: 1, size: "1024x1024", quality: "standard", response_format: "url", }); const imageUrl = response.data[0].url; if (!imageUrl) throw new Error('Falha ao gerar imagem na OpenAI'); // Download Image to Local Storage const ext = '.png'; // DALL-E 3 usually png const filename = `${uuidv4()}${ext}`; const localPath = path.join(uploadDir, filename); const publicUrl = `/uploads/${filename}`; const file = fs.createWriteStream(localPath); await new Promise((resolve, reject) => { https.get(imageUrl, function (response) { response.pipe(file); file.on('finish', () => { file.close(); resolve(true); }); }).on('error', function (err) { fs.unlink(localPath, () => { }); reject(err); }); }); // Record Usage await ctx.db('nanobana_usage').insert({ partner_id: partnerId, type }); res.json({ success: true, url: publicUrl, originalPrompt: finalPrompt }); } catch (error: any) { console.error('Nanobana Error:', error); let errorMessage = error.message || 'Erro ao gerar imagem'; // Translate Safety Error if (errorMessage.includes('safety system') || errorMessage.includes('rejected')) { errorMessage = '⚠️ A imagem não pôde ser gerada pois a descrição ou o contexto viola as políticas de segurança da IA (conteúdo impróprio, marcas protegidas ou termos sensíveis). Tente ajustar o texto para ser mais genérico.'; } else if (errorMessage.includes('billing') || errorMessage.includes('quota')) { errorMessage = 'Erro de faturamento na IA. Contate o suporte.'; } res.status(500).json({ error: errorMessage }); } }); return router; }