Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ID OCR Engine</title> | |
| <link rel="stylesheet" href="/static/css/style.css"> | |
| </head> | |
| <body> | |
| <header id="app-header"> | |
| <div class="header-left"> | |
| <h1>ID OCR Engine</h1> | |
| </div> | |
| <div class="header-right"> | |
| <div class="health-status"> | |
| <span id="health-indicator" class="health-dot offline" title="Checking..."></span> | |
| <span class="health-label">API</span> | |
| </div> | |
| <div class="api-key-group"> | |
| <label for="api-key-input">API Key</label> | |
| <div class="input-wrapper"> | |
| <input type="password" id="api-key-input" placeholder="Optional" autocomplete="off"> | |
| <button id="toggle-key-vis" type="button" title="Show/hide key"> | |
| <span class="eye-icon">◎</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <main id="app-main"> | |
| <!-- Upload Panel --> | |
| <section id="upload-panel" class="card"> | |
| <h2>Upload</h2> | |
| <!-- Document Type Selector --> | |
| <div class="doc-type-selector"> | |
| <label for="doc-type-select">Document Type</label> | |
| <select id="doc-type-select"> | |
| <option value="sa_id_card">SA Smart Card</option> | |
| <option value="sa_id_book">SA Green Book</option> | |
| <option value="passport">Passport</option> | |
| <option value="product_serial">Product Serial Number</option> | |
| </select> | |
| </div> | |
| <!-- Front Image --> | |
| <div class="upload-group"> | |
| <label class="upload-label">Front Image <span class="required">*</span></label> | |
| <div id="drop-zone-front" class="drop-zone" tabindex="0" role="button" aria-label="Drop front image here or click to browse"> | |
| <div class="drop-zone-content"> | |
| <div class="drop-icon">📄</div> | |
| <p>Drag & drop front image here</p> | |
| <p class="text-muted text-small">PNG, JPG, BMP, WEBP • Max 10MB</p> | |
| <button class="btn btn-ghost btn-small btn-camera" data-side="front" type="button">🎥 Use Camera</button> | |
| </div> | |
| <div class="drop-zone-camera-only hidden"> | |
| <div class="drop-icon">🎥</div> | |
| <p>Scan serial number with camera</p> | |
| <button class="btn btn-primary btn-camera" data-side="front" type="button">Open Camera</button> | |
| </div> | |
| <div class="drop-zone-preview hidden"> | |
| <img class="preview-thumb" alt="Front preview"> | |
| <span class="preview-name"></span> | |
| <button class="btn-remove" title="Remove">×</button> | |
| </div> | |
| </div> | |
| <input type="file" id="file-input-front" accept=".png,.jpg,.jpeg,.bmp,.webp" hidden> | |
| </div> | |
| <!-- Back Image (only for sa_id_card) --> | |
| <div id="back-upload-group" class="upload-group"> | |
| <label class="upload-label">Back Image <span class="text-muted">(optional, barcode)</span></label> | |
| <div id="drop-zone-back" class="drop-zone drop-zone-small" tabindex="0" role="button" aria-label="Drop back image here or click to browse"> | |
| <div class="drop-zone-content"> | |
| <div class="drop-icon">📄</div> | |
| <p>Back of ID card (has barcode)</p> | |
| <p class="text-muted text-small">Optional — improves accuracy</p> | |
| </div> | |
| <div class="drop-zone-preview hidden"> | |
| <img class="preview-thumb" alt="Back preview"> | |
| <span class="preview-name"></span> | |
| <button class="btn-remove" title="Remove">×</button> | |
| </div> | |
| </div> | |
| <input type="file" id="file-input-back" accept=".png,.jpg,.jpeg,.bmp,.webp" hidden> | |
| </div> | |
| <button id="btn-process" class="btn btn-primary btn-full" disabled>Process Document</button> | |
| </section> | |
| <!-- Results Panel --> | |
| <section id="results-panel" class="card"> | |
| <h2>Results</h2> | |
| <div id="results-content"> | |
| <div id="results-empty" class="empty-state"> | |
| <div class="empty-icon">📷</div> | |
| <p>Upload a document image to see results</p> | |
| </div> | |
| <div id="image-preview-container" class="hidden"> | |
| <img id="image-preview" alt="Uploaded document"> | |
| </div> | |
| <div id="results-loading" class="loading-overlay hidden"> | |
| <div class="spinner"></div> | |
| <p>Processing image...</p> | |
| <p id="elapsed-timer" class="text-muted text-small"></p> | |
| </div> | |
| <div id="results-data" class="hidden"> | |
| <!-- Overall Result Banner --> | |
| <div id="overall-result-banner"></div> | |
| <!-- Extraction Method --> | |
| <div id="extraction-info" class="extraction-info"></div> | |
| <h3>Extracted Fields</h3> | |
| <table id="fields-table"> | |
| <tbody></tbody> | |
| </table> | |
| <h3>Checks</h3> | |
| <div id="checks-section"></div> | |
| <h3>Confidence</h3> | |
| <div id="confidence-section"></div> | |
| <details id="raw-text-section"> | |
| <summary>Raw OCR Text</summary> | |
| <div id="raw-text-content"></div> | |
| </details> | |
| <details id="machine-data-section" class="hidden"> | |
| <summary>Machine-Readable Data</summary> | |
| <div id="machine-data-content"></div> | |
| </details> | |
| </div> | |
| <div id="serial-results" class="hidden"> | |
| <div id="serial-result-banner"></div> | |
| <div id="serial-fail-guidance" class="serial-fail-guidance hidden"></div> | |
| <div id="serial-crop-container" class="hidden"> | |
| <h3>Detected Region</h3> | |
| <img id="serial-crop-preview" alt="Detected serial number region"> | |
| </div> | |
| <h3>Extracted Fields</h3> | |
| <table id="serial-fields-table"> | |
| <tbody></tbody> | |
| </table> | |
| <details id="serial-validation-section"> | |
| <summary>Validation Details</summary> | |
| <div id="serial-validation-content"></div> | |
| </details> | |
| </div> | |
| <div id="results-error" class="hidden"> | |
| <div class="error-icon">⚠</div> | |
| <p id="error-message"></p> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Statistics Panel --> | |
| <section id="stats-panel" class="card"> | |
| <h2>Statistics</h2> | |
| <div id="stats-content"> | |
| <div id="stats-empty" class="empty-state"> | |
| <p class="text-muted">Process some images to see statistics</p> | |
| </div> | |
| <div id="stats-data" class="hidden"> | |
| <div id="stat-cards"></div> | |
| <h3>Field Extraction Rates</h3> | |
| <div id="field-rates"></div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- History Panel --> | |
| <section id="history-panel" class="card"> | |
| <div class="history-header"> | |
| <h2>History</h2> | |
| <div class="history-actions"> | |
| <button id="btn-export-csv" class="btn btn-ghost btn-small" disabled>Export CSV</button> | |
| <button id="btn-clear-history" class="btn btn-ghost btn-small btn-danger" disabled>Clear</button> | |
| </div> | |
| </div> | |
| <div id="history-content"> | |
| <div id="history-empty" class="empty-state"> | |
| <p class="text-muted">No processing history yet</p> | |
| </div> | |
| <div id="history-table-wrapper" class="hidden"> | |
| <table id="history-table"> | |
| <thead> | |
| <tr> | |
| <th>#</th> | |
| <th>Time</th> | |
| <th>Type</th> | |
| <th>File</th> | |
| <th>ID/Passport #</th> | |
| <th>Name</th> | |
| <th>Method</th> | |
| <th>Result</th> | |
| <th></th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Camera Capture Modal --> | |
| <div id="camera-modal" class="camera-modal hidden" role="dialog" aria-label="Camera capture" aria-modal="true"> | |
| <div class="camera-modal-backdrop"></div> | |
| <div class="camera-modal-container"> | |
| <div class="camera-modal-header"> | |
| <h3 class="camera-modal-title">Scan Document</h3> | |
| <button id="camera-close-btn" class="btn-remove" type="button" title="Close camera">×</button> | |
| </div> | |
| <div class="camera-modal-body"> | |
| <div class="camera-video-wrapper"> | |
| <video id="camera-video" autoplay playsinline muted></video> | |
| <div id="camera-scan-guide" class="camera-scan-guide hidden"> | |
| <div class="scan-guide-rect"> | |
| <div class="scan-guide-corner tl"></div> | |
| <div class="scan-guide-corner tr"></div> | |
| <div class="scan-guide-corner bl"></div> | |
| <div class="scan-guide-corner br"></div> | |
| </div> | |
| <p class="scan-guide-label">Position serial number here</p> | |
| </div> | |
| <div id="camera-loading" class="camera-loading"> | |
| <div class="spinner"></div> | |
| <p>Starting camera...</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="camera-scan-status" class="camera-scan-status hidden"> | |
| <div class="scan-pulse"></div> | |
| <span id="camera-scan-text">Scanning...</span> | |
| </div> | |
| <div class="camera-modal-footer"> | |
| <button id="camera-switch-btn" class="btn btn-ghost hidden" type="button" title="Switch camera">🔄 Switch</button> | |
| <button id="camera-scan-btn" class="btn btn-primary" type="button" disabled>🔍 Scan ID</button> | |
| <button id="camera-capture-btn" class="btn btn-ghost" type="button">📷 Capture</button> | |
| <button id="camera-cancel-btn" class="btn btn-ghost" type="button">Cancel</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="toast-container"></div> | |
| <script src="/static/js/utils.js"></script> | |
| <script src="/static/js/api.js"></script> | |
| <script src="/static/js/results.js"></script> | |
| <script src="/static/js/history.js"></script> | |
| <script src="/static/js/stats.js"></script> | |
| <script src="/static/js/camera.js"></script> | |
| <script src="/static/js/upload.js"></script> | |
| <script src="/static/js/app.js"></script> | |
| </body> | |
| </html> | |