"""Конфигурация приложения. Всё читается из переменных окружения. На Hugging Face Spaces значения задаются в Settings → Variables and secrets. Локально — через .env (см. .env.example). """ from __future__ import annotations import os from dataclasses import dataclass, field from pathlib import Path try: # локальный запуск: подхватываем .env, если он есть from dotenv import load_dotenv load_dotenv(override=False) except ImportError: # на Spaces переменные приходят из окружения pass APP_NAME = "Витрина" APP_TAGLINE = "Иллюстрации и тексты карточек товаров для маркетплейсов" _TRUE = {"1", "true", "yes", "on", "да"} def _flag(name: str, default: bool = False) -> bool: raw = os.getenv(name) return default if raw is None else raw.strip().lower() in _TRUE def _int(name: str, default: int) -> int: try: return int(os.getenv(name, default)) except (TypeError, ValueError): return default @dataclass(frozen=True) class Settings: # --- ключи (могут быть пустыми: тогда пользователь вводит свой в интерфейсе) --- openai_api_key: str = field(default_factory=lambda: os.getenv("OPENAI_API_KEY", "").strip()) groq_api_key: str = field(default_factory=lambda: os.getenv("GROQ_API_KEY", "").strip()) openai_base_url: str | None = field( default_factory=lambda: os.getenv("OPENAI_BASE_URL", "").strip() or None ) # --- модели --- # Названия моделей у провайдеров меняются. Всё вынесено в env, # чтобы поменять их без правки кода. image_model: str = field(default_factory=lambda: os.getenv("OPENAI_IMAGE_MODEL", "gpt-image-1")) image_model_fallback: str = field( default_factory=lambda: os.getenv("OPENAI_IMAGE_MODEL_FALLBACK", "dall-e-3") ) groq_text_model: str = field( default_factory=lambda: os.getenv("GROQ_TEXT_MODEL", "llama-3.3-70b-versatile") ) groq_vision_model: str = field( default_factory=lambda: os.getenv( "GROQ_VISION_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct" ) ) # --- лимиты --- max_images: int = field(default_factory=lambda: _int("MAX_IMAGES", 4)) request_timeout: int = field(default_factory=lambda: _int("REQUEST_TIMEOUT", 180)) max_retries: int = field(default_factory=lambda: _int("MAX_RETRIES", 3)) # --- поведение --- allow_user_keys: bool = field(default_factory=lambda: _flag("ALLOW_USER_KEYS", True)) enable_queue_concurrency: int = field(default_factory=lambda: _int("CONCURRENCY", 2)) output_dir: Path = field(default_factory=lambda: Path(os.getenv("OUTPUT_DIR", "outputs"))) log_level: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO").upper()) def resolve_openai_key(self, user_key: str | None) -> str: """Ключ пользователя приоритетнее серверного, если это разрешено.""" if self.allow_user_keys and user_key and user_key.strip(): return user_key.strip() return self.openai_api_key def resolve_groq_key(self, user_key: str | None) -> str: if self.allow_user_keys and user_key and user_key.strip(): return user_key.strip() return self.groq_api_key settings = Settings() settings.output_dir.mkdir(parents=True, exist_ok=True)