Spaces:
Running
Running
| import gradio as gr | |
| import time | |
| import json | |
| # Mock OCR Function to simulate the backend logic | |
| def process_document(file): | |
| if file is None: | |
| return None, None | |
| # Simulate processing time (Scanner effect) | |
| time.sleep(2.5) | |
| # Mock extracted data | |
| mock_data = { | |
| "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", | |
| "confidence_score": 0.98 | |
| } | |
| return file, mock_data | |
| # Custom CSS to match the "VisionExtract" dark/glass theme from the request | |
| custom_css = """ | |
| :root { | |
| --primary: #6366f1; | |
| --secondary: #a855f7; | |
| --accent: #ec4899; | |
| --bg-dark: #0f172a; | |
| --bg-card: #1e293b; | |
| --text-main: #f8fafc; | |
| --text-muted: #94a3b8; | |
| --success: #10b981; | |
| --border: #334155; | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| font-family: 'Inter', sans-serif; | |
| 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%); | |
| } | |
| .gradio-container { | |
| background: transparent !important; | |
| } | |
| /* Sidebar Styling */ | |
| .sidebar-container { | |
| background: var(--bg-card); | |
| border-right: 1px solid var(--border); | |
| color: var(--text-main); | |
| } | |
| .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; | |
| font-weight: 500; | |
| } | |
| .nav-item:hover { | |
| background: rgba(63, 66, 241, 0.1); | |
| color: var(--text-main); | |
| } | |
| /* Card Styling */ | |
| .gr-box { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Header Badge */ | |
| .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; | |
| display: inline-block; | |
| margin-bottom: 10px; | |
| } | |
| .anycoder-badge:hover { | |
| transform: translateY(-2px); | |
| text-decoration: none; | |
| color: white; | |
| } | |
| /* Results Panel */ | |
| .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; | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| .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; | |
| } | |
| /* Scanner Animation Overlay (Simulated via HTML component) */ | |
| .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; | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| @keyframes scan { | |
| 0% { top: -100%; } | |
| 100% { top: 100%; } | |
| } | |
| """ | |
| def create_app(): | |
| with gr.Blocks() as demo: | |
| # --- Sidebar --- | |
| with gr.Sidebar(position="left", open=True): | |
| gr.Markdown("<div style='color: #6366f1; font-weight: 700; font-size: 1.2rem; margin-bottom: 20px;'>ποΈ VisionExtract</div>") | |
| gr.Markdown("<div class='nav-item'>π New Extraction</div>") | |
| gr.Markdown("<div class='nav-item'>β‘ Batch Process</div>") | |
| gr.Markdown("<div class='nav-item'>βοΈ Settings</div>") | |
| gr.Markdown("<div style='font-size: 0.8rem; text-transform: uppercase; letter-spacing: 1px; color: #94a3b8; margin-top: 40px; margin-bottom: 15px;'>Recent Scans</div>") | |
| gr.Markdown("<div style='padding: 10px; border-bottom: 1px solid #334155; font-size: 0.9rem; color: #94a3b8;'>Invoice_001.png</div>") | |
| gr.Markdown("<div style='padding: 10px; border-bottom: 1px solid #334155; font-size: 0.9rem; color: #94a3b8;'>Form_Submit_22.jpg</div>") | |
| gr.Markdown("<div style='padding: 10px; border-bottom: 1px solid #334155; font-size: 0.9rem; color: #94a3b8;'>Receipt_Starbucks.png</div>") | |
| gr.Markdown("<div style='margin-top: 20px; font-size: 0.7rem; color: #94a3b8;'>v2.4.0 Stable</div>") | |
| # --- Main Content --- | |
| with gr.Column(): | |
| # Header | |
| with gr.Row(): | |
| gr.Markdown("<h1 style='font-size: 1.8rem; margin-bottom: 5px;'>OCR Data Extraction</h1><p style='color: #94a3b8; font-size: 0.9rem;'>Upload a document to automatically extract structured fields.</p>") | |
| gr.HTML("<a href='https://huggingface.co/spaces/akhaliq/anycoder' class='anycoder-badge'>Built with anycoder</a>") | |
| # Workspace Grid | |
| with gr.Row(): | |
| # Left Column: Upload/Preview | |
| with gr.Column(scale=1): | |
| with gr.Group(): | |
| gr.Markdown("<div style='font-weight: 600; display: flex; align-items: center; gap: 8px; margin-bottom: 20px;'>πΌοΈ Source Document</div>") | |
| # File Input | |
| file_input = gr.File(label="Upload Document", file_types=["image"], height=150) | |
| # Image Preview (Hidden initially, shown after upload) | |
| image_preview = gr.Image(label="Preview", interactive=False, height=400, visible=False) | |
| # Scanner Effect Overlay (HTML Component) | |
| scanner_html = gr.HTML( | |
| value="<div class='scanner-overlay'></div>", | |
| visible=False, | |
| label="Processing" | |
| ) | |
| clear_btn = gr.Button("ποΈ Clear", variant="secondary", size="sm") | |
| # Right Column: Results | |
| with gr.Column(scale=1): | |
| with gr.Group(): | |
| gr.Markdown("<div style='font-weight: 600; display: flex; align-items: center; gap: 8px; margin-bottom: 20px;'>π Extracted Data</div>") | |
| # Results Display | |
| results_json = gr.JSON(label="Raw JSON", visible=False) | |
| results_markdown = gr.Markdown(value="<div style='text-align: center; color: #94a3b8; padding: 40px;'>π€ Waiting for input...</div>", label="Structured Fields") | |
| copy_btn = gr.Button("π Copy JSON", variant="secondary", size="sm", visible=False) | |
| # --- Logic --- | |
| def clear_all(): | |
| return None, None, None, False, False, False, "<div style='text-align: center; color: #94a3b8; padding: 40px;'>π€ Waiting for input...</div>", False, False | |
| def process_logic(file): | |
| if file is None: | |
| return None, None, False, False, None, False, "<div style='text-align: center; color: #94a3b8; padding: 40px;'>π€ Waiting for input...</div>", False, False | |
| # Run OCR simulation | |
| img, data = process_document(file) | |
| # Format Markdown Results | |
| html_fields = "" | |
| for key, value in data.items(): | |
| if key != "confidence_score": | |
| html_fields += f""" | |
| <div class="data-field"> | |
| <div class="field-label"> | |
| {key.replace('_', ' ').upper()} | |
| <span class="confidence-badge">98% Conf</span> | |
| </div> | |
| <div class="field-value">{value}</div> | |
| </div> | |
| """ | |
| html_fields += f""" | |
| <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.dumps(data, indent=2)} | |
| </pre> | |
| </div> | |
| """ | |
| return img, img, True, True, data, True, html_fields, True, True | |
| # Event Listeners | |
| file_input.upload( | |
| fn=process_logic, | |
| inputs=[file_input], | |
| outputs=[image_preview, image_preview, scanner_html, scanner_html, results_json, copy_btn, results_markdown, copy_btn, copy_btn] | |
| ) | |
| clear_btn.click( | |
| fn=clear_all, | |
| inputs=[], | |
| outputs=[file_input, image_preview, scanner_html, scanner_html, results_json, copy_btn, results_markdown, copy_btn, copy_btn] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create_app() | |
| demo.launch( | |
| theme=gr.themes.Base(), | |
| css=custom_css, | |
| footer_links=[{"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"}] | |
| ) |