mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-07-24 15:44:26 +08: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