From f34d9e2988d9149b529d62957d0815c28400acdd Mon Sep 17 00:00:00 2001 From: pjmalandrino Date: Wed, 21 May 2025 21:46:56 +0200 Subject: [PATCH] Update frontend to split in multiple files --- app.py | 158 +-------- index.html | 872 +--------------------------------------------- static/app.js | 317 +++++++++++++++++ static/styles.css | 140 ++++++++ 4 files changed, 490 insertions(+), 997 deletions(-) create mode 100644 static/app.js create mode 100644 static/styles.css diff --git a/app.py b/app.py index 47cda0c..60bc6c8 100644 --- a/app.py +++ b/app.py @@ -4,19 +4,14 @@ import os import time import threading import logging -import json -import re from pathlib import Path -import glob -from PIL import Image -import pdf2image # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) -app = Flask(__name__) +app = Flask(__name__, static_folder='static') # Dictionary to store background task results task_results = {} @@ -28,10 +23,22 @@ def ensure_results_folder(): results_dir.mkdir() return results_dir +# Ensure static folder exists +def ensure_static_folder(): + static_dir = Path("static") + if not static_dir.exists(): + static_dir.mkdir() + logger.info(f"Created static directory: {static_dir}") + return static_dir + @app.route('/') def index(): return send_file('index.html') +@app.route('/static/') +def serve_static(filename): + return send_file(os.path.join('static', filename)) + @app.route('/pdf-files') def pdf_files(): try: @@ -363,143 +370,8 @@ def run_manual_command(): logger.error(f"Error running manual command: {str(e)}") return jsonify({'success': False, 'error': str(e)}), 500 -# NEW ROUTES TO SUPPORT ENHANCED UI - -@app.route('/generate-preview', methods=['POST']) -def generate_preview(): - """Generate a preview image of a PDF page""" - try: - # Get parameters - pdf_file = request.form.get('pdf_file') - page_num = int(request.form.get('page_num', 1)) - - if not pdf_file or not os.path.exists(pdf_file): - return jsonify({'success': False, 'error': 'PDF file not found'}), 400 - - # Ensure results directory exists - results_dir = ensure_results_folder() - preview_path = results_dir / f"preview_{os.path.basename(pdf_file)}_{page_num}.png" - - # Convert PDF page to image - try: - # Get total page count - from pdf2image.pdf2image import pdfinfo_from_path - pdf_info = pdfinfo_from_path(pdf_file) - page_count = pdf_info["Pages"] - - # Ensure the page number is valid - if page_num > page_count: - return jsonify({'success': False, 'error': f'Page number {page_num} exceeds total pages {page_count}'}), 400 - - # Convert the page - images = pdf2image.convert_from_path( - pdf_file, - dpi=150, # Lower DPI for preview - first_page=page_num, - last_page=page_num - ) - - if images: - # Resize for preview if too large - MAX_HEIGHT = 600 - img = images[0] - ratio = MAX_HEIGHT / img.height if img.height > MAX_HEIGHT else 1 - if ratio < 1: - new_width = int(img.width * ratio) - img = img.resize((new_width, MAX_HEIGHT), Image.Resampling.LANCZOS) - - # Save the preview - img.save(preview_path) - - return jsonify({ - 'success': True, - 'preview_image': f'/results/{preview_path.name}', - 'page_count': page_count - }) - else: - return jsonify({'success': False, 'error': 'Failed to convert PDF page'}), 500 - - except Exception as e: - logger.error(f"Error converting PDF: {str(e)}") - return jsonify({'success': False, 'error': str(e)}), 500 - - except Exception as e: - logger.error(f"Error generating preview: {str(e)}") - return jsonify({'success': False, 'error': str(e)}), 500 - -@app.route('/get-doctags-content') -def get_doctags_content(): - """Get the content of a DocTags file""" - try: - path = request.args.get('path') - if not path or not os.path.exists(path): - # Try prepending 'results/' if the path doesn't exist - if not path.startswith('results/'): - path = f"results/{path}" - - if not os.path.exists(path): - return "DocTags file not found", 404 - - with open(path, 'r', encoding='utf-8') as f: - content = f.read() - - return content - except Exception as e: - logger.error(f"Error reading DocTags file: {str(e)}") - return f"Error: {str(e)}", 500 - -@app.route('/get-extracted-images') -def get_extracted_images(): - """Get a list of extracted images for a specific PDF page""" - try: - pdf_file = request.args.get('pdf') - page_num = request.args.get('page', 1) - - if not pdf_file: - return jsonify({'success': False, 'error': 'PDF file not specified'}), 400 - - # Get the base name of the PDF without extension - pdf_basename = os.path.splitext(os.path.basename(pdf_file))[0] - - # Look for extracted images in the results/pictures directory - pictures_dir = Path("results") / "pictures" - - if not pictures_dir.exists(): - return jsonify({'success': True, 'images': []}) - - # Pattern to match: picture_*.* - image_pattern = pictures_dir / "picture_*.png" - image_files = glob.glob(str(image_pattern)) - - images = [] - for image_file in image_files: - img_path = Path(image_file) - img_id = "unknown" - img_caption = "" - - # Try to extract ID from filename (picture_1_caption.png) - match = re.search(r'picture_(\d+)', img_path.name) - if match: - img_id = match.group(1) - - # Look for a matching caption file - caption_file = img_path.with_suffix('.txt') - if caption_file.exists(): - with open(caption_file, 'r', encoding='utf-8') as f: - img_caption = f.read().strip() - - images.append({ - 'id': img_id, - 'path': f'/results/pictures/{img_path.name}', - 'caption': img_caption - }) - - return jsonify({'success': True, 'images': images}) - - except Exception as e: - logger.error(f"Error getting extracted images: {str(e)}") - return jsonify({'success': False, 'error': str(e)}), 500 - if __name__ == '__main__': + # Ensure folders exist ensure_results_folder() + ensure_static_folder() app.run(debug=True, host='127.0.0.1', port=5000) \ No newline at end of file diff --git a/index.html b/index.html index 5100b85..0db0469 100644 --- a/index.html +++ b/index.html @@ -4,364 +4,16 @@ DocTags Tools - + -
-

