""" 云屋平台 - 配置设置 管理系统和环境变量 统一使用根目录的 .env 文件 """ import os from pathlib import Path from typing import List, Dict from pydantic_settings import BaseSettings, SettingsConfigDict ROOT_DIR = Path(__file__).resolve().parent.parent.parent ENV_FILE_PATH = ROOT_DIR / '.env' class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=str(ENV_FILE_PATH), env_file_encoding='utf-8', extra='allow', protected_namespaces=() ) APP_NAME: str = "AI Pet Companion API" DEBUG: bool = False HOST: str = "0.0.0.0" PORT: int = 8001 DATABASE_URL: str = os.getenv("DATABASE_URL", "mysql+pymysql://root:taiyi1224@localhost:3306/pet_companion") JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production") JWT_ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 REFRESH_TOKEN_EXPIRE_DAYS: int = 7 ALLOWED_ORIGINS_STR: str = os.getenv("ALLOWED_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000,http://localhost:8080,http://localhost:3001," "http://127.0.0.1:3001,http://8.152.169.84:3000,https://www.wentuu.com,https://api.wentuu.com") @property def ALLOWED_ORIGINS(self) -> List[str]: return [origin.strip() for origin in self.ALLOWED_ORIGINS_STR.split(",") if origin.strip()] WECHAT_APP_ID: str = os.getenv("WECHAT_APP_ID", "") WECHAT_APP_SECRET: str = os.getenv("WECHAT_APP_SECRET", "") VOLCANO_APP_ID: str = os.getenv("VOLCANO_APP_ID", "2661618707") VOLCANO_ACCESS_TOKEN: str = os.getenv("VOLCANO_ACCESS_TOKEN", "Ip4UpS92mfQBzkbTMHyGaIfpGcLUe1ZH") VOLCANO_SECRET_KEY: str = os.getenv("VOLCANO_SECRET_KEY", "6lHMht8UPgS2yBV0c3AIRXUO7tB5dVTa") VOLCANO_BIDIRECTIONAL_ENABLED: bool = os.getenv("VOLCANO_BIDIRECTIONAL_ENABLED", "False").lower() == "true" VOLCANO_TTS_CLUSTER: str = os.getenv("VOLCANO_TTS_CLUSTER", "volcano_bigtts") VOLCANO_ASR_CLUSTER: str = os.getenv("VOLCANO_ASR_CLUSTER", "volcano_bigtts") VOLCANO_TTS_VOICE: str = os.getenv("VOLCANO_TTS_VOICE", "zh_female_vv_uranus_bigtts") VOLCANO_TTS_RESOURCE_ID: str = os.getenv("VOLCANO_TTS_RESOURCE_ID", "seed-tts-2.0") VOLCANO_ASR_RESOURCE_ID: str = os.getenv("VOLCANO_ASR_RESOURCE_ID", "volc.bigasr.sauc.duration") ARK_API_KEY: str = os.getenv("ARK_API_KEY", "34e2f893-8133-440f-906d-f93406342bc0") ARK_BASE_URL: str = os.getenv("ARK_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3") DOUBAO_API_KEY: str = os.getenv("DOUBAO_API_KEY", "") DOUBAO_SECRET_KEY: str = os.getenv("DOUBAO_SECRET_KEY", "") DOUBAO_APP_ID: str = os.getenv("DOUBAO_APP_ID", "") DOUBAO_MODEL_NAME: str = os.getenv("DOUBAO_MODEL_NAME", "doubao-seed-1-6-lite-251015") DOUBAO_REALTIME_URL: str = os.getenv("DOUBAO_REALTIME_URL", "wss://ark.cn-beijing.volces.com/api/v3/realtime") REALTIME_VOICE_APP_ID: str = os.getenv("REALTIME_VOICE_APP_ID", "2661618707") REALTIME_VOICE_ACCESS_KEY: str = os.getenv("REALTIME_VOICE_ACCESS_KEY", "Ip4UpS92mfQBzkbTMHyGaIfpGcLUe1ZH") REALTIME_VOICE_API_URL: str = os.getenv("REALTIME_VOICE_API_URL", "wss://openspeech.bytedance.com/api/v3/realtime/dialogue") REALTIME_VOICE_RESOURCE_ID: str = os.getenv("REALTIME_VOICE_RESOURCE_ID", "volc.speech.dialog") ALIYUN_SMS_ACCESS_KEY_ID: str = os.getenv("ALIYUN_SMS_ACCESS_KEY_ID", "") ALIYUN_SMS_ACCESS_KEY_SECRET: str = os.getenv("ALIYUN_SMS_ACCESS_KEY_SECRET", "") ALIYUN_SMS_REGION_ID: str = os.getenv("ALIYUN_SMS_REGION_ID", "cn-hangzhou") ALIYUN_SMS_SIGN_NAME: str = os.getenv("ALIYUN_SMS_SIGN_NAME", "") ALIYUN_SMS_TEMPLATE_CODE: str = os.getenv("ALIYUN_SMS_TEMPLATE_CODE", "") ALIYUN_SMS_ENABLED: bool = os.getenv("ALIYUN_SMS_ENABLED", "false").lower() == "true" SMS_CODE_LENGTH: int = int(os.getenv("SMS_CODE_LENGTH", "6")) SMS_CODE_VALID_SECONDS: int = int(os.getenv("SMS_CODE_VALID_SECONDS", "300")) SMS_SEND_INTERVAL_SECONDS: int = int(os.getenv("SMS_SEND_INTERVAL_SECONDS", "60")) UPLOAD_FOLDER: str = os.getenv("UPLOAD_FOLDER", "./uploads") MAX_CONTENT_LENGTH: int | None = None REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0") REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "") LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") DEFAULT_BACKGROUND_ID: int = int(os.getenv("DEFAULT_BACKGROUND_ID", "1")) DEFAULT_DAILY_QUOTA: int = int(os.getenv("DEFAULT_DAILY_QUOTA", "20")) # 聊天上下文轮数(1轮=一组用户+AI),0表示不带历史上下文 CHAT_CONTEXT_ROUNDS: int = int(os.getenv("CHAT_CONTEXT_ROUNDS", "0")) settings = Settings() # 运行时可动态更新(用于后台系统配置即时生效) _runtime_chat_context_rounds = max(0, int(getattr(settings, "CHAT_CONTEXT_ROUNDS", 0) or 0)) def get_chat_context_rounds() -> int: return max(0, int(_runtime_chat_context_rounds)) def set_chat_context_rounds(rounds: int) -> None: global _runtime_chat_context_rounds _runtime_chat_context_rounds = max(0, int(rounds)) _runtime_token_charge_config = { "realtime_chat_fallback_tokens": int(os.getenv("REALTIME_CHAT_FALLBACK_TOKENS", "200")), "e2e_realtime_voice_fallback_tokens": int(os.getenv("E2E_REALTIME_VOICE_FALLBACK_TOKENS", "200")), "doubao_realtime_fallback_tokens": int(os.getenv("DOUBAO_REALTIME_FALLBACK_TOKENS", "200")), "voice_chat_fallback_tokens": int(os.getenv("VOICE_CHAT_FALLBACK_TOKENS", "200")), "voice_realtime_call_fallback_tokens": int(os.getenv("VOICE_REALTIME_CALL_FALLBACK_TOKENS", "200")), } def get_token_charge_config() -> Dict[str, int]: return { "realtime_chat_fallback_tokens": max(0, int(_runtime_token_charge_config.get("realtime_chat_fallback_tokens", 200))), "e2e_realtime_voice_fallback_tokens": max(0, int(_runtime_token_charge_config.get("e2e_realtime_voice_fallback_tokens", 200))), "doubao_realtime_fallback_tokens": max(0, int(_runtime_token_charge_config.get("doubao_realtime_fallback_tokens", 200))), "voice_chat_fallback_tokens": max(0, int(_runtime_token_charge_config.get("voice_chat_fallback_tokens", 200))), "voice_realtime_call_fallback_tokens": max(0, int(_runtime_token_charge_config.get("voice_realtime_call_fallback_tokens", 200))), } def set_token_charge_config(config: Dict[str, int]) -> None: if not isinstance(config, dict): return for key in _runtime_token_charge_config.keys(): if key not in config: continue try: value = int(config[key]) except (TypeError, ValueError): continue _runtime_token_charge_config[key] = max(0, value)