"""原子信息 REST 路由""" from fastapi import APIRouter, HTTPException import heurams.kernel.particles as pt import heurams.services.timer as timer from heurams.context import config_var from heurams.services.favorite_service import favorite_manager from heurams.services.logger import get_logger from ..dependencies import get_repo, compute_repo_progress logger = get_logger(__name__) router = APIRouter(prefix="/api/repos/{package}", tags=["atoms"]) def _safe_lastdate(e) -> int: try: return e.lastdate() except (KeyError, TypeError): return 0 @router.get("/atoms") def list_atoms(package: str, page: int = 1, page_size: int = 50) -> dict: """获取仓库的原子列表""" repo = get_repo(package) if repo is None: raise HTTPException(status_code=404, detail=f"仓库 '{package}' 未找到") idents = list(repo.ident_index) total = len(idents) start = (page - 1) * page_size end = start + page_size page_idents = idents[start:end] items = [] for ident in page_idents: e = pt.Electron.from_data( electronic_data=repo.electronic_data_lict.get_itemic_unit(ident), algo_name=repo.config["algorithm"], ) items.append({ "ident": ident, "activated": bool(e.is_activated()), "due": e.is_due(), "rept": e.rept(real_rept=True), "interval": e["interval"], "next_date": e.nextdate(), "last_date": _safe_lastdate(e), }) return {"total": total, "page": page, "page_size": page_size, "items": items} @router.get("/atoms/{ident}") def get_atom(package: str, ident: str) -> dict: """获取单个原子的完整信息""" repo = get_repo(package) if repo is None: raise HTTPException(status_code=404, detail=f"仓库 '{package}' 未找到") if ident not in repo.ident_index: raise HTTPException(status_code=404, detail=f"原子 '{ident}' 未找到") n = pt.Nucleon.from_data( nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(ident) ) e = pt.Electron.from_data( electronic_data=repo.electronic_data_lict.get_itemic_unit(ident), algo_name=repo.config["algorithm"], ) nucleon_data = {} for key in n: if key in ("puzzles",): continue try: nucleon_data[key] = n[key] except Exception: pass return { "ident": ident, "electron": { "activated": bool(e.is_activated()), "due": e.is_due(), "rept": e.rept(real_rept=True), "interval": e["interval"], "next_date": e.nextdate(), "last_date": _safe_lastdate(e), "last_modify": e.last_modify(), "algodata": e.algodata.get(e.algoname, {}), }, "nucleon": nucleon_data, } @router.get("/prepare") def prepare_repo(package: str) -> dict: """获取仓库复习准备数据(repo 信息 + 所有原子状态预览)""" repo = get_repo(package) if repo is None: raise HTTPException(status_code=404, detail=f"仓库 '{package}' 未找到") progress = compute_repo_progress(repo) today = timer.get_daystamp() atoms = [] review_count = 0 new_count = 0 for i in repo.ident_index: e = pt.Electron.from_data( electronic_data=repo.electronic_data_lict.get_itemic_unit(i), algo_name=repo.config["algorithm"], ) activated = bool(e.is_activated()) due = e.is_due() if activated and due: status = "R" review_count += 1 elif activated: status = "A" else: status = "U" new_count += 1 n = pt.Nucleon.from_data( nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i) ) content = n.get("content", "") or n.get("tts_text", "") or i atoms.append({ "ident": i, "status": status, "rept": e.rept(real_rept=True), "interval": e["interval"], "next_date": e.nextdate(), "content": str(content)[:60], }) schedule_num = repo.config["scheduled_num"] return { "repo": { "package": repo.manifest.get("package", package), "title": repo.manifest.get("title", package), "author": repo.manifest.get("author", ""), "desc": repo.manifest.get("desc", ""), "source": str(repo.source), "algorithm": repo.config["algorithm"], "scheduled_num": schedule_num, }, "progress": progress, "preview": {"review": review_count, "new": new_count}, "today": today, "atoms": atoms, "total_atoms": len(atoms), } @router.get("/atoms/{ident}/favorite") def get_favorite(package: str, ident: str) -> dict: """检查原子是否已收藏""" repo = get_repo(package) if repo is None: raise HTTPException(status_code=404) rel_path = _rel_repo_path(repo) return {"favorited": favorite_manager.has(rel_path, ident)} @router.post("/atoms/{ident}/favorite") def toggle_favorite(package: str, ident: str) -> dict: """切换收藏状态""" repo = get_repo(package) if repo is None: raise HTTPException(status_code=404) rel_path = _rel_repo_path(repo) if favorite_manager.has(rel_path, ident): favorite_manager.remove(rel_path, ident) return {"favorited": False, "action": "removed"} else: favorite_manager.add(rel_path, ident) return {"favorited": True, "action": "added"} def _rel_repo_path(repo) -> str: """获取仓库相对路径""" from heurams.context import workdir data_repo = workdir / "data" / "repo" try: return str(repo.source.relative_to(data_repo)) except (ValueError, AttributeError): return str(repo.source)