""" 配置管理 统一从项目根目录的 .env 文件加载配置 """ import os from dotenv import load_dotenv # 加载项目根目录的 .env 文件 # 路径: MiroFish/.env (相对于 backend/app/config.py) project_root_env = os.path.join(os.path.dirname(__file__), '../../.env') if os.path.exists(project_root_env): load_dotenv(project_root_env, override=True) else: # 如果根目录没有 .env,尝试加载环境变量(用于生产环境) load_dotenv(override=True) class Config: """Flask配置类""" # Flask配置 SECRET_KEY = os.environ.get('SECRET_KEY', 'mirofish-secret-key') DEBUG = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true' # JSON配置 - 禁用ASCII转义,让中文直接显示(而不是 \uXXXX 格式) JSON_AS_ASCII = False # LLM(OpenAI 兼容)。优先使用 LLM_1 / LLM_2,避免逗号拼接误配 LLM_1 = os.environ.get("LLM_1", "").strip() LLM_2 = os.environ.get("LLM_2", "").strip() LLM_API_KEY = os.environ.get('LLM_API_KEY') LLM_BASE_URL = os.environ.get('LLM_BASE_URL', 'https://api.openai.com/v1') LLM_MODEL_NAME = os.environ.get('LLM_MODEL_NAME', 'gpt-4o-mini') # Zep:优先 ZEP_1 / ZEP_2 ZEP_1 = os.environ.get("ZEP_1", "").strip() ZEP_2 = os.environ.get("ZEP_2", "").strip() ZEP_API_KEY = os.environ.get('ZEP_API_KEY') @classmethod def get_zep_api_keys(cls): keys = [k for k in (cls.ZEP_1, cls.ZEP_2) if k] if keys: return keys raw = (cls.ZEP_API_KEY or "").strip() if not raw: return [] if "," in raw: return [k.strip() for k in raw.split(",") if k.strip()] return [raw] @classmethod def get_llm_api_keys(cls): keys = [k for k in (cls.LLM_1, cls.LLM_2) if k] if keys: return keys raw = (cls.LLM_API_KEY or "").strip() if not raw: return [] if "," in raw: return [k.strip() for k in raw.split(",") if k.strip()] return [raw] # Razorpay + simulation unlock (website) RAZORPAY_KEY_ID = os.environ.get("RAZORPAY_KEY_ID", "").strip() RAZORPAY_KEY_SECRET = os.environ.get("RAZORPAY_KEY_SECRET", "").strip() RAZORPAY_AMOUNT_PAISE = int(os.environ.get("RAZORPAY_AMOUNT_PAISE", "49900")) RAZORPAY_CURRENCY = os.environ.get("RAZORPAY_CURRENCY", "INR").strip() or "INR" PAYMENT_COUPON_CODES = os.environ.get("PAYMENT_COUPON_CODES", "").strip() PAYMENT_JWT_SECRET = os.environ.get("PAYMENT_JWT_SECRET", "").strip() @classmethod def payment_coupon_set(cls): if not cls.PAYMENT_COUPON_CODES: return set() return {c.strip().lower() for c in cls.PAYMENT_COUPON_CODES.split(",") if c.strip()} @classmethod def billing_enabled(cls) -> bool: razorpay_ok = bool(cls.RAZORPAY_KEY_ID and cls.RAZORPAY_KEY_SECRET) return razorpay_ok or bool(cls.payment_coupon_set()) @classmethod def payment_signing_secret(cls) -> str: return (cls.PAYMENT_JWT_SECRET or cls.PIPELINE_CLIENT_SECRET or cls.SECRET_KEY or "mirofish-pay").strip() # 文件上传配置 MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), '../uploads') ALLOWED_EXTENSIONS = {'pdf', 'md', 'txt', 'markdown'} # 文本处理配置 DEFAULT_CHUNK_SIZE = 500 # 默认切块大小 DEFAULT_CHUNK_OVERLAP = 50 # 默认重叠大小 # OASIS模拟配置 OASIS_DEFAULT_MAX_ROUNDS = int(os.environ.get('OASIS_DEFAULT_MAX_ROUNDS', '10')) OASIS_SIMULATION_DATA_DIR = os.path.join(os.path.dirname(__file__), '../uploads/simulations') # OASIS平台可用动作配置 OASIS_TWITTER_ACTIONS = [ 'CREATE_POST', 'LIKE_POST', 'REPOST', 'FOLLOW', 'DO_NOTHING', 'QUOTE_POST' ] OASIS_REDDIT_ACTIONS = [ 'LIKE_POST', 'DISLIKE_POST', 'CREATE_POST', 'CREATE_COMMENT', 'LIKE_COMMENT', 'DISLIKE_COMMENT', 'SEARCH_POSTS', 'SEARCH_USER', 'TREND', 'REFRESH', 'DO_NOTHING', 'FOLLOW', 'MUTE' ] # 全自动流水线(website → /api/pipeline/start) PIPELINE_MAX_ROUNDS = os.environ.get('PIPELINE_MAX_ROUNDS') # optional cap on OASIS rounds PIPELINE_PARALLEL_PROFILES = int(os.environ.get('PIPELINE_PARALLEL_PROFILES', '5')) # Supabase: report storage + simulation_runs table (free tier friendly) SUPABASE_URL = os.environ.get('SUPABASE_URL', '').rstrip('/') SUPABASE_SERVICE_ROLE_KEY = os.environ.get('SUPABASE_SERVICE_ROLE_KEY', '') # Project Settings → API → JWT Secret (verify user access tokens from the website) SUPABASE_JWT_SECRET = os.environ.get('SUPABASE_JWT_SECRET', '') SUPABASE_REPORTS_BUCKET = os.environ.get('SUPABASE_REPORTS_BUCKET', 'reports') # Optional: signing secret for legacy MiroFish Bearer JWTs. Defaults to SECRET_KEY. MIROFISH_SESSION_JWT_SECRET = os.environ.get('MIROFISH_SESSION_JWT_SECRET', '').strip() # Same value as website VITE_PIPELINE_CLIENT_SECRET (browser sends X-Mirofish-User-Id + X-Pipeline-Client-Secret). PIPELINE_CLIENT_SECRET = os.environ.get('PIPELINE_CLIENT_SECRET', '').strip() # Report Agent配置 REPORT_AGENT_MAX_TOOL_CALLS = int(os.environ.get('REPORT_AGENT_MAX_TOOL_CALLS', '5')) REPORT_AGENT_MAX_REFLECTION_ROUNDS = int(os.environ.get('REPORT_AGENT_MAX_REFLECTION_ROUNDS', '2')) REPORT_AGENT_TEMPERATURE = float(os.environ.get('REPORT_AGENT_TEMPERATURE', '0.5')) @classmethod def validate(cls): """验证必要配置""" errors = [] if not cls.get_llm_api_keys(): errors.append("LLM_1 / LLM_2 or LLM_API_KEY 未配置") if not cls.get_zep_api_keys(): errors.append("ZEP_1 / ZEP_2 or ZEP_API_KEY 未配置") return errors