DocTags Analyzer and Visualizer

-
+

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

-
@@ -380,529 +32,41 @@
- -
- -
-

1 Analyze PDF

+
+
+

Step 1: Analyze PDF

Extract DocTags structure from the PDF page

- - -
- -
-

2 Generate Visualization

+
+

Step 2: Generate Visualization

Create visual representation of the extracted DocTags

- +
- - -
- -
-

3 Extract Pictures

+
+

Step 3: Extract Pictures

Extract images from the document based on DocTags

- +
- - -
- + + + -
-

Advanced Options

- + \ No newline at end of file diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..a733be2 --- /dev/null +++ b/static/app.js @@ -0,0 +1,317 @@ +// Keep track of active tasks +const activeTasks = {}; +let pollingInterval = null; + +// Load PDF files on page load +window.addEventListener('DOMContentLoaded', function() { + const pdfStatus = document.getElementById('pdf-load-status'); + pdfStatus.innerHTML = '
Loading PDF files...'; + + fetch('/pdf-files') + .then(response => { + if (!response.ok) { + throw new Error('Failed to load PDF files'); + } + return response.json(); + }) + .then(data => { + const select = document.getElementById('pdf_file'); + if (data.length === 0) { + pdfStatus.innerHTML = 'No PDF files found in the current directory'; + return; + } + + data.forEach(file => { + const option = document.createElement('option'); + option.value = file; + option.textContent = file; + select.appendChild(option); + }); + pdfStatus.innerHTML = 'Loaded ' + data.length + ' PDF files'; + }) + .catch(error => { + console.error('Error loading PDFs:', error); + pdfStatus.innerHTML = 'Error: ' + error.message + ''; + }); + + // Run an environment check on startup + checkEnvironment(); +}); + +// Check the environment +function checkEnvironment() { + const envDiv = document.getElementById('environment-check'); + const envDetails = document.getElementById('env-details'); + + envDiv.classList.remove('hidden'); + envDetails.innerHTML = '
Checking environment...'; + + fetch('/check-environment') + .then(response => response.json()) + .then(data => { + let html = '
    '; + + // Current working directory + html += '
  • Working directory: ' + data.cwd + '
  • '; + + // Python version + html += '
  • Python version: ' + data.python_version + '
  • '; + + // Required scripts check + if (data.missing_scripts.length === 0) { + html += '
  • ✓ All required scripts found
  • '; + } else { + 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(', ') + '
  • '; + } else { + html += '
  • ✗ No PDF files found in the working directory
  • '; + } + + // Results directory check + if (data.results_dir_exists) { + html += '
  • ✓ Results directory exists
  • '; + if (data.results_dir_writable) { + html += '
  • ✓ Results directory is writable
  • '; + } else { + html += '
  • ✗ Results directory is not writable
  • '; + } + } else { + html += '
  • ✗ Results directory does not exist
  • '; + } + + html += '
