84 lines
2.3 KiB
Python
84 lines
2.3 KiB
Python
"""本地演示/测试素材:供控制台一键预填需求与上传文件。
|
||
|
||
目录默认:仓库 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,
|
||
)
|