open-notebook/api/routers/notes.py
Luis Novo b7e656a319
Version 1 (#160)
New front-end
Launch Chat API
Manage Sources
Enable re-embedding of all contents
Sources can be added without a notebook now
Improved settings
Enable model selector on all chats
Background processing for better experience
Dark mode
Improved Notes

Improved Docs: 
- Remove all Streamlit references from documentation
- Update deployment guides with React frontend setup
- Fix Docker environment variables format (SURREAL_URL, SURREAL_PASSWORD)
- Update docker image tag from :latest to :v1-latest
- Change navigation references (Settings → Models to just Models)
- Update development setup to include frontend npm commands
- Add MIGRATION.md guide for users upgrading from Streamlit
- Update quick-start guide with correct environment variables
- Add port 5055 documentation for API access
- Update project structure to reflect frontend/ directory
- Remove outdated source-chat documentation files
2025-10-18 12:46:22 -03:00

180 lines
No EOL
6.4 KiB
Python

from typing import List, Literal, Optional
from fastapi import APIRouter, HTTPException, Query
from loguru import logger
from api.models import NoteCreate, NoteResponse, NoteUpdate
from open_notebook.domain.notebook import Note
from open_notebook.exceptions import InvalidInputError
router = APIRouter()
@router.get("/notes", response_model=List[NoteResponse])
async def get_notes(
notebook_id: Optional[str] = Query(None, description="Filter by notebook ID")
):
"""Get all notes with optional notebook filtering."""
try:
if notebook_id:
# Get notes for a specific notebook
from open_notebook.domain.notebook import Notebook
notebook = await Notebook.get(notebook_id)
if not notebook:
raise HTTPException(status_code=404, detail="Notebook not found")
notes = await notebook.get_notes()
else:
# Get all notes
notes = await Note.get_all(order_by="updated desc")
return [
NoteResponse(
id=note.id or "",
title=note.title,
content=note.content,
note_type=note.note_type,
created=str(note.created),
updated=str(note.updated),
)
for note in notes
]
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching notes: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error fetching notes: {str(e)}")
@router.post("/notes", response_model=NoteResponse)
async def create_note(note_data: NoteCreate):
"""Create a new note."""
try:
# Auto-generate title if not provided and it's an AI note
title = note_data.title
if not title and note_data.note_type == "ai" and note_data.content:
from open_notebook.graphs.prompt import graph as prompt_graph
prompt = "Based on the Note below, please provide a Title for this content, with max 15 words"
result = await prompt_graph.ainvoke(
{ # type: ignore[arg-type]
"input_text": note_data.content,
"prompt": prompt
}
)
title = result.get("output", "Untitled Note")
# Validate note_type
note_type: Optional[Literal["human", "ai"]] = None
if note_data.note_type in ("human", "ai"):
note_type = note_data.note_type # type: ignore[assignment]
elif note_data.note_type is not None:
raise HTTPException(status_code=400, detail="note_type must be 'human' or 'ai'")
new_note = Note(
title=title,
content=note_data.content,
note_type=note_type,
)
await new_note.save()
# Add to notebook if specified
if note_data.notebook_id:
from open_notebook.domain.notebook import Notebook
notebook = await Notebook.get(note_data.notebook_id)
if not notebook:
raise HTTPException(status_code=404, detail="Notebook not found")
await new_note.add_to_notebook(note_data.notebook_id)
return NoteResponse(
id=new_note.id or "",
title=new_note.title,
content=new_note.content,
note_type=new_note.note_type,
created=str(new_note.created),
updated=str(new_note.updated),
)
except HTTPException:
raise
except InvalidInputError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error creating note: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error creating note: {str(e)}")
@router.get("/notes/{note_id}", response_model=NoteResponse)
async def get_note(note_id: str):
"""Get a specific note by ID."""
try:
note = await Note.get(note_id)
if not note:
raise HTTPException(status_code=404, detail="Note not found")
return NoteResponse(
id=note.id or "",
title=note.title,
content=note.content,
note_type=note.note_type,
created=str(note.created),
updated=str(note.updated),
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching note {note_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error fetching note: {str(e)}")
@router.put("/notes/{note_id}", response_model=NoteResponse)
async def update_note(note_id: str, note_update: NoteUpdate):
"""Update a note."""
try:
note = await Note.get(note_id)
if not note:
raise HTTPException(status_code=404, detail="Note not found")
# Update only provided fields
if note_update.title is not None:
note.title = note_update.title
if note_update.content is not None:
note.content = note_update.content
if note_update.note_type is not None:
if note_update.note_type in ("human", "ai"):
note.note_type = note_update.note_type # type: ignore[assignment]
else:
raise HTTPException(status_code=400, detail="note_type must be 'human' or 'ai'")
await note.save()
return NoteResponse(
id=note.id or "",
title=note.title,
content=note.content,
note_type=note.note_type,
created=str(note.created),
updated=str(note.updated),
)
except HTTPException:
raise
except InvalidInputError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error updating note {note_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error updating note: {str(e)}")
@router.delete("/notes/{note_id}")
async def delete_note(note_id: str):
"""Delete a note."""
try:
note = await Note.get(note_id)
if not note:
raise HTTPException(status_code=404, detail="Note not found")
await note.delete()
return {"message": "Note deleted successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting note {note_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error deleting note: {str(e)}")