feat: air-gapped deployment guide, no implicit downloads

- Ship tiktoken's cl100k_base inside the package and build the encoding
  from it, so token counting never downloads anything.
- Default EMBEDDINGS_CACHE_DIR to <data home>/models instead of FastEmbed's
  temp dir, and read tokenizer.json and repo metadata from that cache, so
  a model downloads once and survives reboots.
- TTS_PROVIDER=none and STT_PROVIDER=none switch the speech features off:
  the endpoints return 404, audio files fail to ingest with a clear
  message, /api/config reports tts_available/stt_available, and the UI
  hides the Speak and microphone buttons.
- Drop the Google Fonts Roboto import from the web UI.
- prefetch-models fills the cache the app reads; verify-offline checks the
  packaged encoding.
- Docs: new Air-Gapped Deployment guide, settings and cache notes.
This commit is contained in:
Alex committed 2026-09-15 17:54:24 +01:00
1 parent ca69d1ea29
commit 7da46c2bea
38 files changed
+100892 -95

No files matched your search

+19 -2
View File
@@ -309,7 +309,7 @@ class TestTokenizerFile:
def test_cache_hit_makes_no_online_call(self, monkeypatch):
calls = []
def fake_download(repo, filename, local_files_only=False):
def fake_download(repo, filename, local_files_only=False, cache_dir=None):
calls.append(local_files_only)
return "/cache/tokenizer.json"
@@ -322,7 +322,7 @@ class TestTokenizerFile:
def test_cache_miss_falls_back_to_online(self, monkeypatch):
calls = []
def fake_download(repo, filename, local_files_only=False):
def fake_download(repo, filename, local_files_only=False, cache_dir=None):
calls.append(local_files_only)
if local_files_only:
raise FileNotFoundError("not cached")
@@ -333,3 +333,20 @@ class TestTokenizerFile:
monkeypatch.setitem(sys.modules, "huggingface_hub", fake_hub)
assert tokenization._tokenizer_file("org/model") == "/downloaded/tokenizer.json"
assert calls == [True, False]
def test_reads_the_embedding_model_cache(self, monkeypatch):
"""FastEmbed's snapshot already holds tokenizer.json; a second cache downloads it again."""
cache_dirs = []
def fake_download(repo, filename, local_files_only=False, cache_dir=None):
cache_dirs.append(cache_dir)
if local_files_only:
raise FileNotFoundError("not cached")
return "/models/tokenizer.json"
fake_hub = types.ModuleType("huggingface_hub")
fake_hub.hf_hub_download = fake_download
monkeypatch.setitem(sys.modules, "huggingface_hub", fake_hub)
monkeypatch.setattr(tokenization.settings, "EMBEDDINGS_CACHE_DIR", "/models")
tokenization._tokenizer_file("org/model")
assert cache_dirs == ["/models", "/models"]