api.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. """FastAPI:把 ck_post / ck_knowledge_item 暴露给 web 可视化,并托管单页前端。
  2. 启动:
  3. CK_ENV_FILE=/path/.env uvicorn creation_knowledge.api:app --host 0.0.0.0 --port 8900
  4. API 在 /api/*,前端单页在 /。
  5. """
  6. from __future__ import annotations
  7. import os
  8. from functools import lru_cache
  9. from pathlib import Path
  10. from fastapi import FastAPI, HTTPException
  11. from fastapi.middleware.cors import CORSMiddleware
  12. from fastapi.staticfiles import StaticFiles
  13. from creation_knowledge.config import PgConfig, Settings
  14. from creation_knowledge.integrations import extractor as extractor_mod
  15. from creation_knowledge.integrations.db import CkStore
  16. from creation_knowledge.prompts import PROMPT_VERSION, load_prompt
  17. from creation_knowledge.stages import deconstruct as decon_stage
  18. from creation_knowledge.stages import screen as screen_stage
  19. from creation_knowledge.stages import split as split_stage
  20. WEB_DIR = Path(__file__).resolve().parent.parent / "web"
  21. def _env_file() -> str:
  22. return os.getenv("CK_ENV_FILE", ".env")
  23. @lru_cache(maxsize=1)
  24. def _store() -> CkStore:
  25. return CkStore(PgConfig.from_env(_env_file()))
  26. @lru_cache(maxsize=1)
  27. def _settings() -> Settings:
  28. return Settings.from_env(_env_file())
  29. app = FastAPI(title="创作知识")
  30. app.add_middleware(
  31. CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
  32. )
  33. @app.get("/api/posts")
  34. def list_posts() -> list[dict]:
  35. return _store().posts_overview()
  36. @app.get("/api/posts/{post_id}")
  37. def get_post(post_id: str) -> dict:
  38. post = _store().read_post(post_id)
  39. if not post:
  40. raise HTTPException(status_code=404, detail="post not found")
  41. return post
  42. @app.get("/api/posts/{post_id}/items")
  43. def get_items(post_id: str) -> list[dict]:
  44. return _store().read_items(post_id)
  45. @app.get("/api/prompts")
  46. def get_prompts() -> dict:
  47. """各环节用到的提示词(system + user 模板),供前端弹窗查看。"""
  48. s = _settings()
  49. return {
  50. "version": PROMPT_VERSION,
  51. "items": [
  52. {"key": "extract", "label": "图文提取", "model": s.video_model,
  53. "system": extractor_mod._SYSTEM_PROMPT, "user": load_prompt("extract")},
  54. {"key": "extract_video", "label": "视频提炼(原生整段)", "model": s.video_model,
  55. "system": "(无独立 system,提示词自含)", "user": load_prompt("extract_video")},
  56. {"key": "screen", "label": "筛选", "model": s.llm_model,
  57. "system": screen_stage.SYSTEM, "user": load_prompt("screen")},
  58. {"key": "split", "label": "拆分", "model": s.llm_model,
  59. "system": split_stage.SYSTEM, "user": load_prompt("split")},
  60. {"key": "deconstruct", "label": "解构", "model": s.llm_model,
  61. "system": decon_stage.SYSTEM, "user": load_prompt("deconstruct")},
  62. ],
  63. }
  64. # 视频帧静态服务(card.url = /frames/<post_id>/<file>)
  65. _frames_dir = Path(_settings().frames_dir)
  66. _frames_dir.mkdir(parents=True, exist_ok=True)
  67. app.mount("/frames", StaticFiles(directory=str(_frames_dir)), name="frames")
  68. # 媒体静态服务(card.url = /data/<run_id>/<platform>/<post_id>/<file>)
  69. _data_dir = Path(_settings().data_dir or "data")
  70. _data_dir.mkdir(parents=True, exist_ok=True)
  71. app.mount("/data", StaticFiles(directory=str(_data_dir)), name="data")
  72. # 单页前端挂在最后(catch-all),不影响上面的 /api、/frames、/data
  73. if WEB_DIR.exists():
  74. app.mount("/", StaticFiles(directory=str(WEB_DIR), html=True), name="web")