cognee/cognee-frontend/src/app/dashboard/NotebooksAccordion.tsx
Boris aaa1776293
feat: implement new local UI (#1279)
<!-- .github/pull_request_template.md -->

## Description
<!-- Provide a clear description of the changes in this PR -->

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.

---------

Co-authored-by: Daulet Amirkhanov <damirkhanov01@gmail.com>
2025-09-05 15:39:04 +02:00

150 lines
5.4 KiB
TypeScript

"use client";
import { FormEvent, useCallback, useState } from "react";
import { useBoolean } from "@/utils";
import { Accordion, CTAButton, GhostButton, IconButton, Input, Modal } from "@/ui/elements";
import { CloseIcon, MinusIcon, NotebookIcon, PlusIcon } from "@/ui/Icons";
import { Notebook } from "@/ui/elements/Notebook/types";
import { LoadingIndicator } from "@/ui/App";
import { useModal } from "@/ui/elements/Modal";
interface NotebooksAccordionProps {
notebooks: Notebook[];
addNotebook: (name: string) => Promise<Notebook>;
removeNotebook: (id: string) => Promise<void>;
openNotebook: (id: string) => void;
}
export default function NotebooksAccordion({
notebooks,
addNotebook,
removeNotebook,
openNotebook,
}: NotebooksAccordionProps) {
const {
value: isNotebookPanelOpen,
setTrue: openNotebookPanel,
setFalse: closeNotebookPanel,
} = useBoolean(true);
const {
value: isNotebookLoading,
setTrue: notebookLoading,
setFalse: notebookLoaded,
} = useBoolean(false);
// Notebook removal modal
const [notebookToRemove, setNotebookToRemove] = useState<Notebook | null>(null);
const handleNotebookRemove = (notebook: Notebook) => {
setNotebookToRemove(notebook);
openRemoveNotebookModal();
};
const {
value: isRemoveNotebookModalOpen,
setTrue: openRemoveNotebookModal,
setFalse: closeRemoveNotebookModal,
} = useBoolean(false);
const handleNotebookRemoveCancel = () => {
closeRemoveNotebookModal();
setNotebookToRemove(null);
};
const handleNotebookRemoveConfirm = () => {
notebookLoading();
removeNotebook(notebookToRemove!.id)
.finally(notebookLoaded)
.finally(closeRemoveNotebookModal);
setNotebookToRemove(null);
};
const handleNotebookAdd = useCallback((_: object, formEvent?: FormEvent<HTMLFormElement>) => {
if (!formEvent) {
return;
}
formEvent.preventDefault();
const formElements = formEvent.currentTarget;
const notebookName = formElements.notebookName.value.trim();
return addNotebook(notebookName)
}, [addNotebook]);
const {
isModalOpen: isNewNotebookModalOpen,
openModal: openNewNotebookModal,
closeModal: closeNewNotebookModal,
confirmAction: handleNewNotebookSubmit,
isActionLoading: isNewDatasetLoading,
} = useModal<Notebook | void>(false, handleNotebookAdd);
return (
<>
<Accordion
title={<span>Notebooks</span>}
isOpen={isNotebookPanelOpen}
openAccordion={openNotebookPanel}
closeAccordion={closeNotebookPanel}
tools={isNewDatasetLoading ? (
<LoadingIndicator />
) : (
<IconButton onClick={openNewNotebookModal}><PlusIcon /></IconButton>
)}
>
{notebooks.length === 0 && (
<div className="flex flex-row items-baseline-last text-sm text-gray-400 mt-2 px-2">
<span>No notebooks here, add one by clicking +</span>
</div>
)}
{notebooks.map((notebook: Notebook) => (
<div key={notebook.id} className="flex flex-row gap-2.5 items-center justify-between py-1.5 first:pt-3">
<button onClick={() => openNotebook(notebook.id)} className="flex flex-row gap-2 items-center cursor-pointer">
{isNotebookLoading ? <LoadingIndicator /> : <NotebookIcon />}
<span className="text-xs">{notebook.name}</span>
</button>
<div>
{notebook.deletable && <IconButton onClick={() => handleNotebookRemove(notebook)}><MinusIcon /></IconButton>}
</div>
</div>
))}
</Accordion>
<Modal isOpen={isNewNotebookModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Create a new notebook?</span>
<IconButton onClick={closeNewNotebookModal}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Please provide a name for the notebook being created.</div>
<form onSubmit={handleNewNotebookSubmit}>
<div className="max-w-md">
<Input name="notebookName" type="text" placeholder="Notebook name" required />
{/* {newDatasetError && <span className="text-sm pl-4 text-gray-400">{newDatasetError}</span>} */}
</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={() => closeNewNotebookModal()}>cancel</GhostButton>
<CTAButton type="submit">create</CTAButton>
</div>
</form>
</div>
</Modal>
<Modal isOpen={isRemoveNotebookModalOpen}>
<div className="w-full max-w-2xl">
<div className="flex flex-row items-center justify-between">
<span className="text-2xl">Delete <span className="text-indigo-600">{notebookToRemove?.name}</span> notebook?</span>
<IconButton onClick={handleNotebookRemoveCancel}><CloseIcon /></IconButton>
</div>
<div className="mt-8 mb-6">Are you sure you want to delete <span className="text-indigo-600">{notebookToRemove?.name}</span>? This action cannot be undone.</div>
<div className="flex flex-row gap-4 mt-4 justify-end">
<GhostButton type="button" onClick={handleNotebookRemoveCancel}>cancel</GhostButton>
<CTAButton onClick={handleNotebookRemoveConfirm} type="submit">delete</CTAButton>
</div>
</div>
</Modal>
</>
);
}