"""Application-wide configuration settings.""" import os from dataclasses import dataclass, field from typing import Optional from pathlib import Path from dotenv import load_dotenv load_dotenv() @dataclass class Settings: """Application-wide configuration settings.""" # ============================================ # LLM Provider Settings # ============================================ llm_provider: str = os.getenv("LLM_PROVIDER", "auto") # Hugging Face settings hf_token: str = os.getenv("HF_TOKEN", "") hf_chat_model: str = os.getenv("HF_CHAT_MODEL", "Qwen/Qwen2.5-7B-Instruct") hf_temperature: float = float(os.getenv("HF_TEMPERATURE", "0.001")) hf_max_new_tokens: int = int(os.getenv("HF_MAX_NEW_TOKENS", "512")) # Model settings model_name: str = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct") # ============================================ # Audio Provider Settings # ============================================ audio_provider: str = os.getenv("AUDIO_PROVIDER", "auto") tts_model: str = os.getenv("TTS_MODEL", "hexgrad/Kokoro-82M") stt_model: str = os.getenv("STT_MODEL", "openai/whisper-large-v3") # ============================================ # VAD (Voice Activity Detection) Settings # ============================================ vad_enabled: bool = os.getenv("VAD_ENABLED", "true").lower() == "true" vad_sample_rate: int = int(os.getenv("VAD_SAMPLE_RATE", "16000")) vad_frame_duration_ms: int = int(os.getenv("VAD_FRAME_DURATION_MS", "30")) vad_aggressiveness: int = int(os.getenv("VAD_AGGRESSIVENESS", "2")) vad_speech_threshold: float = float(os.getenv("VAD_SPEECH_THRESHOLD", "0.5")) vad_silence_threshold: float = float(os.getenv("VAD_SILENCE_THRESHOLD", "0.3")) vad_min_speech_ms: int = int(os.getenv("VAD_MIN_SPEECH_MS", "300")) vad_max_speech_s: float = float(os.getenv("VAD_MAX_SPEECH_S", "30.0")) vad_post_speech_silence_ms: int = int(os.getenv("VAD_POST_SPEECH_SILENCE_MS", "800")) # ============================================ # Screen/Vision Settings # ============================================ screen_capture_interval: float = float(os.getenv("SCREEN_CAPTURE_INTERVAL", "1.0")) screen_compression_quality: int = int(os.getenv("SCREEN_COMPRESSION_QUALITY", "50")) max_width: int = int(os.getenv("SCREEN_MAX_WIDTH", "3440")) max_height: int = int(os.getenv("SCREEN_MAX_HEIGHT", "1440")) # Vision model (Nebius) NEBIUS_MODEL: str = os.getenv("NEBIUS_MODEL", "google/gemma-3-27b-it-fast") NEBIUS_API_KEY: str = os.getenv("NEBIUS_API_KEY", "") NEBIUS_BASE_URL: str = os.getenv("NEBIUS_BASE_URL", "https://api.studio.nebius.com/v1/") # Auto-enable vision when screen context is needed vision_auto_enabled: bool = os.getenv("VISION_AUTO_ENABLED", "true").lower() == "true" vision_fps: float = float(os.getenv("VISION_FPS", "0.05")) # Frames per second # ============================================ # MCP Server Settings # ============================================ mcp_server_url: str = os.getenv("MCP_SERVER_URL", "http://localhost:8000") mcp_auto_start: bool = os.getenv("MCP_AUTO_START", "true").lower() == "true" # ============================================ # CRM Data Settings # ============================================ crm_data_dir: str = os.getenv("CRM_DATA_DIR", "./data") # ============================================ # Hyper-V Settings (Legacy) # ============================================ hyperv_enabled: bool = os.getenv("HYPERV_ENABLED", "false").lower() == "true" hyperv_host: str = os.getenv("HYPERV_HOST", "localhost") hyperv_username: Optional[str] = os.getenv("HYPERV_USERNAME") hyperv_password: Optional[str] = os.getenv("HYPERV_PASSWORD") # ============================================ # Application Settings # ============================================ max_conversation_history: int = int(os.getenv("MAX_CONVERSATION_HISTORY", "50")) temp_dir: str = os.getenv("TEMP_DIR", "./temp") log_level: str = os.getenv("LOG_LEVEL", "INFO") # Feature flags enable_screen_sharing_button: bool = os.getenv("ENABLE_SCREEN_SHARING_BUTTON", "true").lower() == "true" enable_voice_input: bool = os.getenv("ENABLE_VOICE_INPUT", "true").lower() == "true" def __post_init__(self): """Initialize directories and validate settings.""" # Ensure necessary directories exist Path(self.temp_dir).mkdir(exist_ok=True, parents=True) Path("./config").mkdir(exist_ok=True, parents=True) Path("./logs").mkdir(exist_ok=True, parents=True) Path(self.crm_data_dir).mkdir(exist_ok=True, parents=True) # 🔁 Refresh dynamic, env-backed values so they pick up changes done at runtime self.hf_token = os.getenv("HF_TOKEN", self.hf_token) self.NEBIUS_API_KEY = os.getenv("NEBIUS_API_KEY", self.NEBIUS_API_KEY) def is_hf_token_valid(self) -> bool: """Check if HuggingFace token is set and looks like a real HF token.""" token = os.getenv("HF_TOKEN", "") # always read the latest env return bool(token and token.startswith("hf_") and len(token) > 20) @property def effective_llm_provider(self) -> str: if self.llm_provider == "auto": return "huggingface" if self.is_hf_token_valid() else "openai" return self.llm_provider @property def effective_audio_provider(self) -> str: if self.audio_provider == "auto": return "huggingface" if self.is_hf_token_valid() else "openai" return self.audio_provider @property def llm_endpoint(self) -> str: if self.effective_llm_provider == "huggingface": return f"https://api-inference.huggingface.co/models/{self.hf_chat_model}" return getattr(self, 'openai_endpoint', '') @property def llm_api_key(self) -> str: if self.effective_llm_provider == "huggingface": return os.getenv("HF_TOKEN", "") # latest HF token return getattr(self, "openai_api_key", "") @property def effective_model_name(self) -> str: return self.hf_chat_model if self.effective_llm_provider == "huggingface" else self.model_name def get_vad_config(self) -> dict: """Get VAD configuration as a dictionary.""" return { "sample_rate": self.vad_sample_rate, "frame_duration_ms": self.vad_frame_duration_ms, "aggressiveness": self.vad_aggressiveness, "speech_threshold": self.vad_speech_threshold, "silence_threshold": self.vad_silence_threshold, "min_speech_duration_ms": self.vad_min_speech_ms, "max_speech_duration_s": self.vad_max_speech_s, "post_speech_silence_ms": self.vad_post_speech_silence_ms, }