Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| from typing import Any | |
| from fastapi import APIRouter, HTTPException | |
| from app.services.observability_summary import observability_summary | |
| from app.services.project_activity import ProjectActivityService | |
| from app.services.project_intelligence import ProjectIntelligenceService | |
| from app.services.project_service import ProjectService | |
| from app.services.student_memory import StudentMemoryService | |
| router = APIRouter(prefix="/api/projects", tags=["control-room"]) | |
| _project_service = ProjectService() | |
| _activity = ProjectActivityService() | |
| def _is_demo_mode() -> bool: | |
| return os.getenv("DEPLOYMENT_ENV", "desktop") == "demo" | |
| def _artifact_roots() -> dict[str, str]: | |
| home = Path.home() / ".studybuddy" | |
| return { | |
| "graphs": str(home / "graphs"), | |
| "sessions": str(home / "sessions"), | |
| "citation_graphs": str(home / "citation_graphs"), | |
| "cache": str(home / "cache"), | |
| "annotations": str(home / "annotations"), | |
| "summaries": str(home / "summaries"), | |
| "cognee": str(home / "cognee"), | |
| } | |
| def _exists(path: str) -> bool: | |
| return bool(path) and os.path.exists(path) | |
| def _count_files(path: str, suffix: str = "") -> int: | |
| if not os.path.isdir(path): | |
| return 0 | |
| return len([name for name in os.listdir(path) if not suffix or name.endswith(suffix)]) | |
| def _file_meta(path: str) -> tuple[int, float]: | |
| if not _exists(path): | |
| return 0, 0.0 | |
| try: | |
| return os.path.getsize(path), os.path.getmtime(path) | |
| except OSError: | |
| return 0, 0.0 | |
| def _artifact(id_: str, label: str, saved: bool, detail: str = "", path: str = "") -> dict[str, Any]: | |
| size, updated_at = _file_meta(path) if path else (0, 0.0) | |
| return { | |
| "id": id_, | |
| "key": id_, | |
| "label": label, | |
| "status": "saved" if saved else "missing", | |
| "exists": saved, | |
| "detail": detail, | |
| "path": path or detail, | |
| "size_bytes": size, | |
| "updated_at": updated_at, | |
| } | |
| def project_artifacts(project_id: str, document_id: str = "") -> list[dict[str, Any]]: | |
| roots = _artifact_roots() | |
| graph_path = os.path.join(roots["graphs"], f"{project_id}.json") | |
| doc_graph_path = os.path.join(roots["graphs"], f"doc_{document_id}.json") if document_id else "" | |
| session_path = os.path.join(roots["sessions"], f"{project_id}.json") | |
| citation_path = os.path.join(roots["citation_graphs"], f"{project_id}.json") | |
| annotation_count = _count_files(roots["annotations"], ".json") | |
| summary_count = _count_files(roots["summaries"], ".md") | |
| cache_count = _count_files(roots["cache"], ".json") | |
| return [ | |
| _artifact("project_graph", "Project graph", _exists(graph_path), graph_path, graph_path), | |
| _artifact("document_graph_cache", "Document graph cache", _exists(doc_graph_path), doc_graph_path, doc_graph_path), | |
| _artifact("project_snapshot", "Project snapshot", _exists(session_path), session_path, session_path), | |
| _artifact("citation_graph", "Citation Graph", _exists(citation_path), citation_path, citation_path), | |
| _artifact("output_cache", "Generated output cache", cache_count > 0, f"{cache_count} cached item(s)"), | |
| _artifact("annotations", "Annotations", annotation_count > 0, f"{annotation_count} annotation file(s)"), | |
| _artifact("summary", "Summary artifacts", summary_count > 0, f"{summary_count} markdown summary file(s)"), | |
| _artifact("cognee", "Cognee memory store", os.path.isdir(roots["cognee"]), roots["cognee"], roots["cognee"]), | |
| ] | |
| def get_project_activity(project_id: str, limit: int = 50): | |
| if _project_service.load(project_id) is None: | |
| raise HTTPException(404, "Project not found") | |
| return {"project_id": project_id, "items": [row.model_dump() for row in _activity.list(project_id, limit=limit)]} | |
| def get_project_artifacts(project_id: str): | |
| project = _project_service.load(project_id) | |
| if project is None: | |
| raise HTTPException(404, "Project not found") | |
| document_id = _project_service.derive_document_id(project) | |
| return {"project_id": project_id, "items": project_artifacts(project_id, document_id=document_id)} | |
| async def get_control_room(project_id: str, fast: bool = True): | |
| project = _project_service.load(project_id) | |
| if project is None: | |
| raise HTTPException(404, "Project not found") | |
| document_id = _project_service.derive_document_id(project) | |
| activities = [row.model_dump() for row in _activity.list(project_id, limit=25)] | |
| artifacts = project_artifacts(project_id, document_id=document_id) | |
| active_jobs = [row for row in activities if row.get("status") == "running"][:5] | |
| memory = ( | |
| { | |
| "project_id": project_id, | |
| "state": "disabled", | |
| "profile_dataset": False, | |
| "project_dataset": False, | |
| "inventory_ok": False, | |
| "provenance_ok": False, | |
| "export_ok": False, | |
| "last_error": "Cognee memory is disabled in demo mode.", | |
| } | |
| if _is_demo_mode() | |
| else { | |
| "project_id": project_id, | |
| "state": "refreshing" if active_jobs else "cached", | |
| "profile_dataset": None, | |
| "project_dataset": None, | |
| "inventory_ok": False, | |
| "provenance_ok": False, | |
| "export_ok": False, | |
| "last_error": "Memory inspection is loading in the background.", | |
| } | |
| if fast | |
| else await StudentMemoryService().memory_status(project_id, include_liveness=True) | |
| ) | |
| intelligence = ProjectIntelligenceService().status(project) | |
| return { | |
| "project": {**project.model_dump(), "document_id": document_id}, | |
| "brief": intelligence["brief"], | |
| "stats": intelligence["stats"], | |
| "settings_summary": intelligence["settings_summary"], | |
| "live_log": activities, | |
| "memory": memory, | |
| "memory_status": memory, | |
| "activity": activities, | |
| "artifacts": artifacts, | |
| "debug_artifacts": artifacts, | |
| "active_jobs": active_jobs, | |
| "observability": observability_summary(), | |
| } | |