From 9a8dda8fc74cd8209f1c9bf13cad6d9324f33122 Mon Sep 17 00:00:00 2001 From: balibabu Date: Thu, 24 Apr 2025 14:51:20 +0800 Subject: [PATCH] Feat: Delete and rename files in the knowledge base #3221 (#7268) ### What problem does this PR solve? Feat: Delete and rename files in the knowledge base #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/rename-dialog/index.tsx | 2 +- .../components/rename-dialog/rename-form.tsx | 6 +- web/src/hooks/use-document-request.ts | 58 ++++++++++ .../dataset/dataset/dataset-action-cell.tsx | 101 ++++++++++++++++++ .../pages/dataset/dataset/dataset-table.tsx | 22 ++++ web/src/pages/dataset/dataset/hooks.ts | 29 ----- .../dataset/dataset/parsing-status-cell.tsx | 16 ++- .../dataset/use-dataset-table-columns.tsx | 51 ++------- .../dataset/dataset/use-rename-document.ts | 49 +++++++++ 9 files changed, 259 insertions(+), 75 deletions(-) create mode 100644 web/src/pages/dataset/dataset/dataset-action-cell.tsx create mode 100644 web/src/pages/dataset/dataset/use-rename-document.ts diff --git a/web/src/components/rename-dialog/index.tsx b/web/src/components/rename-dialog/index.tsx index 97c48f0e6..d9c002626 100644 --- a/web/src/components/rename-dialog/index.tsx +++ b/web/src/components/rename-dialog/index.tsx @@ -16,7 +16,7 @@ export function RenameDialog({ initialName, onOk, loading, -}: IModalProps & { initialName: string }) { +}: IModalProps & { initialName?: string }) { const { t } = useTranslation(); return ( diff --git a/web/src/components/rename-dialog/rename-form.tsx b/web/src/components/rename-dialog/rename-form.tsx index 27af77bd9..e1b3c285f 100644 --- a/web/src/components/rename-dialog/rename-form.tsx +++ b/web/src/components/rename-dialog/rename-form.tsx @@ -22,7 +22,7 @@ export function RenameForm({ initialName, hideModal, onOk, -}: IModalProps & { initialName: string }) { +}: IModalProps & { initialName?: string }) { const { t } = useTranslation(); const FormSchema = z.object({ name: z @@ -46,7 +46,9 @@ export function RenameForm({ } useEffect(() => { - form.setValue('name', initialName); + if (initialName) { + form.setValue('name', initialName); + } }, [form, initialName]); return ( diff --git a/web/src/hooks/use-document-request.ts b/web/src/hooks/use-document-request.ts index c8d129ef6..dff09fbdf 100644 --- a/web/src/hooks/use-document-request.ts +++ b/web/src/hooks/use-document-request.ts @@ -18,6 +18,8 @@ export const enum DocumentApiAction { FetchDocumentList = 'fetchDocumentList', UpdateDocumentStatus = 'updateDocumentStatus', RunDocumentByIds = 'runDocumentByIds', + RemoveDocument = 'removeDocument', + SaveDocumentName = 'saveDocumentName', } export const useUploadNextDocument = () => { @@ -189,3 +191,59 @@ export const useRunDocument = () => { return { runDocumentByIds: mutateAsync, loading, data }; }; + +export const useRemoveDocument = () => { + const queryClient = useQueryClient(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [DocumentApiAction.RemoveDocument], + mutationFn: async (documentIds: string | string[]) => { + const { data } = await kbService.document_rm({ doc_id: documentIds }); + if (data.code === 0) { + message.success(i18n.t('message.deleted')); + queryClient.invalidateQueries({ + queryKey: [DocumentApiAction.FetchDocumentList], + }); + } + return data.code; + }, + }); + + return { data, loading, removeDocument: mutateAsync }; +}; + +export const useSaveDocumentName = () => { + const queryClient = useQueryClient(); + + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [DocumentApiAction.SaveDocumentName], + mutationFn: async ({ + name, + documentId, + }: { + name: string; + documentId: string; + }) => { + const { data } = await kbService.document_rename({ + doc_id: documentId, + name: name, + }); + if (data.code === 0) { + message.success(i18n.t('message.renamed')); + queryClient.invalidateQueries({ + queryKey: [DocumentApiAction.FetchDocumentList], + }); + } + return data.code; + }, + }); + + return { loading, saveName: mutateAsync, data }; +}; diff --git a/web/src/pages/dataset/dataset/dataset-action-cell.tsx b/web/src/pages/dataset/dataset/dataset-action-cell.tsx new file mode 100644 index 000000000..74c08c7af --- /dev/null +++ b/web/src/pages/dataset/dataset/dataset-action-cell.tsx @@ -0,0 +1,101 @@ +import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; +import { Button } from '@/components/ui/button'; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from '@/components/ui/hover-card'; +import { useRemoveDocument } from '@/hooks/use-document-request'; +import { IDocumentInfo } from '@/interfaces/database/document'; +import { formatFileSize } from '@/utils/common-util'; +import { formatDate } from '@/utils/date'; +import { downloadDocument } from '@/utils/file-util'; +import { ArrowDownToLine, Pencil, ScrollText, Trash2 } from 'lucide-react'; +import { useCallback } from 'react'; +import { UseRenameDocumentShowType } from './use-rename-document'; +import { isParserRunning } from './utils'; + +const Fields = ['name', 'size', 'type', 'create_time', 'update_time']; + +const FunctionMap = { + size: formatFileSize, + create_time: formatDate, + update_time: formatDate, +}; + +export function DatasetActionCell({ + record, + showRenameModal, +}: { record: IDocumentInfo } & UseRenameDocumentShowType) { + const { id, run } = record; + const isRunning = isParserRunning(run); + + const { removeDocument } = useRemoveDocument(); + + const onDownloadDocument = useCallback(() => { + downloadDocument({ + id, + filename: record.name, + }); + }, [id, record.name]); + + const handleRemove = useCallback(() => { + removeDocument(id); + }, [id, removeDocument]); + + const handleRename = useCallback(() => { + showRenameModal(record); + }, [record, showRenameModal]); + + return ( +
+ + + + + +
    + {Object.entries(record) + .filter(([key]) => Fields.some((x) => x === key)) + + .map(([key, value], idx) => { + return ( +
  • + {key}: +
    + {key in FunctionMap + ? FunctionMap[key as keyof typeof FunctionMap](value) + : value} +
    +
  • + ); + })} +
