File size: 4,696 Bytes
4b8eb24
29857ea
 
 
 
 
 
40ec306
 
 
 
 
 
 
 
 
 
4b8eb24
 
 
12bad22
4b8eb24
12bad22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29857ea
12bad22
 
 
 
4b8eb24
29857ea
4b8eb24
 
29857ea
 
4b8eb24
29857ea
 
 
 
 
4b8eb24
29857ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b8eb24
 
 
29857ea
4b8eb24
 
29857ea
4b8eb24
40ec306
 
 
29857ea
 
 
4b8eb24
29857ea
4b8eb24
29857ea
 
4b8eb24
29857ea
 
 
4b8eb24
 
40ec306
4b8eb24
 
40ec306
4b8eb24
212bd1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""
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)