munals's picture
Upload 33 files
214f910 verified
import logging
import os
from pathlib import Path
from typing import Any, Dict
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from pydantic import ValidationError
from src.models import ItineraryPlan, EvaluationResult, UserRequest, PlanResponse, SystemResponse
from src.graph import build_qiddiya_graph, QiddiyaState
from src.db import init_db, save_plan_result, save_evaluation_result
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
)
logger = logging.getLogger("qiddiya.app")
app = FastAPI(title="Qiddiya Smart Guide", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
_base = Path(__file__).parent
app.mount("/static", StaticFiles(directory=str(_base / "static")), name="static")
graph = build_qiddiya_graph()
graph_app = graph.compile()
@app.on_event("startup")
async def on_startup() -> None:
base = Path(__file__).parent
data_dir = base / "data"
data_dir.mkdir(exist_ok=True)
init_db()
logger.info("Qiddiya Smart Guide backend started.")
@app.get("/health")
async def health() -> Dict[str, str]:
return {"status": "ok"}
@app.post("/api/v1/plan", response_model=PlanResponse)
async def plan_itinerary(request: UserRequest) -> Any:
try:
initial_state: QiddiyaState = {
"user_request": request.model_dump(),
"logs": [],
"wait_time_forecast": None,
"raw_plan": None,
"final_plan": None,
"critique": None,
"reflection_round": 0,
}
result_state = graph_app.invoke(initial_state)
plan = ItineraryPlan.model_validate(result_state["final_plan"])
save_plan_result(request, plan)
system = SystemResponse(
logs=result_state.get("logs") or [],
reflection_round=int(result_state.get("reflection_round") or 0),
critique=(result_state.get("critique") or ""),
wait_time_forecast=result_state.get("wait_time_forecast"),
)
return PlanResponse(plan=plan, system=system)
except ValidationError as ve:
logger.exception("Validation error during planning")
return JSONResponse(
status_code=422,
content={"detail": ve.errors()},
)
except Exception:
logger.exception("Unexpected error during planning")
return JSONResponse(
status_code=500,
content={"detail": "Internal server error during planning"},
)
@app.post("/api/v1/evaluate", response_model=EvaluationResult)
async def evaluate_plan(request: UserRequest) -> Any:
try:
initial_state: QiddiyaState = {
"user_request": request.model_dump(),
"logs": [],
"wait_time_forecast": None,
"raw_plan": None,
"final_plan": None,
"critique": None,
"reflection_round": 0,
}
result_state = graph_app.invoke(initial_state)
plan = ItineraryPlan.model_validate(result_state["final_plan"])
from src.evaluation import evaluate_itinerary
evaluation = evaluate_itinerary(request=request, plan=plan)
save_evaluation_result(request, plan, evaluation)
return evaluation
except ValidationError as ve:
logger.exception("Validation error during evaluation")
return JSONResponse(
status_code=422,
content={"detail": ve.errors()},
)
except Exception:
logger.exception("Unexpected error during evaluation")
return JSONResponse(
status_code=500,
content={"detail": "Internal server error during evaluation"},
)
@app.get("/gradio", include_in_schema=False)
@app.get("/gradio/", include_in_schema=False)
async def redirect_gradio() -> RedirectResponse:
"""Redirect old /gradio bookmarks to the main UI."""
return RedirectResponse(url="/", status_code=302)
@app.get("/", response_class=HTMLResponse)
async def root() -> HTMLResponse:
"""Serve a single-page, modern HTML UI."""
html = (_base / "templates" / "index.html").read_text(encoding="utf-8")
return HTMLResponse(content=html)
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", "7860"))
uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)