""" Echo FastAPI backend. Run with: uvicorn main:app --reload --host 0.0.0.0 --port 8000 """ from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from typing import AsyncGenerator import time from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from app.api.routes import router from app.services.recommender import recommender logging.basicConfig(level=logging.INFO, format="%(levelname)s | %(name)s | %(message)s") logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: logger.info("Starting Echo backend…") recommender.load() # Load CLAP + FAISS index once at startup yield logger.info("Shutting down Echo backend.") app = FastAPI( title="Echo API", version="1.0.0", lifespan=lifespan, ) @app.middleware("http") async def add_process_time_header(request: Request, call_next): start_time = time.perf_counter() response = await call_next(request) process_time = time.perf_counter() - start_time response.headers["X-Process-Time"] = f"{process_time:.6f}" return response app.add_middleware( CORSMiddleware, allow_origins=["*"], # Tighten in production allow_methods=["*"], allow_headers=["*"], ) app.include_router(router) # Mount web client static assets and root page STATIC_DIR = Path(__file__).parent / "static" if STATIC_DIR.exists(): app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") @app.get("/", include_in_schema=False) async def serve_index() -> FileResponse: return FileResponse(STATIC_DIR / "index.html")