"""
Configuration dataclass for LocalVoiceMode.
"""
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
@dataclass
class Config:
"""Application configuration with environment variable loading."""
# Paths - can be overridden, defaults computed from base_dir
base_dir: Optional[Path] = None
skills_dir: Optional[Path] = None
voice_refs_dir: Optional[Path] = None
# LLM settings - supports any OpenAI-compatible API
api_url: str = "http://localhost:1234/v1" # OpenAI-compatible endpoint
api_key: Optional[str] = None # API key (optional for local models)
model: Optional[str] = None # Model name (optional, uses server default if not set)
# Legacy settings (kept for backward compatibility)
opencode_url: str = "http://localhost:4096"
lm_studio_url: str = "http://localhost:1234/v1"
use_lm_studio: bool = True # Deprecated: use api_url instead
# Voice settings
tts_voice: str = "alba"
asr_backend: str = "parakeet" # "parakeet" (default, GPU), "whisper" (CPU fallback)
device: str = "cuda" # "cuda" for GPU, "cpu" for CPU
sample_rate: int = 16000
smart_turn_threshold: float = 0.5 # Smart Turn completion probability threshold
# Interaction mode
mode: str = "vad" # "vad" or "ptt"
headless: bool = False # Run without UI for MCP integration
# Active skill
active_skill: Optional[str] = None
def __post_init__(self):
"""Load configuration from environment variables."""
# Compute default paths if not provided
if self.base_dir is None:
self.base_dir = Path(__file__).parent.parent.parent.parent.absolute()
if self.skills_dir is None:
self.skills_dir = self.base_dir / "skills"
if self.voice_refs_dir is None:
self.voice_refs_dir = self.base_dir / "voice_references"
# Load from environment
self.api_url = os.environ.get("VOICE_API_URL", self.api_url)
self.api_key = os.environ.get("VOICE_API_KEY", self.api_key)
self.model = os.environ.get("VOICE_MODEL", self.model)
# Legacy env vars
self.opencode_url = os.environ.get("OPENCODE_URL", self.opencode_url)
self.lm_studio_url = os.environ.get("LM_STUDIO_URL", self.lm_studio_url)
self.use_lm_studio = os.environ.get("USE_LM_STUDIO", "true").lower() == "true"
self.tts_voice = os.environ.get("VOICE_TTS_VOICE", self.tts_voice)
self.asr_backend = os.environ.get("VOICE_ASR_BACKEND", self.asr_backend)
self.device = os.environ.get("VOICE_DEVICE", self.device)
# Smart Turn settings
threshold = os.environ.get("VOICE_SMART_TURN_THRESHOLD")
if threshold:
self.smart_turn_threshold = float(threshold)
@classmethod
def from_base_dir(cls, base_dir: Path) -> "Config":
"""Create config with explicit base directory."""
return cls(
base_dir=base_dir,
skills_dir=base_dir / "skills",
voice_refs_dir=base_dir / "voice_references",
)