/**
* AI Article Generator - API Service
* Sistema completo para geração de artigos com IA
* Compatível com Google Gemini e OpenAI
*/class AIArticleService {
constructor() {
this.settings = this.loadSettings();
this.isGenerating = false;
}loadSettings() {
try {
return JSON.parse(localStorage.getItem('ai-generator-settings') || '{}');
} catch {
return {};
}
}// Métodos principais de geração
async generateArticle(formData) {
if (this.isGenerating) {
throw new Error('Já existe uma geração em andamento. Aguarde.');
}this.isGenerating = true;try {
const provider = formData.textProvider || this.settings.defaults?.provider || 'google';
// Gerar conteúdo do artigo
const contentResult = await this.generateContent(formData, provider);
// Gerar imagens
const images = await this.generateImages(contentResult.title, formData.images, provider);
return {
title: contentResult.title,
content: contentResult.content,
images: images
};
} finally {
this.isGenerating = false;
}
}async generateContent(formData, provider) {
const prompt = this.buildContentPrompt(formData);
if (provider === 'google') {
return await this.generateWithGemini(prompt);
} else if (provider === 'openai') {
return await this.generateWithOpenAI(prompt);
} else {
throw new Error('Provedor de IA não suportado: ' + provider);
}
}async generateImages(title, quantity, provider) {
const imageCount = this.getImageCount(quantity);
const images = [];
for (let i = 0; i < imageCount; i++) {
try {
let imageUrl;
if (provider === 'google') {
imageUrl = await this.generateImageWithGemini(title, i);
} else if (provider === 'openai') {
imageUrl = await this.generateImageWithOpenAI(title, i);
}
if (imageUrl) {
images.push(imageUrl);
}
} catch (error) {
console.warn(`Erro ao gerar imagem ${i + 1}:`, error.message);
}
}
return images;
}// Google Gemini Integration
async generateWithGemini(prompt) {
const apiKey = this.settings.apiKeys?.google;
if (!apiKey) {
throw new Error('Chave da API do Google não configurada');
}const response = await this.makeRequest('https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-goog-api-key': apiKey
},
body: JSON.stringify({
contents: [{
parts: [{
text: prompt
}]
}],
generationConfig: {
temperature: 0.7,
topK: 40,
topP: 0.95,
maxOutputTokens: 8192,
}
})
});if (!response.candidates || !response.candidates[0]) {
throw new Error('Resposta inválida da API do Google');
}const content = response.candidates[0].content.parts[0].text;
return this.parseGeneratedContent(content);
}async generateImageWithGemini(title, index) {
// Google Imagen integration would go here
// For now, return a placeholder or use alternative service
return this.generatePlaceholderImage(title, index);
}// OpenAI Integration
async generateWithOpenAI(prompt) {
const apiKey = this.settings.apiKeys?.openai;
if (!apiKey) {
throw new Error('Chave da API da OpenAI não configurada');
}const response = await this.makeRequest('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'gpt-4-turbo-preview',
messages: [
{
role: 'system',
content: 'Você é um especialista em criação de conteúdo e SEO. Sempre responda no formato JSON especificado.'
},
{
role: 'user',
content: prompt
}
],
response_format: { type: "json_object" },
temperature: 0.7,
max_tokens: 4000
})
});if (!response.choices || !response.choices[0]) {
throw new Error('Resposta inválida da API da OpenAI');
}const content = response.choices[0].message.content;
try {
return JSON.parse(content);
} catch {
return this.parseGeneratedContent(content);
}
}async generateImageWithOpenAI(title, index) {
const apiKey = this.settings.apiKeys?.openai;
if (!apiKey) {
return this.generatePlaceholderImage(title, index);
}try {
const prompt = `Professional blog header image for: "${title}". High quality, modern, clean style. No text or letters in the image.`;
const response = await this.makeRequest('https://api.openai.com/v1/images/generations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'dall-e-3',
prompt: prompt,
n: 1,
size: '1792x1024',
quality: 'hd',
response_format: 'b64_json'
})
});if (response.data && response.data[0] && response.data[0].b64_json) {
return `data:image/png;base64,${response.data[0].b64_json}`;
}
} catch (error) {
console.warn('Erro ao gerar imagem com DALL-E:', error.message);
}return this.generatePlaceholderImage(title, index);
}// Utility methods
buildContentPrompt(formData) {
const typeDescriptions = {
'how-to': 'um guia passo a passo (how-to)',
'listicle': 'um artigo em formato de lista',
'informative': 'um artigo informativo e educacional',
'review': 'uma análise detalhada ou review'
};const toneDescriptions = {
'friendly': 'amigável, informal e conversacional',
'professional': 'profissional, formal e autoritativo',
'expert': 'especialista, técnico e detalhado',
'casual': 'casual, relaxado e acessível'
};const lengthGuides = {
'short': 'entre 800 e 1200 palavras',
'medium': 'entre 1200 e 2000 palavras',
'long': 'entre 2000 e 3000 palavras',
'extensive': 'mais de 3000 palavras'
};const articleType = typeDescriptions[formData.type] || 'um artigo de blog';
const tone = toneDescriptions[formData.tone] || 'neutro';
const length = lengthGuides[formData.length] || 'entre 1200 e 2000 palavras';return `
Você é um especialista em criação de conteúdo e SEO. Crie um artigo completo e de alta qualidade seguindo estas especificações:**Tópico:** "${formData.topic}"
**Formato:** ${articleType}
**Tom:** ${tone}
**Tamanho:** ${length}
**Instruções especiais:** ${formData.instructions || 'Nenhuma.'}**Requisitos de saída:**
- A resposta DEVE ser um objeto JSON válido
- O JSON deve conter duas chaves: "title" (string) e "content" (string)
- O "title" deve ser um título atrativo e otimizado para SEO
- O "content" deve ser o artigo completo formatado em HTML limpo
- Use tags HTML apropriadas como
,,
,
,,- , , etc.
- NÃO inclua tags, ou
- Certifique-se de que todas as strings no JSON estejam corretamente escapadas
- O conteúdo deve ser bem estruturado com títulos e parágrafos claros
- O artigo deve aderir estritamente ao tópico, formato, tom e tamanho especificadosExemplo de formato de resposta:
{
"title": "Título Atrativo e Otimizado para SEO",
"content": "
Introdução
Conteúdo do artigo...
"
}
`;
}parseGeneratedContent(content) {
// Try to extract JSON from the content
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
return JSON.parse(jsonMatch[0]);
} catch {
// Fall through to text parsing
}
}// Fallback: extract title and content from text
const lines = content.split('\n').filter(line => line.trim());
const title = lines[0]?.replace(/^#*\s*/, '') || 'Artigo Gerado por IA';
const contentLines = lines.slice(1).join('\n');return {
title: title,
content: this.formatContentAsHTML(contentLines)
};
}formatContentAsHTML(text) {
// Simple text to HTML conversion
return text
.split('\n\n')
.map(paragraph => {
if (paragraph.startsWith('## ')) {
return `${paragraph.substring(3)}
`;
} else if (paragraph.startsWith('### ')) {
return `${paragraph.substring(4)}
`;
} else if (paragraph.trim().startsWith('- ')) {
const items = paragraph.split('\n').map(item =>
item.trim().startsWith('- ') ? `- ${item.substring(2)}
` : item
).join('');
return ``;
} else {
return `${paragraph}
`;
}
})
.join('\n');
}getImageCount(quantity) {
const counts = {
'one': 1,
'few': 2,
'moderate': 3,
'many': 4,
'auto': 3
};
return counts[quantity] || 3;
}generatePlaceholderImage(title, index) {
// Generate a placeholder image URL
const colors = ['667eea', '764ba2', '10b981', 'f59e0b', 'ef4444'];
const color = colors[index % colors.length];
const encodedTitle = encodeURIComponent(title.substring(0, 50));
return `https://via.placeholder.com/800x450/${color}/ffffff?text=${encodedTitle}`;
}async makeRequest(url, options) {
const timeout = this.settings.advanced?.timeout || 60;
const maxRetries = this.settings.advanced?.maxRetries || 3;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout * 1000);
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.text();
throw new Error(`API Error (${response.status}): ${errorData}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, attempt * 1000));
}
}
}// Bulk generation methods
async generateBulkArticles(topics, settings) {
const results = [];
const errors = [];
for (let i = 0; i < topics.length; i++) {
try {
const formData = {
topic: topics[i],
type: settings.type || 'informative',
tone: settings.tone || 'friendly',
length: settings.length || 'medium',
images: settings.images || 'moderate',
textProvider: settings.provider || 'google'
};
const result = await this.generateArticle(formData);
results.push({
index: i,
topic: topics[i],
success: true,
data: result
});
// Add delay between generations to respect API limits
if (i < topics.length - 1) {
await new Promise(resolve => setTimeout(resolve, 2000));
}
} catch (error) {
errors.push({
index: i,
topic: topics[i],
error: error.message
});
}
}
return { results, errors };
}// WordPress integration methods
async publishToWordPress(article, settings) {
// This would integrate with WordPress REST API
// For now, we'll just simulate the process
return new Promise((resolve) => {
setTimeout(() => {
resolve({
success: true,
postId: Math.floor(Math.random() * 1000) + 1,
url: '#'
});
}, 1000);
});
}
}// Export for global use
window.AIArticleService = AIArticleService;// Initialize global instance
window.aiService = new AIArticleService();