mirror of
				https://github.com/infiniflow/ragflow.git
				synced 2025-10-31 17:59:43 +00:00 
			
		
		
		
	### 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)
This commit is contained in:
		
							parent
							
								
									ff442c48b5
								
							
						
					
					
						commit
						9a8dda8fc7
					
				| @ -16,7 +16,7 @@ export function RenameDialog({ | ||||
|   initialName, | ||||
|   onOk, | ||||
|   loading, | ||||
| }: IModalProps<any> & { initialName: string }) { | ||||
| }: IModalProps<any> & { initialName?: string }) { | ||||
|   const { t } = useTranslation(); | ||||
| 
 | ||||
|   return ( | ||||
|  | ||||
| @ -22,7 +22,7 @@ export function RenameForm({ | ||||
|   initialName, | ||||
|   hideModal, | ||||
|   onOk, | ||||
| }: IModalProps<any> & { initialName: string }) { | ||||
| }: IModalProps<any> & { 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 ( | ||||
|  | ||||
| @ -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 }; | ||||
| }; | ||||
|  | ||||
							
								
								
									
										101
									
								
								web/src/pages/dataset/dataset/dataset-action-cell.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								web/src/pages/dataset/dataset/dataset-action-cell.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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 ( | ||||
|     <section className="flex gap-4 items-center"> | ||||
|       <HoverCard> | ||||
|         <HoverCardTrigger> | ||||
|           <Button variant="ghost" size={'icon'} disabled={isRunning}> | ||||
|             <ScrollText /> | ||||
|           </Button> | ||||
|         </HoverCardTrigger> | ||||
|         <HoverCardContent className="w-[40vw] max-h-[40vh] overflow-auto"> | ||||
|           <ul className="space-y-2"> | ||||
|             {Object.entries(record) | ||||
|               .filter(([key]) => Fields.some((x) => x === key)) | ||||
| 
 | ||||
|               .map(([key, value], idx) => { | ||||
|                 return ( | ||||
|                   <li key={idx} className="flex gap-2"> | ||||
|                     {key}: | ||||
|                     <div> | ||||
|                       {key in FunctionMap | ||||
|                         ? FunctionMap[key as keyof typeof FunctionMap](value) | ||||
|                         : value} | ||||
|                     </div> | ||||
|                   </li> | ||||
|                 ); | ||||
|               })} | ||||
|           </ul> | ||||
|         </HoverCardContent> | ||||
|       </HoverCard> | ||||
|       <Button | ||||
|         variant={'ghost'} | ||||
|         size={'icon'} | ||||
|         disabled={isRunning} | ||||
|         onClick={handleRename} | ||||
|       > | ||||
|         <Pencil /> | ||||
|       </Button> | ||||
|       <Button | ||||
|         variant={'ghost'} | ||||
|         size={'icon'} | ||||
|         onClick={onDownloadDocument} | ||||
|         disabled={isRunning} | ||||
|       > | ||||
|         <ArrowDownToLine /> | ||||
|       </Button> | ||||
|       <ConfirmDeleteDialog onOk={handleRemove}> | ||||
|         <Button variant={'ghost'} size={'icon'} disabled={isRunning}> | ||||
|           <Trash2 className="text-text-delete-red" /> | ||||
|         </Button> | ||||
|       </ConfirmDeleteDialog> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
| @ -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} | ||||
|         ></ChunkMethodDialog> | ||||
|       )} | ||||
| 
 | ||||
|       {renameVisible && ( | ||||
|         <RenameDialog | ||||
|           visible={renameVisible} | ||||
|           onOk={onRenameOk} | ||||
|           loading={renameLoading} | ||||
|           hideModal={hideRenameModal} | ||||
|           initialName={initialName} | ||||
|         ></RenameDialog> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| @ -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(); | ||||
| 
 | ||||
|  | ||||
| @ -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 }) { | ||||
|         <Separator orientation="vertical" /> | ||||
|       </div> | ||||
|       <ConfirmDeleteDialog | ||||
|         hidden={isZeroChunk} | ||||
|         title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })} | ||||
|         hidden={isZeroChunk || isRunning} | ||||
|         onOk={handleOperationIconClick(true)} | ||||
|         onCancel={handleOperationIconClick(false)} | ||||
|       > | ||||
|         <Button | ||||
|           variant={'ghost'} | ||||
|           size={'sm'} | ||||
|           onClick={isZeroChunk ? handleOperationIconClick(false) : () => {}} | ||||
|           onClick={ | ||||
|             isZeroChunk || isRunning | ||||
|               ? handleOperationIconClick(false) | ||||
|               : () => {} | ||||
|           } | ||||
|         > | ||||
|           {operationIcon} | ||||
|         </Button> | ||||
|       </ConfirmDeleteDialog> | ||||
|       {isParserRunning(run) ? ( | ||||
|         <Progress value={p} className="h-1" /> | ||||
|         <div className="flex items-center gap-1"> | ||||
|           <Progress value={p} className="h-1 flex-1 min-w-10" /> | ||||
|           {p}% | ||||
|         </div> | ||||
|       ) : ( | ||||
|         <ParsingCard record={record}></ParsingCard> | ||||
|       )} | ||||
|  | ||||
| @ -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<typeof useChangeDocumentParser>, | ||||
|   '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 ( | ||||
|           <section className="flex gap-4 items-center"> | ||||
|             <Button | ||||
|               variant="icon" | ||||
|               size={'icon'} | ||||
|               onClick={onShowChangeParserModal(record)} | ||||
|             > | ||||
|               <Wrench /> | ||||
|             </Button> | ||||
|             <Button variant="icon" size={'icon'}> | ||||
|               <Pencil /> | ||||
|             </Button> | ||||
|             <DropdownMenu> | ||||
|               <DropdownMenuTrigger asChild> | ||||
|                 <Button variant="icon" size={'icon'}> | ||||
|                   <MoreHorizontal /> | ||||
|                 </Button> | ||||
|               </DropdownMenuTrigger> | ||||
|               <DropdownMenuContent align="end"> | ||||
|                 <DropdownMenuLabel>Actions</DropdownMenuLabel> | ||||
|                 <DropdownMenuItem | ||||
|                   onClick={() => navigator.clipboard.writeText(record.id)} | ||||
|                 > | ||||
|                   Copy payment ID | ||||
|                 </DropdownMenuItem> | ||||
|                 <DropdownMenuSeparator /> | ||||
|                 <DropdownMenuItem>View customer</DropdownMenuItem> | ||||
|                 <DropdownMenuItem>View payment details</DropdownMenuItem> | ||||
|               </DropdownMenuContent> | ||||
|             </DropdownMenu> | ||||
|           </section> | ||||
|           <DatasetActionCell | ||||
|             record={record} | ||||
|             showRenameModal={showRenameModal} | ||||
|           ></DatasetActionCell> | ||||
|         ); | ||||
|       }, | ||||
|     }, | ||||
|  | ||||
							
								
								
									
										49
									
								
								web/src/pages/dataset/dataset/use-rename-document.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								web/src/pages/dataset/dataset/use-rename-document.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<IDocumentInfo>(); | ||||
| 
 | ||||
|   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<typeof useRenameDocument>, | ||||
|   'showRenameModal' | ||||
| >; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 balibabu
						balibabu