from __future__ import annotations from functools import lru_cache from pathlib import Path from urllib.parse import quote_plus from pydantic_settings import BaseSettings, SettingsConfigDict PROJECT_ROOT = Path(__file__).resolve().parents[2] class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=PROJECT_ROOT / ".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", ) app_name: str = "矿山通防智能管控平台" app_env: str = "development" debug: bool = True api_v1_prefix: str = "/api/v1" secret_key: str = "change-me-in-production" host: str = "0.0.0.0" port: int = 8090 log_level: str = "INFO" database_dialect: str = "sqlite" sqlite_path: str = "data/vent_platform.sqlite" mysql_host: str = "127.0.0.1" mysql_port: int = 3306 mysql_user: str = "root" mysql_password: str = "" mysql_database: str = "vent_platform" database_echo: bool = False openai_api_key: str = "" openai_base_url: str = "https://api.deepseek.com" deepagent_model: str = "deepseek-flash" deepagent_thinking: str = "off" openai_timeout: int = 180 mineru_api_base: str = "https://mineru.net" mineru_token: str = "" mineru_timeout: int = 900 mineru_poll_interval: float = 2.0 document_extraction_max_chars: int = 80000 knowledge_service_base: str = "" knowledge_service_api_key: str = "" knowledge_service_timeout: float = 30.0 knowledge_catalog_cache_ttl: int = 300 knowledge_search_mode: str = "keyword" knowledge_search_top_k: int = 5 knowledge_search_min_score: float = 0.0 knowledge_search_min_match_ratio: float = 0.0 knowledge_rag_context_max_chars: int = 12000 knowledge_rag_source_max_chars: int = 2800 cors_origins: str = ( "http://127.0.0.1:5173,http://localhost:5173," "http://127.0.0.1:8080,http://localhost:8080" ) default_mine_code: str = "shangwan" upload_dir: str = "data/uploads" storage_capacity_gb: float = 1024.0 seed_demo_data: bool = False request_trace_header: str = "X-Trace-Id" @property def cors_origin_list(self) -> list[str]: return [item.strip() for item in self.cors_origins.split(",") if item.strip()] @property def sqlite_file(self) -> Path: path = Path(self.sqlite_path) return path if path.is_absolute() else PROJECT_ROOT / path @property def database_url(self) -> str: dialect = self.database_dialect.strip().lower() if dialect == "sqlite": return f"sqlite:///{self.sqlite_file.as_posix()}" if dialect in {"mysql", "mysql+pymysql"}: user = quote_plus(self.mysql_user) password = quote_plus(self.mysql_password) return ( f"mysql+pymysql://{user}:{password}@" f"{self.mysql_host}:{self.mysql_port}/{self.mysql_database}" "?charset=utf8mb4" ) raise ValueError("DATABASE_DIALECT 仅支持 sqlite 或 mysql") @property def upload_path(self) -> Path: path = Path(self.upload_dir) return path if path.is_absolute() else PROJECT_ROOT / path @property def model_configured(self) -> bool: return bool(self.openai_api_key) @property def knowledge_service_configured(self) -> bool: return bool(self.knowledge_service_base.strip()) @lru_cache(maxsize=1) def get_settings() -> Settings: return Settings() settings = get_settings()