chore(ops): cleanly purge old clube67 folder references
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
This commit is contained in:
@@ -1,165 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createRoutes = createRoutes;
|
||||
const express_1 = require("express");
|
||||
const plugin_config_1 = require("../../../backend/src/core/plugin-config");
|
||||
const openai_1 = __importDefault(require("openai"));
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const https_1 = __importDefault(require("https"));
|
||||
const uuid_1 = require("uuid");
|
||||
const config_1 = require("../../../backend/src/config");
|
||||
function createRoutes(ctx) {
|
||||
const router = (0, express_1.Router)();
|
||||
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 = plugin_config_1.pluginConfig.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_1.default({ 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_1.default.basename(refUrl);
|
||||
imagePath = path_1.default.join(config_1.config.upload.dir, filename);
|
||||
}
|
||||
if (imagePath && fs_1.default.existsSync(imagePath)) {
|
||||
const imageBuffer = fs_1.default.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 = `${(0, uuid_1.v4)()}${ext}`;
|
||||
const localPath = path_1.default.join(config_1.config.upload.dir, filename);
|
||||
const publicUrl = `/uploads/${filename}`;
|
||||
const file = fs_1.default.createWriteStream(localPath);
|
||||
await new Promise((resolve, reject) => {
|
||||
https_1.default.get(imageUrl, function (response) {
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
resolve(true);
|
||||
});
|
||||
}).on('error', function (err) {
|
||||
fs_1.default.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) {
|
||||
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;
|
||||
}
|
||||
//# sourceMappingURL=routes.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
@@ -1,183 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
import { PluginContext } from '../../../backend/src/core/types';
|
||||
|
||||
import { pluginConfig } from '../../../backend/src/core/plugin-config';
|
||||
import OpenAI from 'openai';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import https from 'https';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { config } from '../../../backend/src/config';
|
||||
|
||||
export function createRoutes(ctx: PluginContext): Router {
|
||||
const router = Router();
|
||||
|
||||
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 = pluginConfig.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(config.upload.dir, 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(config.upload.dir, 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;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const routes_1 = require("./backend/routes");
|
||||
const manifest_json_1 = __importDefault(require("./manifest.json"));
|
||||
const plugin = {
|
||||
manifest: manifest_json_1.default,
|
||||
async activate(ctx) {
|
||||
ctx.logger.info('Registering Nanobana routes...');
|
||||
ctx.app.use(manifest_json_1.default.backend.routePrefix, (0, routes_1.createRoutes)(ctx));
|
||||
ctx.logger.info('Nanobana activated!');
|
||||
},
|
||||
async deactivate(ctx) {
|
||||
ctx.logger.info('Nanobana deactivated');
|
||||
},
|
||||
};
|
||||
exports.default = plugin;
|
||||
//# sourceMappingURL=index.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../plugins/nanobana/index.ts"],"names":[],"mappings":";;;;;AACA,6CAAgD;AAChD,oEAAuC;AAEvC,MAAM,MAAM,GAAmB;IAC3B,QAAQ,EAAE,uBAAe;IACzB,KAAK,CAAC,QAAQ,CAAC,GAAkB;QAC7B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAElD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,uBAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,IAAA,qBAAY,EAAC,GAAG,CAAC,CAAC,CAAC;QAE7D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3C,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,GAAkB;QAC/B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC5C,CAAC;CACJ,CAAC;AAEF,kBAAe,MAAM,CAAC"}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { PluginInstance, PluginContext } from '../../backend/src/core/types';
|
||||
import { createRoutes } from './backend/routes';
|
||||
import manifest from './manifest.json';
|
||||
|
||||
const plugin: PluginInstance = {
|
||||
manifest: manifest as any,
|
||||
async activate(ctx: PluginContext): Promise<void> {
|
||||
ctx.logger.info('Registering Nanobana routes...');
|
||||
|
||||
ctx.app.use(manifest.backend.routePrefix, createRoutes(ctx));
|
||||
|
||||
ctx.logger.info('Nanobana activated!');
|
||||
},
|
||||
async deactivate(ctx: PluginContext): Promise<void> {
|
||||
ctx.logger.info('Nanobana deactivated');
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "nanobana",
|
||||
"displayName": "Nanobana",
|
||||
"version": "1.0.0",
|
||||
"description": "Exposes a global API endpoint for system consumption.",
|
||||
"author": "Clube67",
|
||||
"category": "utility",
|
||||
"enabled": true,
|
||||
"canDisable": true,
|
||||
"dependencies": [],
|
||||
"backend": {
|
||||
"routePrefix": "/api/plugins/nanobana",
|
||||
"hasMigrations": true
|
||||
},
|
||||
"frontend": {
|
||||
"menuItems": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user