diff --git a/voice-to-mermaid/README.md b/voice-to-mermaid/README.md index 6c2b94d..e693236 100644 --- a/voice-to-mermaid/README.md +++ b/voice-to-mermaid/README.md @@ -66,18 +66,31 @@ WebSocket /ws/mermaid --- -## Environment Variables +## Configuration -### Backend (`backend/.env`) +### `backend/config.yaml` — non-secret settings + +Edit this file to change model names, Whisper settings, prompt path, and model filter. +It is volume-mounted in Docker, so changes take effect on restart without rebuilding. + +| Key | Default | Description | +|---|---|---| +| `ollama.default_model` | `qwen3:8b` | Ollama model for diagram generation | +| `ollama.model_filter` | _(list)_ | Model name prefixes shown in the UI picker | +| `openai.model` | `gpt-4o-mini` | OpenAI-compatible model name | +| `whisper.enabled` | `false` | Set `true` after installing `requirements.stt.txt` | +| `whisper.model` | `medium.en` | Whisper model size | +| `whisper.device` | `auto` | `auto` / `cpu` / `cuda` | +| `paths.prompt` | `prompts/mermaid.txt` | Path to LLM prompt template | +| `paths.log_dir` | `data/logs` | Directory for generation logs | + +### Backend (`backend/.env`) — secrets only | Variable | Default | Description | |---|---|---| | `OLLAMA_URL` | `http://localhost:11434` | Ollama server URL | -| `OLLAMA_MODEL` | `qwen2.5:14b` | Model for diagram generation | | `OPENAI_BASE_URL` | _(empty)_ | OpenAI-compatible API base (optional) | | `OPENAI_API_KEY` | _(empty)_ | API key for OpenAI-compatible API | -| `WHISPER_MODEL` | `medium` | `tiny` / `base` / `small` / `medium` / `large-v3` | -| `WHISPER_DEVICE` | `auto` | `auto` / `cpu` / `cuda` | | `API_KEY` | _(empty)_ | If set, require `X-Api-Key` header on all requests | | `PORT` | `7625` | Server port | diff --git a/voice-to-mermaid/backend/.env.example b/voice-to-mermaid/backend/.env.example index b30b093..1ac6706 100644 --- a/voice-to-mermaid/backend/.env.example +++ b/voice-to-mermaid/backend/.env.example @@ -1,31 +1,19 @@ # voice-to-mermaid backend — copy to .env and fill in +# +# Non-secret settings (model names, Whisper config, prompt path, model filter) +# are in config.yaml — edit that file instead of adding them here. -# ── LLM backend (pick one) ──────────────────────────────────────────────── +# ── LLM backend URLs (secrets — keep out of config.yaml) ───────────────── # Option A: Ollama (local, recommended for self-hosting) OLLAMA_URL=http://localhost:11434 -OLLAMA_MODEL=qwen2.5:14b -# Comma-separated model name prefixes shown in the picker (empty = show all) -OLLAMA_MODEL_FILTER=qwen2.5:,qwen3.5: # Option B: OpenAI-compatible API (vLLM, OpenAI, Together, etc.) # OPENAI_BASE_URL=https://api.openai.com/v1 # OPENAI_API_KEY=sk-... -# OPENAI_MODEL=gpt-4o-mini - -# ── Whisper (transcription) ─────────────────────────────────────────────── -# Model size: tiny | base | small | medium | large-v3 -# Device: auto | cpu | cuda -WHISPER_MODEL=medium -WHISPER_DEVICE=auto # ── Server ──────────────────────────────────────────────────────────────── PORT=7625 # Shared secret — Next.js proxy sends this as X-Api-Key header. # Leave blank to disable auth (dev only). API_KEY= - -# ── Prompts ─────────────────────────────────────────────────────────────── -# Path to mermaid prompt template (relative to this file). -# Edit prompts/mermaid.txt to customise diagram generation behaviour. -MERMAID_PROMPT_PATH=prompts/mermaid.txt diff --git a/voice-to-mermaid/backend/config.yaml b/voice-to-mermaid/backend/config.yaml index 5f40488..9acf4ae 100644 --- a/voice-to-mermaid/backend/config.yaml +++ b/voice-to-mermaid/backend/config.yaml @@ -18,7 +18,11 @@ openai: model: "gpt-4o-mini" whisper: - enabled: false # set true only when requirements.stt.txt deps are installed + # true = attempt to load WhisperLiveKit at startup. + # If import fails (deps not installed), _stt_available stays False and + # /v1/config returns stt_enabled: false — no crash. + # Install requirements.stt.txt (or add it to Dockerfile) before setting true. + enabled: true model: "medium.en" language: "en" device: "auto" # "auto", "cuda", "cpu" — maps to WhisperLiveKitConfig backend diff --git a/voice-to-mermaid/backend/main.py b/voice-to-mermaid/backend/main.py index 8442ff2..dedfa34 100644 --- a/voice-to-mermaid/backend/main.py +++ b/voice-to-mermaid/backend/main.py @@ -71,21 +71,41 @@ def _load_config() -> dict: _cfg = _load_config() + + +def _config_section(name: str) -> dict: + section = _cfg.get(name, {}) + if isinstance(section, dict): + return section + log.warning("config.%s is not a mapping — using defaults for that section", name) + return {} + + +_ollama_cfg = _config_section("ollama") +_openai_cfg = _config_section("openai") +_whisper_cfg = _config_section("whisper") +_paths_cfg = _config_section("paths") + + # Secrets — env only OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434").rstrip("/") OPENAI_URL = os.environ.get("OPENAI_BASE_URL", "").rstrip("/") OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "") # Settings — config.yaml only; edit config.yaml to change these -OLLAMA_MODEL = _cfg.get("ollama", {}).get("default_model", "qwen3:8b") -OPENAI_MODEL = _cfg.get("openai", {}).get("model", "gpt-4o-mini") -WHISPER_ENABLED = _cfg.get("whisper", {}).get("enabled", False) -WHISPER_MODEL = _cfg.get("whisper", {}).get("model", "medium.en") -WHISPER_LANG = _cfg.get("whisper", {}).get("language", "en") -WHISPER_DEVICE = _cfg.get("whisper", {}).get("device", "auto") -PROMPT_PATH = Path(_cfg.get("paths", {}).get("prompt", "prompts/mermaid.txt")) -LOG_DIR = Path(_cfg.get("paths", {}).get("log_dir", "data/logs")) -_MODEL_FILTER: list[str] = [str(p) for p in (_cfg.get("ollama", {}).get("model_filter") or [])] +OLLAMA_MODEL = _ollama_cfg.get("default_model", "qwen3:8b") +OPENAI_MODEL = _openai_cfg.get("model", "gpt-4o-mini") +WHISPER_ENABLED = _whisper_cfg.get("enabled", False) +WHISPER_MODEL = _whisper_cfg.get("model", "medium.en") +WHISPER_LANG = _whisper_cfg.get("language", "en") +WHISPER_DEVICE = _whisper_cfg.get("device", "auto") +PROMPT_PATH = Path(_paths_cfg.get("prompt", "prompts/mermaid.txt")) +LOG_DIR = Path(_paths_cfg.get("log_dir", "data/logs")) +_MODEL_FILTER: list[str] = [str(p) for p in (_ollama_cfg.get("model_filter") or [])] + +# Set True only after WhisperLiveKit loads successfully at startup. +# WHISPER_ENABLED=True but import fails → _stt_available stays False. +_stt_available: bool = False _ollama_models_cache: list[dict] | None = None @@ -253,7 +273,7 @@ async def _to_mermaid( @asynccontextmanager async def _lifespan(app: FastAPI): - global _transcription_engine + global _transcription_engine, _stt_available _init_log_file() if WHISPER_ENABLED: try: @@ -279,6 +299,7 @@ async def _lifespan(app: FastAPI): pcm_input=False, ) ) + _stt_available = True log.info("WhisperLiveKit ready") else: log.info("STT disabled (whisper.enabled=false) — audio transcription unavailable") @@ -319,8 +340,8 @@ async def config(): "ollama_model": OLLAMA_MODEL, "ollama_models": _ollama_models_cache, "openai_model": OPENAI_MODEL, - "stt_enabled": WHISPER_ENABLED, - "whisper_model": WHISPER_MODEL if WHISPER_ENABLED else None, + "stt_enabled": _stt_available, + "whisper_model": WHISPER_MODEL if _stt_available else None, }