import os from dataclasses import dataclass from typing import List from dotenv import load_dotenv load_dotenv(override=False) def _clean_env_value(value: str) -> str: # Strip whitespace and optional surrounding quotes. v = value.strip() if len(v) >= 2 and ((v[0] == v[-1] == '"') or (v[0] == v[-1] == "'")): v = v[1:-1].strip() return v def _get_env(name: str, default: str | None = None) -> str: value = os.getenv(name, default) if value is None or value == "": raise RuntimeError(f"Missing required environment variable: {name}") return _clean_env_value(value) def _get_env_optional(name: str, default: str | None = None) -> str | None: value = os.getenv(name, default) if value is None or value == "": return None return _clean_env_value(value) def _get_bool_env(name: str, default: bool = False) -> bool: raw = os.getenv(name) if raw is None: return default return raw.strip().lower() in {"1", "true", "yes", "y", "on"} def _get_bool_env_optional(name: str) -> bool | None: raw = os.getenv(name) if raw is None or raw.strip() == "": return None return raw.strip().lower() in {"1", "true", "yes", "y", "on"} def _parse_cors_origins(raw: str | None) -> List[str]: if not raw: return ["*"] parts = [p.strip() for p in raw.split(",")] return [p for p in parts if p] @dataclass(frozen=True) class Settings: groq_api_key: str groq_model: str voice_transcription_model: str google_api_key: str | None openrouter_api_key: str | None default_provider: str default_model: str mongodb_uri: str mongodb_tls: bool | None mongodb_db: str mongo_sessions_collection: str mongo_messages_collection: str mongo_users_collection: str max_history_messages: int max_attachment_chars_per_file: int max_total_attachment_chars: int temperature: float debug: bool cors_origins: List[str] jwt_secret: str jwt_algorithm: str jwt_exp_minutes: int tavily_api_key: str | None tavily_max_results: int google_client_id: str | None google_client_secret: str | None google_redirect_uri: str | None google_scope: str enable_vector_memory: bool enable_local_vision: bool def get_settings() -> Settings: debug = _get_bool_env("DEBUG", False) jwt_secret = _get_env_optional("JWT_SECRET", None) if not jwt_secret and not debug: raise RuntimeError( "Missing JWT_SECRET. Set it in your .env (or enable DEBUG=true for a dev-only insecure secret)." ) return Settings( groq_api_key=_get_env_optional("GROQ_API_KEY", None) or "", groq_model=os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile"), voice_transcription_model=os.getenv("VOICE_TRANSCRIPTION_MODEL", "whisper-large-v3-turbo").strip(), google_api_key=_get_env_optional("GOOGLE_API_KEY", None), openrouter_api_key=_get_env_optional("OPENROUTER_API_KEY", None), default_provider=os.getenv("DEFAULT_LLM_PROVIDER", "groq").strip().lower(), default_model=os.getenv("DEFAULT_LLM_MODEL", "llama-3.3-70b-versatile").strip(), mongodb_uri=_get_env("MONGODB_URI", "mongodb://localhost:27017"), mongodb_tls=_get_bool_env_optional("MONGODB_TLS"), mongodb_db=os.getenv("MONGODB_DB", "own_gpt"), mongo_sessions_collection=os.getenv("MONGO_SESSIONS_COLLECTION", "sessions"), mongo_messages_collection=os.getenv("MONGO_MESSAGES_COLLECTION", "messages"), mongo_users_collection=os.getenv("MONGO_USERS_COLLECTION", "users"), max_history_messages=int(os.getenv("MAX_HISTORY_MESSAGES", "20")), max_attachment_chars_per_file=int(os.getenv("MAX_ATTACHMENT_CHARS_PER_FILE", "8000")), max_total_attachment_chars=int(os.getenv("MAX_TOTAL_ATTACHMENT_CHARS", "16000")), temperature=float(os.getenv("GROQ_TEMPERATURE", "0.2")), debug=debug, cors_origins=_parse_cors_origins(_get_env_optional("CORS_ORIGINS", "*")), jwt_secret=jwt_secret or "dev-only-insecure-jwt-secret", jwt_algorithm=os.getenv("JWT_ALGORITHM", "HS256"), jwt_exp_minutes=int(os.getenv("JWT_EXP_MINUTES", "60")), tavily_api_key=_get_env_optional("TAVILY_API_KEY", None), tavily_max_results=int(os.getenv("TAVILY_MAX_RESULTS", "5")), google_client_id=_get_env_optional("GOOGLE_CLIENT_ID", None), google_client_secret=_get_env_optional("GOOGLE_CLIENT_SECRET", None), google_redirect_uri=_get_env_optional("GOOGLE_REDIRECT_URI", None), google_scope=os.getenv("GOOGLE_SCOPE", "openid email profile"), enable_vector_memory=_get_bool_env("ENABLE_VECTOR_MEMORY", True), enable_local_vision=_get_bool_env("ENABLE_LOCAL_VISION", False), )