"""全局配置:从环境变量加载(pydantic-settings)。 简化点(相比原 Netlify 版): - 不再支持 XTC_PROVIDER_N_* 多槽位环境变量,仅用 GEMINI/OPENAI 做种子 - 不再支持配置加密(XTC_CONFIG_CRYPT_KEY) - 不再支持 XTC_CONFIG_ENV_ONLY / XTC_ADMIN_REQUIRE_KEY_ON_PAGE / XTC_ACCESS_SHORT_KEY / XTC_ACCESS_PIN - 不再支持 Edge 路径与三段式 env 读取 - 新增 XTC_JWT_SECRET 用于签发临时令牌 - 存储主体为持久卷(/data/xtc.db);配置文件额外通过 HF Hub dataset 仓库做备份 """ from __future__ import annotations import os from functools import lru_cache from typing import List, Optional from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict def _split_key_list(value: Optional[str]) -> List[str]: if not value: return [] out = [] for item in str(value).replace("\n", ",").replace(";", ",").split(","): s = item.strip() if s: out.append(s) return list(dict.fromkeys(out)) # 去重保序 class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore", case_sensitive=False, ) # ===== 鉴权 ===== xtc_admin_key: str = Field(default="", alias="XTC_ADMIN_KEY") xtc_access_key: str = Field(default="", alias="XTC_ACCESS_KEY") xtc_jwt_secret: str = Field(default="", alias="XTC_JWT_SECRET") # ===== 紧急开关 ===== xtc_disable_admin: bool = Field(default=False, alias="XTC_DISABLE_ADMIN") # ===== /admin 页面访问门禁(可选)===== # 配置后访问 /admin 必须携带 ?k= 才能进入登录页, # 否则返回 404 伪装该路径不存在。未配置时 /admin 行为不变。 # 注意:此为页面门禁,/admin/api/* 仍由 XTC_ADMIN_KEY 鉴权。 xtc_admin_access_token: str = Field(default="", alias="XTC_ADMIN_ACCESS_TOKEN") # ===== 种子厂商 ===== gemini_api_key: str = Field(default="", alias="GEMINI_API_KEY") openai_api_key: str = Field(default="", alias="OPENAI_API_KEY") openai_base_url: str = Field(default="https://api.openai.com", alias="OPENAI_BASE_URL") # ===== 超时 ===== upstream_timeout_ms: int = Field(default=120000, alias="UPSTREAM_TIMEOUT_MS") upstream_stream_timeout_ms: int = Field(default=120000, alias="UPSTREAM_STREAM_TIMEOUT_MS") # ===== HF Hub 配置备份(可选)===== # 配置文件通过 HF Hub dataset 仓库做持久化备份,解决免费档磁盘 ephemeral 问题 # 未配置时降级为纯本地存储(功能正常,只是 restart/stop 后配置会丢失) hf_token: str = Field(default="", alias="HF_TOKEN") hf_config_repo: str = Field(default="", alias="HF_CONFIG_REPO") # ===== 临时令牌 ===== xtc_access_token_ttl_sec: int = Field(default=1800, alias="XTC_ACCESS_TOKEN_TTL_SEC") xtc_access_token_length: int = Field(default=8, alias="XTC_ACCESS_TOKEN_LENGTH") # ===== 运行 ===== port: int = Field(default=7860, alias="PORT") # ===== 数据库路径 ===== db_path: str = Field(default="/data/xtc.db", alias="XTC_DB_PATH") @field_validator("xtc_access_token_length") @classmethod def _clamp_token_length(cls, v: int) -> int: return max(6, min(24, v)) @field_validator("xtc_access_token_ttl_sec") @classmethod def _clamp_token_ttl(cls, v: int) -> int: return max(60, min(86400, v)) @property def gemini_api_keys(self) -> List[str]: # 兼容 GEMINI_API_KEYS(逗号分隔) return _split_key_list(os.environ.get("GEMINI_API_KEYS")) or _split_key_list(self.gemini_api_key) @property def openai_api_keys(self) -> List[str]: return _split_key_list(os.environ.get("OPENAI_API_KEYS")) or _split_key_list(self.openai_api_key) @property def is_admin_enabled(self) -> bool: return not self.xtc_disable_admin @property def has_admin_key(self) -> bool: return bool(self.xtc_admin_key) @property def has_admin_access_token(self) -> bool: return bool(self.xtc_admin_access_token) @property def has_access_key(self) -> bool: return bool(self.xtc_access_key) @property def has_jwt_secret(self) -> bool: return bool(self.xtc_jwt_secret) @lru_cache(maxsize=1) def get_settings() -> Settings: return Settings() # Gemini 上游地址(固定) GEMINI_BASE_URL = "https://generativelanguage.googleapis.com" GEMINI_API_VERSION = "v1beta" GEMINI_API_CLIENT = "google-genai-sdk/1.34.0" GEMINI_DEFAULT_MODEL = "gemini-flash-latest" GEMINI_DEFAULT_EMBEDDINGS_MODEL = "gemini-embedding-001" # RR 计数器数据库 key 前缀 RR_KEY_PREFIX = "rr:provider:" # 伪流式会话 TTL PSEUDO_SESSION_TTL_SEC = 10 * 60 # 模型列表缓存 TTL MODELS_CACHE_TTL_SEC = 30