mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-25 08:06:48 +08:00
### What problem does this PR solve? Feat: Display document parsing status #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
17
web/src/pages/dataset/dataset/constant.ts
Normal file
17
web/src/pages/dataset/dataset/constant.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { RunningStatus } from '@/constants/knowledge';
|
||||
|
||||
export const RunningStatusMap = {
|
||||
[RunningStatus.UNSTART]: {
|
||||
label: 'UNSTART',
|
||||
color: 'cyan',
|
||||
},
|
||||
[RunningStatus.RUNNING]: {
|
||||
label: 'Parsing',
|
||||
color: 'blue',
|
||||
},
|
||||
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'orange' },
|
||||
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'blue' },
|
||||
[RunningStatus.FAIL]: { label: 'FAIL', color: 'red' },
|
||||
};
|
||||
|
||||
export * from '@/constants/knowledge';
|
||||
@ -23,8 +23,8 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { useFetchNextDocumentList } from '@/hooks/document-hooks';
|
||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { useMemo } from 'react';
|
||||
@ -38,7 +38,7 @@ export function DatasetTable() {
|
||||
pagination,
|
||||
// handleInputChange,
|
||||
setPagination,
|
||||
} = useFetchNextDocumentList();
|
||||
} = useFetchDocumentList();
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[],
|
||||
|
||||
@ -2,7 +2,6 @@ import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useCreateNextDocument,
|
||||
useNextWebCrawl,
|
||||
useRunNextDocument,
|
||||
useSaveNextDocumentName,
|
||||
useSetNextDocumentParser,
|
||||
} from '@/hooks/document-hooks';
|
||||
@ -159,35 +158,3 @@ export const useHandleWebCrawl = () => {
|
||||
showWebCrawlUploadModal,
|
||||
};
|
||||
};
|
||||
|
||||
export const useHandleRunDocumentByIds = (id: string) => {
|
||||
const { runDocumentByIds, loading } = useRunNextDocument();
|
||||
const [currentId, setCurrentId] = useState<string>('');
|
||||
const isLoading = loading && currentId !== '' && currentId === id;
|
||||
|
||||
const handleRunDocumentByIds = async (
|
||||
documentId: string,
|
||||
isRunning: boolean,
|
||||
shouldDelete: boolean = false,
|
||||
) => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
setCurrentId(documentId);
|
||||
try {
|
||||
await runDocumentByIds({
|
||||
documentIds: [documentId],
|
||||
run: isRunning ? 2 : 1,
|
||||
shouldDelete,
|
||||
});
|
||||
setCurrentId('');
|
||||
} catch (error) {
|
||||
setCurrentId('');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleRunDocumentByIds,
|
||||
loading: isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
@ -19,7 +19,7 @@ export default function Dataset() {
|
||||
|
||||
return (
|
||||
<section className="p-8">
|
||||
<ListFilterBar title="Files">
|
||||
<ListFilterBar title="Dataset">
|
||||
<Button
|
||||
variant={'tertiary'}
|
||||
size={'sm'}
|
||||
|
||||
101
web/src/pages/dataset/dataset/parsing-card.tsx
Normal file
101
web/src/pages/dataset/dataset/parsing-card.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { RunningStatus, RunningStatusMap } from './constant';
|
||||
|
||||
interface IProps {
|
||||
record: IDocumentInfo;
|
||||
}
|
||||
|
||||
function Dot({ run }: { run: RunningStatus }) {
|
||||
const runningStatus = RunningStatusMap[run];
|
||||
return (
|
||||
<span
|
||||
className={'size-2 inline-block rounded'}
|
||||
style={{ backgroundColor: runningStatus.color }}
|
||||
></span>
|
||||
);
|
||||
}
|
||||
|
||||
export const PopoverContent = ({ record }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const label = t(`knowledgeDetails.runningStatus${record.run}`);
|
||||
|
||||
const replaceText = (text: string) => {
|
||||
// Remove duplicate \n
|
||||
const nextText = text.replace(/(\n)\1+/g, '$1');
|
||||
|
||||
const replacedText = reactStringReplace(
|
||||
nextText,
|
||||
/(\[ERROR\].+\s)/g,
|
||||
(match, i) => {
|
||||
return (
|
||||
<span key={i} className={'text-red-600'}>
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return replacedText;
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'process_begin_at',
|
||||
label: t('knowledgeDetails.processBeginAt'),
|
||||
children: record.process_begin_at,
|
||||
},
|
||||
{
|
||||
key: 'knowledgeDetails.process_duation',
|
||||
label: t('processDuration'),
|
||||
children: `${record.process_duation.toFixed(2)} s`,
|
||||
},
|
||||
{
|
||||
key: 'progress_msg',
|
||||
label: t('knowledgeDetails.progressMsg'),
|
||||
children: replaceText(record.progress_msg.trim()),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="flex gap-2 items-center pb-2">
|
||||
<Dot run={record.run}></Dot> {label}
|
||||
</div>
|
||||
<div className="flex flex-col max-h-[50vh] overflow-auto">
|
||||
{items.map((x, idx) => {
|
||||
return (
|
||||
<div key={x.key} className={idx < 2 ? 'flex gap-2' : ''}>
|
||||
<b>{x.label}:</b>
|
||||
<div className={'w-full whitespace-pre-line text-wrap '}>
|
||||
{x.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export function ParsingCard({ record }: IProps) {
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Button variant={'ghost'} size={'sm'}>
|
||||
<Dot run={record.run}></Dot>
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-[40vw]">
|
||||
<PopoverContent record={record}></PopoverContent>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
}
|
||||
62
web/src/pages/dataset/dataset/parsing-status-cell.tsx
Normal file
62
web/src/pages/dataset/dataset/parsing-status-cell.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
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 { RunningStatus } from './constant';
|
||||
import { ParsingCard } from './parsing-card';
|
||||
import { useHandleRunDocumentByIds } from './use-run-document';
|
||||
import { isParserRunning } from './utils';
|
||||
|
||||
const IconMap = {
|
||||
[RunningStatus.UNSTART]: <Play />,
|
||||
[RunningStatus.RUNNING]: <CircleX />,
|
||||
[RunningStatus.CANCEL]: <RefreshCw />,
|
||||
[RunningStatus.DONE]: <RefreshCw />,
|
||||
[RunningStatus.FAIL]: <RefreshCw />,
|
||||
};
|
||||
|
||||
export function ParsingStatusCell({ record }: { record: IDocumentInfo }) {
|
||||
const { run, parser_id, progress, chunk_num, id } = record;
|
||||
const operationIcon = IconMap[run];
|
||||
const p = Number((progress * 100).toFixed(2));
|
||||
const { handleRunDocumentByIds } = useHandleRunDocumentByIds(id);
|
||||
const isRunning = isParserRunning(run);
|
||||
const isZeroChunk = chunk_num === 0;
|
||||
|
||||
const handleOperationIconClick =
|
||||
(shouldDelete: boolean = false) =>
|
||||
() => {
|
||||
handleRunDocumentByIds(record.id, isRunning, shouldDelete);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="flex gap-2 items-center ">
|
||||
<div>
|
||||
<Button variant={'ghost'} size={'sm'}>
|
||||
{parser_id}
|
||||
</Button>
|
||||
<Separator orientation="vertical" />
|
||||
</div>
|
||||
<ConfirmDeleteDialog
|
||||
hidden={isZeroChunk}
|
||||
onOk={handleOperationIconClick(true)}
|
||||
onCancel={handleOperationIconClick(false)}
|
||||
>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
size={'sm'}
|
||||
onClick={isZeroChunk ? handleOperationIconClick(false) : () => {}}
|
||||
>
|
||||
{operationIcon}
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
{isParserRunning(run) ? (
|
||||
<Progress value={p} className="h-1" />
|
||||
) : (
|
||||
<ParsingCard record={record}></ParsingCard>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -16,6 +16,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useSetDocumentStatus } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatDate } from '@/utils/date';
|
||||
@ -25,6 +26,7 @@ import { ArrowUpDown, MoreHorizontal, Pencil, Wrench } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useChangeDocumentParser } from './hooks';
|
||||
import { ParsingStatusCell } from './parsing-status-cell';
|
||||
|
||||
type UseDatasetTableColumnsType = Pick<
|
||||
ReturnType<typeof useChangeDocumentParser>,
|
||||
@ -57,6 +59,7 @@ export function useDatasetTableColumns({
|
||||
// }, [setRecord, showSetMetaModal]);
|
||||
|
||||
const { navigateToChunkParsedResult } = useNavigatePage();
|
||||
const { setDocumentStatus } = useSetDocumentStatus();
|
||||
|
||||
const columns: ColumnDef<IDocumentInfo>[] = [
|
||||
{
|
||||
@ -94,7 +97,7 @@ export function useDatasetTableColumns({
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
meta: { cellClassName: 'max-w-[20vw]' },
|
||||
// meta: { cellClassName: 'max-w-[20vw]' },
|
||||
cell: ({ row }) => {
|
||||
const name: string = row.getValue('name');
|
||||
|
||||
@ -142,20 +145,34 @@ export function useDatasetTableColumns({
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'parser_id',
|
||||
header: t('chunkMethod'),
|
||||
accessorKey: 'status',
|
||||
header: t('enabled'),
|
||||
cell: ({ row }) => {
|
||||
const id = row.original.id;
|
||||
return (
|
||||
<Switch
|
||||
checked={row.getValue('status') === '1'}
|
||||
onCheckedChange={(e) => {
|
||||
setDocumentStatus({ status: e, documentId: id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'chunk_num',
|
||||
header: t('chunkNumber'),
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue('parser_id')}</div>
|
||||
<div className="capitalize">{row.getValue('chunk_num')}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'run',
|
||||
header: t('parsingStatus'),
|
||||
cell: ({ row }) => (
|
||||
<Button variant="destructive" size={'sm'}>
|
||||
{row.getValue('run')}
|
||||
</Button>
|
||||
),
|
||||
// meta: { cellClassName: 'min-w-[20vw]' },
|
||||
cell: ({ row }) => {
|
||||
return <ParsingStatusCell record={row.original}></ParsingStatusCell>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
@ -166,7 +183,6 @@ export function useDatasetTableColumns({
|
||||
|
||||
return (
|
||||
<section className="flex gap-4 items-center">
|
||||
<Switch id="airplane-mode" />
|
||||
<Button
|
||||
variant="icon"
|
||||
size={'icon'}
|
||||
|
||||
34
web/src/pages/dataset/dataset/use-run-document.ts
Normal file
34
web/src/pages/dataset/dataset/use-run-document.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { useRunDocument } from '@/hooks/use-document-request';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useHandleRunDocumentByIds = (id: string) => {
|
||||
const { runDocumentByIds, loading } = useRunDocument();
|
||||
const [currentId, setCurrentId] = useState<string>('');
|
||||
const isLoading = loading && currentId !== '' && currentId === id;
|
||||
|
||||
const handleRunDocumentByIds = async (
|
||||
documentId: string,
|
||||
isRunning: boolean,
|
||||
shouldDelete: boolean = false,
|
||||
) => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
setCurrentId(documentId);
|
||||
try {
|
||||
await runDocumentByIds({
|
||||
documentIds: [documentId],
|
||||
run: isRunning ? 2 : 1,
|
||||
shouldDelete,
|
||||
});
|
||||
setCurrentId('');
|
||||
} catch (error) {
|
||||
setCurrentId('');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleRunDocumentByIds,
|
||||
loading: isLoading,
|
||||
};
|
||||
};
|
||||
6
web/src/pages/dataset/dataset/utils.ts
Normal file
6
web/src/pages/dataset/dataset/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { RunningStatus } from './constant';
|
||||
|
||||
export const isParserRunning = (text: RunningStatus) => {
|
||||
const isRunning = text === RunningStatus.RUNNING;
|
||||
return isRunning;
|
||||
};
|
||||
Reference in New Issue
Block a user