File size: 4,788 Bytes
2e818da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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"}