Spaces:
Running
Running
| """Runtime configuration for AI Prof. | |
| Both models are reached over OpenAI-compatible HTTP, which is what the common | |
| self-hosted runtimes expose: | |
| - MiniCPM-V (eyes) -> llama.cpp ``llama-server`` with ``--mmproj`` | |
| - Nemotron (brain) -> vLLM / llama.cpp chat endpoint | |
| If a base URL is left blank, that model falls back to MOCK mode so the whole | |
| app still runs end-to-end with no weights — useful for UI work and demos. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from dataclasses import dataclass | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| def _clean(value: str | None) -> str | None: | |
| """Treat blank / placeholder env values as unset.""" | |
| if value is None: | |
| return None | |
| value = value.strip() | |
| return value or None | |
| class ModelConfig: | |
| base_url: str | None | |
| api_key: str | |
| model: str | |
| def is_live(self) -> bool: | |
| return self.base_url is not None | |
| def openai_base_url(self) -> str | None: | |
| """Return an OpenAI-compatible base URL ending in /v1.""" | |
| if self.base_url is None: | |
| return None | |
| base = self.base_url.rstrip("/") | |
| return base if base.endswith("/v1") else f"{base}/v1" | |
| class Config: | |
| vision: ModelConfig | |
| brain: ModelConfig | |
| tts: ModelConfig | |
| stt: ModelConfig | |
| slide_dpi: int | |
| tts_voice: str | |
| deck_cache_dir: str | |
| hf_deck_cache_repo: str | None | |
| hf_token: str | None | |
| hf_deck_cache_write: bool | |
| def fully_mocked(self) -> bool: | |
| return not self.vision.is_live and not self.brain.is_live | |
| def load_config() -> Config: | |
| return Config( | |
| vision=ModelConfig( | |
| base_url=_clean(os.getenv("VISION_BASE_URL")), | |
| api_key=_clean(os.getenv("VISION_API_KEY")) or "sk-no-key-required", | |
| model=_clean(os.getenv("VISION_MODEL")) or "minicpm-v", | |
| ), | |
| brain=ModelConfig( | |
| base_url=_clean(os.getenv("BRAIN_BASE_URL")), | |
| api_key=_clean(os.getenv("BRAIN_API_KEY")) or "sk-no-key-required", | |
| model=_clean(os.getenv("BRAIN_MODEL")) or "nemotron-3-nano", | |
| ), | |
| tts=ModelConfig( | |
| base_url=_clean(os.getenv("TTS_BASE_URL")), | |
| api_key=_clean(os.getenv("TTS_API_KEY")) or "sk-no-key-required", | |
| model=_clean(os.getenv("TTS_MODEL")) or "tts-1", | |
| ), | |
| stt=ModelConfig( | |
| base_url=_clean(os.getenv("STT_BASE_URL")), | |
| api_key=_clean(os.getenv("STT_API_KEY")) or "sk-no-key-required", | |
| model=_clean(os.getenv("STT_MODEL")) or "whisper-1", | |
| ), | |
| slide_dpi=int(_clean(os.getenv("SLIDE_DPI")) or "150"), | |
| tts_voice=_clean(os.getenv("TTS_VOICE")) or "default", | |
| deck_cache_dir=_clean(os.getenv("DECK_CACHE_DIR")) or ".cache/ai-prof/decks", | |
| hf_deck_cache_repo=_clean(os.getenv("HF_DECK_CACHE_REPO")), | |
| hf_token=_clean(os.getenv("HF_TOKEN")), | |
| hf_deck_cache_write=( | |
| (_clean(os.getenv("HF_DECK_CACHE_WRITE")) or "").lower() | |
| in {"1", "true", "yes", "on"} | |
| ), | |
| ) | |
| CONFIG = load_config() | |