""" VoiceVault — FastAPI Server ============================ Custom web frontend entry point. Serves the HTML/CSS/JS SPA and REST API. Run: python server.py URL: http://localhost:7860 """ from __future__ import annotations # ── Environment overrides (must be first, before any ML imports) ──────── import os # RTX 5070 (sm_120) incompatible with packaged PyTorch — force CPU os.environ["CUDA_VISIBLE_DEVICES"] = "-1" # Suppress Windows symlink warning from huggingface_hub cache os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" # ──────────────────────────────────────────────────────────────────────── import logging import sys from contextlib import asynccontextmanager from pathlib import Path import uvicorn from fastapi import FastAPI from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from config import cfg from voicevault import __version__ # ------------------------------------------------------------------ # # Logging # # ------------------------------------------------------------------ # logging.basicConfig( level=logging.DEBUG if cfg.debug else logging.INFO, format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", handlers=[logging.StreamHandler(sys.stdout)], ) logger = logging.getLogger(__name__) # Suppress noisy third-party loggers that spam the console for _noisy in ("httpx", "httpcore", "huggingface_hub", "sentence_transformers", "transformers", "filelock", "urllib3"): logging.getLogger(_noisy).setLevel(logging.WARNING) _CENTRAL_DB_PATH = cfg.data_dir / "voicevault.db" _STATIC_DIR = Path(__file__).parent / "static" # ------------------------------------------------------------------ # # Startup # # ------------------------------------------------------------------ # def _startup(): """Initialize all pipeline components.""" cfg.ensure_directories() logger.info("=" * 60) logger.info("VoiceVault v%s — FastAPI Server Starting", __version__) logger.info("Data directory : %s", cfg.data_dir.resolve()) logger.info("Central DB : %s", _CENTRAL_DB_PATH) logger.info("Groq key : %s", "✓ configured" if cfg.has_groq_key() else "✗ not set") logger.info("Gemini key : %s", "✓ configured" if cfg.has_gemini_key() else "✗ not set") logger.info("Static dir : %s", _STATIC_DIR.resolve()) logger.info("=" * 60) if not cfg.has_any_llm_key(): logger.warning("No LLM API key found — set GROQ_API_KEY or GEMINI_API_KEY in .env") from voicevault.kb.kb_manager import KBManager kb_manager = KBManager(db_path=_CENTRAL_DB_PATH) logger.info("KBManager ready — %d KB(s) found", len(kb_manager.list_kbs())) # Prefer Groq Whisper API (instant, ~200ms) over local model (slow CPU) if cfg.has_groq_key(): from voicevault.asr.groq_transcriber import GroqTranscriber transcriber = GroqTranscriber() logger.info("Transcriber: Groq Whisper API (whisper-large-v3-turbo) — fast cloud mode") else: from voicevault.asr.whisper_transcriber import WhisperTranscriber transcriber = WhisperTranscriber() logger.info("Transcriber: local Whisper (CPU) — set GROQ_API_KEY for instant transcription") from voicevault.generation.answer_chain import AnswerChain answer_chain = AnswerChain() logger.info("AnswerChain ready (Groq → Gemini fallback)") return kb_manager, transcriber, answer_chain # ------------------------------------------------------------------ # # App Factory # # ------------------------------------------------------------------ # @asynccontextmanager async def _lifespan(app: FastAPI): """Initialize pipeline singletons before the server starts accepting requests.""" kb_manager, transcriber, answer_chain = _startup() from api.routes import init_routes init_routes(kb_manager, transcriber, answer_chain, _CENTRAL_DB_PATH) logger.info("All routes initialized. App is ready.") yield # (shutdown hooks go here if needed) def create_app() -> FastAPI: app = FastAPI( title="VoiceVault", version=__version__, docs_url=None, redoc_url=None, lifespan=_lifespan, ) # Serve static assets app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static") from api.routes import router app.include_router(router) @app.get("/") async def index(): return FileResponse(_STATIC_DIR / "index.html") return app # ------------------------------------------------------------------ # # Entry Point # # ------------------------------------------------------------------ # app = create_app() if __name__ == "__main__": uvicorn.run( "server:app", host=cfg.host, port=cfg.port, reload=False, log_level="info", )