from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from starlette.responses import FileResponse from app.config import settings from app.clients.hana_client import hana_client from app.api import models, compare, history STATIC_DIR = Path(__file__).resolve().parent.parent / "static" logging.basicConfig(level=logging.INFO) LOG = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): LOG.info("Starting up — authenticating with HANA...") try: await hana_client.authenticate() await hana_client.get_models() LOG.info("HANA auth complete, persona cache populated.") except Exception as exc: LOG.warning("HANA auth failed at startup (%s) — will retry on first request", exc) yield LOG.info("Shutting down — closing HTTP clients...") await hana_client.close() app = FastAPI(title="LLM Comparison Tool", version="0.1.0", lifespan=lifespan) LOG.info("CORS origins: %s", settings.cors_origin_list) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(models.router, prefix="/api") app.include_router(compare.router, prefix="/api") app.include_router(history.router, prefix="/api") @app.get("/api/health") async def health(): return {"status": "ok"} if STATIC_DIR.is_dir(): app.mount("/static", StaticFiles(directory=str(STATIC_DIR / "static")), name="static-assets") @app.get("/{full_path:path}") async def spa_fallback(full_path: str): file_path = STATIC_DIR / full_path if full_path and file_path.is_file(): return FileResponse(file_path) return FileResponse(STATIC_DIR / "index.html")