Compare commits
3 Commits
de1d32f593
...
1db7b228bd
| Author | SHA1 | Date | |
|---|---|---|---|
| 1db7b228bd | |||
| 96d1684849 | |||
| 7e6e105fdb |
@@ -168,13 +168,19 @@ class WgetCloner {
|
||||
console.log(`${'='.repeat(60)}\n`);
|
||||
});
|
||||
|
||||
// countFiles inline helper
|
||||
var countFiles = function(dir) { var ts=0,fc=0; (function rec(d){if(!fs.existsSync(d))return;fs.readdirSync(d).forEach(function(i){var p=path.join(d,i),s=fs.statSync(p);if(s.isDirectory()&&i.indexOf("cloned-sites")==-1&&i.indexOf(".versions")==-1)rec(p);else if(!s.isDirectory()){ts+=s.size;fc++;}});})(dir);return{fileCount:fc,totalSize:ts};};
|
||||
|
||||
// Criar arquivo de informações
|
||||
const stats = countFiles(siteDir);
|
||||
const info = {
|
||||
originalUrl: url,
|
||||
clonedAt: new Date().toISOString(),
|
||||
directory: siteDir,
|
||||
method: 'wget',
|
||||
success: true
|
||||
success: true,
|
||||
fileCount: stats.fileCount,
|
||||
totalSize: stats.totalSize
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
|
||||
@@ -58,26 +58,39 @@ async function scoutSearch() {
|
||||
}
|
||||
}
|
||||
|
||||
async function askAI(systemPrompt, userMessage) {
|
||||
const response = await fetch('/api/ai/agent', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ systemPrompt, userMessage })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!result.success) throw new Error(result.error);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function simulateScoutSearch(niche, region) {
|
||||
// Simula busca do Scout - retornaria do agente real
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
// Busca do Scout - retorna do agente real MiniMax
|
||||
const prompt = `Você é um agente Scout. Gere uma lista JSON com 4 empresas realistas fictícias do nicho '${niche}' na região '${region}'.
|
||||
Retorne APENAS um array JSON, sem marcação markdown.
|
||||
Estrutura: [{"url": "...", "name": "...", "score": 8, "reason": "...", "social": "..."}]`;
|
||||
|
||||
const examples = [
|
||||
{ url: 'https://lojaexemplo.com.br', name: 'Loja Exemplo Ltda', score: 8, reason: 'Site de 2012, sem responsividade', social: '@lojaexemplo' },
|
||||
{ url: 'https://padariajoao.com.br', name: 'Padaria João', score: 9, reason: 'Site estático, layout antigo', social: '@padariajoao' },
|
||||
{ url: 'https://petshopamigo.com', name: 'Pet Shop Amigo', score: 7, reason: 'Sem SSL, sem mobile', social: '@petshopamigo' },
|
||||
{ url: 'https://restaurantesabor.com.br', name: 'Restaurante Sabor', score: 8, reason: 'Menu em PDF, sem pedido online', social: '@restaurantesabor' },
|
||||
{ url: 'https://academiafit.com.br', name: 'Academia Fit', score: 6, reason: 'Template WordPress genérico', social: '@academiafit' },
|
||||
{ url: 'https://bijouteriajoia.com.br', name: 'Bijouteria Joia', score: 9, reason: 'Site Flash antigo', social: '@bijouteriajoia' },
|
||||
];
|
||||
|
||||
return examples.map((t, i) => ({
|
||||
id: i + 1,
|
||||
...t,
|
||||
niche,
|
||||
region,
|
||||
analyzedAt: new Date().toISOString()
|
||||
}));
|
||||
try {
|
||||
const rawResponse = await askAI(prompt, "Inicie a prospecção.");
|
||||
const jsonStr = rawResponse.replace(/```json/gi, '').replace(/```/g, '').trim();
|
||||
const examples = JSON.parse(jsonStr);
|
||||
|
||||
return examples.map((t, i) => ({
|
||||
id: i + 1,
|
||||
...t,
|
||||
niche,
|
||||
region,
|
||||
analyzedAt: new Date().toISOString()
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error("Erro na IA:", e);
|
||||
throw new Error("O agente falhou em gerar a lista de prospecção. Tente novamente.");
|
||||
}
|
||||
}
|
||||
|
||||
function renderScoutResults(targets) {
|
||||
@@ -189,12 +202,13 @@ async function startDesign() {
|
||||
throw new Error(cloneResult.error || 'Falha ao clonar');
|
||||
}
|
||||
|
||||
agentsState.designer.currentClone.directory = cloneResult.directory;
|
||||
const directory = cloneResult.data?.directory || cloneResult.data?.path || cloneResult.directory;
|
||||
agentsState.designer.currentClone.directory = directory;
|
||||
updateDesignerStatus('✅ Site clonado com sucesso');
|
||||
|
||||
// Step 2: Apply ultra-modern redesign
|
||||
updateDesignerStatus('🎨 Aplicando redesign ultra-moderno...');
|
||||
const redesignResult = await applyUltraModernRedesign(cloneResult.directory);
|
||||
const redesignResult = await applyUltraModernRedesign(directory);
|
||||
|
||||
updateDesignerStatus('✨ Redesign concluído!');
|
||||
showDesignerPreview(redesignResult.previewUrl);
|
||||
@@ -208,7 +222,7 @@ async function startDesign() {
|
||||
}
|
||||
|
||||
async function cloneSite(url) {
|
||||
const response = await fetch('/api/wget-clone', {
|
||||
const response = await fetch('/api/smart-clone', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url })
|
||||
@@ -217,16 +231,30 @@ async function cloneSite(url) {
|
||||
}
|
||||
|
||||
async function applyUltraModernRedesign(cloneDir) {
|
||||
// Simula o redesign - em produção chamaria bsw-designer agent
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
const prompt = `Você é um Engenheiro de Frontend Especialista (Agente Designer), focado em aplicar designs Ultra-Modernos (baseados nas skills clone-website e performance).
|
||||
Retorne APENAS um objeto JSON simulando as alterações que você faria, com a estrutura:
|
||||
{"appliedStyles": ["estilo 1", "estilo 2"], "elementsRedesigned": ["elemento 1", "elemento 2"]}`;
|
||||
|
||||
let appliedStyles = ['glassmorphism', 'gradient-bg', 'smooth-animations', 'dark-mode'];
|
||||
let elementsRedesigned = ['hero', 'cards', 'buttons', 'navigation', 'footer'];
|
||||
|
||||
try {
|
||||
const rawResponse = await askAI(prompt, `Planeje o redesign do site no diretório: ${cloneDir}`);
|
||||
const jsonStr = rawResponse.replace(/```json/gi, '').replace(/```/g, '').trim();
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
if (parsed.appliedStyles) appliedStyles = parsed.appliedStyles;
|
||||
if (parsed.elementsRedesigned) elementsRedesigned = parsed.elementsRedesigned;
|
||||
} catch (e) {
|
||||
console.error("Erro na IA do Designer:", e);
|
||||
}
|
||||
|
||||
const previewUrl = `http://localhost:${window.location.port || 3000}/${cloneDir.split('/').pop()}/index.html`;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
previewUrl,
|
||||
appliedStyles: ['glassmorphism', 'gradient-bg', 'smooth-animations', 'dark-mode'],
|
||||
elementsRedesigned: ['hero', 'cards', 'buttons', 'navigation', 'footer']
|
||||
appliedStyles,
|
||||
elementsRedesigned
|
||||
};
|
||||
}
|
||||
|
||||
@@ -277,10 +305,9 @@ function updateDesignerStatus(status) {
|
||||
|
||||
async function sendOutreachMessage() {
|
||||
const targetId = document.getElementById('outreachTargetId')?.value;
|
||||
const message = document.getElementById('outreachMessage')?.value?.trim();
|
||||
|
||||
if (!targetId || !message) {
|
||||
showAgentError('Outreach', 'Selecione um alvo e escreva a mensagem');
|
||||
if (!targetId) {
|
||||
showAgentError('Outreach', 'Selecione um alvo');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -290,20 +317,29 @@ async function sendOutreachMessage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Simula envio - em produção usaria API de email/whatsapp
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
// Gerar mensagem via IA
|
||||
const prompt = `Você é um Copywriter (Agente Outreach). Escreva uma mensagem de vendas persuasiva para o cliente '${target.name}' cujo site '${target.url}' tem o problema: '${target.reason}'. Ofereça um redesign ultra-moderno. Responda apenas o texto da mensagem (sem formatação json ou markdown).`;
|
||||
|
||||
agentsState.outreach.messages.push({
|
||||
to: target.name,
|
||||
toUrl: target.url,
|
||||
message,
|
||||
sentAt: new Date().toISOString(),
|
||||
status: 'sent'
|
||||
});
|
||||
|
||||
agentsState.outreach.sent++;
|
||||
updateOutreachStats();
|
||||
showOutreachConfirmation(target.name);
|
||||
try {
|
||||
const message = await askAI(prompt, "Crie o e-mail de vendas.");
|
||||
|
||||
const msgBox = document.getElementById('outreachMessage');
|
||||
if (msgBox) msgBox.value = message;
|
||||
|
||||
agentsState.outreach.messages.push({
|
||||
to: target.name,
|
||||
toUrl: target.url,
|
||||
message,
|
||||
sentAt: new Date().toISOString(),
|
||||
status: 'sent'
|
||||
});
|
||||
|
||||
agentsState.outreach.sent++;
|
||||
updateOutreachStats();
|
||||
showOutreachConfirmation(target.name);
|
||||
} catch(e) {
|
||||
showAgentError('Outreach', "Falha ao gerar mensagem: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function showOutreachConfirmation(targetName) {
|
||||
@@ -336,29 +372,72 @@ async function runOrchestratorPipeline() {
|
||||
}
|
||||
|
||||
setOrchestratorRunning(true);
|
||||
updateOrchestratorStatus('🎬 Iniciando pipeline completo...');
|
||||
updateOrchestratorStatus('🎬 Iniciando pipeline completo com IA (Orchestrator)...');
|
||||
|
||||
agentsState.orchestrator.pipeline = targets;
|
||||
agentsState.orchestrator.currentStep = 0;
|
||||
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
const target = targets[i];
|
||||
updateOrchestratorStatus(`📋 [${i+1}/${targets.length}] Processando: ${target.name}`);
|
||||
updateOrchestratorStatus(`📋 [${i+1}/${targets.length}] Planejando com IA: ${target.name}`);
|
||||
|
||||
// Orchestrator AI Prompt integrating the 3 skills
|
||||
const orchPrompt = `Você é o Agente Orchestrator mestre do WebClone. Baseado nas skills 'firecrawl-website-design-clone', 'clone-website' e 'performance', analise o alvo '${target.name}' (${target.url}) que possui o problema '${target.reason}'. Defina uma estratégia de clonagem e otimização em uma única frase concisa.`;
|
||||
|
||||
try {
|
||||
const strategy = await askAI(orchPrompt, "Defina a estratégia de clonagem.");
|
||||
console.log(`[Orchestrator Strategy - ${target.name}]:`, strategy);
|
||||
target.orchestratorStrategy = strategy;
|
||||
} catch (e) {
|
||||
console.error("Erro na IA do Orchestrator:", e);
|
||||
target.orchestratorStrategy = "Estratégia padrão de clonagem e otimização de performance.";
|
||||
}
|
||||
|
||||
// Step 1: Clone
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
updateOrchestratorStatus(`📥 [${i+1}/${targets.length}] Clonando: ${target.name}`);
|
||||
let directory = `cloned-sites/${target.name.toLowerCase().replace(/[^a-z0-9]/g, '')}`;
|
||||
try {
|
||||
const cloneRes = await cloneSite(target.url);
|
||||
if (cloneRes.success && (cloneRes.data?.directory || cloneRes.data?.path || cloneRes.directory)) {
|
||||
directory = cloneRes.data?.directory || cloneRes.data?.path || cloneRes.directory;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Aviso ao clonar ${target.name}, usando diretório de fallback:`, e.message);
|
||||
}
|
||||
target.directory = directory;
|
||||
|
||||
// Step 2: Redesign
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
updateOrchestratorStatus(`🎨 [${i+1}/${targets.length}] Aplicando Redesign AI: ${target.name}`);
|
||||
try {
|
||||
const redesignRes = await applyUltraModernRedesign(directory);
|
||||
target.redesign = redesignRes;
|
||||
} catch (e) {
|
||||
console.error(`Erro no redesign de ${target.name}:`, e);
|
||||
}
|
||||
|
||||
// Step 3: Save to history
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
// Step 3: Outreach AI generation
|
||||
updateOrchestratorStatus(`✉️ [${i+1}/${targets.length}] Gerando Copy de Outreach: ${target.name}`);
|
||||
const outreachPrompt = `Você é um Copywriter (Agente Outreach). Escreva uma mensagem de vendas persuasiva para '${target.name}' (${target.url}) focando em resolver '${target.reason}' com um redesign ultra-moderno e alta performance. Responda apenas o texto do e-mail.`;
|
||||
try {
|
||||
const message = await askAI(outreachPrompt, "Crie o e-mail de vendas.");
|
||||
agentsState.outreach.messages.push({
|
||||
to: target.name,
|
||||
toUrl: target.url,
|
||||
message,
|
||||
sentAt: new Date().toISOString(),
|
||||
status: 'ready'
|
||||
});
|
||||
agentsState.outreach.sent++;
|
||||
updateOutreachStats();
|
||||
} catch (e) {
|
||||
console.error(`Erro no outreach de ${target.name}:`, e);
|
||||
}
|
||||
|
||||
agentsState.orchestrator.currentStep = i + 1;
|
||||
updateOrchestratorProgress((i + 1) / targets.length * 100);
|
||||
}
|
||||
|
||||
updateOrchestratorStatus(`✅ Pipeline completo! ${targets.length} sites processados.`);
|
||||
updateOrchestratorStatus(`✅ Pipeline completo! ${targets.length} sites processados com IA.`);
|
||||
setOrchestratorRunning(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ function populateOverview(data) {
|
||||
if (data.cloneInfo) {
|
||||
overviewItems.push(
|
||||
{ label: 'Clone Status', value: data.cloneInfo.success ? '✅ Sucesso' : '❌ Falhou' },
|
||||
{ label: 'Assets Baixados', value: `${data.cloneInfo.assetsDownloaded}/${data.cloneInfo.totalAssets}` },
|
||||
{ label: 'Assets Baixados', value: data.cloneInfo.fileCount ? `${data.cloneInfo.fileCount} arquivos (${formatBytes(data.cloneInfo.totalSize || 0)})` : 'verificando...' },
|
||||
{ label: 'Diretório Local', value: data.cloneInfo.directory }
|
||||
);
|
||||
}
|
||||
@@ -435,18 +435,8 @@ function openCloneFolder(directory) {
|
||||
}
|
||||
|
||||
function openClonedSite(directory) {
|
||||
// Tentar abrir o index.html do site clonado
|
||||
fetch('/api/open-site', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ path: directory })
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
alert('Não foi possível abrir o site automaticamente.\nAbra manualmente: ' + directory + '\\index.html');
|
||||
}
|
||||
}).catch(error => {
|
||||
alert('Não foi possível abrir o site automaticamente.\nAbra manualmente: ' + directory + '\\index.html');
|
||||
});
|
||||
// Extract folder name and open site in browser via window.open
|
||||
const folderName = directory.split('/').pop();
|
||||
const siteUrl = window.location.protocol + '//' + window.location.host + '/' + folderName + '/index.html';
|
||||
window.open(siteUrl, '_blank');
|
||||
}
|
||||
+44
-7
@@ -77,12 +77,13 @@ app.post('/api/wget-clone', async (req, res) => {
|
||||
console.log('✅ Wget clone completed successfully');
|
||||
|
||||
// Sync to host /root/Desktop/Clonados
|
||||
if (response.data && response.data.success && response.data.directory) {
|
||||
const directory = response.data.data?.directory || response.data.directory;
|
||||
if (response.data && response.data.success && directory) {
|
||||
const { exec } = require('child_process');
|
||||
const containerId = require('os').hostname();
|
||||
const cloneDir = response.data.directory.split('/').pop();
|
||||
const cloneDir = directory.split('/').pop();
|
||||
const hostDir = '/root/Desktop/Clonados';
|
||||
exec(`docker cp b38l2zsxjv08xkxg4gd5rl9r-210808689309:${response.data.directory} ${hostDir}/ 2>/dev/null`, (err) => {
|
||||
exec(`docker cp ${containerId}:${directory} ${hostDir}/ 2>/dev/null`, (err) => {
|
||||
if (err) console.log('⚠️ Sync to host failed:', err.message);
|
||||
else console.log('✅ Clone synced to host:', cloneDir);
|
||||
});
|
||||
@@ -193,8 +194,8 @@ app.post('/api/open-folder', (req, res) => {
|
||||
return res.status(400).json({ error: 'Caminho da pasta é obrigatório' });
|
||||
}
|
||||
|
||||
// Abrir pasta no Windows Explorer
|
||||
exec(`explorer "${folderPath}"`, (error) => {
|
||||
// Abrir pasta no gerenciador de ficheiros
|
||||
exec(`xdg-open "${folderPath}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Erro ao abrir pasta:', error);
|
||||
return res.status(500).json({ error: 'Não foi possível abrir a pasta' });
|
||||
@@ -221,8 +222,8 @@ app.post('/api/open-site', (req, res) => {
|
||||
|
||||
console.log(`🌍 Opening site via HTTP: ${siteUrl}`);
|
||||
|
||||
// Abrir URL no navegador padrão
|
||||
exec(`start "" "${siteUrl}"`, (error) => {
|
||||
// Abrir URL no navegador padrão (Linux)
|
||||
exec(`xdg-open "${siteUrl}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Erro ao abrir site:', error);
|
||||
return res.status(500).json({ error: 'Não foi possível abrir o site via HTTP' });
|
||||
@@ -281,6 +282,42 @@ app.get('/index', (req, res) => {
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// AI Agent Route (MiniMax)
|
||||
app.post('/api/ai/agent', async (req, res) => {
|
||||
const { systemPrompt, userMessage } = req.body;
|
||||
const apiKey = process.env.MINIMAX_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
return res.status(500).json({ error: 'MINIMAX_API_KEY is missing in .env' });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('https://api.minimax.io/v1/chat/completions', {
|
||||
model: "MiniMax-M2.7",
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt || "Você é um assistente de inteligência artificial útil." },
|
||||
{ role: "user", content: userMessage }
|
||||
],
|
||||
max_tokens: 2048
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 90000 // 90s
|
||||
});
|
||||
|
||||
let reply = response.data.choices?.[0]?.message?.content || 'Processamento concluído, porém sem retorno.';
|
||||
// Strip <think> reasoning tags if present
|
||||
reply = reply.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
||||
|
||||
res.json({ success: true, data: reply });
|
||||
} catch (error) {
|
||||
console.error('MiniMax AI Error:', error.response?.data || error.message);
|
||||
res.status(500).json({ error: error.message, details: error.response?.data });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle 404s
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
|
||||
Reference in New Issue
Block a user