File size: 3,602 Bytes
11ecc5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""Space entrypoint: fetch the index, then hand off to src/serve.py.

WHY THE INDEX IS NOT IN THIS REPO
---------------------------------
It lives in a separate HF Dataset. Pushing a rebuilt index into the Space repo
would trigger a full Docker rebuild (~10 min, and it re-bakes 2.3 GB of models)
where a restart (~40 s) is all that is actually needed. The split also keeps the
Space's build context small enough to push over a hotel wifi.

Configuration is entirely environment variables, because that is what the Space
settings UI gives you:

  VOICERAG_INDEX_REPO   required   HF dataset holding index/ -- "you/voicerag-index"
  ELEVENLABS_API_KEY    required   requirement 1; set it as a SECRET, not a variable
  VOICERAG_CORS         optional   origin of the Vercel page
  VOICERAG_LANGS        optional   subset to serve; default = everything in the manifest
  VOICERAG_PREWARM      optional   langs to warm TTS for, or "all"
  HF_TOKEN              optional   only if the dataset is private
"""
from __future__ import annotations

import os
import pathlib
import subprocess
import sys
import time

ROOT = pathlib.Path(os.environ.get("VOICERAG_ROOT", "/home/user/app/data"))
INDEX_REPO = os.environ.get("VOICERAG_INDEX_REPO", "").strip()


def fetch_index() -> None:
    manifest = ROOT / "index" / "manifest.json"
    if manifest.exists():
        print(f"==> index already at {ROOT}, skipping download")
        return
    if not INDEX_REPO:
        sys.exit(
            "!! VOICERAG_INDEX_REPO is not set.\n"
            "   Build the index with notebooks/build_index.ipynb, push it to an\n"
            "   HF dataset, then set VOICERAG_INDEX_REPO in the Space settings."
        )
    from huggingface_hub import snapshot_download

    t0 = time.time()
    print(f"==> pulling index from {INDEX_REPO}")
    ROOT.mkdir(parents=True, exist_ok=True)
    snapshot_download(repo_id=INDEX_REPO, repo_type="dataset",
                      local_dir=str(ROOT),
                      token=os.environ.get("HF_TOKEN") or None)
    if not manifest.exists():
        sys.exit(f"!! {INDEX_REPO} has no index/manifest.json -- wrong repo?")
    mb = sum(f.stat().st_size for f in (ROOT / "index").rglob("*")) / 2**20
    print(f"==> {mb:.0f} MB in {time.time() - t0:.1f}s")


def build_cmd(env: dict, python: str = sys.executable) -> list[str]:
    """serve.py's argv, assembled from Space environment variables.

    An unset variable must not become an empty flag: `--cors ""` reaches
    argparse as a real value and CORSMiddleware would be installed with an
    empty origin list, which blocks the Vercel page as surely as no middleware
    at all -- but silently, and only in the browser.
    """
    cmd = [python, "src/serve.py",
           "--host", "0.0.0.0",
           "--port", env.get("PORT", "7860")]
    for name, flag in (("VOICERAG_LANGS", "--langs"),
                       ("VOICERAG_CORS", "--cors"),
                       ("VOICERAG_PREWARM", "--prewarm-tts")):
        if env.get(name, "").strip():
            cmd += [flag, env[name].strip()]
    return cmd


def main() -> int:
    fetch_index()

    if not os.environ.get("ELEVENLABS_API_KEY"):
        # Not fatal: the text path is the whole of requirements 2-6 and stays
        # up. /health reports asr:false so the failure is visible, not silent.
        print("!! ELEVENLABS_API_KEY unset -- voice input will return an error")

    cmd = build_cmd(dict(os.environ))
    print("==>", " ".join(cmd), flush=True)
    return subprocess.call(cmd)


if __name__ == "__main__":
    raise SystemExit(main())