oraculo / templates /index.html
victorafarias's picture
Correções e evoluções
1f56a52
raw
history blame
15 kB
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sistema Multi-Agente de IA</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div id="loader-overlay" style="display: none;">
<div class="loader-content">
<div class="loader-spinner"></div>
<p id="loader-message">Processando sua solicitação...</p>
<div class="progress-bar-container"><div id="progress-bar" class="progress-bar"></div></div>
</div>
</div>
<button id="merge-btn" class="floating-merge-btn" style="display: none;">Processar Merge</button>
<div class="container">
<div class="header-container">
<div>
<h1>Sistema Multi-Agente IA</h1>
<p id="flow-description">GROK ➔ Claude Sonnet ➔ Gemini</p>
</div>
<div class="controls-container">
<div class="mode-toggle" title="A versão 'Hierárquica' gerará um único no texto que passará por revisão em duas instâncias. Na versão 'Atômica', serão gerados 3 textos, um em cada modelo de IA; e depois um 4º texto será gerado fazendo um texto final consolidado dessas 3 versões.">
<span>Hierárquico</span>
<label class="switch"><input type="checkbox" id="processing-mode-switch"><span class="slider round"></span></label>
<span>Atômico</span>
</div>
<div class="mode-toggle">
<span>Modo Real</span>
<label class="switch"><input type="checkbox" id="mode-switch"><span class="slider round"></span></label>
<span>Modo Teste</span>
</div>
<button class="refresh-btn" onclick="window.location.href='/'" title="Limpar e começar de novo">Nova Consulta</button>
</div>
</div>
<div id="error-box-container"></div>
<div id="real-form-container">
<form id="request-form-real">
<label for="solicitacao_usuario">Digite sua solicitação (ou arraste arquivos aqui):</label>
<textarea name="solicitacao_usuario" id="solicitacao_usuario" rows="8" required></textarea>
<div id="file-list-container"><p>Arquivos Anexados:</p><ul id="file-list"></ul></div>
<button type="submit">Processar com IA</button>
</form>
</div>
<div id="mock-form-container" style="display: none;">
<form id="request-form-mock">
<label for="mock_text">Cole o texto de simulação aqui:</label>
<textarea name="mock_text" id="mock_text" rows="10" required></textarea>
<button type="submit">Simular Resposta</button>
</form>
</div>
<div id="results-container" class="results-container" style="display: none;">
<div class="result-column"><div class="column-header"><h2>GROK</h2><button class="copy-btn" onclick="copyToClipboard('grok-output')">Copiar</button></div><div class="output-box" id="grok-output"></div></div>
<div class="result-column"><div class="column-header"><h2>Claude Sonnet</h2><button class="copy-btn" onclick="copyToClipboard('sonnet-output')">Copiar</button></div><div class="output-box" id="sonnet-output"></div></div>
<div class="result-column"><div class="column-header"><h2>Gemini</h2><button class="copy-btn" onclick="copyToClipboard('gemini-output')">Copiar</button></div><div class="output-box" id="gemini-output"></div></div>
</div>
<!-- Container do Resultado Final com ID no título -->
<div id="final-result-container" style="display: none;">
<h2 id="final-result-title">Texto Final</h2>
<div class="output-box" id="final-output"></div>
</div>
</div>
<script>
// --- Variáveis Globais e Lógica de UI (sem alterações) ---
const processingModeSwitch = document.getElementById('processing-mode-switch');
const modeSwitch = document.getElementById('mode-switch');
const realContainer = document.getElementById('real-form-container');
const mockContainer = document.getElementById('mock-form-container');
const loader = document.getElementById('loader-overlay');
const loaderMessage = document.getElementById('loader-message');
const progressBar = document.getElementById('progress-bar');
const resultsContainer = document.getElementById('results-container');
const errorContainer = document.getElementById('error-box-container');
const textarea = document.getElementById('solicitacao_usuario');
const fileList = document.getElementById('file-list');
const mergeBtn = document.getElementById('merge-btn');
const finalResultContainer = document.getElementById('final-result-container');
const finalOutput = document.getElementById('final-output');
let attachedFiles = [];
let originalUserQuery = "";
modeSwitch.addEventListener('change', function() {
realContainer.style.display = this.checked ? 'none' : 'block';
mockContainer.style.display = this.checked ? 'block' : 'none';
});
processingModeSwitch.addEventListener('change', function() {
document.getElementById('flow-description').textContent = this.checked ? "GROK | Claude Sonnet | Gemini (Paralelo)" : "GROK ➔ Claude Sonnet ➔ Gemini";
});
// --- Lógica de Upload (sem alterações) ---
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
textarea.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => textarea.addEventListener(eventName, () => textarea.classList.add('drag-over'), false));
['dragleave', 'drop'].forEach(eventName => textarea.addEventListener(eventName, () => textarea.classList.remove('drag-over'), false));
textarea.addEventListener('drop', handleDrop, false);
function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); }
function handleDrop(e) { handleFiles(e.dataTransfer.files); }
function handleFiles(files) {
[...files].forEach(file => {
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain'];
if (!allowedTypes.includes(file.type)) { showError(`Formato não suportado: ${file.name}`); return; }
if (file.size > 100 * 1024 * 1024) { showError(`Arquivo muito grande: ${file.name}`); return; }
attachedFiles.push(file);
});
updateFileList();
}
function updateFileList() {
fileList.innerHTML = '';
document.getElementById('file-list-container').style.display = attachedFiles.length > 0 ? 'block' : 'none';
attachedFiles.forEach((file, index) => {
const li = document.createElement('li');
li.textContent = `${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`;
const removeBtn = document.createElement('span');
removeBtn.textContent = '×';
removeBtn.className = 'remove-file-btn';
removeBtn.onclick = () => { attachedFiles.splice(index, 1); updateFileList(); };
li.appendChild(removeBtn);
fileList.appendChild(li);
});
}
// --- Lógica de Submissão Principal (sem alterações) ---
document.getElementById('request-form-real').addEventListener('submit', handleFormSubmit);
document.getElementById('request-form-mock').addEventListener('submit', handleFormSubmit);
async function handleFormSubmit(event) {
event.preventDefault();
errorContainer.innerHTML = '';
resultsContainer.style.display = 'none';
finalResultContainer.style.display = 'none';
mergeBtn.style.display = 'none';
document.querySelectorAll('.output-box').forEach(box => box.innerHTML = '');
loaderMessage.textContent = 'Iniciando conexão...';
progressBar.style.width = '0%';
loader.style.display = 'flex';
const formData = new FormData();
formData.append('processing_mode', processingModeSwitch.checked ? 'atomic' : 'hierarchical');
if (modeSwitch.checked) {
formData.append('mode', 'test');
formData.append('mock_text', document.getElementById('mock_text').value);
originalUserQuery = "Simulação de teste.";
} else {
formData.append('mode', 'real');
originalUserQuery = document.getElementById('solicitacao_usuario').value;
formData.append('solicitacao', originalUserQuery);
attachedFiles.forEach(file => { formData.append('files', file); });
}
try {
const response = await fetch('/process', { method: 'POST', body: formData });
if (!response.ok || !response.body) throw new Error(`Erro na resposta do servidor: ${response.statusText}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n\n');
lines.forEach(line => {
if (line.startsWith('data: ')) {
const jsonData = line.substring(6);
if (jsonData.trim()) {
try {
const data = JSON.parse(jsonData);
processStreamData(data, false);
} catch (e) { console.error("Erro ao parsear JSON:", jsonData); }
}
}
});
}
} catch (error) {
showError('A conexão com o servidor falhou.');
loader.style.display = 'none';
console.error("Fetch Error:", error);
}
}
// --- Lógica do Botão de Merge com Streaming ---
mergeBtn.addEventListener('click', async function() {
loaderMessage.textContent = 'Processando o merge dos textos...';
progressBar.style.width = '0%';
loader.style.display = 'flex';
this.style.display = 'none';
const payload = {
solicitacao_usuario: originalUserQuery,
grok_text: document.getElementById('grok-output').innerText,
sonnet_text: document.getElementById('sonnet-output').innerText,
gemini_text: document.getElementById('gemini-output').innerText,
};
try {
const response = await fetch('/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok || !response.body) throw new Error(`Erro na resposta do servidor: ${response.statusText}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n\n');
lines.forEach(line => {
if (line.startsWith('data: ')) {
const jsonData = line.substring(6);
if (jsonData.trim()) {
try {
const data = JSON.parse(jsonData);
processStreamData(data, true); // true = é merge
} catch (e) { console.error("Erro ao parsear JSON do merge:", jsonData); }
}
}
});
}
} catch (error) {
showError("A conexão falhou ao tentar processar o merge.");
loader.style.display = 'none';
}
});
// --- Função de Processamento de Stream ATUALIZADA ---
function processStreamData(data, isMerge) {
if (data.error) {
showError(data.error);
loader.style.display = 'none';
return;
}
loaderMessage.textContent = data.message;
progressBar.style.width = data.progress + '%';
if (isMerge && data.final_result) {
const finalTitle = document.getElementById('final-result-title');
finalOutput.innerHTML = data.final_result.content;
// ATUALIZAÇÃO: Modifica o título com a contagem de palavras
if (data.final_result.word_count) {
finalTitle.textContent = `Texto Final (${data.final_result.word_count} palavras)`;
}
finalResultContainer.style.display = 'block';
} else if (data.partial_result) {
resultsContainer.style.display = 'flex';
const targetBox = document.getElementById(data.partial_result.id);
if (targetBox) targetBox.innerHTML = data.partial_result.content;
}
if (data.done) {
setTimeout(() => {
loader.style.display = 'none';
if (data.mode === 'atomic' && !isMerge) {
mergeBtn.style.display = 'block';
}
}, 1000);
}
}
function showError(message) {
errorContainer.innerHTML = `<div class="error-box"><strong>Erro:</strong> ${message}<span class="close-btn-error" onclick="this.parentElement.style.display='none';" title="Fechar">&times;</span></div>`;
}
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
navigator.clipboard.writeText(element.innerText).then(() => { alert('Texto copiado!'); });
}
</script>
</body>
</html>