import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles import uvicorn logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(name)s: %(message)s") from config import settings from auth_middleware import SessionAuthMiddleware from routers import chat, google_chat, nextcloud_talk, files, distill, auth, orchestrator from routers import ui, onboarding @asynccontextmanager async def lifespan(app: FastAPI): import scheduler scheduler.start() yield scheduler.stop() from llm_client import cleanup await cleanup() app = FastAPI(title="Cortex Dispatcher", lifespan=lifespan) app.add_middleware(SessionAuthMiddleware) # API routers app.include_router(chat.router) app.include_router(google_chat.router) app.include_router(nextcloud_talk.router) app.include_router(files.router) app.include_router(distill.router) app.include_router(auth.router) app.include_router(orchestrator.router) # Static files — must be mounted BEFORE ui.router so /static/* is matched first. # ui.router has a wildcard /{username}/{persona} that would otherwise catch /static/style.css etc. app.mount("/static", StaticFiles(directory="static"), name="static") # Onboarding (invite tokens + persona creation — before ui.router) app.include_router(onboarding.router) # UI router (login + /{user}/{persona} — must be last to avoid swallowing API paths) app.include_router(ui.router) @app.get("/health") async def health() -> dict: return {"status": "ok"} if __name__ == "__main__": uvicorn.run( "main:app", host=settings.host, port=settings.port, reload=True, )