Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>OCR + Form Data Extraction Tool</title> | |
| <!-- Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <!-- Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #6366f1; | |
| --secondary: #a855f7; | |
| --accent: #ec4899; | |
| --bg-dark: #0f172a; | |
| --bg-card: #1e293b; | |
| --text-main: #f8fafc; | |
| --text-muted: #94a3b8; | |
| --success: #10b981; | |
| --border: #334155; | |
| --glass: rgba(30, 41, 58, 0.7); | |
| --scan-color: #00ff9d; | |
| } | |
| * { | |
| box-sizing: boxed; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Inter', sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| height: 100vh; | |
| display: flex; | |
| overflow: hidden; | |
| background-image: radial-gradient(circle at 10% 20%, rgba(63, 66, 241, 0.1) 0%, transparent 20%), | |
| radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.1) 0%, transparent 20%); | |
| } | |
| /* --- Layout --- */ | |
| .app-container { | |
| display: flex; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* Sidebar */ | |
| .sidebar { | |
| width: 280px; | |
| background: var(--bg-card); | |
| border-right: 1px solid var(--border); | |
| display: flex; | |
| flex-direction: column; | |
| padding: 20px; | |
| transition: transform 0.3s ease; | |
| z-index: 10; | |
| } | |
| .logo-area { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 30px; | |
| font-weight: 700; | |
| font-size: 1.2rem; | |
| color: var(--primary); | |
| } | |
| .logo-area i { | |
| font-size: 1.5rem; | |
| } | |
| .nav-item { | |
| padding: 12px 15px; | |
| border-radius: 8px; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 5px; | |
| } | |
| .nav-item:hover, .nav-item.active { | |
| background: rgba(63, 66, 241, 0.1); | |
| color: var(--text-main); | |
| } | |
| .nav-item.active { | |
| border-left: 3px solid var(--primary); | |
| } | |
| .history-section { | |
| margin-top: 40px; | |
| flex-grow: 1; | |
| } | |
| .history-title { | |
| font-size: 0.8rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-muted); | |
| margin-bottom: 15px; | |
| } | |
| .history-item { | |
| padding: 10px; | |
| border-bottom: 1px solid var(--border); | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| } | |
| .history-item:hover { | |
| color: var(--text-main); | |
| } | |
| /* Main Content */ | |
| .main-content { | |
| flex-grow: 1; | |
| padding: 30px; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| position: relative; | |
| } | |
| /* Header */ | |
| header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| .header-title h1 { | |
| font-size: 1.8rem; | |
| margin-bottom: 5px; | |
| } | |
| .header-title p { | |
| color: var(--text-muted); | |
| font-size: 0.9rem; | |
| } | |
| .anycoder-badge { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| padding: 8px 16px; | |
| border-radius: 20px; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| color: white; | |
| text-decoration: none; | |
| box-shadow: 0 4px 15px rgba(63, 66, 241, 0.3); | |
| transition: transform 0.2s; | |
| } | |
| .anycoder-badge:hover { | |
| transform: translateY(-2px); | |
| } | |
| /* Workspace Grid */ | |
| .workspace { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| height: 100%; | |
| } | |
| /* Card Styles */ | |
| .card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .card-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| border-bottom: 1px solid var(--border); | |
| padding-bottom: 10px; | |
| } | |
| .card-title { | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| /* Upload Zone */ | |
| .upload-zone { | |
| flex-grow: 1; | |
| border: 2px dashed var(--border); | |
| border-radius: 12px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| position: relative; | |
| background: rgba(15, 23, 42, 0.3); | |
| } | |
| .upload-zone:hover { | |
| border-color: var(--primary); | |
| background: rgba(63, 66, 241, 0.05); | |
| } | |
| .upload-icon { | |
| font-size: 3rem; | |
| color: var(--text-muted); | |
| margin-bottom: 15px; | |
| } | |
| .upload-text { | |
| color: var(--text-muted); | |
| font-weight: 500; | |
| } | |
| .upload-subtext { | |
| color: var(--text-muted); | |
| font-size: 0.8rem; | |
| opacity: 0.7; | |
| margin-top: 5px; | |
| } | |
| /* Image Preview Area */ | |
| .preview-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| display: none; /* Hidden by default */ | |
| background: #000; | |
| border-radius: 12px; | |
| overflow: hidden; | |
| } | |
| .preview-image { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| opacity: 0.6; | |
| } | |
| /* Scanner Effect */ | |
| .scanner-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(to bottom, transparent, rgba(0, 255, 157, 0.2), transparent); | |
| animation: scan 3s linear infinite; | |
| display: none; | |
| } | |
| @keyframes scan { | |
| 0% { top: -100%; } | |
| 100% { top: 100%; } | |
| } | |
| /* Results Panel */ | |
| .results-content { | |
| flex-grow: 1; | |
| overflow-y: auto; | |
| } | |
| .data-field { | |
| background: rgba(15, 23, 42, 0.5); | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 15px; | |
| border: 1px solid var(--border); | |
| transition: border-color 0.3s; | |
| } | |
| .data-field:hover { | |
| border-color: var(--secondary); | |
| } | |
| .field-label { | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| margin-bottom: 5px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .field-value { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| color: var(--text-main); | |
| } | |
| .confidence-badge { | |
| font-size: 0.7rem; | |
| padding: 2px 6px; | |
| background: var(--success); | |
| color: #fff; | |
| border-radius: 4px; | |
| margin-left: 10px; | |
| float: right; | |
| } | |
| /* Loading State */ | |
| .processing-state { | |
| display: none; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100%; | |
| } | |
| .loader { | |
| width: 50px; | |
| height: 50px; | |
| border: 3px solid transparent; | |
| border-top-color: var(--primary); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .loader-text { | |
| margin-top: 20px; | |
| color: var(--text-main); | |
| font-weight: 600; | |
| } | |
| /* Toast Notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| background: var(--bg-card); | |
| border-left: 4px solid var(--success); | |
| padding: 15px 25px; | |
| border-radius: 8px; | |
| color: var(--text-main); | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.3); | |
| transform: translateY(100px); | |
| opacity: 0; | |
| transition: all 0.3s; | |
| z-index: 100; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 900px) { | |
| .workspace { | |
| grid-template-columns: 1fr; | |
| } | |
| .sidebar { | |
| display: none; /* Simplify for mobile */ | |
| } | |
| .app-container { | |
| display: block; | |
| } | |
| .main-content { | |
| padding: 15px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <!-- Sidebar --> | |
| <aside class="sidebar"> | |
| <div class="logo-area"> | |
| <i class="fa-solid fa-eye"></i> | |
| <span>VisionExtract</span> | |
| </div> | |
| <div class="nav-item active"> | |
| <i class="fa-solid fa-layer-group"></i> | |
| <span>New Extraction</span> | |
| </div> | |
| <div class="nav-item"> | |
| <i class="fa-solid fa-bolt"></i> | |
| <span>Batch Process</span> | |
| </div> | |
| <div class="nav-item"> | |
| <i class="fa-solid fa-cog"></i> | |
| <span>Settings</span> | |
| </div> | |
| <div class="history-section"> | |
| <div class="history-title">Recent Scans</div> | |
| <div class="history-item">Invoice_001.png</div> | |
| <div class="history-item">Form_Submit_22.jpg</div> | |
| <div class="history-item">Receipt_Starbucks.png</div> | |
| </div> | |
| <div style="margin-top: 20px; font-size: 0.7rem; color: var(--text-muted);"> | |
| v2.4.0 Stable | |
| </div> | |
| </aside> | |
| <!-- Main Content --> | |
| <main class="main-content"> | |
| <header> | |
| <div class="header-title"> | |
| <h1>OCR Data Extraction</h1> | |
| <p>Upload a document to automatically extract structured fields.</p> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-badge"> | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <div class="workspace"> | |
| <!-- Left Column: Upload/Preview --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <div class="card-title"> | |
| <i class="fa-solid fa-image"></i> Source Document | |
| </div> | |
| <button id="clearBtn" class="nav-item" style="padding: 5px 10px; font-size: 0.8rem;"> | |
| <i class="fa-solid fa-trash"></i> Clear | |
| </button> | |
| </div> | |
| <!-- Upload Zone --> | |
| <div class="upload-zone" id="dropZone"> | |
| <input type="file" id="fileInput" hidden accept="image/png, image/jpeg"> | |
| <i class="fa-solid fa-cloud-arrow-up upload-icon"></i> | |
| <div class="upload-text">Click or Drag image here</div> | |
| <div class="upload-subtext">Supports PNG, JPG (Max 5MB)</div> | |
| </div> | |
| <!-- Preview Container (Hidden initially) --> | |
| <div class="preview-container" id="previewContainer"> | |
| <img src="" alt="Preview" class="preview-image" id="previewImage"> | |
| <div class="scanner-overlay" id="scannerOverlay"></div> | |
| </div> | |
| <!-- Processing State (Hidden initially) --> | |
| <div class="processing-state" id="processingState"> | |
| <div class="loader"></div> | |
| <div class="loader-text">Analyzing Document...</div> | |
| <div style="font-size: 0.8rem; color: var(--text-muted); margin-top: 5px;">Extracting fields: Name, Date, Total...</div> | |
| </div> | |
| </div> | |
| <!-- Right Column: Results --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <div class="card-title"> | |
| <i class="fa-solid fa-file-code"></i> Extracted Data | |
| </div> | |
| <button id="copyJsonBtn" class="nav-item" style="padding: 5px 10px; font-size: 0.8rem;"> | |
| <i class="fa-solid fa-copy"></i> Copy JSON | |
| </button> | |
| </div> | |
| <div class="results-content" id="resultsContent"> | |
| <div style="text-align: center; color: var(--text-muted); padding: 40px;"> | |
| <i class="fa-solid fa-robot" style="font-size: 2rem; opacity: 0.5; margin-bottom: 10px;"></i> | |
| <p>Waiting for input...</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div class="toast" id="toast"> | |
| <i class="fa-solid fa-check-circle" style="color: var(--success); margin-right: 10px;"></i> | |
| <span id="toastMessage">Action completed successfully</span> | |
| </div> | |
| <script> | |
| // --- DOM Elements --- | |
| const dropZone = document.getElementById('dropZone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const previewContainer = document.getElementById('previewContainer'); | |
| const previewImage = document.getElementById('previewImage'); | |
| const scannerOverlay = document.getElementById('scannerOverlay'); | |
| const processingState = document.getElementById('processingState'); | |
| const resultsContent = document.getElementById('resultsContent'); | |
| const copyJsonBtn = document.getElementById('copyJsonBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toastMessage'); | |
| // --- Event Handlers --- | |
| // Drag & Drop | |
| dropZone.addEventListener('click', () => fileInput.click()); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#6366f1'; | |
| dropZone.style.background = 'rgba(63, 66, 241, 0.1)'; | |
| }); | |
| dropZone.addEventListener('dragleave', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#334155'; | |
| dropZone.style.background = 'rgba(15, 23, 42, 0.3)'; | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#334155'; | |
| dropZone.style.background = 'rgba(15, 23, 42, 0.3)'; | |
| if (e.dataTransfer.files.length) { | |
| handleFile(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length) { | |
| handleFile(e.target.files[0]); | |
| } | |
| }); | |
| clearBtn.addEventListener('click', resetUI); | |
| copyJsonBtn.addEventListener('click', copyToClipboard); | |
| // --- Core Logic --- | |
| function handleFile(file) { | |
| if (!file.type.startsWith('image/')) { | |
| showToast('Please upload an image file.'); | |
| return; | |
| } | |
| // 1. Show Preview | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| previewImage.src = e.target.result; | |
| dropZone.style.display = 'none'; | |
| previewContainer.style.display = 'block'; | |
| // 2. Start Simulation | |
| startProcessing(); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function startProcessing() { | |
| // UI Transition | |
| previewContainer.style.display = 'none'; | |
| processingState.style.display = 'flex'; | |
| scannerOverlay.style.display = 'block'; // Show scanner animation | |
| // Simulate OCR Delay (2.5 seconds) | |
| setTimeout(() => { | |
| processingState.style.display = 'none'; | |
| scannerOverlay.style.display = 'none'; | |
| previewContainer.style.display = 'block'; // Keep image visible | |
| // Generate Mock Data | |
| generateMockResults(); | |
| showToast('Extraction Complete'); | |
| }, 2500); | |
| } | |
| function generateMockResults() { | |
| // Mock Data representing what an OCR engine might find | |
| const mockData = { | |
| "document_type": "Invoice", | |
| "invoice_number": "INV-2023-0045", | |
| "date": "2023-10-15", | |
| "client": "Acme Corp", | |
| "total_amount": "$1,250.00", | |
| "tax": "$125.00", | |
| "status": "Paid" | |
| }; | |
| let html = ''; | |
| for (const [key, value] of Object.entries(mockData)) { | |
| html += ` | |
| <div class="data-field"> | |
| <div class="field-label"> | |
| ${key.replace('_', ' ').toUpperCase()} | |
| <span class="confidence-badge">98% Conf</span> | |
| </div> | |
| <div class="field-value">${value}</div> | |
| </div> | |
| `; | |
| } | |
| // Add raw JSON view toggle logic (optional enhancement) | |
| html += ` | |
| <div class="data-field" style="background: #0f172a; border: 1px solid #334155;"> | |
| <div class="field-label">RAW JSON OUTPUT</div> | |
| <pre style="color: #a855f7; font-size: 0.8rem; overflow-x: auto;"> | |
| ${JSON.stringify(mockData, null, 2)} | |
| </pre> | |
| </div> | |
| `; | |
| resultsContent.innerHTML = html; | |
| } | |
| function copyToClipboard() { | |
| // Extract text from the pre tag | |
| const rawJson = resultsContent.querySelector('pre').innerText; | |
| navigator.clipboard.writeText(rawJson).then(() => { | |
| showToast('JSON copied to clipboard'); | |
| }); | |
| } | |
| function resetUI() { | |
| dropZone.style.display = 'flex'; | |
| previewContainer.style.display = 'none'; | |
| processingState.style.display = 'none'; | |
| scannerOverlay.style.display = 'none'; | |
| fileInput.value = ''; | |
| previewImage.src = ''; | |
| resultsContent.innerHTML = ` | |
| <div style="text-align: center; color: var(--text-muted); padding: 40px;"> | |
| <i class="fa-solid fa-robot" style="font-size: 2rem; opacity: 0.5; margin-bottom: 10px;"></i> | |
| <p>Waiting for input...</p> | |
| </div> | |
| `; | |
| } | |
| function showToast(message) { | |
| toastMessage.innerText = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> |