From a10e61735d9ec8d9d876dcb4c126ed9b84719b9f Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 12 Jul 2025 18:00:54 +0800 Subject: [PATCH] feat(fe): complete kb ui --- web/package.json | 1 + .../knowledge/components/kb-docs/KBDoc.tsx | 30 +- .../components/kb-docs/documents/columns.tsx | 42 ++- web/src/app/infra/entities/api/index.ts | 2 +- web/src/app/infra/http/HttpClient.ts | 11 +- web/src/components/ui/dropdown-menu.tsx | 257 ++++++++++++++++++ web/src/i18n/locales/en-US.ts | 4 + 7 files changed, 339 insertions(+), 8 deletions(-) create mode 100644 web/src/components/ui/dropdown-menu.tsx diff --git a/web/package.json b/web/package.json index d5e8542c..8cd6b8dc 100644 --- a/web/package.json +++ b/web/package.json @@ -22,6 +22,7 @@ "@hookform/resolvers": "^5.0.1", "@radix-ui/react-checkbox": "^1.3.1", "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-hover-card": "^1.1.13", "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-popover": "^1.1.14", diff --git a/web/src/app/home/knowledge/components/kb-docs/KBDoc.tsx b/web/src/app/home/knowledge/components/kb-docs/KBDoc.tsx index fc3109ef..0a779112 100644 --- a/web/src/app/home/knowledge/components/kb-docs/KBDoc.tsx +++ b/web/src/app/home/knowledge/components/kb-docs/KBDoc.tsx @@ -4,20 +4,31 @@ import { KnowledgeBaseFile } from '@/app/infra/entities/api'; import { columns, DocumentFile } from './documents/columns'; import { DataTable } from './documents/data-table'; import FileUploadZone from './FileUploadZone'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; export default function KBDoc({ kbId }: { kbId: string }) { const [documentsList, setDocumentsList] = useState([]); + const { t } = useTranslation(); useEffect(() => { getDocumentsList(); - }, []); + + const intervalId = setInterval(() => { + getDocumentsList(); + }, 5000); + + return () => { + clearInterval(intervalId); + }; + }, [kbId]); async function getDocumentsList() { const resp = await httpClient.getKnowledgeBaseFiles(kbId); setDocumentsList( resp.files.map((file: KnowledgeBaseFile) => { return { - id: file.file_id, + id: file.id, name: file.file_name, status: file.status, }; @@ -35,6 +46,19 @@ export default function KBDoc({ kbId }: { kbId: string }) { console.error('Upload failed:', error); }; + const handleDelete = (id: string) => { + httpClient + .deleteKnowledgeBaseFile(kbId, id) + .then(() => { + getDocumentsList(); + toast.success(t('knowledge.documentsTab.fileDeleteSuccess')); + }) + .catch((error) => { + console.error('Delete failed:', error); + toast.error(t('knowledge.documentsTab.fileDeleteFailed')); + }); + }; + return (
- +
); } diff --git a/web/src/app/home/knowledge/components/kb-docs/documents/columns.tsx b/web/src/app/home/knowledge/components/kb-docs/documents/columns.tsx index d43afd68..0a43cf8f 100644 --- a/web/src/app/home/knowledge/components/kb-docs/documents/columns.tsx +++ b/web/src/app/home/knowledge/components/kb-docs/documents/columns.tsx @@ -1,6 +1,16 @@ 'use client'; import { ColumnDef } from '@tanstack/react-table'; +import { MoreHorizontal } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { useTranslation } from 'react-i18next'; export type DocumentFile = { @@ -9,7 +19,9 @@ export type DocumentFile = { status: string; }; -export const columns = (): ColumnDef[] => { +export const columns = ( + onDelete: (id: string) => void, +): ColumnDef[] => { const { t } = useTranslation(); return [ { @@ -20,5 +32,33 @@ export const columns = (): ColumnDef[] => { accessorKey: 'status', header: t('knowledge.documentsTab.status'), }, + { + id: 'actions', + cell: ({ row }) => { + const document = row.original; + + return ( + + + + + + + {t('knowledge.documentsTab.actions')} + + + onDelete(document.id)}> + {t('knowledge.documentsTab.delete')} + + + + ); + }, + }, ]; }; diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index b230cf9e..46d007d8 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -155,7 +155,7 @@ export interface ApiRespKnowledgeBaseFiles { } export interface KnowledgeBaseFile { - file_id: string; + id: string; file_name: string; status: string; } diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts index 3a0b5f35..a2d42f66 100644 --- a/web/src/app/infra/http/HttpClient.ts +++ b/web/src/app/infra/http/HttpClient.ts @@ -463,9 +463,7 @@ class HttpClient { uuid: string, file_id: string, ): Promise { - return this.post(`/api/v1/knowledge/bases/${uuid}/files`, { - file_id, - }); + return this.delete(`/api/v1/knowledge/bases/${uuid}/files/${file_id}`); } public getKnowledgeBaseFiles( @@ -474,6 +472,13 @@ class HttpClient { return this.get(`/api/v1/knowledge/bases/${uuid}/files`); } + public deleteKnowledgeBaseFile( + uuid: string, + file_id: string, + ): Promise { + return this.delete(`/api/v1/knowledge/bases/${uuid}/files/${file_id}`); + } + // ============ Plugins API ============ public getPlugins(): Promise { return this.get('/api/v1/plugins'); diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..26027549 --- /dev/null +++ b/web/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +'use client'; + +import * as React from 'react'; +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuItem({ + className, + inset, + variant = 'default', + ...props +}: React.ComponentProps & { + inset?: boolean; + variant?: 'default' | 'destructive'; +}) { + return ( + + ); +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + ); +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<'span'>) { + return ( + + ); +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +}; diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index cfb50966..53729084 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -262,6 +262,10 @@ const enUS = { uploadSuccess: 'File uploaded successfully!', uploadError: 'File upload failed, please try again', uploadingFile: 'Uploading file...', + actions: 'Actions', + delete: 'Delete File', + fileDeleteSuccess: 'File deleted successfully', + fileDeleteFailed: 'File deletion failed', }, }, register: {