""" Configuration Settings for Smart Auto-Complete Manages environment variables and application configuration """ import logging import os from typing import Any, Dict, Optional from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() logger = logging.getLogger(__name__) class AppSettings: """ Application settings manager Loads configuration from environment variables with sensible defaults """ def __init__(self): """Initialize settings from environment variables""" # API Configuration self.OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "").strip() self.ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "").strip() self.DEFAULT_PROVIDER = os.getenv("DEFAULT_PROVIDER", "openai").lower() # Application Settings self.MAX_SUGGESTIONS = int(os.getenv("MAX_SUGGESTIONS", "5")) self.DEBOUNCE_DELAY = int(os.getenv("DEBOUNCE_DELAY", "300")) # milliseconds self.CACHE_TTL = int(os.getenv("CACHE_TTL", "3600")) # seconds self.MAX_INPUT_LENGTH = int(os.getenv("MAX_INPUT_LENGTH", "1000")) # Cache Configuration self.CACHE_MAX_SIZE = int(os.getenv("CACHE_MAX_SIZE", "1000")) self.CACHE_ENABLED = os.getenv("CACHE_ENABLED", "true").lower() == "true" # Logging Configuration self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() self.LOG_FORMAT = os.getenv( "LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) # API Rate Limiting self.RATE_LIMIT_REQUESTS_PER_MINUTE = int( os.getenv("RATE_LIMIT_REQUESTS_PER_MINUTE", "60") ) self.RATE_LIMIT_ENABLED = ( os.getenv("RATE_LIMIT_ENABLED", "true").lower() == "true" ) # Model Configuration self.OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini") self.ANTHROPIC_MODEL = os.getenv("ANTHROPIC_MODEL", "claude-3-haiku-20240307") # Temperature settings for different contexts self.TEMPERATURE_EMAIL = float(os.getenv("TEMPERATURE_EMAIL", "0.6")) self.TEMPERATURE_CREATIVE = float(os.getenv("TEMPERATURE_CREATIVE", "0.8")) self.TEMPERATURE_LINKEDIN = float(os.getenv("TEMPERATURE_LINKEDIN", "0.7")) # Default token limits for different contexts self.DEFAULT_TOKENS_EMAIL = int(os.getenv("DEFAULT_TOKENS_EMAIL", "200")) self.DEFAULT_TOKENS_CREATIVE = int(os.getenv("DEFAULT_TOKENS_CREATIVE", "250")) self.DEFAULT_TOKENS_LINKEDIN = int(os.getenv("DEFAULT_TOKENS_LINKEDIN", "200")) # UI Configuration self.UI_THEME = os.getenv("UI_THEME", "soft") self.UI_TITLE = os.getenv("UI_TITLE", "🚀 Smart Auto-Complete") self.UI_DESCRIPTION = os.getenv( "UI_DESCRIPTION", "Intelligent text completion powered by AI" ) # Server Configuration self.SERVER_HOST = os.getenv("SERVER_HOST", "0.0.0.0") self.SERVER_PORT = int(os.getenv("SERVER_PORT", "7860")) self.SERVER_SHARE = os.getenv("SERVER_SHARE", "false").lower() == "true" # Security Settings self.ENABLE_INPUT_SANITIZATION = ( os.getenv("ENABLE_INPUT_SANITIZATION", "true").lower() == "true" ) self.MAX_CONCURRENT_REQUESTS = int(os.getenv("MAX_CONCURRENT_REQUESTS", "10")) # Development Settings self.DEBUG_MODE = os.getenv("DEBUG_MODE", "false").lower() == "true" self.ENABLE_ANALYTICS = os.getenv("ENABLE_ANALYTICS", "true").lower() == "true" # Validate settings after initialization self._validate_settings() logger.info("Application settings loaded successfully") def _validate_settings(self): """Validate configuration settings""" errors = [] warnings = [] # Check API keys if not self.OPENAI_API_KEY and not self.ANTHROPIC_API_KEY: errors.append( "No API keys configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY" ) # Validate provider if self.DEFAULT_PROVIDER not in ["openai", "anthropic"]: warnings.append( f"Invalid DEFAULT_PROVIDER: {self.DEFAULT_PROVIDER}. Using 'openai'" ) self.DEFAULT_PROVIDER = "openai" # Validate numeric ranges if not (1 <= self.MAX_SUGGESTIONS <= 20): warnings.append( f"MAX_SUGGESTIONS should be 1-20, got {self.MAX_SUGGESTIONS}" ) self.MAX_SUGGESTIONS = max(1, min(20, self.MAX_SUGGESTIONS)) if not (100 <= self.DEBOUNCE_DELAY <= 2000): warnings.append( f"DEBOUNCE_DELAY should be 100-2000ms, got {self.DEBOUNCE_DELAY}" ) self.DEBOUNCE_DELAY = max(100, min(2000, self.DEBOUNCE_DELAY)) if not (100 <= self.MAX_INPUT_LENGTH <= 10000): warnings.append( f"MAX_INPUT_LENGTH should be 100-10000, got {self.MAX_INPUT_LENGTH}" ) self.MAX_INPUT_LENGTH = max(100, min(10000, self.MAX_INPUT_LENGTH)) # Validate temperature ranges for temp_attr in [ "TEMPERATURE_EMAIL", "TEMPERATURE_CREATIVE", "TEMPERATURE_LINKEDIN", ]: temp_value = getattr(self, temp_attr) if not (0.0 <= temp_value <= 2.0): warnings.append(f"{temp_attr} should be 0.0-2.0, got {temp_value}") setattr(self, temp_attr, max(0.0, min(2.0, temp_value))) # Log validation results if errors: for error in errors: logger.error(f"Configuration error: {error}") if warnings: for warning in warnings: logger.warning(f"Configuration warning: {warning}") def validate_api_keys(self) -> bool: """ Validate that at least one API key is properly configured Returns: True if at least one valid API key is available """ from src.utils import validate_api_key openai_valid = self.OPENAI_API_KEY and validate_api_key( self.OPENAI_API_KEY, "openai" ) anthropic_valid = self.ANTHROPIC_API_KEY and validate_api_key( self.ANTHROPIC_API_KEY, "anthropic" ) return openai_valid or anthropic_valid def get_context_config(self, context: str) -> Dict[str, Any]: """ Get configuration for a specific context Args: context: Context name (email, code, creative, linkedin) Returns: Dictionary with context-specific configuration """ context_configs = { "email": { "temperature": self.TEMPERATURE_EMAIL, "default_tokens": self.DEFAULT_TOKENS_EMAIL, "model_preference": "openai", # Generally better for professional text }, "creative": { "temperature": self.TEMPERATURE_CREATIVE, "default_tokens": self.DEFAULT_TOKENS_CREATIVE, "model_preference": "anthropic", # Often better for creative content }, "linkedin": { "temperature": self.TEMPERATURE_LINKEDIN, "default_tokens": self.DEFAULT_TOKENS_LINKEDIN, "model_preference": self.DEFAULT_PROVIDER, }, } return context_configs.get(context, context_configs["linkedin"]) def get_model_for_provider(self, provider: str) -> str: """ Get the model name for a specific provider Args: provider: Provider name (openai, anthropic) Returns: Model name string """ if provider == "openai": return self.OPENAI_MODEL elif provider == "anthropic": return self.ANTHROPIC_MODEL else: return self.OPENAI_MODEL # Default fallback def to_dict(self) -> Dict[str, Any]: """ Convert settings to dictionary (excluding sensitive data) Returns: Dictionary with non-sensitive configuration """ return { "max_suggestions": self.MAX_SUGGESTIONS, "debounce_delay": self.DEBOUNCE_DELAY, "cache_ttl": self.CACHE_TTL, "max_input_length": self.MAX_INPUT_LENGTH, "cache_enabled": self.CACHE_ENABLED, "log_level": self.LOG_LEVEL, "rate_limit_enabled": self.RATE_LIMIT_ENABLED, "rate_limit_requests_per_minute": self.RATE_LIMIT_REQUESTS_PER_MINUTE, "default_provider": self.DEFAULT_PROVIDER, "openai_model": self.OPENAI_MODEL, "anthropic_model": self.ANTHROPIC_MODEL, "ui_theme": self.UI_THEME, "ui_title": self.UI_TITLE, "server_host": self.SERVER_HOST, "server_port": self.SERVER_PORT, "debug_mode": self.DEBUG_MODE, "has_openai_key": bool(self.OPENAI_API_KEY), "has_anthropic_key": bool(self.ANTHROPIC_API_KEY), } def update_from_dict(self, config_dict: Dict[str, Any]): """ Update settings from a dictionary Args: config_dict: Dictionary with configuration updates """ for key, value in config_dict.items(): if hasattr(self, key.upper()): setattr(self, key.upper(), value) logger.info(f"Updated setting {key.upper()} = {value}") # Re-validate after updates self._validate_settings() def __str__(self) -> str: """String representation of settings (safe for logging)""" safe_dict = self.to_dict() return f"AppSettings({safe_dict})" # Global settings instance settings = AppSettings()