feat: Add browser mode fallback for Kaspersky audio blocking

- Add sidecar management to backend (sidecar_manager.py)
- Add sidecar API router for browser mode (/api/sidecar/*)
- Add browser-api.js polyfill for running in Chrome/Edge
- Add "Open in Browser" button when audio access fails
- Update build scripts with new sidecar modules
- Add start-browser.sh for development browser mode

Browser mode allows users to open the app in their system browser
when Electron's audio access is blocked by security software.
The backend manages the sidecar process in browser mode (BROWSER_MODE=true).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-22 16:41:25 +08:00
parent e7a06e2b8f
commit 7d3fc72bd2
12 changed files with 1374 additions and 3 deletions

View File

@@ -1,9 +1,19 @@
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
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
@@ -11,8 +21,25 @@ 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 (cleanup if needed)
# Shutdown - only stop if we started it
if browser_mode:
sidecar_mgr.stop()
app = FastAPI(
@@ -36,9 +63,42 @@ 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")