router / deeptutor /api /routers /notebook.py
Piyush1225's picture
push
5df8a73
"""
Notebook API Router
Provides notebook creation, querying, updating, deletion, and record management functions
"""
import json
from typing import AsyncGenerator, Literal
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from deeptutor.agents.notebook import NotebookSummarizeAgent
from deeptutor.services.notebook import notebook_manager
router = APIRouter()
# === Request/Response Models ===
class CreateNotebookRequest(BaseModel):
"""Create notebook request"""
name: str
description: str = ""
color: str = "#3B82F6"
icon: str = "book"
class UpdateNotebookRequest(BaseModel):
"""Update notebook request"""
name: str | None = None
description: str | None = None
color: str | None = None
icon: str | None = None
class AddRecordRequest(BaseModel):
"""Add record request"""
notebook_ids: list[str]
record_type: Literal["solve", "question", "research", "co_writer", "chat", "guided_learning"]
title: str
summary: str = ""
user_query: str
output: str
metadata: dict = {}
kb_name: str | None = None
class RemoveRecordRequest(BaseModel):
"""Remove record request"""
record_id: str
class UpdateRecordRequest(BaseModel):
"""Update an existing notebook record."""
title: str | None = None
summary: str | None = None
user_query: str | None = None
output: str | None = None
metadata: dict | None = None
kb_name: str | None = None
# === API Endpoints ===
async def _build_record_summary(request: AddRecordRequest) -> str:
if request.summary.strip():
return request.summary.strip()
agent = NotebookSummarizeAgent(language=str(request.metadata.get("ui_language", "en")))
return await agent.summarize(
title=request.title,
record_type=request.record_type,
user_query=request.user_query,
output=request.output,
metadata=request.metadata,
)
async def _stream_add_record_with_summary(
request: AddRecordRequest,
) -> AsyncGenerator[str, None]:
try:
agent = NotebookSummarizeAgent(language=str(request.metadata.get("ui_language", "en")))
summary_parts: list[str] = []
if request.summary.strip():
summary_parts.append(request.summary.strip())
yield f"data: {json.dumps({'type': 'summary_chunk', 'content': request.summary.strip()}, ensure_ascii=False)}\n\n"
else:
async for chunk in agent.stream_summary(
title=request.title,
record_type=request.record_type,
user_query=request.user_query,
output=request.output,
metadata=request.metadata,
):
if not chunk:
continue
summary_parts.append(chunk)
yield f"data: {json.dumps({'type': 'summary_chunk', 'content': chunk}, ensure_ascii=False)}\n\n"
summary = "".join(summary_parts).strip()
result = notebook_manager.add_record(
notebook_ids=request.notebook_ids,
record_type=request.record_type,
title=request.title,
summary=summary,
user_query=request.user_query,
output=request.output,
metadata=request.metadata,
kb_name=request.kb_name,
)
payload = {
"type": "result",
"success": True,
"summary": summary,
"record": result["record"],
"added_to_notebooks": result["added_to_notebooks"],
}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
except Exception as exc:
payload = {"type": "error", "detail": str(exc)}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
@router.get("/list")
async def list_notebooks():
"""
Get all notebook list
Returns:
Notebook list (includes summary information)
"""
try:
notebooks = notebook_manager.list_notebooks()
return {"notebooks": notebooks, "total": len(notebooks)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/statistics")
async def get_statistics():
"""
Get notebook statistics
Returns:
Statistics information
"""
try:
stats = notebook_manager.get_statistics()
return stats
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/create")
async def create_notebook(request: CreateNotebookRequest):
"""
Create new notebook
Args:
request: Create request
Returns:
Created notebook information
"""
try:
notebook = notebook_manager.create_notebook(
name=request.name,
description=request.description,
color=request.color,
icon=request.icon,
)
return {"success": True, "notebook": notebook}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{notebook_id}")
async def get_notebook(notebook_id: str):
"""
Get notebook details
Args:
notebook_id: Notebook ID
Returns:
Notebook details (includes all records)
"""
try:
notebook = notebook_manager.get_notebook(notebook_id)
if not notebook:
raise HTTPException(status_code=404, detail="Notebook not found")
return notebook
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{notebook_id}")
async def update_notebook(notebook_id: str, request: UpdateNotebookRequest):
"""
Update notebook information
Args:
notebook_id: Notebook ID
request: Update request
Returns:
Updated notebook information
"""
try:
notebook = notebook_manager.update_notebook(
notebook_id=notebook_id,
name=request.name,
description=request.description,
color=request.color,
icon=request.icon,
)
if not notebook:
raise HTTPException(status_code=404, detail="Notebook not found")
return {"success": True, "notebook": notebook}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{notebook_id}")
async def delete_notebook(notebook_id: str):
"""
Delete notebook
Args:
notebook_id: Notebook ID
Returns:
Deletion result
"""
try:
success = notebook_manager.delete_notebook(notebook_id)
if not success:
raise HTTPException(status_code=404, detail="Notebook not found")
return {"success": True, "message": "Notebook deleted successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/add_record")
async def add_record(request: AddRecordRequest):
"""
Add record to notebook
Args:
request: Add record request
Returns:
Addition result
"""
try:
summary = await _build_record_summary(request)
result = notebook_manager.add_record(
notebook_ids=request.notebook_ids,
record_type=request.record_type,
title=request.title,
summary=summary,
user_query=request.user_query,
output=request.output,
metadata=request.metadata,
kb_name=request.kb_name,
)
return {
"success": True,
"summary": summary,
"record": result["record"],
"added_to_notebooks": result["added_to_notebooks"],
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/add_record_with_summary")
async def add_record_with_summary(request: AddRecordRequest):
"""Add record to notebook and stream generated summary."""
return StreamingResponse(
_stream_add_record_with_summary(request),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
@router.delete("/{notebook_id}/records/{record_id}")
async def remove_record(notebook_id: str, record_id: str):
"""
Remove record from notebook
Args:
notebook_id: Notebook ID
record_id: Record ID
Returns:
Deletion result
"""
try:
success = notebook_manager.remove_record(notebook_id, record_id)
if not success:
raise HTTPException(status_code=404, detail="Record not found")
return {"success": True, "message": "Record removed successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{notebook_id}/records/{record_id}")
async def update_record(notebook_id: str, record_id: str, request: UpdateRecordRequest):
"""Update an existing notebook record in place."""
try:
updated = notebook_manager.update_record(
notebook_id=notebook_id,
record_id=record_id,
title=request.title,
summary=request.summary,
user_query=request.user_query,
output=request.output,
metadata=request.metadata,
kb_name=request.kb_name,
)
if not updated:
raise HTTPException(status_code=404, detail="Record not found")
return {"success": True, "record": updated}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/health")
async def health_check():
"""Health check"""
return {"status": "healthy", "service": "notebook"}