Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions app/(main)/knowledge-base/page.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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);
Expand All @@ -39,6 +45,9 @@ export default function KnowledgeBasePage() {
const [showDocPreviewModal, setShowDocPreviewModal] = useState(false);
const [previewDoc, setPreviewDoc] = useState<Document | null>(null);
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
const [collectionToEdit, setCollectionToEdit] = useState<Collection | null>(
null,
);

const [collectionName, setCollectionName] = useState("");
const [collectionDescription, setCollectionDescription] = useState("");
Expand All @@ -65,6 +74,7 @@ export default function KnowledgeBasePage() {
const handleCreateNew = () => {
setShowCreateForm(true);
setSelectedCollection(null);
fetchDocuments();
};

const handleCancelCreate = () => {
Expand All @@ -81,19 +91,32 @@ export default function KnowledgeBasePage() {
description: collectionDescription,
documentIds: Array.from(selectedDocuments),
};
const ok = await createCollection(params);
if (!ok) return;
setShowCreateForm(false);
setShowDocumentPicker(false);
setCollectionName("");
setCollectionDescription("");
setSelectedDocuments(new Set());
await createCollection(params);
};

const handleRequestDelete = (collectionId: string) => {
setCollectionToDelete(collectionId);
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);
Expand Down Expand Up @@ -142,6 +165,7 @@ export default function KnowledgeBasePage() {
isLoading={isLoading}
onSelect={handleSelectCollection}
onRequestDelete={handleRequestDelete}
onRequestEdit={handleRequestEdit}
onCreateNew={handleCreateNew}
/>

Expand All @@ -154,6 +178,7 @@ export default function KnowledgeBasePage() {
setCollectionDescription={setCollectionDescription}
selectedDocuments={selectedDocuments}
availableDocuments={availableDocuments}
isLoadingDocuments={isLoadingDocuments}
onToggleDocument={toggleDocumentSelection}
onOpenDocumentPicker={() => setShowDocumentPicker(true)}
isCreating={isCreating}
Expand Down Expand Up @@ -207,6 +232,7 @@ export default function KnowledgeBasePage() {
onClose={handleCancelCreate}
maxWidth="max-w-2xl"
maxHeight="max-h-[90vh]"
showClose={false}
>
<CreateCollectionForm
collectionName={collectionName}
Expand All @@ -220,6 +246,7 @@ export default function KnowledgeBasePage() {
isCreating={isCreating}
onCancel={handleCancelCreate}
onCreate={handleCreateClick}
onClose={handleCancelCreate}
/>
</Modal>

Expand All @@ -228,6 +255,7 @@ export default function KnowledgeBasePage() {
onClose={() => setSelectedCollection(null)}
maxWidth="max-w-2xl"
maxHeight="max-h-[90vh]"
showClose={false}
>
{selectedCollection && (
<CollectionDetail
Expand All @@ -237,6 +265,7 @@ export default function KnowledgeBasePage() {
handleRequestDelete(id);
}}
onPreviewDocument={handlePreviewDocument}
onClose={() => setSelectedCollection(null)}
/>
)}
</Modal>
Expand Down Expand Up @@ -272,6 +301,13 @@ export default function KnowledgeBasePage() {
isLoading={isPreviewLoading}
onSelectDocument={handleSelectPreviewDoc}
/>

<EditCollectionModal
open={!!collectionToEdit}
collection={collectionToEdit}
onClose={() => setCollectionToEdit(null)}
onSave={handleSaveEdit}
/>
</div>
);
}
31 changes: 29 additions & 2 deletions app/api/collections/[collection_id]/route.ts
Original file line number Diff line number Diff line change
@@ -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 }> },
Expand All @@ -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 }> },
Expand Down
2 changes: 1 addition & 1 deletion app/components/document/UploadDocumentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 26 additions & 11 deletions app/components/knowledge-base/CollectionDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button } from "@/app/components/ui";
import {
CheckLineIcon,
ChevronDownIcon,
CloseIcon,
CopyIcon,
} from "@/app/components/icons";
import { formatDate } from "@/app/components/utils";
Expand All @@ -14,12 +15,14 @@ interface CollectionDetailProps {
collection: Collection;
onRequestDelete: (collectionId: string) => void;
onPreviewDocument: (firstDocument: Document) => void;
onClose?: () => void;
}

export default function CollectionDetail({
collection,
onRequestDelete,
onPreviewDocument,
onClose,
}: CollectionDetailProps) {
const [showAllDocs, setShowAllDocs] = useState(false);
const [copied, setCopied] = useState(false);
Expand All @@ -42,8 +45,8 @@ export default function CollectionDetail({
return (
<>
<div className="p-6 border-b border-border">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<h2 className="text-xl font-semibold text-text-primary">
{collection.name}
</h2>
Expand All @@ -53,15 +56,27 @@ export default function CollectionDetail({
</p>
)}
</div>
{!isOptimistic && (
<Button
variant="danger"
size="sm"
onClick={() => onRequestDelete(collection.id)}
>
Delete
</Button>
)}
<div className="flex items-center gap-2 shrink-0">
{!isOptimistic && (
<Button
variant="danger"
size="sm"
onClick={() => onRequestDelete(collection.id)}
>
Delete
</Button>
)}
{onClose && (
<button
type="button"
onClick={onClose}
aria-label="Close"
className="p-1 rounded-md text-text-secondary transition-colors hover:bg-neutral-100 hover:text-text-primary cursor-pointer"
>
<CloseIcon className="w-5 h-5" />
</button>
)}
</div>
</div>

<div className="space-y-3 mt-6">
Expand Down
34 changes: 24 additions & 10 deletions app/components/knowledge-base/CollectionsList.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -12,6 +12,7 @@ interface CollectionsListProps {
isLoading: boolean;
onSelect: (collectionId: string) => void;
onRequestDelete: (collectionId: string) => void;
onRequestEdit: (collection: Collection) => void;
onCreateNew: () => void;
}

Expand All @@ -21,6 +22,7 @@ export default function CollectionsList({
isLoading,
onSelect,
onRequestDelete,
onRequestEdit,
onCreateNew,
}: CollectionsListProps) {
return (
Expand Down Expand Up @@ -81,15 +83,27 @@ export default function CollectionsList({
</p>
</div>
{!isOptimistic && (
<span
onClick={(e) => {
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"
>
<TrashIcon className="w-3.5 h-3.5" />
<span className="flex items-center gap-1.5 shrink-0">
<span
onClick={(e) => {
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"
>
<EditIcon className="w-3.5 h-3.5" />
</span>
<span
onClick={(e) => {
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"
>
<TrashIcon className="w-3.5 h-3.5" />
</span>
</span>
)}
</div>
Expand Down
Loading
Loading