File size: 5,303 Bytes
85f900d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
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",
    )