Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>BitCheck Document API Tester</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-color: #0f172a; | |
| --surface: #1e293b; | |
| --surface-hover: #334155; | |
| --primary: #3b82f6; | |
| --primary-hover: #2563eb; | |
| --text-main: #f8fafc; | |
| --text-muted: #94a3b8; | |
| --border: #334155; | |
| --success: #10b981; | |
| --danger: #ef4444; | |
| --warning: #f59e0b; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--bg-color); | |
| color: var(--text-main); | |
| margin: 0; | |
| padding: 2rem; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| .header { | |
| margin-bottom: 2rem; | |
| text-align: center; | |
| } | |
| h1 { | |
| font-weight: 700; | |
| margin-bottom: 0.5rem; | |
| font-size: 2.5rem; | |
| background: linear-gradient(135deg, #60a5fa, #a78bfa); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .subtitle { | |
| color: var(--text-muted); | |
| font-size: 1.1rem; | |
| } | |
| .container { | |
| max-width: 1100px; | |
| width: 100%; | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 2rem; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .card { | |
| background: rgba(30, 41, 59, 0.6); | |
| backdrop-filter: blur(16px); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .form-group { | |
| margin-bottom: 1.2rem; | |
| } | |
| label { | |
| display: block; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| input[type="text"], input[type="number"], select { | |
| width: 100%; | |
| padding: 0.75rem 1rem; | |
| background: rgba(15, 23, 42, 0.8); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| color: var(--text-main); | |
| font-family: 'Inter', sans-serif; | |
| transition: all 0.2s ease; | |
| box-sizing: border-box; | |
| font-size: 0.95rem; | |
| } | |
| input[type="text"]:focus, input[type="number"]:focus, select:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); | |
| } | |
| .grid-2 { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1rem; | |
| } | |
| .checkbox-container { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 0.75rem; | |
| margin-bottom: 1.5rem; | |
| background: rgba(15, 23, 42, 0.3); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| border: 1px solid var(--border); | |
| } | |
| .checkbox-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| cursor: pointer; | |
| text-transform: none; | |
| letter-spacing: normal; | |
| font-weight: 500; | |
| color: var(--text-main); | |
| margin: 0; | |
| user-select: none; | |
| } | |
| .checkbox-label input { | |
| cursor: pointer; | |
| width: 1.2rem; | |
| height: 1.2rem; | |
| accent-color: var(--primary); | |
| margin: 0; | |
| } | |
| .file-drop { | |
| border: 2px dashed var(--border); | |
| border-radius: 12px; | |
| padding: 2.5rem 2rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| margin-bottom: 1.5rem; | |
| background: rgba(15, 23, 42, 0.5); | |
| position: relative; | |
| } | |
| .file-drop:hover, .file-drop.dragover { | |
| border-color: var(--primary); | |
| background: rgba(59, 130, 246, 0.05); | |
| } | |
| .file-drop input[type="file"] { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0; | |
| cursor: pointer; | |
| } | |
| .file-icon { | |
| font-size: 2.5rem; | |
| margin-bottom: 1rem; | |
| color: var(--primary); | |
| } | |
| .file-drop p { | |
| margin: 0; | |
| color: var(--text-muted); | |
| font-size: 0.95rem; | |
| } | |
| #fileName { | |
| color: var(--text-main); | |
| font-weight: 600; | |
| margin-top: 0.75rem; | |
| display: block; | |
| word-break: break-all; | |
| } | |
| button { | |
| width: 100%; | |
| padding: 1rem; | |
| background: linear-gradient(135deg, var(--primary), #8b5cf6); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 0.75rem; | |
| box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); | |
| margin-top: auto; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 15px rgba(59, 130, 246, 0.4); | |
| } | |
| button:active { | |
| transform: translateY(0); | |
| } | |
| button:disabled { | |
| background: var(--surface-hover); | |
| box-shadow: none; | |
| cursor: not-allowed; | |
| color: var(--text-muted); | |
| transform: none; | |
| } | |
| .spinner { | |
| width: 1.2rem; | |
| height: 1.2rem; | |
| border: 3px solid rgba(255,255,255,0.3); | |
| border-radius: 50%; | |
| border-top-color: white; | |
| animation: spin 1s linear infinite; | |
| display: none; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .result-card { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| } | |
| .status-bar { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| padding-bottom: 1rem; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| padding: 0.35rem 0.85rem; | |
| border-radius: 999px; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| .status-idle { background: rgba(148, 163, 184, 0.1); color: #94a3b8; border: 1px solid #475569; } | |
| .status-success { background: rgba(16, 185, 129, 0.1); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); } | |
| .status-error { background: rgba(239, 68, 68, 0.1); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); } | |
| .status-warning { background: rgba(245, 158, 11, 0.1); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.3); } | |
| .time-badge { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| font-family: monospace; | |
| } | |
| .json-viewer { | |
| flex-grow: 1; | |
| background: #0f172a; | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| overflow-y: auto; | |
| font-family: 'Consolas', monospace; | |
| font-size: 0.85rem; | |
| color: #a5b4fc; | |
| border: 1px solid rgba(255,255,255,0.05); | |
| white-space: pre-wrap; | |
| max-height: 550px; | |
| line-height: 1.5; | |
| box-shadow: inset 0 2px 10px rgba(0,0,0,0.2); | |
| } | |
| .json-viewer .string { color: #86efac; } | |
| .json-viewer .number { color: #fca5a5; } | |
| .json-viewer .boolean { color: #fcd34d; } | |
| .json-viewer .null { color: #94a3b8; } | |
| .json-viewer .key { color: #93c5fd; font-weight: 600; } | |
| .endpoint-selector { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .endpoint-selector button { | |
| background: var(--surface-hover); | |
| color: var(--text-muted); | |
| border-radius: 6px; | |
| padding: 0.5rem; | |
| box-shadow: none; | |
| font-size: 0.85rem; | |
| margin: 0; | |
| flex: 1; | |
| } | |
| .endpoint-selector button.active { | |
| background: rgba(59, 130, 246, 0.2); | |
| color: var(--primary); | |
| border: 1px solid var(--primary); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>BitCheck Tester</h1> | |
| <div class="subtitle">Evaluate Document Verification API Endpoints</div> | |
| </div> | |
| <div class="container"> | |
| <!-- Configuration Card --> | |
| <div class="card"> | |
| <div class="endpoint-selector"> | |
| <button type="button" id="btn-hf" class="active" onclick="setEndpoint('hf')">Hugging Face Space</button> | |
| <button type="button" id="btn-local" onclick="setEndpoint('local')">Local Server</button> | |
| </div> | |
| <div class="form-group"> | |
| <label for="apiUrl">API Endpoint URL</label> | |
| <input type="text" id="apiUrl" value="https://jaykay73-bitcheck-document.hf.space/verify/document"> | |
| </div> | |
| <div class="file-drop" id="dropZone"> | |
| <div class="file-icon">📄</div> | |
| <p>Drag & drop a document here</p> | |
| <p style="font-size: 0.8rem; margin-top: 0.5rem; opacity: 0.7;">Supports PDF, JPG, PNG, WEBP (Max 20MB)</p> | |
| <input type="file" id="fileInput" accept=".pdf,.jpg,.jpeg,.png,.webp"> | |
| <span id="fileName">No file selected</span> | |
| </div> | |
| <div class="grid-2"> | |
| <div class="form-group"> | |
| <label for="docType">Document Type</label> | |
| <select id="docType"> | |
| <option value="general">General</option> | |
| <option value="certificate">Certificate</option> | |
| <option value="identity_document">Identity Document</option> | |
| <option value="academic_result">Academic Result</option> | |
| <option value="invoice">Invoice</option> | |
| <option value="receipt">Receipt</option> | |
| <option value="bank_statement">Bank Statement</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="maxPages">Max PDF Pages</label> | |
| <input type="number" id="maxPages" value="5" min="1" max="50"> | |
| </div> | |
| </div> | |
| <label style="margin-top: 0.5rem;">Analysis Modules</label> | |
| <div class="checkbox-container"> | |
| <label class="checkbox-label"> | |
| <input type="checkbox" id="runOcr" checked> Run OCR | |
| </label> | |
| <label class="checkbox-label"> | |
| <input type="checkbox" id="runForensics" checked> Visual Forensics | |
| </label> | |
| <label class="checkbox-label"> | |
| <input type="checkbox" id="runQr" checked> QR / Barcode | |
| </label> | |
| <label class="checkbox-label"> | |
| <input type="checkbox" id="runLiveQr"> Live QR Check | |
| </label> | |
| <label class="checkbox-label"> | |
| <input type="checkbox" id="runLlm" checked> DeepSeek LLM | |
| </label> | |
| </div> | |
| <button id="submitBtn" onclick="submitRequest()"> | |
| <div class="spinner" id="spinner"></div> | |
| <span id="btnText">Analyze Document</span> | |
| </button> | |
| </div> | |
| <!-- Result Card --> | |
| <div class="card result-card"> | |
| <div class="status-bar"> | |
| <div id="statusBadge" class="status-badge status-idle">Ready</div> | |
| <div id="timeBadge" class="time-badge">0ms</div> | |
| </div> | |
| <div class="json-viewer" id="jsonOutput">Waiting for submission...</div> | |
| </div> | |
| </div> | |
| <script> | |
| // Preset endpoints | |
| const endpoints = { | |
| 'hf': 'https://jaykay73-bitcheck-document.hf.space/verify/document', | |
| 'local': 'http://localhost:7860/verify/document' | |
| }; | |
| function setEndpoint(type) { | |
| document.getElementById('apiUrl').value = endpoints[type]; | |
| document.getElementById('btn-hf').classList.toggle('active', type === 'hf'); | |
| document.getElementById('btn-local').classList.toggle('active', type === 'local'); | |
| } | |
| // File handling | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileNameDisplay = document.getElementById('fileName'); | |
| const dropZone = document.getElementById('dropZone'); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length > 0) { | |
| fileNameDisplay.textContent = e.target.files[0].name; | |
| } else { | |
| fileNameDisplay.textContent = 'No file selected'; | |
| } | |
| }); | |
| // Drag and drop effects | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, () => dropZone.classList.add('dragover'), false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, () => dropZone.classList.remove('dragover'), false); | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| let dt = e.dataTransfer; | |
| let files = dt.files; | |
| fileInput.files = files; | |
| if(files.length > 0) { | |
| fileNameDisplay.textContent = files[0].name; | |
| } | |
| }); | |
| // JSON syntax highlighting | |
| function syntaxHighlight(json) { | |
| if (typeof json != 'string') { | |
| json = JSON.stringify(json, undefined, 2); | |
| } | |
| json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
| return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { | |
| var cls = 'number'; | |
| if (/^"/.test(match)) { | |
| if (/:$/.test(match)) { | |
| cls = 'key'; | |
| } else { | |
| cls = 'string'; | |
| } | |
| } else if (/true|false/.test(match)) { | |
| cls = 'boolean'; | |
| } else if (/null/.test(match)) { | |
| cls = 'null'; | |
| } | |
| return '<span class="' + cls + '">' + match + '</span>'; | |
| }); | |
| } | |
| async function submitRequest() { | |
| const url = document.getElementById('apiUrl').value; | |
| const file = fileInput.files[0]; | |
| const output = document.getElementById('jsonOutput'); | |
| const statusBadge = document.getElementById('statusBadge'); | |
| const timeBadge = document.getElementById('timeBadge'); | |
| const btn = document.getElementById('submitBtn'); | |
| const btnText = document.getElementById('btnText'); | |
| const spinner = document.getElementById('spinner'); | |
| if (!file) { | |
| alert('Please select a file to upload.'); | |
| return; | |
| } | |
| // Setup form data | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| formData.append('document_type', document.getElementById('docType').value); | |
| formData.append('max_pages', document.getElementById('maxPages').value); | |
| formData.append('run_ocr', document.getElementById('runOcr').checked); | |
| formData.append('run_forensics', document.getElementById('runForensics').checked); | |
| formData.append('run_qr', document.getElementById('runQr').checked); | |
| formData.append('run_live_qr_check', document.getElementById('runLiveQr').checked); | |
| formData.append('run_llm_analysis', document.getElementById('runLlm').checked); | |
| // Update UI state | |
| btn.disabled = true; | |
| btnText.textContent = 'Analyzing...'; | |
| spinner.style.display = 'block'; | |
| output.innerHTML = 'Sending request...'; | |
| statusBadge.className = 'status-badge status-idle'; | |
| statusBadge.textContent = 'Processing'; | |
| timeBadge.textContent = '...'; | |
| const startTime = performance.now(); | |
| try { | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| body: formData, | |
| headers: { | |
| 'Accept': 'application/json' | |
| } | |
| }); | |
| const data = await response.json(); | |
| const endTime = performance.now(); | |
| const duration = Math.round(endTime - startTime); | |
| output.innerHTML = syntaxHighlight(data); | |
| timeBadge.textContent = `${duration}ms`; | |
| if (response.ok) { | |
| if (data.status === 'completed_with_warnings') { | |
| statusBadge.className = 'status-badge status-warning'; | |
| statusBadge.textContent = 'Warnings'; | |
| } else { | |
| statusBadge.className = 'status-badge status-success'; | |
| statusBadge.textContent = 'Success'; | |
| } | |
| } else { | |
| statusBadge.className = 'status-badge status-error'; | |
| statusBadge.textContent = `HTTP ${response.status}`; | |
| } | |
| } catch (error) { | |
| const endTime = performance.now(); | |
| timeBadge.textContent = `${Math.round(endTime - startTime)}ms`; | |
| output.innerHTML = `<span style="color:#ef4444;">Error: ${error.message}</span>\n\nMake sure the API is running and CORS is configured if calling cross-origin.`; | |
| statusBadge.className = 'status-badge status-error'; | |
| statusBadge.textContent = 'Failed'; | |
| } finally { | |
| btn.disabled = false; | |
| btnText.textContent = 'Analyze Document'; | |
| spinner.style.display = 'none'; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |