const API_URL = "/api"; let currentFiles = []; let pageOrder = []; let currentToolMode = ''; function updateThemeButtonLabel(isDark) { const btn = document.getElementById('themeToggleBtn'); if (btn) { btn.innerHTML = isDark ? '☀️ Modo Claro' : '🌙 Modo Escuro'; } } function toggleTheme() { const isDark = document.body.classList.toggle('dark-mode'); localStorage.setItem('sopdf_theme', isDark ? 'dark' : 'light'); updateThemeButtonLabel(isDark); } (function initTheme() { const savedTheme = localStorage.getItem('sopdf_theme'); if (savedTheme === 'dark') { document.body.classList.add('dark-mode'); updateThemeButtonLabel(true); } })(); function scrollToTools() { document.getElementById('ferramentas').scrollIntoView({ behavior: 'smooth' }); } function openTool(toolType) { const homeView = document.getElementById('home-view'); const toolView = document.getElementById('tool-view'); const body = document.getElementById('tool-content'); homeView.style.display = 'none'; toolView.style.display = 'block'; let content = ''; currentFiles = []; pageOrder = []; switch(toolType) { case 'delete': content = `

🗑️ Excluir e Reordenar Páginas

📄

Selecione o PDF

`; break; case 'merge': content = `

🔗 Mesclar e Reordenar PDFs

📄

Selecione Múltiplos PDFs

`; break; case 'compress': content = `

🗜️ Comprimir PDF

📄

Selecione o PDF

`; break; case 'split': content = `

✂️ Particionar PDF

Nota: Se uma única página for maior que o limite, ela não poderá ser dividida.

📄

Selecione o PDF

`; break; case 'convert': content = `

🔄 Conversões

📄

Selecione o Arquivo

`; break; case 'security': content = `

🛡️ Segurança AES

📄

Selecione o PDF

`; break; case 'compare': content = `

👁️ Comparar PDFs

`; break; case 'ocr': content = `

🔍 Reconhecimento de Texto (OCR)

📄

Selecione o PDF

`; break; case 'validate_signature': content = `

✒️ Validar Assinaturas

Verifique a integridade de assinaturas digitais padrão ICP-Brasil ou Gov.br contidas no documento PDF.

📄

Selecione o PDF Assinado

`; break; case 'repair': content = `

🩹 Reparar PDF

Reconstrua PDFs corrompidos para tentar recuperar o acesso ao arquivo original.

📄

Selecione o PDF Corrompido

`; break; case 'ai_assistant': content = `

🤖 Inteligência Artificial

Gere resumos ou traduza documentos inteiros de forma automática.

📄

Selecione o PDF Base

`; break; case 'editor': content = `

📝 Editar Conteúdo

Extraia e edite o conteúdo textual do seu PDF e exporte novamente.

📄

Selecione o PDF Base

`; // Initialize Quill only when the DOM is ready. We will initialize it inside handleEditorLoad or slightly after. break; } body.innerHTML = content; setupDragAndDrop(); window.scrollTo({ top: 0, behavior: 'smooth' }); } function setupDragAndDrop() { const uploadBox = document.getElementById('uploadBox'); if (!uploadBox) return; ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { uploadBox.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { uploadBox.addEventListener(eventName, () => { uploadBox.style.borderColor = 'var(--ceara-orange-1)'; uploadBox.style.background = 'var(--badge-orange-bg)'; }, false); }); ['dragleave', 'drop'].forEach(eventName => { uploadBox.addEventListener(eventName, () => { uploadBox.style.borderColor = ''; uploadBox.style.background = ''; }, false); }); uploadBox.addEventListener('drop', (e) => { const dt = e.dataTransfer; const files = dt.files; if (files && files.length > 0) { const fileInput = document.getElementById('fileInput'); if (fileInput) { fileInput.files = files; const event = new Event('change', { bubbles: true }); fileInput.dispatchEvent(event); } } }, false); } function closeTool() { document.getElementById('tool-view').style.display = 'none'; document.getElementById('home-view').style.display = 'block'; window.scrollTo({ top: 0, behavior: 'smooth' }); } function updateFileName(input) { if (input.files.length > 0) { document.getElementById('fileName').innerText = "Selecionado: " + input.files[0].name; document.getElementById('uploadBox').style.borderColor = "var(--ceara-green-1)"; document.getElementById('uploadBox').style.background = "var(--upload-hover-bg)"; } } function updateFileNameMultiple(input) { const newFiles = Array.from(input.files); if (newFiles.length === 0) return; currentFiles = currentFiles.concat(newFiles); const fileNamesHtml = currentFiles.map(f => `
${f.name}
`).join(''); document.getElementById('fileName').innerHTML = `${currentFiles.length} arquivo(s) selecionado(s):
${fileNamesHtml}
`; document.getElementById('uploadBox').style.borderColor = "var(--ceara-green-1)"; document.getElementById('uploadBox').style.background = "var(--upload-hover-bg)"; input.value = ''; // clear so same file can be selected again } // Reusable function to render a download button function renderDownloadBtn(blob, filename, extraText="") { const url = window.URL.createObjectURL(blob); const resultArea = document.getElementById('resultArea'); resultArea.innerHTML = `

Processamento concluído com sucesso!

${extraText ? '

'+extraText+'

' : ''} 📥 Baixar Arquivo
`; } // Previews async function loadPreviews(input, mode) { currentToolMode = mode; const newFiles = Array.from(input.files); if(newFiles.length === 0) return; let fileOffset = 0; if(mode === 'delete' || mode === 'split') { currentFiles = newFiles; updateFileName(input); } else { fileOffset = currentFiles.length; currentFiles = currentFiles.concat(newFiles); const fileNamesHtml = currentFiles.map(f => `
${f.name}
`).join(''); document.getElementById('fileName').innerHTML = `${currentFiles.length} arquivo(s) selecionado(s):
${fileNamesHtml}
`; document.getElementById('uploadBox').style.borderColor = "var(--ceara-green-1)"; document.getElementById('uploadBox').style.background = "var(--upload-hover-bg)"; } const fd = new FormData(); newFiles.forEach(f => fd.append("files", f)); document.getElementById('loadingPreview').style.display = 'block'; if(mode === 'delete' || mode === 'split') { document.getElementById('previewContainer').innerHTML = ''; pageOrder = []; } try { const response = await fetch(API_URL + '/preview', { method: 'POST', body: fd }); if(response.ok) { const data = await response.json(); data.files.forEach(file => { file.pages.forEach((b64, idx) => { pageOrder.push({ id: Math.random().toString(36).substr(2, 9), file_index: file.file_index + fileOffset, page_index: idx, b64: b64, deleted: false, filename: file.filename }); }); }); renderGrid(); document.getElementById('submitBtn').style.display = 'block'; } else { alert("Erro ao carregar pré-visualização."); } } catch (e) { alert("Erro de conexão."); } finally { document.getElementById('loadingPreview').style.display = 'none'; if(mode === 'merge') input.value = ''; } } function renderGrid() { const container = document.getElementById('previewContainer'); container.innerHTML = ''; pageOrder.forEach((page, index) => { const card = document.createElement('div'); card.className = 'page-card' + (page.deleted ? ' deleted' : ''); card.dataset.id = page.id; let htmlContent = ''; if (currentToolMode !== 'split') { htmlContent += ``; } htmlContent += `
${page.filename}
Pág ${page.page_index + 1}
`; card.innerHTML = htmlContent; container.appendChild(card); }); // SortableJS (apenas se não for modo split) if (currentToolMode !== 'split') { new Sortable(container, { animation: 150, ghostClass: 'sortable-ghost', onEnd: function (evt) { const itemEl = evt.item; const movedItem = pageOrder.splice(evt.oldIndex, 1)[0]; pageOrder.splice(evt.newIndex, 0, movedItem); }, }); } } window.toggleZoom = function(cardElement) { if (cardElement.classList.contains('zoomed')) { cardElement.classList.remove('zoomed'); } else { document.querySelectorAll('.page-card.zoomed').forEach(el => el.classList.remove('zoomed')); cardElement.classList.add('zoomed'); } }; window.toggleDelete = function(id) { const page = pageOrder.find(p => p.id === id); if(page) { page.deleted = !page.deleted; renderGrid(); } }; // ========================================== // OCR Logic // ========================================== async function handleSimpleSubmit(e, path, typeField, extraFields, isFile) { e.preventDefault(); const fileInput = document.getElementById('fileInput'); if (fileInput.files.length === 0) return alert("Selecione um arquivo."); const formData = new FormData(); formData.append(isFile ? 'file' : 'files', fileInput.files[0]); if(typeField) formData.append('type', document.getElementById(typeField).value); await genericPost(path, formData, 'processado.pdf'); } async function handleValidateSignatureSubmit(e) { e.preventDefault(); const fileInput = document.getElementById('fileInput'); if (!fileInput.files || fileInput.files.length === 0) return; const formData = new FormData(); formData.append("file", fileInput.files[0]); document.getElementById('loading').style.display = 'block'; document.getElementById('submitBtn').disabled = true; document.getElementById('resultArea').innerHTML = ''; try { const response = await fetch(`${API_URL}/verify-signatures`, { method: 'POST', body: formData }); if (!response.ok) { const errText = await response.text(); throw new Error(errText); } const data = await response.json(); let html = ''; if (data.total_signatures === 0) { html = `

Nenhuma Assinatura Encontrada

O documento não possui assinaturas digitais embutidas detectáveis (apenas marcas de carimbo visuais não contam como assinaturas criptográficas válidas).

`; } else { html = `

${data.total_signatures} Assinatura(s) Detectada(s)

`; html += `
`; data.signatures.forEach((sig, index) => { const isTrust = sig.trust_type && sig.trust_type !== "unknown"; const isIntact = sig.is_intact; const isError = sig.signer_name === "Assinatura Desconhecida"; let borderColor = isIntact && isTrust ? "var(--ceara-green-1)" : "var(--ceara-orange-1)"; if (isError || !isIntact) borderColor = "#dc2626"; if (isIntact && sig.trust_type === "govbr") borderColor = "#0284c7"; if (isIntact && sig.trust_type === "suite") borderColor = "#65a30d"; let badgeHtml = ''; if (isIntact) { badgeHtml += `Documento Íntegro `; } else { badgeHtml += `Adulterado após assinado `; } if (sig.trust_type === "icp") { badgeHtml += `[${sig.trust_class}] ${sig.trust_name}`; } else if (sig.trust_type === "govbr") { badgeHtml += `[${sig.trust_class}] ${sig.trust_name}`; } else if (sig.trust_type === "suite") { badgeHtml += `[${sig.trust_class}] ${sig.trust_name}`; } else { badgeHtml += `Emissor Comum/Desconhecido`; } html += `

${index + 1}. ${sig.signer_name}

${badgeHtml}

Data: ${sig.date}

Emissor/Autoridade: ${sig.issuer}

${isError ? `

${sig.validation_message}

` : ''}
`; }); html += `
`; } document.getElementById('resultArea').innerHTML = html; } catch (err) { document.getElementById('resultArea').innerHTML = `

Erro ao validar:

${err.message}

`; } finally { document.getElementById('loading').style.display = 'none'; document.getElementById('submitBtn').disabled = false; } } async function handleOCRSubmit(e) { e.preventDefault(); const fileInput = document.getElementById('fileInput'); if (fileInput.files.length === 0) return alert("Selecione um arquivo."); const lang = document.getElementById('ocr-lang').value; document.getElementById('submitBtn').disabled = true; document.getElementById('loading').style.display = 'block'; document.getElementById('resultArea').innerHTML = ''; try { const formData = new FormData(); formData.append('file', fileInput.files[0]); formData.append('lang', lang); const response = await fetch(`${API_URL}/ocr`, { method: 'POST', body: formData }); if (!response.ok) { const errText = await response.text(); throw new Error(errText || 'Erro ao processar OCR'); } const blob = await response.blob(); const originalName = fileInput.files[0].name.replace('.pdf', ''); renderDownloadBtn(blob, `${originalName}_pesquisavel.pdf`); } catch (error) { alert('Erro no OCR: ' + error.message); } finally { document.getElementById('submitBtn').disabled = false; document.getElementById('loading').style.display = 'none'; } } async function handleReconstruct(e, filename) { e.preventDefault(); const activePages = pageOrder.filter(p => !p.deleted); if(activePages.length === 0) { alert("Você deve deixar pelo menos 1 página ativa."); return; } const fd = new FormData(); currentFiles.forEach(f => fd.append("files", f)); const orderData = activePages.map(p => ({file_index: p.file_index, page_index: p.page_index})); fd.append("order", JSON.stringify(orderData)); await genericPost('/reconstruct', fd, filename); } async function genericPost(url, formData, defaultFilename) { document.getElementById('submitBtn').style.display = 'none'; document.getElementById('loading').style.display = 'block'; document.getElementById('resultArea').innerHTML = ''; try { const response = await fetch(API_URL + url, { method: 'POST', body: formData }); if (response.ok) { const blob = await response.blob(); const disposition = response.headers.get('Content-Disposition'); let filename = defaultFilename; if (disposition && disposition.indexOf('filename=') !== -1) { filename = disposition.split('filename=')[1].replace(/"/g, ''); } renderDownloadBtn(blob, filename); } else { const err = await response.json(); alert("Erro: " + (err.detail || "Falha no processamento.")); } } catch (error) { console.error(error); alert("Erro de conexão com o servidor."); } finally { if(document.getElementById('submitBtn')) document.getElementById('submitBtn').style.display = 'block'; if(document.getElementById('loading')) document.getElementById('loading').style.display = 'none'; } } async function handleCompress(e) { e.preventDefault(); const fd = new FormData(); fd.append("file", document.getElementById('fileInput').files[0]); fd.append("level", document.getElementById('compressLevel').value); document.getElementById('submitBtn').style.display = 'none'; document.getElementById('loading').style.display = 'block'; document.getElementById('resultArea').innerHTML = ''; try { const response = await fetch(API_URL + '/compress', { method: 'POST', body: fd }); if (response.ok) { const blob = await response.blob(); const sizeMB = (blob.size / (1024 * 1024)).toFixed(2); let filename = "comprimido.pdf"; const disposition = response.headers.get('Content-Disposition'); if (disposition && disposition.indexOf('filename=') !== -1) { filename = disposition.split('filename=')[1].replace(/["']/g, ''); } renderDownloadBtn(blob, filename, "Tamanho final: " + sizeMB + " MB"); } else { alert("Erro ao processar."); } } catch (e) { alert("Erro de conexão."); } finally { if(document.getElementById('submitBtn')) document.getElementById('submitBtn').style.display = 'block'; if(document.getElementById('loading')) document.getElementById('loading').style.display = 'none'; } } async function handleSplit(e) { e.preventDefault(); const fd = new FormData(); fd.append("file", document.getElementById('fileInput').files[0]); fd.append("level", document.getElementById('splitCompress').value); const mode = document.getElementById('splitMode').value; fd.append("mode", mode); if (mode === 'size') { fd.append("max_size_mb", document.getElementById('maxMb').value); } else { const intervals = document.getElementById('splitIntervals').value.trim(); if (!intervals) { alert("Por favor, digite os intervalos desejados."); return; } fd.append("intervals", intervals); fd.append("merge", document.getElementById('splitMerge').checked); } document.getElementById('submitBtn').style.display = 'none'; document.getElementById('loading').style.display = 'block'; const resultArea = document.getElementById('resultArea'); resultArea.innerHTML = ''; try { const response = await fetch(API_URL + '/split', { method: 'POST', body: fd }); if (response.ok) { const blob = await response.blob(); let zipFilename = "particoes.zip"; const disposition = response.headers.get('Content-Disposition'); if (disposition && disposition.indexOf('filename=') !== -1) { zipFilename = disposition.split('filename=')[1].replace(/["']/g, ''); } // Read ZIP using JSZip const jszip = new JSZip(); const zip = await jszip.loadAsync(blob); let html = '
'; html += '

Partições Geradas

'; html += ''; html += '📥 Baixar Todas as Partições (.zip)'; html += '
'; resultArea.innerHTML = html; } else { alert("Erro ao particionar."); } } catch (e) { console.error(e); alert("Erro de conexão."); } finally { if(document.getElementById('submitBtn')) document.getElementById('submitBtn').style.display = 'block'; if(document.getElementById('loading')) document.getElementById('loading').style.display = 'none'; } } async function handleConvert(e) { e.preventDefault(); const fd = new FormData(); const type = document.getElementById('convertType').value; if(type === 'pdf-from-images') { if(currentFiles.length === 0) return alert("Selecione pelo menos um arquivo."); for(let i=0; i 0 ? currentFiles[0] : (fileInput.files.length > 0 ? fileInput.files[0] : null); if(!singleFile) return alert("Selecione um arquivo."); fd.append("file", singleFile); let ext = type === 'word' ? '.docx' : type === 'excel' ? '.xlsx' : type === 'pptx' ? '.pptx' : '.zip'; await genericPost('/convert/' + type, fd, 'convertido' + ext); } } async function handleSecurity(e) { e.preventDefault(); const fd = new FormData(); fd.append("file", document.getElementById('fileInput').files[0]); fd.append("password", document.getElementById('secPassword').value); const action = document.getElementById('secAction').value; await genericPost('/security/' + action, fd, 'seguranca.pdf'); } async function handleCompare(e) { e.preventDefault(); const fd = new FormData(); fd.append("file_a", document.getElementById('fileA').files[0]); fd.append("file_b", document.getElementById('fileB').files[0]); document.getElementById('submitBtn').style.display = 'none'; document.getElementById('loading').style.display = 'block'; const resultArea = document.getElementById('resultArea'); resultArea.innerHTML = ''; try { const response = await fetch(API_URL + '/security/compare', { method: 'POST', body: fd }); if (response.ok) { const data = await response.json(); let html = ''; for(let i=0; i'; if(data.images_a[i]) html += ''; html += ''; html += '
'; html += '

Modificado - Página ' + (i+1) + '

'; if(data.images_b[i]) html += ''; html += '
'; html += ''; } resultArea.innerHTML = html; } else { alert("Erro ao comparar PDFs."); } } catch (error) { console.error(error); alert("Erro de conexão."); } finally { if(document.getElementById('submitBtn')) document.getElementById('submitBtn').style.display = 'block'; if(document.getElementById('loading')) document.getElementById('loading').style.display = 'none'; } } function toggleSplitMode() { const mode = document.getElementById('splitMode').value; if (mode === 'size') { document.getElementById('sizeModeParams').style.display = 'block'; document.getElementById('intervalModeParams').style.display = 'none'; } else { document.getElementById('sizeModeParams').style.display = 'none'; document.getElementById('intervalModeParams').style.display = 'block'; } } async function handleRepairSubmit(e) { e.preventDefault(); const fd = new FormData(); fd.append("file", document.getElementById('fileInput').files[0]); await genericPost('/repair', fd, 'reparado.pdf'); } let quillEditor = null; let currentEditorFile = null; async function handleEditorLoad(e) { e.preventDefault(); const file = document.getElementById('fileInput').files[0]; if (!file) return; currentEditorFile = file; const fd = new FormData(); fd.append("file", file); document.getElementById('submitBtn').style.display = 'none'; document.getElementById('loading').style.display = 'block'; try { const response = await fetch(API_URL + '/extract-html', { method: 'POST', body: fd }); if (response.ok) { const data = await response.json(); document.getElementById('editor-container').style.display = 'block'; if (!quillEditor) { quillEditor = new Quill('#quill-editor', { theme: 'snow', modules: { toolbar: [ [{ 'header': [1, 2, 3, false] }], ['bold', 'italic', 'underline', 'strike'], [{ 'color': [] }, { 'background': [] }], [{ 'list': 'ordered'}, { 'list': 'bullet' }], [{ 'align': [] }], ['clean'] ] } }); } quillEditor.clipboard.dangerouslyPasteHTML(data.html || ""); } else { alert("Erro ao extrair conteúdo."); } } catch (err) { alert("Erro de conexão."); } finally { document.getElementById('submitBtn').style.display = 'block'; document.getElementById('loading').style.display = 'none'; } } async function triggerAIActions(action, param) { const file = document.getElementById('fileInput').files[0]; if (!file) { alert("Selecione um arquivo PDF primeiro!"); return; } const fd = new FormData(); fd.append("file", file); if (action === 'summarize') { fd.append("length", param); } else if (action === 'translate') { fd.append("lang", document.getElementById('translate-lang').value); } document.getElementById('loading').style.display = 'block'; document.getElementById('resultContainer').style.display = 'none'; try { const response = await fetch(API_URL + '/' + action, { method: 'POST', body: fd }); if (response.ok) { const data = await response.json(); document.getElementById('resultContainer').style.display = 'block'; document.getElementById('previewArea').innerHTML = data.html || ""; } else { let errMsg = "Erro na inteligência artificial."; try { const errData = await response.json(); errMsg += " Detalhes: " + (errData.detail || errData.error || ""); } catch(e) {} alert(errMsg); } } catch (err) { alert("Erro de conexão."); } finally { document.getElementById('loading').style.display = 'none'; } } async function downloadBackendPDF(html, defaultName) { const fd = new FormData(); fd.append("html_content", html); // Mostra um aviso enquanto baixa const btn = document.activeElement; const oldText = btn ? btn.innerText : ""; if (btn) btn.innerText = "⏳ Gerando..."; try { const response = await fetch(API_URL + '/export-pdf', { method: 'POST', body: fd }); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = defaultName; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); a.remove(); } else { alert("Erro ao exportar o PDF pelo servidor."); } } catch(err) { alert("Erro de conexão ao exportar PDF."); } finally { if (btn) btn.innerText = oldText; } } function downloadPreviewPDF(defaultName) { const content = document.getElementById('previewArea'); if (!content) return; downloadBackendPDF(content.innerHTML, defaultName); } function downloadEditorPDF() { if (!quillEditor) return; const content = document.querySelector('.ql-editor').innerHTML; downloadBackendPDF(content, 'SOPDF_Editado.pdf'); }