'; + + // List of all files for debugging + 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 + '
'; + }); +} + +// Manual command execution for debugging +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'); + + // Create form data + const formData = new FormData(); + formData.append('command', command); + + // Send the command directly to backend + fetch('/run-manual-command', { + method: 'POST', + body: formData + }) + .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 : ''); + }) + .catch(error => { + outputDiv.textContent = 'Error running command: ' + error.message; + }); +} + +// Start polling for task updates +function startPolling() { + if (pollingInterval) { + return; // Already polling + } + + pollingInterval = setInterval(pollTasks, 1000); +} + +// Stop polling when no active tasks +function checkAndStopPolling() { + if (Object.keys(activeTasks).length === 0 && pollingInterval) { + clearInterval(pollingInterval); + pollingInterval = null; + } +} + +// Poll for task updates +function pollTasks() { + for (const taskId in activeTasks) { + const taskInfo = activeTasks[taskId]; + + fetch(`/task-status/${taskId}`) + .then(response => { + if (!response.ok) { + throw new Error('Failed to check task status'); + } + return response.json(); + }) + .then(data => { + // Update status display + const statusElement = document.getElementById(`${taskInfo.type}-status`); + + if (data.done) { + if (data.success) { + // Task completed successfully + statusElement.innerHTML = '✓ Completed'; + + // Enable button + document.getElementById(`${taskInfo.type}-btn`).disabled = false; + + // Display output + const outputDiv = document.getElementById('output'); + outputDiv.textContent = data.output; + outputDiv.classList.remove('hidden'); + + // 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'); + } + + // Enable next step button if applicable + if (taskInfo.type === 'analyzer') { + document.getElementById('visualizer-btn').disabled = false; + } else if (taskInfo.type === 'visualizer') { + document.getElementById('extractor-btn').disabled = false; + } + + // Remove from active tasks + delete activeTasks[taskId]; + checkAndStopPolling(); + } else { + // Task failed + statusElement.innerHTML = '✗ Failed: ' + (data.error || 'Unknown error') + ''; + document.getElementById(`${taskInfo.type}-btn`).disabled = false; + + // Display error output + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Error: ' + (data.error || 'Unknown error'); + outputDiv.classList.remove('hidden'); + + // Remove from active tasks + delete activeTasks[taskId]; + checkAndStopPolling(); + } + } else { + // Still running + statusElement.innerHTML = '
Running...'; + } + }) + .catch(error => { + console.error('Error checking task status:', error); + }); + } +} + +function runScript(script) { + const pdfFile = document.getElementById('pdf_file').value; + const pageNum = document.getElementById('page_num').value; + const adjust = document.getElementById('adjust').checked; + + if (!pdfFile) { + alert('Please select a PDF file'); + return; + } + + // Disable button + const button = document.getElementById(`${script}-btn`); + button.disabled = true; + + // Create form data + const formData = new FormData(); + formData.append('pdf_file', pdfFile); + formData.append('page_num', pageNum); + formData.append('adjust', adjust); + + // Show running status + const statusElement = document.getElementById(`${script}-status`); + statusElement.innerHTML = '
Starting...'; + + // Clear previous output + document.getElementById('output').classList.add('hidden'); + + // Hide previous image if not running visualizer + if (script !== 'visualizer') { + document.getElementById('image-container').classList.add('hidden'); + } + + // Determine endpoint + let endpoint; + switch(script) { + case 'analyzer': + endpoint = '/run-analyzer'; + // Disable next step buttons + document.getElementById('visualizer-btn').disabled = true; + document.getElementById('extractor-btn').disabled = true; + break; + case 'visualizer': + endpoint = '/run-visualizer'; + // Disable next step button + document.getElementById('extractor-btn').disabled = true; + break; + case 'extractor': + endpoint = '/run-extractor'; + break; + } + + // Send request + fetch(endpoint, { + method: 'POST', + body: formData + }) + .then(response => { + if (!response.ok) { + throw new Error('Failed to start task'); + } + return response.json(); + }) + .then(data => { + if (data.success && data.task_id) { + // Store task information + activeTasks[data.task_id] = { + type: script, + pageNum: pageNum + }; + + // Start polling for updates + startPolling(); + + // Update status + statusElement.innerHTML = '
Running...'; + + // Show initial output + const outputDiv = document.getElementById('output'); + outputDiv.textContent = data.message || 'Task started, please wait...'; + outputDiv.classList.remove('hidden'); + } else { + // Failed to start task + statusElement.innerHTML = '✗ Failed to start task'; + button.disabled = false; + + // Show error + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Error: ' + (data.error || 'Failed to start task'); + outputDiv.classList.remove('hidden'); + } + }) + .catch(error => { + console.error('Error starting task:', error); + statusElement.innerHTML = '✗ ' + error.message + ''; + button.disabled = false; + + // Show error + const outputDiv = document.getElementById('output'); + outputDiv.textContent = 'Error: ' + error.message; + outputDiv.classList.remove('hidden'); + }); +} \ No newline at end of file diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..021b2c9 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,140 @@ +body { + font-family: Arial, sans-serif; + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +h1 { + color: #333; +} + +.form-group { + margin-bottom: 15px; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +select, input { + padding: 8px; + width: 100%; + max-width: 300px; +} + +button { + padding: 10px 15px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + margin-right: 10px; + margin-bottom: 10px; +} + +button:hover { + background-color: #45a049; +} + +button:disabled { + background-color: #cccccc; + cursor: not-allowed; +} + +.output { + margin-top: 20px; + padding: 15px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #f9f9f9; + white-space: pre-wrap; + max-height: 300px; + overflow-y: auto; +} + +.hidden { + display: none; +} + +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 { + margin-top: 0; +} + +.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; + display: inline-block; + vertical-align: middle; + margin-right: 10px; +} + +@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; +} + +.debug-section { + margin-top: 30px; + border-top: 1px solid #ccc; + padding-top: 20px; +} \ No newline at end of file