+
+
+ + + + + +
+ ); +} diff --git a/web/src/pages/dataset/dataset/dataset-table.tsx b/web/src/pages/dataset/dataset/dataset-table.tsx index 67286a678..28ce4bc69 100644 --- a/web/src/pages/dataset/dataset/dataset-table.tsx +++ b/web/src/pages/dataset/dataset/dataset-table.tsx @@ -14,6 +14,7 @@ import { import * as React from 'react'; import { ChunkMethodDialog } from '@/components/chunk-method-dialog'; +import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; import { Table, @@ -30,6 +31,7 @@ import { getExtension } from '@/utils/document-util'; import { useMemo } from 'react'; import { useChangeDocumentParser } from './hooks'; import { useDatasetTableColumns } from './use-dataset-table-columns'; +import { useRenameDocument } from './use-rename-document'; export function DatasetTable() { const { @@ -57,9 +59,19 @@ export function DatasetTable() { showChangeParserModal, } = useChangeDocumentParser(currentRecord.id); + const { + renameLoading, + onRenameOk, + renameVisible, + hideRenameModal, + showRenameModal, + initialName, + } = useRenameDocument(); + const columns = useDatasetTableColumns({ showChangeParserModal, setCurrentRecord: setRecord, + showRenameModal, }); const currentPagination = useMemo(() => { @@ -196,6 +208,16 @@ export function DatasetTable() { loading={changeParserLoading} > )} + + {renameVisible && ( + + )} ); } diff --git a/web/src/pages/dataset/dataset/hooks.ts b/web/src/pages/dataset/dataset/hooks.ts index e0643bee8..548dd5726 100644 --- a/web/src/pages/dataset/dataset/hooks.ts +++ b/web/src/pages/dataset/dataset/hooks.ts @@ -2,7 +2,6 @@ import { useSetModalState } from '@/hooks/common-hooks'; import { useCreateNextDocument, useNextWebCrawl, - useSaveNextDocumentName, useSetNextDocumentParser, } from '@/hooks/document-hooks'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; @@ -23,34 +22,6 @@ export const useNavigateToOtherPage = () => { return { linkToUploadPage, toChunk }; }; -export const useRenameDocument = (documentId: string) => { - const { saveName, loading } = useSaveNextDocumentName(); - - const { - visible: renameVisible, - hideModal: hideRenameModal, - showModal: showRenameModal, - } = useSetModalState(); - - const onRenameOk = useCallback( - async (name: string) => { - const ret = await saveName({ documentId, name }); - if (ret === 0) { - hideRenameModal(); - } - }, - [hideRenameModal, saveName, documentId], - ); - - return { - renameLoading: loading, - onRenameOk, - renameVisible, - hideRenameModal, - showRenameModal, - }; -}; - export const useCreateEmptyDocument = () => { const { createDocument, loading } = useCreateNextDocument(); diff --git a/web/src/pages/dataset/dataset/parsing-status-cell.tsx b/web/src/pages/dataset/dataset/parsing-status-cell.tsx index a2dc23cd0..3adea2071 100644 --- a/web/src/pages/dataset/dataset/parsing-status-cell.tsx +++ b/web/src/pages/dataset/dataset/parsing-status-cell.tsx @@ -4,6 +4,7 @@ import { Progress } from '@/components/ui/progress'; import { Separator } from '@/components/ui/separator'; import { IDocumentInfo } from '@/interfaces/database/document'; import { CircleX, Play, RefreshCw } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; import { RunningStatus } from './constant'; import { ParsingCard } from './parsing-card'; import { useHandleRunDocumentByIds } from './use-run-document'; @@ -18,6 +19,7 @@ const IconMap = { }; export function ParsingStatusCell({ record }: { record: IDocumentInfo }) { + const { t } = useTranslation(); const { run, parser_id, progress, chunk_num, id } = record; const operationIcon = IconMap[run]; const p = Number((progress * 100).toFixed(2)); @@ -40,20 +42,28 @@ export function ParsingStatusCell({ record }: { record: IDocumentInfo }) { {isParserRunning(run) ? ( - +
+ + {p}% +
) : ( )} diff --git a/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx b/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx index f90a4604e..4bdcba9e0 100644 --- a/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx +++ b/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx @@ -1,14 +1,6 @@ import SvgIcon from '@/components/svg-icon'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; import { Switch } from '@/components/ui/switch'; import { Tooltip, @@ -22,20 +14,25 @@ import { cn } from '@/lib/utils'; import { formatDate } from '@/utils/date'; import { getExtension } from '@/utils/document-util'; import { ColumnDef } from '@tanstack/table-core'; -import { ArrowUpDown, MoreHorizontal, Pencil, Wrench } from 'lucide-react'; +import { ArrowUpDown } from 'lucide-react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { DatasetActionCell } from './dataset-action-cell'; import { useChangeDocumentParser } from './hooks'; import { ParsingStatusCell } from './parsing-status-cell'; +import { UseRenameDocumentShowType } from './use-rename-document'; type UseDatasetTableColumnsType = Pick< ReturnType, 'showChangeParserModal' -> & { setCurrentRecord: (record: IDocumentInfo) => void }; +> & { + setCurrentRecord: (record: IDocumentInfo) => void; +} & UseRenameDocumentShowType; export function useDatasetTableColumns({ showChangeParserModal, setCurrentRecord, + showRenameModal, }: UseDatasetTableColumnsType) { const { t } = useTranslation('translation', { keyPrefix: 'knowledgeDetails', @@ -182,36 +179,10 @@ export function useDatasetTableColumns({ const record = row.original; return ( -
- - - - - - - - Actions - navigator.clipboard.writeText(record.id)} - > - Copy payment ID - - - View customer - View payment details - - -
+ ); }, }, diff --git a/web/src/pages/dataset/dataset/use-rename-document.ts b/web/src/pages/dataset/dataset/use-rename-document.ts new file mode 100644 index 000000000..698a3f9e6 --- /dev/null +++ b/web/src/pages/dataset/dataset/use-rename-document.ts @@ -0,0 +1,49 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { useSaveDocumentName } from '@/hooks/use-document-request'; +import { IDocumentInfo } from '@/interfaces/database/document'; +import { useCallback, useState } from 'react'; + +export const useRenameDocument = () => { + const { saveName, loading } = useSaveDocumentName(); + const [record, setRecord] = useState(); + + const { + visible: renameVisible, + hideModal: hideRenameModal, + showModal: showRenameModal, + } = useSetModalState(); + + const onRenameOk = useCallback( + async (name: string) => { + if (record?.id) { + const ret = await saveName({ documentId: record.id, name }); + if (ret === 0) { + hideRenameModal(); + } + } + }, + [record?.id, saveName, hideRenameModal], + ); + + const handleShow = useCallback( + (row: IDocumentInfo) => { + setRecord(row); + showRenameModal(); + }, + [showRenameModal], + ); + + return { + renameLoading: loading, + onRenameOk, + renameVisible, + hideRenameModal, + showRenameModal: handleShow, + initialName: record?.name, + }; +}; + +export type UseRenameDocumentShowType = Pick< + ReturnType, + 'showRenameModal' +>;