""" DocuLens — Hugging Face Spaces entry point (Gradio SDK wrapper). HF free-tier Spaces only support Gradio/Streamlit SDK, not Docker. This file creates a minimal Gradio UI and mounts all FastAPI API routes onto the same server, so the REST API works exactly as before. How it works: 1. Build a FastAPI app with all DocuLens API routes. 2. Create a minimal Gradio Blocks UI. 3. Mount Gradio onto the FastAPI app via gr.mount_gradio_app(). 4. Expose the combined ASGI app as `app` — HF Spaces detects and serves it automatically. We do NOT call uvicorn.run() or demo.launch() ourselves — HF handles that. The `demo` variable is renamed to `_gradio_ui` so HF's runner doesn't auto-launch it separately (which would cause a port conflict). """ import os import logging # --------------------------------------------------------------------------- # Structured logging — configure once, before anything else logs # --------------------------------------------------------------------------- LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO").upper() LOG_FORMAT = os.environ.get( "LOG_FORMAT", "%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logging.basicConfig( level=getattr(logging, LOG_LEVEL, logging.INFO), format=LOG_FORMAT, datefmt="%Y-%m-%d %H:%M:%S", ) # Quieten noisy third-party loggers for _noisy in ("httpx", "httpcore", "urllib3", "uvicorn.access"): logging.getLogger(_noisy).setLevel(logging.WARNING) logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # 1. Build the FastAPI app with all API routes (same as main.py) # --------------------------------------------------------------------------- from fastapi import FastAPI from fastapi.responses import JSONResponse fastapi_app = FastAPI( title="DocuLens", description="AI-Powered Invoice & Document Extraction API", version="1.2.0", ) # CORS from api import add_cors_middleware add_cors_middleware(fastapi_app) # Mount all API routers from api import api_router from api_runs import runs_router from api_prompts import prompts_router from api_chat import chat_router from api_webhooks import webhooks_router from api_export import export_router from api_batch import batch_router from api_tiers import tiers_router from api_golden import golden_router fastapi_app.include_router(api_router) fastapi_app.include_router(runs_router) fastapi_app.include_router(prompts_router) fastapi_app.include_router(chat_router) fastapi_app.include_router(webhooks_router) fastapi_app.include_router(export_router) fastapi_app.include_router(batch_router) fastapi_app.include_router(tiers_router) fastapi_app.include_router(golden_router) @fastapi_app.get("/health") async def health(): return JSONResponse( { "service": "DocuLens", "version": "1.2.0", "status": "running", "docs": "/docs", } ) # --------------------------------------------------------------------------- # 2. Create a minimal Gradio UI (required by HF Spaces free-tier SDK) # --------------------------------------------------------------------------- import gradio as gr # Use _gradio_ui (not `demo`) to prevent HF from auto-launching it # separately — we mount it onto our FastAPI app below instead. with gr.Blocks(title="DocuLens", theme=gr.themes.Soft()) as _gradio_ui: gr.Markdown( """ # DocuLens API AI-Powered Invoice & Document Extraction. **API Documentation:** Navigate to [`/docs`](/docs) for the interactive Swagger UI, or [`/redoc`](/redoc) for ReDoc. **Health check:** [`/health`](/health) """ ) # --------------------------------------------------------------------------- # 3. Mount Gradio onto the FastAPI app so both coexist on the same server. # --------------------------------------------------------------------------- app = gr.mount_gradio_app(fastapi_app, _gradio_ui, path="/") # --------------------------------------------------------------------------- # 4. Start the server — no __main__ guard. # # HF's `gradio` CLI may import rather than exec the file, so # `if __name__ == "__main__"` can be skipped. We start uvicorn # unconditionally on port 7860 (HF's health-check port). # The Gradio Blocks is named `_gradio_ui` (not `demo`) to prevent # HF from auto-launching a second server that would conflict. # --------------------------------------------------------------------------- import uvicorn port = int(os.environ.get("PORT", "7860")) logger.info("Starting DocuLens on port %d", port) uvicorn.run(app, host="0.0.0.0", port=port)