mcp-image-server
Version:
Servidor MCP para geração de imagens e ícones, integrado ao Vibecoding.
96 lines (95 loc) • 3.72 kB
JavaScript
import OpenAI from 'openai';
import sharp from 'sharp';
import fs from 'fs/promises';
import path from 'path';
export class ImageGenerationService {
openai;
outputDir;
constructor() {
this.openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || ''
});
this.outputDir = process.env.OUTPUT_DIR || './generated-assets';
this.ensureOutputDir();
}
async ensureOutputDir() {
try {
await fs.mkdir(this.outputDir, { recursive: true });
}
catch (error) {
console.error('Erro ao criar diretório de saída:', error);
}
}
async generateImage(options) {
const { prompt, width = 512, height = 512, format = 'png', quality = 80, style, background } = options;
const enhancedPrompt = this.enhancePrompt(prompt, style, background);
try {
const response = await this.openai.images.generate({
model: 'dall-e-3',
prompt: enhancedPrompt,
size: this.getValidSize(width, height),
quality: 'standard',
response_format: 'url'
});
const imageUrl = response?.data && Array.isArray(response.data) && response.data[0]?.url ? response.data[0].url : undefined;
if (!imageUrl)
throw new Error('Nenhuma URL de imagem retornada pela OpenAI');
const imageBuffer = await this.downloadImage(imageUrl);
const processedBuffer = await this.processImage(imageBuffer, { width, height, format, quality, background });
const filename = `image_${Date.now()}.${format}`;
const filePath = path.join(this.outputDir, filename);
await fs.writeFile(filePath, processedBuffer);
return {
filePath: path.resolve(filePath),
filename,
format,
width,
height
};
}
catch (error) {
throw new Error(`Falha na geração da imagem: ${error.message}`);
}
}
enhancePrompt(prompt, style, background) {
let enhanced = prompt;
if (style)
enhanced += `, ${style} style`;
if (background && background !== 'transparent')
enhanced += `, ${background} background`;
else if (background === 'transparent')
enhanced += ', transparent background, isolated subject';
enhanced += ', high quality, clean, professional';
return enhanced;
}
getValidSize(width, height) {
const aspectRatio = width / height;
if (aspectRatio > 1.5)
return '1792x1024';
if (aspectRatio < 0.7)
return '1024x1792';
return '1024x1024';
}
async downloadImage(url) {
const response = await fetch(url);
return Buffer.from(await response.arrayBuffer());
}
async processImage(buffer, options) {
let image = sharp(buffer);
if (options.width !== 1024 || options.height !== 1024) {
image = image.resize(options.width, options.height, { fit: 'contain' });
}
if (options.background && options.background !== 'transparent') {
image = image.flatten({ background: options.background });
}
switch (options.format) {
case 'jpeg':
return await image.jpeg({ quality: options.quality }).toBuffer();
case 'webp':
return await image.webp({ quality: options.quality }).toBuffer();
case 'png':
default:
return await image.png({ quality: options.quality }).toBuffer();
}
}
}