diff --git a/index.html b/index.html index 0db0469..cf389a6 100644 --- a/index.html +++ b/index.html @@ -3,68 +3,136 @@ - DocTags Tools + DocTags Analyzer and Visualizer + + + + -

DocTags Analyzer and Visualizer ,,,,,,,,,,,,,,,

+
+

DocTags Analyzer and Visualizer

- +
+

Document Settings

+
+ + +
+
-
- - -
-
+
+ + +
-
- - -
- -
- -
- -
-
-

Step 1: Analyze PDF

-

Extract DocTags structure from the PDF page

- -
+
+ +
-
-

Step 2: Generate Visualization

-

Create visual representation of the extracted DocTags

- -
+
+
+

+ + + + + + + Step 1: Analyze PDF +

+

Extract DocTags structure from the selected PDF page

+ +
+
+ +
+

+ + + + + + Step 2: Generate Visualization +

+

Create visual representation of the extracted DocTags

+ +
+
+ +
+

+ + + + + + Step 3: Extract Pictures +

+

Extract images from the document based on DocTags

+ +
+
-
-

Step 3: Extract Pictures

-

Extract images from the document based on DocTags

- -
+ + + -
- + - - -
- - +
+ + +
diff --git a/static/app.js b/static/app.js index a733be2..84d08ed 100644 --- a/static/app.js +++ b/static/app.js @@ -2,10 +2,47 @@ const activeTasks = {}; let pollingInterval = null; +// Helper functions for UI +const UI = { + showLoader: (elementId) => { + const el = document.getElementById(elementId); + el.innerHTML = '
Processing...'; + }, + showSuccess: (elementId, message = 'Completed') => { + const el = document.getElementById(elementId); + el.innerHTML = `✓ ${message}`; + }, + showError: (elementId, message = 'Failed') => { + const el = document.getElementById(elementId); + el.innerHTML = `✗ ${message}`; + }, + showOutput: (content) => { + const outputDiv = document.getElementById('output'); + outputDiv.textContent = content; + outputDiv.classList.remove('hidden'); + // Scroll to show output + outputDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, + showImage: (src) => { + const img = document.getElementById('result-image'); + const container = document.getElementById('image-container'); + img.src = src; + container.classList.remove('hidden'); + // Scroll to show image + container.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, + enableButton: (buttonId) => { + document.getElementById(buttonId).disabled = false; + }, + disableButton: (buttonId) => { + document.getElementById(buttonId).disabled = true; + } +}; + // Load PDF files on page load window.addEventListener('DOMContentLoaded', function() { const pdfStatus = document.getElementById('pdf-load-status'); - pdfStatus.innerHTML = '
Loading PDF files...'; + pdfStatus.innerHTML = '
Loading PDF files...'; fetch('/pdf-files') .then(response => { @@ -17,7 +54,7 @@ window.addEventListener('DOMContentLoaded', function() { .then(data => { const select = document.getElementById('pdf_file'); if (data.length === 0) { - pdfStatus.innerHTML = 'No PDF files found in the current directory'; + UI.showError('pdf-load-status', 'No PDF files found in the current directory'); return; } @@ -27,11 +64,11 @@ window.addEventListener('DOMContentLoaded', function() { option.textContent = file; select.appendChild(option); }); - pdfStatus.innerHTML = 'Loaded ' + data.length + ' PDF files'; + UI.showSuccess('pdf-load-status', `Loaded ${data.length} PDF files`); }) .catch(error => { console.error('Error loading PDFs:', error); - pdfStatus.innerHTML = 'Error: ' + error.message + ''; + UI.showError('pdf-load-status', `Error: ${error.message}`); }); // Run an environment check on startup @@ -44,55 +81,59 @@ function checkEnvironment() { const envDetails = document.getElementById('env-details'); envDiv.classList.remove('hidden'); - envDetails.innerHTML = '
Checking environment...'; + envDetails.innerHTML = '
Checking environment...'; fetch('/check-environment') .then(response => response.json()) .then(data => { - let html = '
    '; + let html = '
    '; // Current working directory - html += '
  • Working directory: ' + data.cwd + '
  • '; + html += `

    Working directory: ${data.cwd}

    `; // Python version - html += '
  • Python version: ' + data.python_version + '
  • '; + html += `

    Python version: ${data.python_version}

    `; // Required scripts check if (data.missing_scripts.length === 0) { - html += '
  • ✓ All required scripts found
  • '; + html += '

    All required scripts found

    '; } else { - html += '
  • ✗ Missing scripts: ' + data.missing_scripts.join(', ') + '
  • '; + html += `

    Missing scripts: ${data.missing_scripts.join(', ')}

    `; } // PDF files check if (data.pdf_files.length > 0) { - html += '
  • ✓ Found ' + data.pdf_files.length + ' PDF files: ' + data.pdf_files.join(', ') + '
  • '; + html += `

    Found ${data.pdf_files.length} PDF files

    `; + html += '
    PDF Files
      '; + data.pdf_files.forEach(file => { + html += `
    • ${file}
    • `; + }); + html += '
    '; } else { - html += '
  • ✗ No PDF files found in the working directory
  • '; + html += '

    No PDF files found in the working directory

    '; } // Results directory check if (data.results_dir_exists) { - html += '
  • ✓ Results directory exists
  • '; + html += '

    Results directory exists

    '; if (data.results_dir_writable) { - html += '
  • ✓ Results directory is writable
  • '; + html += '

    Results directory is writable

    '; } else { - html += '
  • ✗ Results directory is not writable
  • '; + html += '

    Results directory is not writable

    '; } } else { - html += '
  • ✗ Results directory does not exist
  • '; + html += '

    Results directory does not exist

    '; } - html += '
'; + html += '
'; - // List of all files for debugging - html += '
All files in directory (' + data.files.length + ' files)
' +
-                data.files.join('\n') + '
'; + // List of all files for debugging (collapsible) + html += `
All files in directory (${data.files.length} files)
${data.files.join('\n')}
`; envDetails.innerHTML = html; }) .catch(error => { - envDetails.innerHTML = '
Error checking environment: ' + error.message + '
'; + envDetails.innerHTML = `
Error checking environment: ${error.message}
`; }); } @@ -101,9 +142,7 @@ function manuallyRunScript() { const command = prompt("Enter command to run (e.g., 'python analyzer.py --image document.pdf --page 1')"); if (!command) return; - const outputDiv = document.getElementById('output'); - outputDiv.textContent = 'Running command: ' + command + '\nPlease wait...'; - outputDiv.classList.remove('hidden'); + UI.showOutput(`Running command: ${command}\nPlease wait...`); // Create form data const formData = new FormData(); @@ -116,13 +155,12 @@ function manuallyRunScript() { }) .then(response => response.json()) .then(data => { - outputDiv.textContent = 'Command: ' + command + '\n\n' + - (data.success ? 'Success!\n\n' : 'Failed!\n\n') + - (data.output || '') + - (data.error ? '\n\nError: ' + data.error : ''); + UI.showOutput( + `Command: ${command}\n\n${data.success ? 'Success!\n\n' : 'Failed!\n\n'}${data.output || ''}${data.error ? '\n\nError: ' + data.error : ''}` + ); }) .catch(error => { - outputDiv.textContent = 'Error running command: ' + error.message; + UI.showOutput(`Error running command: ${error.message}`); }); } @@ -162,27 +200,24 @@ function pollTasks() { if (data.done) { if (data.success) { // Task completed successfully - statusElement.innerHTML = '✓ Completed'; + UI.showSuccess(`${taskInfo.type}-status`, 'Completed'); // Enable button - document.getElementById(`${taskInfo.type}-btn`).disabled = false; + UI.enableButton(`${taskInfo.type}-btn`); // Display output - const outputDiv = document.getElementById('output'); - outputDiv.textContent = data.output; - outputDiv.classList.remove('hidden'); + UI.showOutput(data.output); // Show image if available for visualizer if (taskInfo.type === 'visualizer' && data.image_file) { - document.getElementById('result-image').src = '/' + data.image_file + '?t=' + new Date().getTime(); - document.getElementById('image-container').classList.remove('hidden'); + UI.showImage(`/${data.image_file}?t=${new Date().getTime()}`); } // Enable next step button if applicable if (taskInfo.type === 'analyzer') { - document.getElementById('visualizer-btn').disabled = false; + UI.enableButton('visualizer-btn'); } else if (taskInfo.type === 'visualizer') { - document.getElementById('extractor-btn').disabled = false; + UI.enableButton('extractor-btn'); } // Remove from active tasks @@ -190,13 +225,11 @@ function pollTasks() { checkAndStopPolling(); } else { // Task failed - statusElement.innerHTML = '✗ Failed: ' + (data.error || 'Unknown error') + ''; - document.getElementById(`${taskInfo.type}-btn`).disabled = false; + UI.showError(`${taskInfo.type}-status`, data.error || 'Unknown error'); + UI.enableButton(`${taskInfo.type}-btn`); // Display error output - const outputDiv = document.getElementById('output'); - outputDiv.textContent = 'Error: ' + (data.error || 'Unknown error'); - outputDiv.classList.remove('hidden'); + UI.showOutput(`Error: ${data.error || 'Unknown error'}`); // Remove from active tasks delete activeTasks[taskId]; @@ -204,11 +237,17 @@ function pollTasks() { } } else { // Still running - statusElement.innerHTML = '
Running...'; + statusElement.innerHTML = '
Processing...'; } }) .catch(error => { console.error('Error checking task status:', error); + UI.showError(`${taskInfo.type}-status`, 'Failed to check status'); + UI.enableButton(`${taskInfo.type}-btn`); + + // Remove from active tasks to prevent endless error messages + delete activeTasks[taskId]; + checkAndStopPolling(); }); } } @@ -224,8 +263,7 @@ function runScript(script) { } // Disable button - const button = document.getElementById(`${script}-btn`); - button.disabled = true; + UI.disableButton(`${script}-btn`); // Create form data const formData = new FormData(); @@ -234,8 +272,7 @@ function runScript(script) { formData.append('adjust', adjust); // Show running status - const statusElement = document.getElementById(`${script}-status`); - statusElement.innerHTML = '
Starting...'; + UI.showLoader(`${script}-status`); // Clear previous output document.getElementById('output').classList.add('hidden'); @@ -251,13 +288,13 @@ function runScript(script) { case 'analyzer': endpoint = '/run-analyzer'; // Disable next step buttons - document.getElementById('visualizer-btn').disabled = true; - document.getElementById('extractor-btn').disabled = true; + UI.disableButton('visualizer-btn'); + UI.disableButton('extractor-btn'); break; case 'visualizer': endpoint = '/run-visualizer'; // Disable next step button - document.getElementById('extractor-btn').disabled = true; + UI.disableButton('extractor-btn'); break; case 'extractor': endpoint = '/run-extractor'; @@ -287,31 +324,25 @@ function runScript(script) { startPolling(); // Update status - statusElement.innerHTML = '
Running...'; + UI.showLoader(`${script}-status`); // Show initial output - const outputDiv = document.getElementById('output'); - outputDiv.textContent = data.message || 'Task started, please wait...'; - outputDiv.classList.remove('hidden'); + UI.showOutput(data.message || 'Task started, please wait...'); } else { // Failed to start task - statusElement.innerHTML = '✗ Failed to start task'; - button.disabled = false; + UI.showError(`${script}-status`, 'Failed to start task'); + UI.enableButton(`${script}-btn`); // Show error - const outputDiv = document.getElementById('output'); - outputDiv.textContent = 'Error: ' + (data.error || 'Failed to start task'); - outputDiv.classList.remove('hidden'); + UI.showOutput(`Error: ${data.error || 'Failed to start task'}`); } }) .catch(error => { console.error('Error starting task:', error); - statusElement.innerHTML = '✗ ' + error.message + ''; - button.disabled = false; + UI.showError(`${script}-status`, error.message); + UI.enableButton(`${script}-btn`); // Show error - const outputDiv = document.getElementById('output'); - outputDiv.textContent = 'Error: ' + error.message; - outputDiv.classList.remove('hidden'); + UI.showOutput(`Error: ${error.message}`); }); } \ No newline at end of file diff --git a/static/styles.css b/static/styles.css index 021b2c9..c0c38bb 100644 --- a/static/styles.css +++ b/static/styles.css @@ -1,140 +1,431 @@ +/* Modern stylesheet for DocTags Analyzer and Visualizer */ + +:root { + /* Color palette */ + --bg-color: #f8f1ee; + --primary-color: #195162; + --secondary-color: #618794; + --light-color: #e6eaf1; + --mint: #b9e9da; + --amber: #ffdb96; + --pale-rose: #ffc9c9; + --white: #ffffff; + --error: #e74c3c; + --success: #2ecc71; + --warning: #f39c12; + --shadow: rgba(25, 81, 98, 0.1); + + /* Typography */ + --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-mono: 'Fira Code', 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; +} + +/* Base styles */ body { - font-family: Arial, sans-serif; - max-width: 800px; + font-family: var(--font-sans); + background-color: var(--bg-color); + color: var(--primary-color); + line-height: 1.6; + margin: 0; + padding: 0; +} + +#app-container { + max-width: 1200px; margin: 0 auto; - padding: 20px; + padding: 2rem; +} + +h1, h2, h3, h4, h5, h6 { + color: var(--primary-color); + margin-top: 0; + font-weight: 600; } h1 { - color: #333; + font-size: 2.25rem; + margin-bottom: 1.5rem; + position: relative; + display: inline-block; } +h1::after { + content: ''; + position: absolute; + width: 50%; + height: 4px; + background-color: var(--secondary-color); + bottom: -8px; + left: 0; + border-radius: 2px; +} + +h3 { + font-size: 1.35rem; + color: var(--secondary-color); + margin-bottom: 1rem; +} + +/* Card Component */ +.card { + background-color: var(--white); + border-radius: 12px; + box-shadow: 0 8px 16px var(--shadow); + padding: 1.5rem; + margin-bottom: 1.5rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0 10px 20px var(--shadow); +} + +/* Form Elements */ .form-group { - margin-bottom: 15px; + margin-bottom: 1.5rem; } label { display: block; - margin-bottom: 5px; - font-weight: bold; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--primary-color); } -select, input { - padding: 8px; +select, input[type="number"], input[type="text"] { width: 100%; - max-width: 300px; + padding: 12px 16px; + border: 2px solid var(--light-color); + border-radius: 8px; + background-color: var(--white); + font-size: 1rem; + color: var(--primary-color); + transition: border-color 0.2s ease, box-shadow 0.2s ease; + -webkit-appearance: none; + appearance: none; } +select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23618794' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 16px center; + padding-right: 48px; +} + +select:focus, input:focus { + outline: none; + border-color: var(--secondary-color); + box-shadow: 0 0 0 3px rgba(97, 135, 148, 0.25); +} + +.checkbox-container { + display: flex; + align-items: center; + position: relative; + cursor: pointer; + user-select: none; +} + +.checkbox-container input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.checkmark { + height: 24px; + width: 24px; + background-color: var(--white); + border: 2px solid var(--light-color); + border-radius: 4px; + margin-right: 10px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.checkbox-container:hover input ~ .checkmark { + background-color: var(--light-color); +} + +.checkbox-container input:checked ~ .checkmark { + background-color: var(--secondary-color); + border-color: var(--secondary-color); +} + +.checkmark:after { + content: ""; + display: none; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.checkbox-container input:checked ~ .checkmark:after { + display: block; +} + +/* Buttons */ button { - padding: 10px 15px; - background-color: #4CAF50; + background-color: var(--secondary-color); color: white; border: none; - border-radius: 4px; + border-radius: 8px; + padding: 12px 24px; + font-size: 1rem; + font-weight: 500; cursor: pointer; - margin-right: 10px; - margin-bottom: 10px; + transition: background-color 0.2s ease, transform 0.1s ease; + display: inline-flex; + align-items: center; + justify-content: center; } -button:hover { - background-color: #45a049; +button:hover:not(:disabled) { + background-color: var(--primary-color); + transform: translateY(-2px); +} + +button:active:not(:disabled) { + transform: translateY(0); } button:disabled { - background-color: #cccccc; + background-color: var(--light-color); + color: var(--secondary-color); cursor: not-allowed; + opacity: 0.7; } -.output { - margin-top: 20px; - padding: 15px; - border: 1px solid #ddd; - border-radius: 4px; - background-color: #f9f9f9; +button svg { + margin-right: 8px; +} + +/* Status indicators */ +.status { + margin-top: 1rem; + font-weight: 500; + display: flex; + align-items: center; +} + +.error { + color: var(--error); +} + +.success { + color: var(--success); +} + +.working { + color: var(--secondary-color); +} + +.loader { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 10px; + border: 3px solid var(--light-color); + border-radius: 50%; + border-top-color: var(--secondary-color); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Pipeline Container */ +.pipeline-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; + margin: 2rem 0; +} + +/* Step Cards */ +.step-card { + background-color: var(--white); + border-radius: 12px; + border-left: 5px solid var(--mint); + box-shadow: 0 4px 12px var(--shadow); + padding: 1.5rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + display: flex; + flex-direction: column; +} + +.step-card:nth-child(2) { + border-left-color: var(--amber); +} + +.step-card:nth-child(3) { + border-left-color: var(--pale-rose); +} + +.step-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 16px var(--shadow); +} + +.step-card h3 { + margin-top: 0; + display: flex; + align-items: center; +} + +.step-card h3 svg { + margin-right: 10px; +} + +.step-card p { + color: var(--secondary-color); + margin-bottom: 1.5rem; + flex-grow: 1; +} + +/* Environment Check Panel */ +#environment-check { + background-color: var(--white); + border-radius: 12px; + box-shadow: 0 4px 12px var(--shadow); + padding: 1.5rem; + margin-top: 2rem; +} + +.env-success { + color: var(--success); + display: flex; + align-items: center; +} + +.env-error { + color: var(--error); + display: flex; + align-items: center; +} + +.env-success::before, .env-error::before { + content: ''; + display: inline-block; + width: 16px; + height: 16px; + margin-right: 8px; + border-radius: 50%; +} + +.env-success::before { + background-color: var(--success); +} + +.env-error::before { + background-color: var(--error); +} + +/* Output Container */ +#output { + background-color: var(--white); + border-radius: 12px; + padding: 1.5rem; white-space: pre-wrap; + font-family: var(--font-mono); + font-size: 0.9rem; + line-height: 1.6; + color: var(--primary-color); + overflow-x: auto; + box-shadow: 0 4px 12px var(--shadow); max-height: 300px; overflow-y: auto; } -.hidden { - display: none; +/* Image Container */ +#image-container { + background-color: var(--white); + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 4px 12px var(--shadow); + margin-top: 2rem; } -img { - max-width: 100%; - margin-top: 10px; - border: 1px solid #ddd; -} - -.container { - display: flex; - flex-direction: column; - gap: 20px; -} - -.step { - background-color: #f5f5f5; - padding: 15px; - border-radius: 5px; - border-left: 5px solid #4CAF50; -} - -.step h3 { +#image-container h2 { margin-top: 0; + margin-bottom: 1rem; + display: flex; + align-items: center; } -.status { - font-weight: bold; - margin-top: 5px; -} - -.error { - color: #d32f2f; -} - -.success { - color: #43a047; -} - -.working { - color: #1976d2; -} - -.loader { - border: 4px solid #f3f3f3; - border-top: 4px solid #3498db; - border-radius: 50%; - width: 20px; - height: 20px; - animation: spin 2s linear infinite; +#image-container h2::before { + content: ''; display: inline-block; - vertical-align: middle; + width: 24px; + height: 24px; margin-right: 10px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23618794' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'/%3E%3Cpolyline points='21 15 16 10 5 21'/%3E%3C/svg%3E"); + background-repeat: no-repeat; } -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -#environment-check { - margin-top: 20px; - padding: 10px; - background-color: #fff3cd; - border: 1px solid #ffeeba; - border-radius: 4px; -} - -.env-success { - color: green; -} - -.env-error { - color: red; +#result-image { + max-width: 100%; + border-radius: 8px; + box-shadow: 0 2px 8px var(--shadow); } +/* Debug Section */ .debug-section { - margin-top: 30px; - border-top: 1px solid #ccc; - padding-top: 20px; + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px dashed var(--light-color); + display: flex; + gap: 1rem; +} + +/* Utility Classes */ +.hidden { + display: none !important; +} + +.mt-1 { + margin-top: 0.5rem; +} + +.mt-2 { + margin-top: 1rem; +} + +.mt-4 { + margin-top: 2rem; +} + +.mb-1 { + margin-bottom: 0.5rem; +} + +.mb-2 { + margin-bottom: 1rem; +} + +.mb-4 { + margin-bottom: 2rem; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + #app-container { + padding: 1rem; + } + + .pipeline-container { + grid-template-columns: 1fr; + } + + button { + width: 100%; + } } \ No newline at end of file