diff --git a/api/models.py b/api/models.py index 4bbea3b..3626b25 100644 --- a/api/models.py +++ b/api/models.py @@ -24,6 +24,8 @@ class NotebookResponse(BaseModel): archived: bool created: str updated: str + source_count: int + note_count: int # Search models diff --git a/api/routers/notebooks.py b/api/routers/notebooks.py index 6ac8ab9..afde809 100644 --- a/api/routers/notebooks.py +++ b/api/routers/notebooks.py @@ -18,22 +18,33 @@ async def get_notebooks( ): """Get all notebooks with optional filtering and ordering.""" try: - notebooks = await Notebook.get_all(order_by=order_by) + # Build the query with counts + query = f""" + SELECT *, + count(<-reference.in) as source_count, + count(<-artifact.in) as note_count + FROM notebook + ORDER BY {order_by} + """ + + result = await repo_query(query) # Filter by archived status if specified if archived is not None: - notebooks = [nb for nb in notebooks if nb.archived == archived] + result = [nb for nb in result if nb.get("archived") == archived] return [ NotebookResponse( - id=nb.id or "", - name=nb.name, - description=nb.description, - archived=nb.archived or False, - created=str(nb.created), - updated=str(nb.updated), + id=str(nb.get("id", "")), + name=nb.get("name", ""), + description=nb.get("description", ""), + archived=nb.get("archived", False), + created=str(nb.get("created", "")), + updated=str(nb.get("updated", "")), + source_count=nb.get("source_count", 0), + note_count=nb.get("note_count", 0), ) - for nb in notebooks + for nb in result ] except Exception as e: logger.error(f"Error fetching notebooks: {str(e)}") @@ -59,6 +70,8 @@ async def create_notebook(notebook: NotebookCreate): archived=new_notebook.archived or False, created=str(new_notebook.created), updated=str(new_notebook.updated), + source_count=0, # New notebook has no sources + note_count=0, # New notebook has no notes ) except InvalidInputError as e: raise HTTPException(status_code=400, detail=str(e)) @@ -73,17 +86,28 @@ async def create_notebook(notebook: NotebookCreate): async def get_notebook(notebook_id: str): """Get a specific notebook by ID.""" try: - notebook = await Notebook.get(notebook_id) - if not notebook: + # Query with counts for single notebook + query = """ + SELECT *, + count(<-reference.in) as source_count, + count(<-artifact.in) as note_count + FROM $notebook_id + """ + result = await repo_query(query, {"notebook_id": ensure_record_id(notebook_id)}) + + if not result: raise HTTPException(status_code=404, detail="Notebook not found") + nb = result[0] return NotebookResponse( - id=notebook.id or "", - name=notebook.name, - description=notebook.description, - archived=notebook.archived or False, - created=str(notebook.created), - updated=str(notebook.updated), + id=str(nb.get("id", "")), + name=nb.get("name", ""), + description=nb.get("description", ""), + archived=nb.get("archived", False), + created=str(nb.get("created", "")), + updated=str(nb.get("updated", "")), + source_count=nb.get("source_count", 0), + note_count=nb.get("note_count", 0), ) except HTTPException: raise @@ -112,6 +136,29 @@ async def update_notebook(notebook_id: str, notebook_update: NotebookUpdate): await notebook.save() + # Query with counts after update + query = """ + SELECT *, + count(<-reference.in) as source_count, + count(<-artifact.in) as note_count + FROM $notebook_id + """ + result = await repo_query(query, {"notebook_id": ensure_record_id(notebook_id)}) + + if result: + nb = result[0] + return NotebookResponse( + id=str(nb.get("id", "")), + name=nb.get("name", ""), + description=nb.get("description", ""), + archived=nb.get("archived", False), + created=str(nb.get("created", "")), + updated=str(nb.get("updated", "")), + source_count=nb.get("source_count", 0), + note_count=nb.get("note_count", 0), + ) + + # Fallback if query fails return NotebookResponse( id=notebook.id or "", name=notebook.name, @@ -119,6 +166,8 @@ async def update_notebook(notebook_id: str, notebook_update: NotebookUpdate): archived=notebook.archived or False, created=str(notebook.created), updated=str(notebook.updated), + source_count=0, + note_count=0, ) except HTTPException: raise diff --git a/frontend/src/app/(dashboard)/notebooks/components/NotebookCard.tsx b/frontend/src/app/(dashboard)/notebooks/components/NotebookCard.tsx index 704c8ee..7cff89d 100644 --- a/frontend/src/app/(dashboard)/notebooks/components/NotebookCard.tsx +++ b/frontend/src/app/(dashboard)/notebooks/components/NotebookCard.tsx @@ -5,7 +5,7 @@ import { NotebookResponse } from '@/lib/types/api' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' -import { MoreHorizontal, Archive, ArchiveRestore, Trash2 } from 'lucide-react' +import { MoreHorizontal, Archive, ArchiveRestore, Trash2, FileText, StickyNote } from 'lucide-react' import { formatDistanceToNow } from 'date-fns' import { DropdownMenu, @@ -108,10 +108,22 @@ export function NotebookCard({ notebook }: NotebookCardProps) { {notebook.description || 'No description'} - +
Updated {formatDistanceToNow(new Date(notebook.updated), { addSuffix: true })}
+ + {/* Item counts footer */} +
+ + + {notebook.source_count} + + + + {notebook.note_count} + +
diff --git a/frontend/src/components/podcasts/forms/EpisodeProfileFormDialog.tsx b/frontend/src/components/podcasts/forms/EpisodeProfileFormDialog.tsx index b6166d6..eafb2ed 100644 --- a/frontend/src/components/podcasts/forms/EpisodeProfileFormDialog.tsx +++ b/frontend/src/components/podcasts/forms/EpisodeProfileFormDialog.tsx @@ -229,7 +229,7 @@ export function EpisodeProfileFormDialog({ type="number" min={3} max={20} - {...register('num_segments')} + {...register('num_segments', { valueAsNumber: true })} /> {errors.num_segments ? (

{errors.num_segments.message}

diff --git a/frontend/src/lib/types/api.ts b/frontend/src/lib/types/api.ts index 55a7ff0..8c0dcdc 100644 --- a/frontend/src/lib/types/api.ts +++ b/frontend/src/lib/types/api.ts @@ -5,6 +5,8 @@ export interface NotebookResponse { archived: boolean created: string updated: string + source_count: number + note_count: number } export interface NoteResponse {