Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -230,82 +230,79 @@ with gr.Blocks(title="AQI Intelligence Engine — HF ZeroGPU Edition", theme=gr.
|
|
| 230 |
"""
|
| 231 |
)
|
| 232 |
|
|
|
|
|
|
|
| 233 |
from starlette.responses import JSONResponse
|
| 234 |
from starlette.requests import Request
|
| 235 |
|
| 236 |
-
#
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
-
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
"""REST API endpoint for historical AQI prediction."""
|
| 247 |
-
try:
|
| 248 |
-
payload = await request.json()
|
| 249 |
-
from services.forecast.service import forecast_aqi
|
| 250 |
-
aqi_hist = payload.get("aqi_history", [])
|
| 251 |
-
lat = payload.get("lat")
|
| 252 |
-
lon = payload.get("lon")
|
| 253 |
-
res = await forecast_aqi(aqi_hist, lat=lat, lon=lon)
|
| 254 |
-
return JSONResponse(res)
|
| 255 |
-
except Exception as e:
|
| 256 |
-
logger.error(f"API Forecast error: {e}")
|
| 257 |
-
return JSONResponse({"error": str(e)}, status_code=400)
|
| 258 |
-
|
| 259 |
-
@gradio_app.post("/api/v1/analyze-image")
|
| 260 |
-
async def api_analyze_image(request: Request):
|
| 261 |
-
"""REST API endpoint for satellite image pollution breakdown."""
|
| 262 |
-
try:
|
| 263 |
-
payload = await request.json()
|
| 264 |
-
import base64
|
| 265 |
-
from services.vision.service import detect_pollution_sources
|
| 266 |
-
base64_str = payload.get("image_base64", "")
|
| 267 |
-
if not base64_str:
|
| 268 |
-
return JSONResponse({"error": "No image_base64 provided"}, status_code=400)
|
| 269 |
-
if "," in base64_str:
|
| 270 |
-
base64_str = base64_str.split(",", 1)[1]
|
| 271 |
-
img_bytes = base64.b64decode(base64_str)
|
| 272 |
-
pil_img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
|
| 273 |
-
res = await detect_pollution_sources(pil_img)
|
| 274 |
-
return JSONResponse(res)
|
| 275 |
-
except Exception as e:
|
| 276 |
-
logger.error(f"API Vision error: {e}")
|
| 277 |
-
return JSONResponse({"error": str(e)}, status_code=400)
|
| 278 |
-
|
| 279 |
-
@gradio_app.get("/api/v1/health")
|
| 280 |
-
async def api_health(request: Request):
|
| 281 |
-
"""Health check endpoint."""
|
| 282 |
-
return JSONResponse({
|
| 283 |
-
"status": "ok",
|
| 284 |
-
"device": get_device(),
|
| 285 |
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
| 286 |
-
"version": "2.0.0",
|
| 287 |
-
})
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
# Register REST API routes on the Gradio underlying FastAPI app
|
| 291 |
-
try:
|
| 292 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 293 |
-
# Explicitly configure CORS on the underlying FastAPI app to allow direct requests from the browser
|
| 294 |
-
demo.app.add_middleware(
|
| 295 |
-
CORSMiddleware,
|
| 296 |
-
allow_origins=["*"],
|
| 297 |
-
allow_credentials=True,
|
| 298 |
-
allow_methods=["*"],
|
| 299 |
-
allow_headers=["*"],
|
| 300 |
-
)
|
| 301 |
-
register_api_routes(demo.app)
|
| 302 |
-
logger.info("CORS middleware and REST API routes registered successfully on Gradio FastAPI app.")
|
| 303 |
-
except Exception as e:
|
| 304 |
-
logger.warning(f"Could not register REST API routes or CORS (will be available via Gradio only): {e}")
|
| 305 |
|
| 306 |
-
#
|
| 307 |
-
app = demo
|
| 308 |
|
| 309 |
if __name__ == "__main__":
|
|
|
|
| 310 |
# Local development only — on HF Spaces, uvicorn runs the app directly
|
| 311 |
-
|
|
|
|
| 230 |
"""
|
| 231 |
)
|
| 232 |
|
| 233 |
+
from fastapi import FastAPI
|
| 234 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 235 |
from starlette.responses import JSONResponse
|
| 236 |
from starlette.requests import Request
|
| 237 |
|
| 238 |
+
# Create the FastAPI app
|
| 239 |
+
app = FastAPI(title="AQI Intelligence Engine API")
|
| 240 |
+
|
| 241 |
+
# Configure CORS directly on the FastAPI app
|
| 242 |
+
app.add_middleware(
|
| 243 |
+
CORSMiddleware,
|
| 244 |
+
allow_origins=["*"],
|
| 245 |
+
allow_credentials=True,
|
| 246 |
+
allow_methods=["*"],
|
| 247 |
+
allow_headers=["*"],
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
# Register REST API routes on the FastAPI app
|
| 251 |
+
@app.post("/api/forecast")
|
| 252 |
+
@app.post("/api/v1/forecast")
|
| 253 |
+
async def api_forecast(request: Request):
|
| 254 |
+
"""REST API endpoint for historical AQI prediction."""
|
| 255 |
+
try:
|
| 256 |
+
payload = await request.json()
|
| 257 |
+
from services.forecast.service import forecast_aqi
|
| 258 |
+
aqi_hist = payload.get("aqi_history", [])
|
| 259 |
+
lat = payload.get("lat")
|
| 260 |
+
lon = payload.get("lon")
|
| 261 |
+
res = await forecast_aqi(aqi_hist, lat=lat, lon=lon)
|
| 262 |
+
return JSONResponse(res)
|
| 263 |
+
except Exception as e:
|
| 264 |
+
logger.error(f"API Forecast error: {e}")
|
| 265 |
+
return JSONResponse({"error": str(e)}, status_code=400)
|
| 266 |
|
| 267 |
+
@app.post("/api/analyze-image")
|
| 268 |
+
@app.post("/api/v1/analyze-image")
|
| 269 |
+
async def api_analyze_image(request: Request):
|
| 270 |
+
"""REST API endpoint for satellite image pollution breakdown."""
|
| 271 |
+
try:
|
| 272 |
+
payload = await request.json()
|
| 273 |
+
import base64
|
| 274 |
+
from services.vision.service import detect_pollution_sources
|
| 275 |
+
base64_str = payload.get("image_base64", "")
|
| 276 |
+
if not base64_str:
|
| 277 |
+
return JSONResponse({"error": "No image_base64 provided"}, status_code=400)
|
| 278 |
+
if "," in base64_str:
|
| 279 |
+
base64_str = base64_str.split(",", 1)[1]
|
| 280 |
+
img_bytes = base64.b64decode(base64_str)
|
| 281 |
+
pil_img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
|
| 282 |
+
res = await detect_pollution_sources(pil_img)
|
| 283 |
+
return JSONResponse(res)
|
| 284 |
+
except Exception as e:
|
| 285 |
+
logger.error(f"API Vision error: {e}")
|
| 286 |
+
return JSONResponse({"error": str(e)}, status_code=400)
|
| 287 |
+
|
| 288 |
+
@app.get("/api/health")
|
| 289 |
+
@app.get("/api/v1/health")
|
| 290 |
+
async def api_health(request: Request):
|
| 291 |
+
"""Health check endpoint."""
|
| 292 |
+
return JSONResponse({
|
| 293 |
+
"status": "ok",
|
| 294 |
+
"device": get_device(),
|
| 295 |
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
| 296 |
+
"version": "2.0.0",
|
| 297 |
+
})
|
| 298 |
|
| 299 |
+
# Initialize the Gradio queue — this creates the underlying FastAPI app object
|
| 300 |
+
demo.queue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
# Mount the Gradio app onto the FastAPI app at root path
|
| 303 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
| 304 |
|
| 305 |
if __name__ == "__main__":
|
| 306 |
+
import uvicorn
|
| 307 |
# Local development only — on HF Spaces, uvicorn runs the app directly
|
| 308 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|