"""本地演示/测试素材:供控制台一键预填需求与上传文件。 目录默认:仓库 test/(可用 DEMO_FIXTURES_DIR 覆盖)。 """ from __future__ import annotations import mimetypes import os from pathlib import Path from fastapi import HTTPException from fastapi.responses import FileResponse _ROOT = Path(__file__).resolve().parents[1] def fixtures_root() -> Path: raw = (os.getenv("DEMO_FIXTURES_DIR") or "").strip() if raw: return Path(raw).expanduser().resolve() return (_ROOT / "test").resolve() # 默认演示包:沉降观测(行业示例,仅测试用) DEFAULT_PACK = { "id": "settlement_demo", "label": "沉降观测演示包(test/)", "prompt": "prompts/prompt_1.txt", "data": "data/settlement.xlsx", "images": ["refs/board_a.png", "refs/board_b.png"], "layouts": ["refs/dashboard.html", "refs/nav.html"], "slug": "settlement_observation_system", } def _safe_file(rel: str) -> Path: root = fixtures_root() rel = (rel or "").replace("\\", "/").lstrip("/") if not rel or ".." in rel.split("/"): raise HTTPException(400, "非法路径") path = (root / rel).resolve() try: path.relative_to(root) except ValueError as e: raise HTTPException(400, "越界路径") from e if not path.is_file(): raise HTTPException(404, f"文件不存在: {rel}") return path def fixtures_manifest() -> dict: root = fixtures_root() pack = dict(DEFAULT_PACK) pack["root"] = str(root) pack["available"] = root.is_dir() def ok(rel: str) -> bool: try: return _safe_file(rel).is_file() except HTTPException: return False pack["files"] = { "prompt": {"path": pack["prompt"], "ok": ok(pack["prompt"])}, "data": {"path": pack["data"], "ok": ok(pack["data"])}, "images": [{"path": p, "ok": ok(p)} for p in pack["images"]], "layouts": [{"path": p, "ok": ok(p)} for p in pack["layouts"]], } return pack def read_prompt_text() -> str: return _safe_file(DEFAULT_PACK["prompt"]).read_text(encoding="utf-8") def file_response(rel: str) -> FileResponse: path = _safe_file(rel) ctype, _ = mimetypes.guess_type(str(path)) return FileResponse( path, media_type=ctype or "application/octet-stream", filename=path.name, )