feat(assistant): #94-#97 historique permanent, sidebar IA, panneau + et bouton rond
This commit is contained in:
@@ -11,6 +11,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from backend.agent.loop import run_agent
|
||||
from backend.ai_chat import chat_completion, stream_completion
|
||||
from backend.ai_history import delete_session, get_session, list_sessions, upsert_session
|
||||
from backend.auth.middleware import check_vault_access, require_auth
|
||||
from backend.bookslm import (
|
||||
build_general_system_prompt,
|
||||
@@ -605,3 +606,86 @@ async def api_bookslm_agent(
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ── Persistent conversation history (#95) ─────────────────────────────
|
||||
|
||||
|
||||
class BookslmSession(BaseModel):
|
||||
"""Full assistant conversation persisted server-side (#95).
|
||||
|
||||
The shape mirrors the client session object so round-tripping is lossless
|
||||
(transient fields such as ``confirmation``/``payload`` are stripped
|
||||
client-side before upload).
|
||||
"""
|
||||
|
||||
id: str = Field(description="Stable session id (s-<ts36>-<rand>)")
|
||||
title: str = Field(default="", description="Derived human title")
|
||||
mode: str = Field(default="general", description="'directory', 'documents' or 'general'")
|
||||
vault: str | None = Field(default=None, description="Vault name for the context")
|
||||
directory: str = Field(default="", description="Relative directory path")
|
||||
documents: list[dict[str, str]] = Field(
|
||||
default_factory=list,
|
||||
description="Open documents for the 'documents' mode",
|
||||
)
|
||||
context: str = Field(default="", description="Client context key of the assistant")
|
||||
createdAt: int | None = Field(default=None, description="Creation ISO ms timestamp")
|
||||
updatedAt: int | None = Field(default=None, description="Last update ISO ms timestamp")
|
||||
messages: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="Conversation turns [{role, content}]",
|
||||
)
|
||||
|
||||
|
||||
def _session_user(current_user) -> str:
|
||||
return current_user.get("username") if isinstance(current_user, dict) else "" # type: ignore[return-value]
|
||||
|
||||
|
||||
@router.get("/history", response_model=dict[str, list[dict[str, Any]]])
|
||||
async def api_bookslm_history_list(current_user=Depends(require_auth)):
|
||||
"""List the user's assistant conversations (summaries, most recent first).
|
||||
|
||||
Messages are not included to keep the list light; fetch the full
|
||||
conversation with ``GET /history/{id}`` when a session is opened.
|
||||
"""
|
||||
return {"sessions": list_sessions(_session_user(current_user))}
|
||||
|
||||
|
||||
@router.get("/history/{session_id}", response_model=dict[str, Any] | None)
|
||||
async def api_bookslm_history_get(
|
||||
session_id: str,
|
||||
current_user=Depends(require_auth),
|
||||
):
|
||||
"""Return a single full conversation, or 404 when unknown."""
|
||||
session = get_session(_session_user(current_user), session_id)
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="Conversation not found")
|
||||
return session
|
||||
|
||||
|
||||
@router.put("/history/{session_id}", response_model=dict[str, Any])
|
||||
async def api_bookslm_history_upsert(
|
||||
session_id: str,
|
||||
session: BookslmSession,
|
||||
current_user=Depends(require_auth),
|
||||
):
|
||||
"""Create or update a conversation for the current user.
|
||||
|
||||
The body id is forced to the path id so a client never writes under a
|
||||
different key by mistake.
|
||||
"""
|
||||
payload = session.model_dump()
|
||||
payload["id"] = session_id
|
||||
stored = upsert_session(_session_user(current_user), payload)
|
||||
if stored is None:
|
||||
raise HTTPException(status_code=400, detail="Invalid session id")
|
||||
return stored
|
||||
|
||||
|
||||
@router.delete("/history/{session_id}", response_model=dict[str, bool])
|
||||
async def api_bookslm_history_delete(
|
||||
session_id: str,
|
||||
current_user=Depends(require_auth),
|
||||
):
|
||||
"""Delete a conversation for the current user."""
|
||||
return {"ok": delete_session(_session_user(current_user), session_id)}
|
||||
|
||||
Reference in New Issue
Block a user