import { useCallback, useEffect, useState, type FormEvent } from "react"; import { Link, useNavigate } from "react-router-dom"; import { docsApi, notebooksApi } from "../api/endpoints"; import type { Doc, NotebookSummary } from "../api/types"; import { DocPicker } from "../components/DocPicker"; import { PlanetIcon } from "../components/icons/Icons"; import { ConfirmButton, errorMessage, formatDate, Loading, Modal, PageHeader } from "../components/ui"; /** New notebook from a topic: typing it finds and pre-selects the matching posts; hand picking is the fallback. */ export function NewNotebookModal({ onClose, initialDocs = [] }: { onClose: () => void; initialDocs?: string[] }) { const [topic, setTopic] = useState(""); const [matches, setMatches] = useState([]); const [selected, setSelected] = useState(initialDocs); const [manual, setManual] = useState(initialDocs.length > 0); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const navigate = useNavigate(); useEffect(() => { if (manual || topic.trim().length < 2) { if (!manual) setMatches([]); return; } const t = setTimeout(async () => { const page = await docsApi.list({ q: topic.trim(), limit: 50 }); const found = page.items.filter((d) => !d.locked); setMatches(found); setSelected(found.map((d) => d.id)); }, 250); return () => clearTimeout(t); }, [topic, manual]); const submit = async (e: FormEvent) => { e.preventDefault(); setBusy(true); setError(null); try { const nb = await notebooksApi.create(topic.trim() || "Untitled notebook", selected); navigate(`/app/notebooks/${nb.id}`); } catch (err) { setError(errorMessage(err)); setBusy(false); } }; return (
{manual ? ( ) : ( <> {topic.trim().length >= 2 && (

{matches.length ? `${matches.length} posts mention it. Untick any that do not belong.` : "No posts mention that yet."}

)} {matches.length > 0 && (
    {matches.map((d) => (
  • ))}
)} )} {error &&

{error}

}
); } function CardMenu({ onRename, onDelete }: { onRename: () => void; onDelete: () => Promise }) { const [open, setOpen] = useState(false); return (
{open && (
setOpen(false)}> Delete
)}
); } export default function Notebooks() { const [notebooks, setNotebooks] = useState(null); const [creating, setCreating] = useState(false); const [renaming, setRenaming] = useState(null); const [name, setName] = useState(""); const load = useCallback(() => notebooksApi.list().then(setNotebooks), []); useEffect(() => { // "All posts" always exists, so asking the whole archive never needs setup. notebooksApi.archive().finally(load); }, [load]); return (
{!notebooks && }
{notebooks?.map((n) => (
{renaming === n.id ? (
{ e.preventDefault(); await notebooksApi.update(n.id, { title: name }); setRenaming(null); load(); }} > setName(e.target.value)} aria-label="Notebook title" />
) : ( {n.is_archive && }

{n.title}

{(n.description || n.summary) &&

{n.description || n.summary}

}

{n.document_count} posts {n.chat_count ? ` · ${n.chat_count} chats` : ""} {n.artifact_count ? ` · ${n.artifact_count} made` : ""} · {formatDate(n.updated_at)}

)} {!n.is_archive && renaming !== n.id && ( { setRenaming(n.id); setName(n.title); }} onDelete={async () => { await notebooksApi.remove(n.id); load(); }} /> )}
))}
{creating && setCreating(false)} />}
); }