Source code for audia.ui.app

"""
FastAPI application factory for the audia web UI.
"""

from __future__ import annotations

from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse, Response
from fastapi.staticfiles import StaticFiles

from audia import __version__
from audia.storage import init_db
from audia.ui.routes.convert import router as convert_router
from audia.ui.routes.library import router as library_router
from audia.ui.routes.projects import router as projects_router
from audia.ui.routes.research import router as research_router
from audia.ui.routes.settings import router as settings_router

_STATIC_DIR = Path(__file__).parent / "static"


[docs] def create_app() -> FastAPI: application = FastAPI( title="audia", description="Turn documents and ideas into audio files.", version=__version__, docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json", ) application.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # Initialise DB on startup (default project) @application.on_event("startup") async def startup() -> None: init_db() # default project # Package info endpoint @application.get("/api/info", include_in_schema=True, tags=["info"]) async def api_info() -> JSONResponse: return JSONResponse({"version": __version__, "name": "audia"}) # API routes application.include_router(projects_router, prefix="/api/projects", tags=["projects"]) application.include_router(convert_router, prefix="/api/convert", tags=["convert"]) application.include_router(research_router, prefix="/api/research", tags=["research"]) application.include_router(library_router, prefix="/api/library", tags=["library"]) application.include_router(settings_router, prefix="/api/settings", tags=["settings"]) # Serve Vite-built assets at their natural paths (/assets/*, /favicon.svg …) # Vite always emits JS/CSS under /assets/ – mount that sub-dir directly so # the browser receives the correct Content-Type (not the SPA catch-all html). _assets_dir = _STATIC_DIR / "assets" if _assets_dir.exists(): application.mount("/assets", StaticFiles(directory=str(_assets_dir)), name="assets") # SPA catch-all: serve root-level static files (favicon, icons, …) when # they exist on disk; fall back to index.html for every other path so the # React router can handle client-side navigation. @application.get("/", include_in_schema=False) @application.get("/{full_path:path}", include_in_schema=False) async def serve_spa(full_path: str = "") -> Response: candidate = _STATIC_DIR / full_path if full_path and candidate.exists() and candidate.is_file(): return FileResponse(str(candidate)) # Always serve index.html with no-cache so the browser re-validates it # after a pip upgrade (hashed JS/CSS assets can still be cached forever # because Vite embeds a content hash in their filenames). return FileResponse( str(_STATIC_DIR / "index.html"), headers={ "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0", }, ) return application
app = create_app()