"""项目配置:路径、模型、网关、发布目标。 原则:API key 不落本项目文件,运行时从 Pi 的 auth.json 读取。 """ from __future__ import annotations import json import os from dataclasses import dataclass from pathlib import Path HOME = Path.home() # ---------------------------------------------------------------- 目录 PROJECT_DIR = Path(__file__).resolve().parents[1] # /home/chendy/df-annals DATA_DIR = PROJECT_DIR / "data" # 原始数据,不入库 EXPORT_DIR = DATA_DIR / "exports" # legends 导出原始文件 OUTPUT_DIR = PROJECT_DIR / "output" # 成品,入库 CHAPTER_DIR = OUTPUT_DIR / "chapters" PROMPT_DIR = PROJECT_DIR / "prompts" def volume_dir(volume: int) -> Path: """每一卷(一个世界)独立的成品目录。""" return OUTPUT_DIR / "volumes" / f"v{volume}" def chapter_dir(volume: int) -> Path: return volume_dir(volume) / "chapters" # ---------------------------------------------------------------- 游戏(Windows 侧) WINDOWS_USER = "23518" WIN_DF_ROOT = Path(f"/mnt/c/Users/{WINDOWS_USER}/DwarfFortress") # 下载/安装根目录 WIN_DF_DIR_CANDIDATES = [ WIN_DF_ROOT / "df_53_16_win", WIN_DF_ROOT / "Dwarf Fortress", WIN_DF_ROOT, ] DF_VERSION = "53.16" DFHACK_VERSION = "53.16-r2" # ---------------------------------------------------------------- LLM 网关 PI_AUTH_JSON = HOME / ".pi/agent/auth.json" PI_MODELS_JSON = HOME / ".pi/agent/models.json" PROVIDER = "workbuddy" MODEL = "cn:deepseek-v4-pro" # 该模型是推理模型:思考与正文共用 max_tokens,必须留足 MAX_TOKENS_CHAPTER = 16000 MAX_TOKENS_UTIL = 4000 # ---------------------------------------------------------------- 发布 GITEA_SSH_HOST = "124.222.29.26" GITEA_SSH_PORT = 2222 GITEA_USER = "gitadmin" GITEA_REPO = "dwarf-fortress-annals" GITEA_WEB = "http://124.222.29.26:3000" GITEA_REMOTE = f"ssh://git@{GITEA_SSH_HOST}:{GITEA_SSH_PORT}/{GITEA_USER}/{GITEA_REPO}.git" SSH_KEY = HOME / ".ssh/id_ed25519" # ---------------------------------------------------------------- 世界与章节 WORLD_SIZE = "Medium" WORLD_HISTORY_YEARS = 250 CHAPTER_MIN_CHARS = 800 CHAPTER_MAX_CHARS = 1600 # 契约上限 1500,留出标点/换行余量 @dataclass class Gateway: base_url: str key: str model: str = MODEL provider: str = PROVIDER def load_gateway(provider: str | None = None) -> Gateway: """从 Pi 配置读取网关地址与密钥。密钥只驻留内存,不打印、不落盘。""" provider = provider or PROVIDER def _read_json(path: Path, label: str) -> dict: try: return json.loads(path.read_text()) except FileNotFoundError: raise SystemExit(f"找不到 Pi 的{label}文件: {path}") from None except json.JSONDecodeError as exc: raise SystemExit(f"Pi 的{label}文件不是合法 JSON: {path} ({exc})") from None auth = _read_json(PI_AUTH_JSON, "凭据") models = _read_json(PI_MODELS_JSON, "模型配置") providers = models.get("providers", models) if provider not in providers: raise SystemExit(f"Pi 配置里没有 provider: {provider}") if provider not in auth: raise SystemExit(f"Pi 未授权 provider: {provider}") entry = auth[provider] key = entry.get("key") if isinstance(entry, dict) else entry if not key: raise SystemExit(f"provider {provider} 的凭据为空") return Gateway(base_url=providers[provider]["baseUrl"].rstrip("/"), key=key, provider=provider) def find_df_dir() -> Path: """定位已安装的 DF 目录(含 Dwarf Fortress.exe)。""" override = os.environ.get("DF_DIR") if override: p = Path(override) if p.is_dir(): return p raise SystemExit(f"DF_DIR 不存在: {p}") for cand in WIN_DF_DIR_CANDIDATES: if (cand / "Dwarf Fortress.exe").is_file(): return cand raise SystemExit( "未找到 Dwarf Fortress.exe。候选路径:" + ", ".join(str(c) for c in WIN_DF_DIR_CANDIDATES) ) def ensure_dirs() -> None: for d in (DATA_DIR, EXPORT_DIR, OUTPUT_DIR, CHAPTER_DIR, PROMPT_DIR): d.mkdir(parents=True, exist_ok=True)