study-buddy / app /services /project_commit.py
GitHub Actions
deploy b3d187d69e5df10bb2ec396e89f539f853465a06
14184e3
Raw
History Blame Contribute Delete
3.19 kB
"""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}