study-buddy / app /routers /session.py
GitHub Actions
deploy d092bea3608b7a29952f16357fda39b7a29e399b
2e818da
Raw
History Blame Contribute Delete
4.79 kB
from typing import Any, Dict, List
from fastapi import APIRouter
from pydantic import BaseModel
from app.agents.brain_agent import BrainAgent
from app.rag.chromadb_client import ChromaDBClient
from app.schemas.session import Intention, Session
from app.services.graph_state import GraphStateManager
from app.services.project_service import ProjectService
from app.services.student_memory import StudentMemoryService
from app.websockets.handlers import get_db, get_graph_manager, _journal
router = APIRouter(prefix="/session", tags=["session"])
_brain = BrainAgent()
_memory = StudentMemoryService()
_project_service = ProjectService()
# In-progress ingestion status
_ingest_status: Dict[str, str] = {} # project_id -> "indexing" | "extracting" | "ready" | "error"
class CreateSessionRequest(BaseModel):
topic: str
intention: Intention
class CreateSessionResponse(BaseModel):
project_id: str
topic: str
intention: Intention
@router.post("/create", response_model=CreateSessionResponse)
def create_session(req: CreateSessionRequest):
project = _project_service.create(req.topic or "Study Project", str(req.intention.value))
_ingest_status[project.project_id] = "indexing"
return CreateSessionResponse(
project_id=project.project_id, topic=req.topic, intention=req.intention
)
@router.get("/ingest-status/{project_id}")
def ingest_status(project_id: str):
return {"status": _ingest_status.get(project_id, "unknown")}
def set_ingest_status(project_id: str, status: str) -> None:
_ingest_status[project_id] = status
class CommitRequest(BaseModel):
project_id: str
topic: str
intention: str
nodes: List[Dict[str, Any]]
content_files: List[str] = []
document_id: str = ""
file_ids: List[str] = []
@router.post("/commit")
async def commit_session(req: CommitRequest):
"""Kept for compatibility -> the frontend no longer calls this directly.
Session History is now committed automatically as part of Push
(COMMIT_PROJECT, see handlers.py's commit_project_snapshot() call),
which calls the same commit_project_snapshot() this route delegates to.
"""
from app.services.project_commit import commit_project_snapshot
return await commit_project_snapshot(
req.project_id, req.topic, req.intention, req.nodes,
req.content_files, req.document_id, req.file_ids,
)
@router.get("/trajectory/{document_id}")
def get_trajectory(document_id: str):
"""Persistent learning trajectory for a paper (for the Evaluation window)."""
from app.services.memory_service import MemoryService
return {"trajectory": MemoryService().read_trajectory(document_id)}
class ClearRequest(BaseModel):
project_id: str
document_id: str = ""
file_ids: List[str] = []
@router.post("/clear")
def clear_session(req: ClearRequest):
"""Wipe everything tied to this session/document -> persistent and in-memory.
A session is exactly one input document set, so clearing means starting over
with nothing: no curriculum tree, no lessons, no chunks, no uploaded files.
Cognee's cross-session student-memory profile is intentionally NOT touched ->
it's meant to persist and grow across documents, not reset with them.
"""
import shutil
from pathlib import Path
from app.rag.evidence_ingestion import EvidenceIngestionService
from app.services.annotation_service import get_annotation_service
from app.services.memory_service import MemoryService
from app.services.project_files import project_upload_dir
_journal.clear_session(req.project_id)
mgr = get_graph_manager()
mgr.clear_session(req.project_id)
EvidenceIngestionService().reset_project(req.project_id)
_ingest_status.pop(req.project_id, None)
base = Path.home() / ".studybuddy"
session_file = base / "sessions" / f"{req.project_id}.json"
if session_file.exists():
session_file.unlink()
document_id = req.document_id
if document_id:
for p in (
base / "sessions" / f"doc_{document_id}.json",
base / "graphs" / f"doc_{document_id}.json",
):
if p.exists():
p.unlink()
get_annotation_service().delete_for_document(document_id)
MemoryService().flush_cluster(document_id)
# document_id is a combined hash of the whole file set, not a single PDF's cache
# filename -> the per-file PDF cache is keyed by file_ids instead.
for file_id in req.file_ids:
p = base / "pdfs" / f"{file_id}.pdf"
if p.exists():
p.unlink()
# This session's own upload folder -> never a shared directory.
shutil.rmtree(project_upload_dir(req.project_id), ignore_errors=True)
return {"status": "cleared"}