Spaces:
Running
Running
Download frontend/script.js from sopce/sopdf-editor: direct link, hf CLI and curl.
- Browser
- Download file 55.8 kB
-
https://huggingface.co/spaces/sopce/sopdf-editor/resolve/main/frontend/script.js
- Command line
-
hf download hf://spaces/sopce/sopdf-editor/frontend/script.js
-
curl -L -o script.js https://huggingface.co/spaces/sopce/sopdf-editor/resolve/main/frontend/script.js
55.8 kB
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 = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">🗑️ Excluir e Reordenar Páginas</h2>
<form id="toolForm" onsubmit="handleReconstruct(event, 'modificado.pdf')">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="loadPreviews(this, 'delete')" required>
</div>
<div id="loadingPreview" style="display: none; text-align: center; color: var(--ceara-yellow); margin-top: 10px;">Carregando visualização...</div>
<div id="previewContainer" class="page-grid"></div>
<button type="submit" class="btn-primary" style="width: 100%; margin-top: 1rem; display:none;" id="submitBtn">Salvar Alterações</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'merge':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-green-1);">🔗 Mesclar e Reordenar PDFs</h2>
<form id="toolForm" onsubmit="handleReconstruct(event, 'mesclado.pdf')">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione Múltiplos PDFs</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" multiple onchange="loadPreviews(this, 'merge')">
</div>
<div id="loadingPreview" style="display: none; text-align: center; color: var(--ceara-yellow); margin-top: 10px;">Carregando visualização...</div>
<div id="previewContainer" class="page-grid"></div>
<button type="submit" class="btn-primary" style="width: 100%; margin-top: 1rem; display:none;" id="submitBtn">Salvar PDF Mesclado</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'compress':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-green-1);">🗜️ Comprimir PDF</h2>
<form id="toolForm" onsubmit="handleCompress(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<div class="input-group">
<label>Nível de Compressão</label>
<select id="compressLevel">
<option value="recomendada">Recomendada (Boa qualidade)</option>
<option value="extrema">Extrema (Menor tamanho possível)</option>
<option value="baixa">Baixa (Alta qualidade)</option>
</select>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Comprimir Arquivo</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando... Aguarde.</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'split':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">✂️ Particionar PDF</h2>
<p style="color: var(--text-secondary); font-size:0.8rem; margin-bottom: 1rem;">Nota: Se uma única página for maior que o limite, ela não poderá ser dividida.</p>
<form id="toolForm" onsubmit="handleSplit(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="loadPreviews(this, 'split')" required>
</div>
<div id="loadingPreview" style="display: none; text-align: center; color: var(--ceara-yellow); margin-top: 10px;">Carregando visualização...</div>
<div id="previewContainer" class="page-grid"></div>
<div class="input-group">
<label>Modo de Partição</label>
<select id="splitMode" onchange="toggleSplitMode()">
<option value="size">Por Tamanho Máximo</option>
<option value="interval">Por Intervalos de Páginas</option>
</select>
</div>
<div id="sizeModeParams">
<div class="input-group">
<label>Tamanho máximo por arquivo (MB)</label>
<input type="number" id="maxMb" value="2.0" step="0.1" min="0.1">
</div>
</div>
<div id="intervalModeParams" style="display: none;">
<div class="input-group">
<label>Intervalos para extrair (ex: 1-5, 8, 11-13)</label>
<input type="text" id="splitIntervals" placeholder="ex: 1-5, 8-10">
</div>
<div class="input-group" style="flex-direction: row; align-items: center; gap: 10px;">
<input type="checkbox" id="splitMerge" style="width: auto;">
<label for="splitMerge" style="margin: 0; font-weight: normal; cursor: pointer;">Mesclar todos os intervalos em um arquivo PDF</label>
</div>
</div>
<div class="input-group">
<label>Compressão Base</label>
<select id="splitCompress">
<option value="nenhuma">Nenhuma (Apenas particionar)</option>
<option value="recomendada">Recomendada</option>
<option value="extrema">Extrema</option>
</select>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Processar Partição</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando partições... Aguarde.</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'convert':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">🔄 Conversões</h2>
<form id="toolForm" onsubmit="handleConvert(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o Arquivo</p>
<input type="file" id="fileInput" accept=".pdf,.png,.jpg,.jpeg" style="display: none;" multiple onchange="updateFileNameMultiple(this)">
</div>
<div class="input-group">
<label>Tipo de Conversão</label>
<select id="convertType">
<option value="word">PDF para Word (.docx)</option>
<option value="excel">PDF para Excel (.xlsx)</option>
<option value="pptx">PDF para PowerPoint (.pptx)</option>
<option value="images">PDF para Imagens (.zip)</option>
<option value="pdf-from-images">Imagens para PDF</option>
</select>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Converter</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Convertendo...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'security':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-green-1);">🛡️ Segurança AES</h2>
<form id="toolForm" onsubmit="handleSecurity(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<div class="input-group">
<label>Ação</label>
<select id="secAction">
<option value="protect">Proteger (Adicionar Senha)</option>
<option value="unlock">Desbloquear (Remover Senha)</option>
</select>
</div>
<div class="input-group">
<label>Senha</label>
<input type="password" id="secPassword" required>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Aplicar</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'compare':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">👁️ Comparar PDFs</h2>
<form id="toolForm" onsubmit="handleCompare(event)">
<div style="display:flex; gap:10px;">
<div class="input-group" style="flex:1;">
<label>PDF A (Original)</label>
<input type="file" id="fileA" accept=".pdf" required style="color: var(--text-primary); width:100%;">
</div>
<div class="input-group" style="flex:1;">
<label>PDF B (Modificado)</label>
<input type="file" id="fileB" accept=".pdf" required style="color: var(--text-primary); width:100%;">
</div>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Comparar Visualmente</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Analisando diferenças...</div>
<div id="resultArea" style="margin-top: 1rem; display: flex; gap: 10px; flex-direction: column;"></div>
</form>
`;
break;
case 'ocr':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">🔍 Reconhecimento de Texto (OCR)</h2>
<form id="toolForm" onsubmit="handleOCRSubmit(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<div class="input-group">
<label>Idioma do Documento</label>
<select id="ocr-lang">
<option value="por">Português</option>
<option value="eng">Inglês</option>
<option value="por+eng">Português + Inglês</option>
</select>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Tornar Pesquisável</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Lendo caracteres (isso pode demorar minutos)...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'validate_signature':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">✒️ Validar Assinaturas</h2>
<p style="margin-bottom: 1rem; color: var(--text-secondary);">Verifique a integridade de assinaturas digitais padrão ICP-Brasil ou Gov.br contidas no documento PDF.</p>
<form id="toolForm" onsubmit="handleValidateSignatureSubmit(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF Assinado</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Analisar Assinaturas</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Lendo chaves e cadeias do certificado...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'repair':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">🩹 Reparar PDF</h2>
<p style="margin-bottom: 1rem; color: var(--text-secondary);">Reconstrua PDFs corrompidos para tentar recuperar o acesso ao arquivo original.</p>
<form id="toolForm" onsubmit="handleRepairSubmit(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF Corrompido</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Tentar Reparar</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Analisando estrutura e reparando XREF...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'ai_assistant':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">🤖 Inteligência Artificial</h2>
<p style="margin-bottom: 1rem; color: var(--text-secondary);">Gere resumos ou traduza documentos inteiros de forma automática.</p>
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF Base</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<div style="display: flex; gap: 10px; margin-top: 1rem; background: var(--bg-subtle); border: 1px solid var(--bg-glass-border); padding: 10px; border-radius: 8px; flex-wrap: wrap;">
<button class="btn-outline" onclick="triggerAIActions('summarize', 'curto')">Resumo Curto</button>
<button class="btn-outline" onclick="triggerAIActions('summarize', 'medio')">Resumo Médio</button>
<button class="btn-outline" onclick="triggerAIActions('summarize', 'longo')">Resumo Longo</button>
<div style="width: 1px; background: var(--input-border); margin: 0 5px;"></div>
<select id="translate-lang" style="padding: 5px 10px; border-radius: 6px; background: var(--input-bg); color: var(--text-primary); border: 1px solid var(--input-border);">
<option value="en">Inglês</option>
<option value="es">Espanhol</option>
<option value="pt" selected>Português</option>
</select>
<button class="btn-outline" onclick="triggerAIActions('translate')">Traduzir Texto</button>
</div>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">A IA está processando o documento, isso pode levar alguns minutos...</div>
<div id="resultContainer" style="display: none; margin-top: 2rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="color: var(--ceara-orange-1);">Pré-visualização</h3>
<button class="btn-primary" onclick="downloadPreviewPDF('Documento_IA.pdf')">📥 Baixar PDF</button>
</div>
<div id="previewArea" style="background: var(--bg-dark-card); color: var(--text-primary); border: 1px solid var(--bg-glass-border); border-radius: 8px; padding: 20px; max-height: 50vh; overflow-y: auto; font-family: 'Inter', sans-serif;"></div>
</div>
`;
break;
case 'editor':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">📝 Editar Conteúdo</h2>
<p style="margin-bottom: 1rem; color: var(--text-secondary);">Extraia e edite o conteúdo textual do seu PDF e exporte novamente.</p>
<form id="toolForm" onsubmit="handleEditorLoad(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF Base</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Carregar Texto no Editor</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Extraindo conteúdo (aguarde)...</div>
</form>
<!-- Editor Container (Oculto inicialmente) -->
<div id="editor-container" style="display: none; margin-top: 2rem;">
<div style="display: flex; gap: 10px; margin-bottom: 10px; background: var(--bg-subtle); border: 1px solid var(--bg-glass-border); padding: 10px; border-radius: 8px; flex-wrap: wrap;">
<button class="btn-primary" onclick="downloadEditorPDF()">📥 Salvar Edição em PDF</button>
</div>
<div style="background: white; color: black; border-radius: 4px;">
<div id="quill-editor" style="height: 60vh; font-family: 'Inter', sans-serif;"></div>
</div>
</div>
`;
// 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 => `<div style="background: var(--badge-green-bg); color: var(--badge-green-text); padding: 4px 10px; border-radius: 6px; display: inline-block; margin: 3px; font-size: 0.85rem; font-weight: 600; border: 1px solid var(--badge-green-border);">${f.name}</div>`).join('');
document.getElementById('fileName').innerHTML = `<b style="display:block; margin-bottom: 8px; color: var(--text-primary);">${currentFiles.length} arquivo(s) selecionado(s):</b><div style="max-height: 120px; overflow-y: auto; padding: 4px;">${fileNamesHtml}</div>`;
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 = `
<div style="padding: 1.2rem; background: var(--upload-hover-bg); border: 1px solid var(--badge-green-border); border-radius: 10px; text-align: center;">
<p style="color: var(--text-primary); font-weight: 600; margin-bottom: 1rem;">Processamento concluído com sucesso!</p>
${extraText ? '<p style="color: var(--ceara-orange-1); margin-bottom: 1rem; font-weight: bold;">'+extraText+'</p>' : ''}
<a href="${url}" download="${filename}" class="btn-primary" style="text-decoration: none; display: inline-block;">📥 Baixar Arquivo</a>
</div>
`;
}
// 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 => `<div style="background: var(--badge-green-bg); color: var(--badge-green-text); padding: 4px 10px; border-radius: 6px; display: inline-block; margin: 3px; font-size: 0.85rem; font-weight: 600; border: 1px solid var(--badge-green-border);">${f.name}</div>`).join('');
document.getElementById('fileName').innerHTML = `<b style="display:block; margin-bottom: 8px; color: var(--text-primary);">${currentFiles.length} arquivo(s) selecionado(s):</b><div style="max-height: 120px; overflow-y: auto; padding: 4px;">${fileNamesHtml}</div>`;
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 += `<button type="button" class="delete-btn" onclick="toggleDelete('${page.id}')" title="Remover/Restaurar">✕</button>`;
}
htmlContent += `
<img src="data:image/jpeg;base64,${page.b64}" onclick="toggleZoom(this.parentNode)">
<div class="page-label">
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;" title="${page.filename}">${page.filename}</div>
<div>Pág ${page.page_index + 1}</div>
</div>
`;
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 = `
<div style="padding: 1.5rem; background: var(--badge-orange-bg); border: 1px solid var(--badge-orange-border); border-radius: 8px; text-align: center;">
<h3 style="color: var(--badge-orange-text); margin-bottom: 0.5rem;">Nenhuma Assinatura Encontrada</h3>
<p style="color: var(--text-secondary);">O documento não possui assinaturas digitais embutidas detectáveis (apenas marcas de carimbo visuais não contam como assinaturas criptográficas válidas).</p>
</div>
`;
} else {
html = `<h3 style="color: var(--text-primary); margin-bottom: 1rem; border-bottom: 1px solid var(--bg-glass-border); padding-bottom: 0.5rem;">${data.total_signatures} Assinatura(s) Detectada(s)</h3>`;
html += `<div style="display: flex; flex-direction: column; gap: 1rem;">`;
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 += `<span style="background: var(--badge-green-bg); color: var(--badge-green-text); border: 1px solid var(--badge-green-border); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold;">Documento Íntegro</span> `;
} else {
badgeHtml += `<span style="background: rgba(220, 38, 38, 0.15); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.3); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold;">Adulterado após assinado</span> `;
}
if (sig.trust_type === "icp") {
badgeHtml += `<span style="background: var(--badge-green-bg); color: var(--badge-green-text); border: 1px solid var(--badge-green-border); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold;">[${sig.trust_class}] ${sig.trust_name}</span>`;
} else if (sig.trust_type === "govbr") {
badgeHtml += `<span style="background: rgba(2, 132, 199, 0.15); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.3); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold;">[${sig.trust_class}] ${sig.trust_name}</span>`;
} else if (sig.trust_type === "suite") {
badgeHtml += `<span style="background: rgba(101, 163, 13, 0.15); color: #84cc16; border: 1px solid rgba(132, 204, 22, 0.3); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold;">[${sig.trust_class}] ${sig.trust_name}</span>`;
} else {
badgeHtml += `<span style="background: var(--bg-subtle); color: var(--text-secondary); border: 1px solid var(--input-border); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem;">Emissor Comum/Desconhecido</span>`;
}
html += `
<div style="padding: 1rem; background: var(--bg-subtle); border: 1px solid var(--bg-glass-border); border-left: 4px solid ${borderColor}; border-radius: 6px;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
<h4 style="color: var(--text-primary); margin: 0; font-size: 1.1rem;">${index + 1}. ${sig.signer_name}</h4>
</div>
<div style="margin-bottom: 0.8rem;">${badgeHtml}</div>
<p style="color: var(--text-secondary); margin: 0 0 0.3rem 0; font-size: 0.9rem;"><strong>Data:</strong> ${sig.date}</p>
<p style="color: var(--text-secondary); margin: 0; font-size: 0.9rem; word-break: break-all;"><strong>Emissor/Autoridade:</strong> ${sig.issuer}</p>
${isError ? `<p style="color: #ef4444; margin-top: 0.5rem; font-size: 0.9rem;">${sig.validation_message}</p>` : ''}
</div>
`;
});
html += `</div>`;
}
document.getElementById('resultArea').innerHTML = html;
} catch (err) {
document.getElementById('resultArea').innerHTML = `
<div style="padding: 1rem; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px;">
<p style="color: #991b1b; font-weight: bold;">Erro ao validar:</p>
<p style="color: #dc2626;">${err.message}</p>
</div>
`;
} 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 = '<div style="padding: 1.2rem; background: var(--upload-hover-bg); border: 1px solid var(--badge-green-border); border-radius: 10px;">';
html += '<h4 style="color: var(--text-primary); margin-bottom:10px;">Partições Geradas</h4>';
html += '<ul style="list-style:none; padding:0; margin-bottom: 15px;">';
const zipUrl = window.URL.createObjectURL(blob);
// Extract entries and sort them by name (e.g. particao_1, particao_2)
// Need a custom sort to handle numerical sorting of "particao_N"
let entries = [];
zip.forEach(function (relativePath, zipEntry) {
entries.push(zipEntry);
});
entries.sort((a, b) => {
const numA = parseInt(a.name.match(/\d+/)) || 0;
const numB = parseInt(b.name.match(/\d+/)) || 0;
return numA - numB;
});
let filesPromises = entries.map(zipEntry => {
return zipEntry.async("blob").then(function (fileData) {
const sizeMB = (fileData.size / (1024 * 1024)).toFixed(2);
const fileUrl = window.URL.createObjectURL(fileData);
return {
name: zipEntry.name,
size: sizeMB,
url: fileUrl
};
});
});
const resolvedFiles = await Promise.all(filesPromises);
resolvedFiles.forEach(file => {
html += '<li style="display:flex; justify-content:space-between; align-items:center; padding: 6px 0; border-bottom: 1px solid var(--input-border);">';
html += '<span style="color:var(--text-primary);">' + file.name + ' <strong style="color:var(--ceara-orange-1)">(' + file.size + ' MB)</strong></span>';
html += '<a href="' + file.url + '" download="' + file.name + '" class="btn-outline" style="padding: 5px 10px; font-size:0.8rem; text-decoration:none;">Baixar Parte</a>';
html += '</li>';
});
html += '</ul>';
html += '<a href="' + zipUrl + '" download="' + zipFilename + '" class="btn-primary" style="text-decoration:none; display:inline-block; width:100%; text-align:center;">📥 Baixar Todas as Partições (.zip)</a>';
html += '</div>';
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<currentFiles.length; i++){ fd.append("files", currentFiles[i]); }
await genericPost('/convert/pdf-from-images', fd, "imagens.pdf");
} else {
const fileInput = document.getElementById('fileInput');
let singleFile = currentFiles.length > 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<data.images_a.length; i++) {
html += '<div style="display:flex; width:100%; margin-bottom:20px; gap:10px;">';
html += '<div style="flex:1; border: 1px solid #444; padding: 5px; background: white; text-align:center;">';
html += '<p style="color:black; font-weight:bold; margin-bottom:5px;">Original - Página ' + (i+1) + '</p>';
if(data.images_a[i]) html += '<img src="data:image/png;base64,' + data.images_a[i] + '" style="max-width: 100%; height: auto;">';
html += '</div>';
html += '<div style="flex:1; border: 1px solid #444; padding: 5px; background: white; text-align:center;">';
html += '<p style="color:black; font-weight:bold; margin-bottom:5px;">Modificado - Página ' + (i+1) + '</p>';
if(data.images_b[i]) html += '<img src="data:image/png;base64,' + data.images_b[i] + '" style="max-width: 100%; height: auto;">';
html += '</div>';
html += '</div>';
}
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');
}
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 = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-orange-1);">🗑️ Excluir e Reordenar Páginas</h2>
<form id="toolForm" onsubmit="handleReconstruct(event, 'modificado.pdf')">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="loadPreviews(this, 'delete')" required>
</div>
<div id="loadingPreview" style="display: none; text-align: center; color: var(--ceara-yellow); margin-top: 10px;">Carregando visualização...</div>
<div id="previewContainer" class="page-grid"></div>
<button type="submit" class="btn-primary" style="width: 100%; margin-top: 1rem; display:none;" id="submitBtn">Salvar Alterações</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'merge':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-green-1);">🔗 Mesclar e Reordenar PDFs</h2>
<form id="toolForm" onsubmit="handleReconstruct(event, 'mesclado.pdf')">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione Múltiplos PDFs</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" multiple onchange="loadPreviews(this, 'merge')">
</div>
<div id="loadingPreview" style="display: none; text-align: center; color: var(--ceara-yellow); margin-top: 10px;">Carregando visualização...</div>
<div id="previewContainer" class="page-grid"></div>
<button type="submit" class="btn-primary" style="width: 100%; margin-top: 1rem; display:none;" id="submitBtn">Salvar PDF Mesclado</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando...</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;
case 'compress':
content = `
<h2 style="margin-bottom: 1rem; color: var(--ceara-green-1);">🗜️ Comprimir PDF</h2>
<form id="toolForm" onsubmit="handleCompress(event)">
<div class="upload-area" id="uploadBox" onclick="document.getElementById('fileInput').click()">
<span style="font-size: 2rem;">📄</span>
<p style="margin-top: 1rem;" id="fileName">Selecione o PDF</p>
<input type="file" id="fileInput" accept=".pdf" style="display: none;" onchange="updateFileName(this)" required>
</div>
<div class="input-group">
<label>Nível de Compressão</label>
<select id="compressLevel">
<option value="recomendada">Recomendada (Boa qualidade)</option>
<option value="extrema">Extrema (Menor tamanho possível)</option>
<option value="baixa">Baixa (Alta qualidade)</option>
</select>
</div>
<button type="submit" class="btn-primary" style="width: 100%;" id="submitBtn">Comprimir Arquivo</button>
<div id="loading" style="display: none; text-align: center; margin-top: 1rem; color: var(--ceara-yellow);">Processando... Aguarde.</div>
<div id="resultArea" style="margin-top: 1rem;"></div>
</form>
`;
break;