{"record":{"id":"5e4c55ae1a36a6e0","repo":"datawhalechina/hello-agents","slug":"historical-review-web-static","errorCode":null,"errorMessage":"前端文件缺失，请检查 historical_review/web/static/","messagePattern":"前端文件缺失，请检查 historical_review/web/static/","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/web/app.py","lineNumber":67,"sourceCode":"\nclass DebateResponse(BaseModel):\n    ok: bool\n    markdown: str | None = None\n    error: str | None = None\n\n\ndef _api_key_error(req: DebateRequest) -> str | None:\n    has_key = bool(req.api_key and req.api_key.strip())\n    if not has_key and not (os.getenv(\"OPENROUTER_API_KEY\") or os.getenv(\"LLM_API_KEY\")):\n        return \"未配置 API Key：请在左侧填写 OpenRouter Key，或在服务器 .env 中设置 OPENROUTER_API_KEY。\"\n    return None\n\n\n@app.get(\"/\")\nasync def index_page() -> FileResponse:\n    html = _STATIC / \"index.html\"\n    if not html.is_file():\n        raise HTTPException(status_code=500, detail=\"前端文件缺失，请检查 historical_review/web/static/\")\n    return FileResponse(html)\n\n\n@app.get(\"/api/health\")\nasync def health() -> dict[str, str]:\n    return {\"status\": \"ok\"}\n\n\n@app.post(\"/api/debate\", response_model=DebateResponse)\nasync def run_debate(req: DebateRequest) -> DebateResponse:\n    topic = req.topic.strip()\n    if not topic:\n        raise HTTPException(status_code=400, detail=\"议题不能为空\")\n\n    key_err = _api_key_error(req)\n    if key_err:\n        return DebateResponse(ok=False, error=key_err)\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/web/app.py#L49-L85","documentation":"The FastAPI index route resolves historical_review/web/static/index.html via pathlib and raises HTTPException(500, '前端文件缺失...') when the file is not a regular file. The backend itself is healthy — this is a packaging/deployment error: the static frontend asset was not shipped alongside the Python package. Because it is raised on GET /, the whole UI appears broken even though /api/health works.","triggerScenarios":"Running the app from a different working directory so the relative _STATIC path resolves elsewhere; installing the package without package_data/include-package-data so static/ is excluded from the wheel; a Docker image that copies *.py but not historical_review/web/static/; a repo checkout where the static folder was gitignored or deleted.","commonSituations":"pip install from a wheel/sdist that forgot static assets; docker build with COPY src/ only; running uvicorn with an odd --app-dir; frontend build step never ran (if index.html is generated rather than committed).","solutions":["Check the filesystem: ls historical_review/web/static/ — if index.html is absent, restore it from git (git checkout -- historical_review/web/static/) or run the frontend build that produces it.","Run the server from the package root (where historical_review/ resolves) or anchor _STATIC to the module file: Path(__file__).parent / \"static\".","For packaging, add package-data entries (\"static/*\" under historical_review.web) in pyproject/setup.py and build with the files included.","In Docker, ensure COPY includes the static directory (COPY historical_review/ historical_review/).","Sanity-check deployments with GET /api/health (ok) vs GET / (500) to distinguish backend vs asset problems."],"exampleFix":"# before\n_STATIC = Path(\"historical_review/web/static\")  # cwd-dependent\n# after\n_STATIC = Path(__file__).resolve().parent / \"static\"  # anchored to app.py\nhtml = _STATIC / \"index.html\"\nif not html.is_file():\n    raise HTTPException(status_code=500, detail=f\"前端文件缺失: {html}\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\n_STATIC = Path(__file__).resolve().parent / \"static\"\nhtml = _STATIC / \"index.html\"\nif not html.is_file():\n    raise SystemExit(f\"static frontend missing at {html} — restore it or fix packaging\")","typeGuard":null,"tryCatchPattern":"from fastapi import HTTPException\ntry:\n    return FileResponse(html)\nexcept (FileNotFoundError, RuntimeError) as e:\n    raise HTTPException(status_code=500, detail=f\"frontend asset failure: {e}\") from e","preventionTips":["Anchor static dirs to __file__, never to cwd","Include static assets in package_data / Docker COPY","Add a deploy-time smoke test: GET / must return 200"],"tags":["fastapi","static-files","packaging","deployment","http-500"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}