From 98e792b5954a754b9583e1a97b9dec2bb87c7695 Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Fri, 22 May 2026 09:49:15 +0530 Subject: [PATCH 1/2] feat(collection): collection edit --- app/(main)/knowledge-base/page.tsx | 47 ++++++-- app/api/collections/[collection_id]/route.ts | 31 +++++- .../document/UploadDocumentModal.tsx | 2 +- .../knowledge-base/CollectionsList.tsx | 34 ++++-- .../knowledge-base/CreateCollectionForm.tsx | 52 ++++++--- .../knowledge-base/EditCollectionModal.tsx | 105 ++++++++++++++++++ app/components/knowledge-base/index.ts | 9 ++ app/hooks/useCollections.ts | 88 ++++++++++++++- app/lib/utils/knowledgeBaseCache.ts | 20 ++++ 9 files changed, 350 insertions(+), 38 deletions(-) create mode 100644 app/components/knowledge-base/EditCollectionModal.tsx create mode 100644 app/components/knowledge-base/index.ts diff --git a/app/(main)/knowledge-base/page.tsx b/app/(main)/knowledge-base/page.tsx index 0d4ffe67..8c756485 100644 --- a/app/(main)/knowledge-base/page.tsx +++ b/app/(main)/knowledge-base/page.tsx @@ -1,18 +1,21 @@ "use client"; import { useState } from "react"; -import CollectionsList from "@/app/components/knowledge-base/CollectionsList"; -import CreateCollectionForm from "@/app/components/knowledge-base/CreateCollectionForm"; -import CollectionDetail from "@/app/components/knowledge-base/CollectionDetail"; -import DocumentPickerModal from "@/app/components/knowledge-base/DocumentPickerModal"; -import DeleteCollectionModal from "@/app/components/knowledge-base/DeleteCollectionModal"; -import DocumentPreviewModal from "@/app/components/knowledge-base/DocumentPreviewModal"; +import { + CollectionDetail, + CollectionsList, + CreateCollectionForm, + DeleteCollectionModal, + DocumentPickerModal, + DocumentPreviewModal, + EditCollectionModal, +} from "@/app/components/knowledge-base"; import { Modal, Loader } from "@/app/components/ui"; import { Sidebar, PageHeader } from "@/app/components"; import { BookOpenIcon } from "@/app/components/icons"; import { useApp } from "@/app/lib/context/AppContext"; import { useCollections } from "@/app/hooks/useCollections"; -import { Document } from "@/app/lib/types/document"; +import { Document, Collection } from "@/app/lib/types/document"; export default function KnowledgeBasePage() { const { sidebarCollapsed } = useApp(); @@ -22,12 +25,15 @@ export default function KnowledgeBasePage() { selectedCollection, isLoading, isLoadingDetail, + isLoadingDocuments, isCreating, setSelectedCollection, fetchCollectionDetails, createCollection, deleteCollection, + updateCollection, fetchAndPreviewDoc, + fetchDocuments, } = useCollections(); const [showCreateForm, setShowCreateForm] = useState(false); @@ -39,6 +45,9 @@ export default function KnowledgeBasePage() { const [showDocPreviewModal, setShowDocPreviewModal] = useState(false); const [previewDoc, setPreviewDoc] = useState(null); const [isPreviewLoading, setIsPreviewLoading] = useState(false); + const [collectionToEdit, setCollectionToEdit] = useState( + null, + ); const [collectionName, setCollectionName] = useState(""); const [collectionDescription, setCollectionDescription] = useState(""); @@ -65,6 +74,7 @@ export default function KnowledgeBasePage() { const handleCreateNew = () => { setShowCreateForm(true); setSelectedCollection(null); + fetchDocuments(); }; const handleCancelCreate = () => { @@ -94,6 +104,18 @@ export default function KnowledgeBasePage() { setShowConfirmDelete(true); }; + const handleRequestEdit = (collection: Collection) => { + setCollectionToEdit(collection); + }; + + const handleSaveEdit = async (patch: { + name?: string; + description?: string; + }) => { + if (!collectionToEdit) return false; + return updateCollection(collectionToEdit.id, patch); + }; + const handleConfirmDelete = async () => { if (!collectionToDelete) return; setShowConfirmDelete(false); @@ -142,6 +164,7 @@ export default function KnowledgeBasePage() { isLoading={isLoading} onSelect={handleSelectCollection} onRequestDelete={handleRequestDelete} + onRequestEdit={handleRequestEdit} onCreateNew={handleCreateNew} /> @@ -154,6 +177,7 @@ export default function KnowledgeBasePage() { setCollectionDescription={setCollectionDescription} selectedDocuments={selectedDocuments} availableDocuments={availableDocuments} + isLoadingDocuments={isLoadingDocuments} onToggleDocument={toggleDocumentSelection} onOpenDocumentPicker={() => setShowDocumentPicker(true)} isCreating={isCreating} @@ -207,6 +231,7 @@ export default function KnowledgeBasePage() { onClose={handleCancelCreate} maxWidth="max-w-2xl" maxHeight="max-h-[90vh]" + showClose={false} > @@ -272,6 +298,13 @@ export default function KnowledgeBasePage() { isLoading={isPreviewLoading} onSelectDocument={handleSelectPreviewDoc} /> + + setCollectionToEdit(null)} + onSave={handleSaveEdit} + /> ); } diff --git a/app/api/collections/[collection_id]/route.ts b/app/api/collections/[collection_id]/route.ts index bee11a02..6b61d1a2 100644 --- a/app/api/collections/[collection_id]/route.ts +++ b/app/api/collections/[collection_id]/route.ts @@ -1,7 +1,6 @@ import { NextResponse } from "next/server"; import { apiClient } from "@/app/lib/apiClient"; -// GET /api/collections/[collection_id] - Get a specific collection export async function GET( request: Request, { params }: { params: Promise<{ collection_id: string }> }, @@ -26,7 +25,35 @@ export async function GET( } } -// DELETE /api/collection/[collection_id] - Delete a collection +export async function PATCH( + request: Request, + { params }: { params: Promise<{ collection_id: string }> }, +) { + const { collection_id } = await params; + try { + const body = await request.json(); + const { status, data } = await apiClient( + request, + `/api/v1/collections/${collection_id}`, + { + method: "PATCH", + body: JSON.stringify(body), + headers: { "Content-Type": "application/json" }, + }, + ); + return NextResponse.json(data, { status }); + } catch (error: unknown) { + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : String(error), + data: null, + }, + { status: 500 }, + ); + } +} + export async function DELETE( request: Request, { params }: { params: Promise<{ collection_id: string }> }, diff --git a/app/components/document/UploadDocumentModal.tsx b/app/components/document/UploadDocumentModal.tsx index fa736bb3..d9d41674 100644 --- a/app/components/document/UploadDocumentModal.tsx +++ b/app/components/document/UploadDocumentModal.tsx @@ -3,7 +3,7 @@ import { useRef } from "react"; import { Button, Modal } from "@/app/components/ui"; import { CloudUploadIcon } from "@/app/components/icons"; -import DocumentChip from "@/app/components/knowledge-base/DocumentChip"; +import { DocumentChip } from "@/app/components/knowledge-base"; import type { UploadPhase } from "@/app/lib/apiClient"; import { ACCEPTED_DOCUMENT_TYPES, diff --git a/app/components/knowledge-base/CollectionsList.tsx b/app/components/knowledge-base/CollectionsList.tsx index a89cfb0a..010d75d9 100644 --- a/app/components/knowledge-base/CollectionsList.tsx +++ b/app/components/knowledge-base/CollectionsList.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@/app/components/ui"; -import { BookOpenIcon, TrashIcon } from "@/app/components/icons"; +import { BookOpenIcon, EditIcon, TrashIcon } from "@/app/components/icons"; import { formatDate } from "@/app/components/utils"; import { Collection } from "@/app/lib/types/document"; import CollectionsListSkeleton from "./CollectionsListSkeleton"; @@ -12,6 +12,7 @@ interface CollectionsListProps { isLoading: boolean; onSelect: (collectionId: string) => void; onRequestDelete: (collectionId: string) => void; + onRequestEdit: (collection: Collection) => void; onCreateNew: () => void; } @@ -21,6 +22,7 @@ export default function CollectionsList({ isLoading, onSelect, onRequestDelete, + onRequestEdit, onCreateNew, }: CollectionsListProps) { return ( @@ -81,15 +83,27 @@ export default function CollectionsList({

{!isOptimistic && ( - { - e.stopPropagation(); - onRequestDelete(collection.id); - }} - className="p-1.5 rounded-md border border-status-error-border bg-bg-primary text-status-error-text hover:bg-status-error-bg transition-colors shrink-0 cursor-pointer" - title="Delete Knowledge Base" - > - + + { + e.stopPropagation(); + onRequestEdit(collection); + }} + className="p-1.5 rounded-md border border-border bg-bg-primary text-text-secondary hover:text-accent-primary hover:border-accent-primary hover:bg-accent-primary/10 transition-colors cursor-pointer" + title="Edit Knowledge Base" + > + + + { + e.stopPropagation(); + onRequestDelete(collection.id); + }} + className="p-1.5 rounded-md border border-status-error-border bg-bg-primary text-status-error-text hover:bg-status-error-bg transition-colors cursor-pointer" + title="Delete Knowledge Base" + > + + )} diff --git a/app/components/knowledge-base/CreateCollectionForm.tsx b/app/components/knowledge-base/CreateCollectionForm.tsx index 532540af..cde0f6ea 100644 --- a/app/components/knowledge-base/CreateCollectionForm.tsx +++ b/app/components/knowledge-base/CreateCollectionForm.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Field } from "@/app/components/ui"; -import { ChevronRightIcon } from "@/app/components/icons"; +import { ChevronRightIcon, CloseIcon } from "@/app/components/icons"; import { Document } from "@/app/lib/types/document"; import DocumentChip from "./DocumentChip"; @@ -12,11 +12,13 @@ interface CreateCollectionFormProps { setCollectionDescription: (value: string) => void; selectedDocuments: Set; availableDocuments: Document[]; + isLoadingDocuments?: boolean; onToggleDocument: (documentId: string) => void; onOpenDocumentPicker: () => void; isCreating: boolean; onCancel: () => void; onCreate: () => void; + onClose?: () => void; } export default function CreateCollectionForm({ @@ -26,24 +28,38 @@ export default function CreateCollectionForm({ setCollectionDescription, selectedDocuments, availableDocuments, + isLoadingDocuments = false, onToggleDocument, onOpenDocumentPicker, isCreating, onCancel, onCreate, + onClose, }: CreateCollectionFormProps) { const isCreateDisabled = isCreating || !collectionName.trim() || selectedDocuments.size === 0; return (
-
-

- Create Knowledge Base -

-

- Group documents into a collection for RAG retrieval -

+
+
+

+ Create Knowledge Base +

+

+ Group documents into a collection for RAG retrieval +

+
+ {onClose && ( + + )}
- - {selectedDocuments.size === 0 - ? "Click to select documents" - : `${selectedDocuments.size} document${selectedDocuments.size > 1 ? "s" : ""} selected`} - + {isLoadingDocuments ? ( + + + Loading documents… + + ) : ( + + {selectedDocuments.size === 0 + ? "Click to select documents" + : `${selectedDocuments.size} document${selectedDocuments.size > 1 ? "s" : ""} selected`} + + )} diff --git a/app/components/knowledge-base/EditCollectionModal.tsx b/app/components/knowledge-base/EditCollectionModal.tsx new file mode 100644 index 00000000..f658df42 --- /dev/null +++ b/app/components/knowledge-base/EditCollectionModal.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Button, Modal, Field } from "@/app/components/ui"; +import { Collection } from "@/app/lib/types/document"; + +interface EditCollectionModalProps { + open: boolean; + collection: Collection | null; + onClose: () => void; + onSave: (patch: { name?: string; description?: string }) => Promise; +} + +export default function EditCollectionModal({ + open, + collection, + onClose, + onSave, +}: EditCollectionModalProps) { + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + if (open && collection) { + setName(collection.name ?? ""); + setDescription(collection.description ?? ""); + } + }, [open, collection]); + + const handleSave = async () => { + if (!collection) return; + const trimmedName = name.trim(); + if (!trimmedName) return; + const patch: { name?: string; description?: string } = {}; + if (trimmedName !== (collection.name ?? "")) patch.name = trimmedName; + if (description !== (collection.description ?? "")) + patch.description = description; + if (Object.keys(patch).length === 0) { + onClose(); + return; + } + setIsSaving(true); + const ok = await onSave(patch); + setIsSaving(false); + if (ok) onClose(); + }; + + const isDisabled = isSaving || !name.trim(); + + return ( + +
+ +
+ +