- Preserve query parameters (e.g., ?id=123) when opening in browser - Add packaged mode detection for CLIENT_DIR path resolution - Include client files in extraResources for backend to serve - Add debug logging for client directory detection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
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
|
|
# In development: backend/../client/src
|
|
# In packaged mode: backend/backend/_internal/../../client (relative to backend executable)
|
|
BACKEND_DIR = Path(__file__).parent.parent
|
|
PROJECT_DIR = BACKEND_DIR.parent
|
|
CLIENT_DIR = PROJECT_DIR / "client" / "src"
|
|
|
|
# Check for packaged mode (PyInstaller sets _MEIPASS)
|
|
import sys
|
|
if getattr(sys, 'frozen', False):
|
|
# Packaged mode: look for client folder relative to executable
|
|
# Backend runs from resources/backend/, client files at resources/backend/client/
|
|
EXEC_DIR = Path(sys.executable).parent.parent # up from backend/backend.exe
|
|
CLIENT_DIR = EXEC_DIR / "client"
|
|
print(f"[Backend] Packaged mode: CLIENT_DIR={CLIENT_DIR}")
|
|
else:
|
|
print(f"[Backend] Development mode: CLIENT_DIR={CLIENT_DIR}")
|
|
|
|
|
|
@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
|
|
print(f"[Backend] CLIENT_DIR exists: {CLIENT_DIR.exists()}")
|
|
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")
|