Compare commits

...

3 Commits

Author SHA1 Message Date
Hermes 1db7b228bd Merge branch 'main' of https://git.reifonas.cloud/admtracksteel/webclone 2026-05-18 11:17:17 +00:00
Hermes 96d1684849 Atualização automática: 2026-05-18 11:16:56 2026-05-18 11:16:56 +00:00
Hermes 7e6e105fdb Atualização automática: 2026-05-18 11:00:54 2026-05-18 11:00:54 +00:00
4 changed files with 183 additions and 71 deletions
+7 -1
View File
@@ -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(
+127 -48
View File
@@ -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);
}
+5 -15
View File
@@ -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
View File
@@ -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({