Spaces:
Sleeping
Sleeping
| """Shared project commit logic. | |
| Used by the COMMIT_PROJECT websocket handler ("Commit"), which durably commits | |
| to Project History as part of the student action. | |
| Snapshots are written under ~/.studybuddy/sessions/ -> that is where the history | |
| listing and resume endpoints (app.routers.library) read from. The primary, | |
| history-facing file is doc_{document_id}.json (one per document set, so re-uploading | |
| the same papers resumes the same project); {project_id}.json and latest.json are | |
| written alongside for convenience. (~/.studybuddy/projects/ is the upload root, a | |
| separate concern -> do not write snapshots there or history won't find them.) | |
| """ | |
| import json | |
| import os | |
| from typing import Any, Dict, List | |
| _SESSIONS_DIR = os.path.expanduser("~/.studybuddy/sessions") | |
| _brain = None | |
| def _get_brain(): | |
| global _brain | |
| if _brain is None: | |
| from app.agents.brain_agent import BrainAgent | |
| _brain = BrainAgent() | |
| return _brain | |
| async def commit_project_snapshot( | |
| project_id: str, | |
| topic: str, | |
| intention: str, | |
| nodes: List[Dict[str, Any]], | |
| content_files: List[str] = None, | |
| document_id: str = "", | |
| file_ids: List[str] = None, | |
| ) -> Dict[str, Any]: | |
| content_files = content_files or [] | |
| file_ids = file_ids or [] | |
| os.makedirs(_SESSIONS_DIR, exist_ok=True) | |
| # A title should describe the project once, not change on every Commit -> reuse | |
| # whatever was already generated for this document set if it exists. Keyed by | |
| # document_id (the history/dedup key) so the title is stable across resumes. | |
| title = "" | |
| doc_path = os.path.join(_SESSIONS_DIR, f"doc_{document_id}.json") if document_id else "" | |
| if doc_path and os.path.exists(doc_path): | |
| try: | |
| with open(doc_path, encoding="utf-8") as f: | |
| title = json.load(f).get("title", "") | |
| except Exception: | |
| pass | |
| if not title: | |
| try: | |
| title = _get_brain().generate_session_title(topic, content_files, intention) | |
| except Exception: | |
| title = topic or "Study Project" | |
| payload = { | |
| "project_id": project_id, | |
| "topic": topic, | |
| "intention": intention, | |
| "nodes": nodes, | |
| "content_files": content_files, | |
| "document_id": document_id, # internal cache key + history/dedup key | |
| "file_ids": file_ids, | |
| "title": title, | |
| } | |
| # doc_{document_id}.json is the file the history listing/resume endpoints read | |
| # (keyed by document set); {project_id}.json + latest.json are written alongside. | |
| paths = [os.path.join(_SESSIONS_DIR, f"{project_id}.json"), | |
| os.path.join(_SESSIONS_DIR, "latest.json")] | |
| if document_id: | |
| paths.append(os.path.join(_SESSIONS_DIR, f"doc_{document_id}.json")) | |
| for path in paths: | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, indent=2) | |
| # Stage snapshot memory through the Cognee service boundary. | |
| payload_str = json.dumps(payload) | |
| from app.services.student_memory import StudentMemoryService | |
| await StudentMemoryService().stage_project_snapshot(project_id, payload_str) | |
| return {"status": "committed", "paths": paths} | |