import os from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from contextlib import asynccontextmanager from .database import init_db_pool, init_tables from .routers import auth, meetings, ai, export, sidecar from .sidecar_manager import get_sidecar_manager # Determine client directory path BACKEND_DIR = Path(__file__).parent.parent PROJECT_DIR = BACKEND_DIR.parent CLIENT_DIR = PROJECT_DIR / "client" / "src" @asynccontextmanager async def lifespan(app: FastAPI): # Startup init_db_pool() init_tables() # Only start sidecar in browser mode (not when Electron manages it) # Set BROWSER_MODE=true in start-browser.sh to enable browser_mode = os.environ.get("BROWSER_MODE", "").lower() == "true" sidecar_mgr = get_sidecar_manager() if browser_mode and sidecar_mgr.is_available(): print("[Backend] Browser mode: Starting sidecar...") await sidecar_mgr.start() elif browser_mode: print("[Backend] Browser mode: Sidecar not available (transcription disabled)") else: print("[Backend] Electron mode: Sidecar managed by Electron") yield # Shutdown - only stop if we started it if browser_mode: sidecar_mgr.stop() app = FastAPI( title="Meeting Assistant API", description="Enterprise meeting knowledge management API", version="1.0.0", lifespan=lifespan, ) # CORS configuration for Electron client app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Include routers app.include_router(auth.router, prefix="/api", tags=["Authentication"]) app.include_router(meetings.router, prefix="/api", tags=["Meetings"]) app.include_router(ai.router, prefix="/api", tags=["AI"]) app.include_router(export.router, prefix="/api", tags=["Export"]) app.include_router(sidecar.router, prefix="/api", tags=["Sidecar"]) @app.get("/api/health") async def health_check(): """Health check endpoint.""" return {"status": "healthy", "service": "meeting-assistant"} # ======================================== # Browser Mode: Serve static files # ======================================== # Check if client directory exists for browser mode if CLIENT_DIR.exists(): # Serve static assets (CSS, JS, etc.) app.mount("/styles", StaticFiles(directory=CLIENT_DIR / "styles"), name="styles") app.mount("/services", StaticFiles(directory=CLIENT_DIR / "services"), name="services") app.mount("/config", StaticFiles(directory=CLIENT_DIR / "config"), name="config") @app.get("/") async def serve_login(): """Serve login page.""" return FileResponse(CLIENT_DIR / "pages" / "login.html") @app.get("/login") async def serve_login_page(): """Serve login page.""" return FileResponse(CLIENT_DIR / "pages" / "login.html") @app.get("/meetings") async def serve_meetings_page(): """Serve meetings list page.""" return FileResponse(CLIENT_DIR / "pages" / "meetings.html") @app.get("/meeting-detail") async def serve_meeting_detail_page(): """Serve meeting detail page.""" return FileResponse(CLIENT_DIR / "pages" / "meeting-detail.html")