Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Aadhaar Card OCR API - Test Dashboard</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| header { | |
| background: white; | |
| border-radius: 10px; | |
| padding: 30px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| } | |
| h1 { | |
| color: #333; | |
| margin-bottom: 10px; | |
| } | |
| .subtitle { | |
| color: #666; | |
| font-size: 14px; | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 10px; | |
| padding: 25px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| } | |
| .card h2 { | |
| color: #667eea; | |
| margin-bottom: 20px; | |
| font-size: 18px; | |
| border-bottom: 2px solid #667eea; | |
| padding-bottom: 10px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| color: #333; | |
| font-weight: 500; | |
| font-size: 14px; | |
| } | |
| input[type="file"] { | |
| display: block; | |
| width: 100%; | |
| padding: 10px; | |
| border: 2px dashed #667eea; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| color: #666; | |
| } | |
| input[type="file"]:hover { | |
| border-color: #764ba2; | |
| background-color: #f8f9ff; | |
| } | |
| button { | |
| width: 100%; | |
| padding: 12px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); | |
| } | |
| button:active { | |
| transform: translateY(0); | |
| } | |
| button:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| color: #667eea; | |
| font-size: 14px; | |
| margin-top: 10px; | |
| } | |
| .loading.active { | |
| display: block; | |
| } | |
| .spinner { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid #667eea; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-right: 8px; | |
| vertical-align: middle; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .results { | |
| background: white; | |
| border-radius: 10px; | |
| padding: 25px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| } | |
| .result-item { | |
| margin-bottom: 20px; | |
| padding: 15px; | |
| background: #f8f9ff; | |
| border-left: 4px solid #667eea; | |
| border-radius: 5px; | |
| } | |
| .result-item.error { | |
| border-left-color: #e74c3c; | |
| background: #ffe6e6; | |
| } | |
| .result-item.success { | |
| border-left-color: #27ae60; | |
| background: #e6ffe6; | |
| } | |
| .result-item h3 { | |
| color: #333; | |
| font-size: 16px; | |
| margin-bottom: 10px; | |
| } | |
| .result-field { | |
| display: grid; | |
| grid-template-columns: 120px 1fr; | |
| gap: 10px; | |
| margin-bottom: 8px; | |
| font-size: 14px; | |
| } | |
| .result-label { | |
| font-weight: 600; | |
| color: #667eea; | |
| } | |
| .result-value { | |
| color: #333; | |
| word-break: break-all; | |
| } | |
| .error-message { | |
| color: #e74c3c; | |
| font-size: 14px; | |
| margin-top: 10px; | |
| } | |
| .health-status { | |
| display: inline-block; | |
| padding: 5px 15px; | |
| background: #27ae60; | |
| color: white; | |
| border-radius: 20px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| } | |
| .health-status.error { | |
| background: #e74c3c; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| @media (max-width: 768px) { | |
| .grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .result-field { | |
| grid-template-columns: 1fr; | |
| } | |
| header { | |
| padding: 20px; | |
| } | |
| .card { | |
| padding: 20px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>π¨ Aadhaar Card OCR API</h1> | |
| <p class="subtitle">Test Dashboard - Extract Aadhaar data from images and PDFs</p> | |
| <div style="margin-top: 15px;"> | |
| Health: <span class="health-status" id="healthStatus">Checking...</span> | |
| </div> | |
| </header> | |
| <div class="grid"> | |
| <!-- Image Upload - Front --> | |
| <div class="card"> | |
| <h2>πΈ Extract Front (Image or PDF)</h2> | |
| <form id="frontImageForm"> | |
| <div class="form-group"> | |
| <label for="frontImageFile">Upload Front Side Image or PDF</label> | |
| <input type="file" id="frontImageFile" accept="image/*,.pdf" required> | |
| </div> | |
| <button type="submit">Extract Front Data</button> | |
| <div class="loading" id="frontImageLoading"> | |
| <span class="spinner"></span> Processing... | |
| </div> | |
| </form> | |
| </div> | |
| <!-- Image Upload - Back --> | |
| <div class="card"> | |
| <h2>πΈ Extract Back (Image or PDF)</h2> | |
| <form id="backImageForm"> | |
| <div class="form-group"> | |
| <label for="backImageFile">Upload Back Side Image or PDF</label> | |
| <input type="file" id="backImageFile" accept="image/*,.pdf" required> | |
| </div> | |
| <button type="submit">Extract Back Data</button> | |
| <div class="loading" id="backImageLoading"> | |
| <span class="spinner"></span> Processing... | |
| </div> | |
| </form> | |
| </div> | |
| <!-- PDF Upload --> | |
| <div class="card"> | |
| <h2>π Extract from PDF (Front & Back)</h2> | |
| <form id="pdfForm"> | |
| <div class="form-group"> | |
| <label for="pdfFile">Upload PDF with Aadhaar Cards (Front & Back)</label> | |
| <input type="file" id="pdfFile" accept=".pdf" required> | |
| </div> | |
| <button type="submit">Extract from PDF</button> | |
| <div class="loading" id="pdfLoading"> | |
| <span class="spinner"></span> Processing... | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Results Section --> | |
| <div class="results hidden" id="resultsSection"> | |
| <h2 style="margin-bottom: 20px; color: #333;">π Results</h2> | |
| <div id="resultsContainer"></div> | |
| </div> | |
| </div> | |
| <script> | |
| const API_BASE = window.location.origin; | |
| // Check health on load | |
| window.addEventListener('load', checkHealth); | |
| async function checkHealth() { | |
| try { | |
| const response = await fetch(`${API_BASE}/health`); | |
| const data = await response.json(); | |
| const status = document.getElementById('healthStatus'); | |
| status.textContent = `β ${data.status}`; | |
| status.classList.remove('error'); | |
| } catch (error) { | |
| const status = document.getElementById('healthStatus'); | |
| status.textContent = 'β Offline'; | |
| status.classList.add('error'); | |
| } | |
| } | |
| // Front Image Form | |
| document.getElementById('frontImageForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| await handleUpload('frontImage', '/extract-front', 'Front Image'); | |
| }); | |
| // Back Image Form | |
| document.getElementById('backImageForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| await handleUpload('backImage', '/extract-back', 'Back Image'); | |
| }); | |
| // PDF Form | |
| document.getElementById('pdfForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| await handleUpload('pdf', '/extract-pdf', 'PDF'); | |
| }); | |
| async function handleUpload(formId, endpoint, label) { | |
| const fileInput = document.getElementById(`${formId}File`); | |
| const loading = document.getElementById(`${formId}Loading`); | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| alert('Please select a file'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| loading.classList.add('active'); | |
| try { | |
| const response = await fetch(`${API_BASE}${endpoint}`, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(data.detail || 'Request failed'); | |
| } | |
| displayResults(data, label, response.ok); | |
| fileInput.value = ''; | |
| } catch (error) { | |
| displayResults({ error: error.message }, label, false); | |
| } finally { | |
| loading.classList.remove('active'); | |
| } | |
| } | |
| function displayResults(data, label, success) { | |
| const resultsSection = document.getElementById('resultsSection'); | |
| const resultsContainer = document.getElementById('resultsContainer'); | |
| resultsSection.classList.remove('hidden'); | |
| let html = ''; | |
| if (Array.isArray(data)) { | |
| data.forEach((item, index) => { | |
| html += formatResultItem(item, `${label} - Item ${index + 1}`); | |
| }); | |
| } else if (data.error) { | |
| html = `<div class="result-item error"> | |
| <h3>${label}</h3> | |
| <div class="error-message">β ${data.error}</div> | |
| </div>`; | |
| } else { | |
| html = formatResultItem(data, label); | |
| } | |
| resultsContainer.insertAdjacentHTML('afterbegin', html); | |
| } | |
| function formatResultItem(item, label) { | |
| const isError = item.error; | |
| const itemClass = isError ? 'error' : 'success'; | |
| const icon = isError ? 'β' : 'β '; | |
| let html = `<div class="result-item ${itemClass}"> | |
| <h3>${icon} ${label}</h3>`; | |
| if (isError) { | |
| html += `<div class="error-message">${item.error}</div>`; | |
| if (item.page) { | |
| html += `<div class="result-field"> | |
| <span class="result-label">Page:</span> | |
| <span class="result-value">${item.page}</span> | |
| </div>`; | |
| } | |
| } else { | |
| // Display page number | |
| if (item.page) { | |
| html += `<div class="result-field"> | |
| <span class="result-label">Page:</span> | |
| <span class="result-value">${item.page}</span> | |
| </div>`; | |
| } | |
| // Display front data if available | |
| if (item.front_data) { | |
| html += `<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #ddd;"> | |
| <div style="font-weight: 600; color: #667eea; margin-bottom: 8px;">π Front Side Data</div>`; | |
| Object.entries(item.front_data).forEach(([key, value]) => { | |
| if (value) { | |
| const displayKey = key.replace(/_/g, ' ').charAt(0).toUpperCase() + | |
| key.replace(/_/g, ' ').slice(1); | |
| html += `<div class="result-field"> | |
| <span class="result-label">${displayKey}:</span> | |
| <span class="result-value">${value}</span> | |
| </div>`; | |
| } | |
| }); | |
| html += `</div>`; | |
| } | |
| if (item.front_error) { | |
| html += `<div style="margin-top: 8px; color: #e74c3c; font-size: 13px;">β Front extraction: ${item.front_error}</div>`; | |
| } | |
| // Display back data if available | |
| if (item.back_data) { | |
| html += `<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #ddd;"> | |
| <div style="font-weight: 600; color: #667eea; margin-bottom: 8px;">πΊ Back Side Data</div>`; | |
| Object.entries(item.back_data).forEach(([key, value]) => { | |
| if (value) { | |
| const displayKey = key.replace(/_/g, ' ').charAt(0).toUpperCase() + | |
| key.replace(/_/g, ' ').slice(1); | |
| html += `<div class="result-field"> | |
| <span class="result-label">${displayKey}:</span> | |
| <span class="result-value">${value}</span> | |
| </div>`; | |
| } | |
| }); | |
| html += `</div>`; | |
| } | |
| if (item.back_error) { | |
| html += `<div style="margin-top: 8px; color: #e74c3c; font-size: 13px;">β Back extraction: ${item.back_error}</div>`; | |
| } | |
| // Display other fields dynamically (non-front, non-back data) | |
| Object.entries(item).forEach(([key, value]) => { | |
| if (key !== 'error' && key !== 'page' && key !== 'front_data' && key !== 'back_data' && | |
| key !== 'front_error' && key !== 'back_error' && value) { | |
| const displayKey = key.replace(/_/g, ' ').charAt(0).toUpperCase() + | |
| key.replace(/_/g, ' ').slice(1); | |
| html += `<div class="result-field"> | |
| <span class="result-label">${displayKey}:</span> | |
| <span class="result-value">${value}</span> | |
| </div>`; | |
| } | |
| }); | |
| } | |
| html += '</div>'; | |
| return html; | |
| } | |
| </script> | |
| </body> | |
| </html> | |