ek-developers/ocr-frontend
0
1import gradio as gr2import requests3import os4 5# ── Config ─────────────────────────────────────────────────────────────────────6API_URL = os.getenv("OCR_API_URL", "https://ek-developers-my-ocr-api.hf.space/ocr")7 8# ── Core function ──────────────────────────────────────────────────────────────9def run_ocr(image, prompt_text):10 if image is None:11 return "⚠️ Please upload an image first."12 13 if not prompt_text or not prompt_text.strip():14 prompt_text = "Free OCR."15 16 try:17 with open(image, "rb") as f:18 response = requests.post(19 API_URL,20 files={"file": ("image.jpg", f, "image/jpeg")},21 data={"prompt_text": prompt_text},22 timeout=180,23 )24 25 if response.status_code == 200:26 text = response.json().get("text", "").strip()27 if not text:28 return "⚠️ No text found in image. Try a clearer photo or different prompt."29 return text30 else:31 return f"❌ API error {response.status_code}: {response.text}"32 33 except requests.exceptions.Timeout:34 return "⌛ Request timed out. The model may still be loading — please try again in 30 seconds."35 except requests.exceptions.ConnectionError:36 return "❌ Could not connect to the OCR backend. It may be sleeping — try again shortly."37 except Exception as e:38 return f"❌ Unexpected error: {str(e)}"39 40 41# ── UI ─────────────────────────────────────────────────────────────────────────42css = """43@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@300;400;600&display=swap');44 45.gradio-container {46 max-width: 1000px !important;47 margin: 0 auto !important;48 font-family: 'IBM Plex Sans', sans-serif !important;49}50 51#app-header {52 text-align: center;53 padding: 2.5rem 0 1.5rem;54 border-bottom: 1px solid #e2e8f0;55 margin-bottom: 2rem;56}57 58#app-header .badge {59 display: inline-block;60 background: #eff6ff;61 color: #2563eb;62 font-family: 'IBM Plex Mono', monospace;63 font-size: 0.7rem;64 letter-spacing: 2px;65 padding: 4px 12px;66 border-radius: 20px;67 border: 1px solid #bfdbfe;68 margin-bottom: 1rem;69 text-transform: uppercase;70}71 72#app-header h1 {73 font-size: 2.6rem;74 font-weight: 600;75 color: #0f172a;76 margin: 0 0 0.5rem;77 letter-spacing: -1px;78 line-height: 1.1;79}80 81#app-header h1 span { color: #2563eb; }82 83#app-header p {84 color: #64748b;85 font-size: 0.95rem;86 font-weight: 300;87 margin: 0;88}89 90.panel-label {91 font-family: 'IBM Plex Mono', monospace;92 font-size: 0.7rem;93 letter-spacing: 2px;94 text-transform: uppercase;95 color: #2563eb;96 margin-bottom: 0.75rem;97 padding-bottom: 0.5rem;98 border-bottom: 1px solid #e2e8f0;99}100 101#run-btn {102 background: #2563eb !important;103 border: none !important;104 color: #ffffff !important;105 font-family: 'IBM Plex Mono', monospace !important;106 font-size: 0.85rem !important;107 letter-spacing: 1.5px !important;108 text-transform: uppercase !important;109 border-radius: 6px !important;110 width: 100% !important;111 margin-top: 0.5rem !important;112 transition: background 0.2s !important;113}114 115#run-btn:hover {116 background: #1d4ed8 !important;117}118 119#clear-btn {120 background: transparent !important;121 border: 1px solid #e2e8f0 !important;122 color: #94a3b8 !important;123 font-family: 'IBM Plex Mono', monospace !important;124 font-size: 0.8rem !important;125 letter-spacing: 1px !important;126 border-radius: 6px !important;127 width: 100% !important;128 transition: all 0.2s !important;129}130 131#clear-btn:hover {132 border-color: #94a3b8 !important;133 color: #475569 !important;134}135 136#output-text textarea {137 font-family: 'IBM Plex Mono', monospace !important;138 font-size: 0.88rem !important;139 line-height: 1.7 !important;140 min-height: 320px !important;141}142 143#status-bar {144 display: flex;145 align-items: center;146 justify-content: space-between;147 padding: 0.6rem 1rem;148 background: #f8fafc;149 border: 1px solid #e2e8f0;150 border-radius: 6px;151 margin-bottom: 0.75rem;152}153 154#status-bar .dot {155 width: 7px;156 height: 7px;157 border-radius: 50%;158 background: #22c55e;159 display: inline-block;160 margin-right: 7px;161 box-shadow: 0 0 6px #22c55e88;162}163 164#status-bar span {165 font-family: 'IBM Plex Mono', monospace;166 font-size: 0.72rem;167 color: #64748b;168 letter-spacing: 1px;169}170 171#status-bar .api-url {172 color: #94a3b8;173 font-size: 0.68rem;174 font-family: 'IBM Plex Mono', monospace;175}176 177#footer {178 text-align: center;179 padding: 2rem 0 2.5rem;180 border-top: 1px solid #e2e8f0;181 margin-top: 2.5rem;182 color: #94a3b8;183 font-family: 'IBM Plex Mono', monospace;184 font-size: 0.72rem;185 letter-spacing: 0.5px;186}187 188#footer a { color: #2563eb; text-decoration: none; }189#footer a:hover { text-decoration: underline; }190 191#copy-btn {192 background: transparent !important;193 border: 1px solid #e2e8f0 !important;194 color: #64748b !important;195 font-family: 'IBM Plex Mono', monospace !important;196 font-size: 0.78rem !important;197 letter-spacing: 1px !important;198 border-radius: 6px !important;199 width: 100% !important;200 margin-top: 0.4rem !important;201 transition: all 0.2s !important;202}203 204#copy-btn:hover {205 border-color: #2563eb !important;206 color: #2563eb !important;207}208"""209 210with gr.Blocks(title="DeepSeek OCR") as demo:211 212 gr.HTML("""213 <div id="app-header">214 <div class="badge">Powered by DeepSeek · Ollama</div>215 <h1>Deep<span>Seek</span> OCR</h1>216 <p>Extract text from any image using AI — receipts, menus, documents, signs</p>217 </div>218 """)219 220 with gr.Row(equal_height=False):221 222 with gr.Column(scale=1):223 gr.HTML('<div class="panel-label">01 · Input</div>')224 225 image_input = gr.Image(226 type="filepath",227 label="Upload Image",228 sources=["upload", "clipboard"],229 )230 231 prompt_input = gr.Textbox(232 label="Prompt",233 value="Free OCR.",234 placeholder="e.g. Extract all menu items and prices.",235 lines=2,236 )237 238 with gr.Row():239 run_btn = gr.Button("▶ RUN OCR", variant="primary", elem_id="run-btn")240 clear_btn = gr.ClearButton(241 components=[image_input, prompt_input],242 value="✕ CLEAR",243 elem_id="clear-btn"244 )245 246 with gr.Column(scale=1):247 gr.HTML('<div class="panel-label">02 · Extracted Text</div>')248 249 gr.HTML(f"""250 <div id="status-bar">251 <span><span class="dot"></span>API CONNECTED</span>252 <span class="api-url">{API_URL}</span>253 </div>254 """)255 256 output_text = gr.Textbox(257 label="Result",258 lines=14,259 elem_id="output-text",260 placeholder="Extracted text will appear here after running OCR...",261 )262 263 copy_btn = gr.Button("⎘ COPY TEXT", elem_id="copy-btn")264 copy_status = gr.HTML("")265 266 copy_btn.click(267 fn=None,268 inputs=output_text,269 outputs=copy_status,270 js="""271 (text) => {272 if (!text) return '<p id="copy-msg">⚠️ Nothing to copy.</p>';273 navigator.clipboard.writeText(text);274 setTimeout(() => {275 const el = document.getElementById('copy-msg');276 if (el) el.innerText = '';277 }, 2000);278 return '<p id="copy-msg" style="color:#22c55e; font-family:monospace; font-size:0.8rem; margin:4px 0;">✓ Copied to clipboard!</p>';279 }280 """281 )282 283 gr.Examples(284 examples=[285 ["Free OCR."],286 ["Extract all menu items and prices."],287 ["Extract only the grand total."],288 ["List all ingredients on the label."],289 ["Read the text on this sign."],290 ["Extract the table contents."],291 ],292 inputs=[prompt_input],293 label="Example Prompts — click to use",294 )295 296 gr.HTML("""297 <div id="footer">298 DeepSeek OCR · Built with Ollama + FastAPI + Gradio · Hosted on299 <a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a>300 · 301 <a href="https://ek-developers-my-ocr-api.hf.space/docs" target="_blank">API Docs →</a>302 </div>303 """)304 305 run_btn.click(fn=run_ocr, inputs=[image_input, prompt_input], outputs=output_text)306 image_input.upload(fn=run_ocr, inputs=[image_input, prompt_input], outputs=output_text)307 308 309demo.launch(css=css)