fix: Modify icon file, knowledge base display style (#10104)

### What problem does this PR solve?

fix: Modify icon file, knowledge base display style #9869

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-09-16 10:37:08 +08:00
committed by GitHub
parent 2b50de3186
commit b79fef1ca8
30 changed files with 668 additions and 254 deletions

View File

@ -3,7 +3,7 @@ export enum LogTabs {
DATASET_LOGS = 'datasetLogs',
}
export enum processingType {
export enum ProcessingType {
knowledgeGraph = 'knowledgeGraph',
raptor = 'raptor',
}

View File

@ -1,10 +1,8 @@
import {
CircleQuestionMark,
Cpu,
FileChartLine,
HardDriveDownload,
} from 'lucide-react';
import { FC, useState } from 'react';
import SvgIcon from '@/components/svg-icon';
import { useIsDarkTheme } from '@/components/theme-provider';
import { toFixed } from '@/utils/common-util';
import { CircleQuestionMark } from 'lucide-react';
import { FC, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LogTabs } from './dataset-common';
import { DatasetFilter } from './dataset-filter';
@ -37,44 +35,43 @@ const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => {
interface CardFooterProcessProps {
total: number;
completed: number;
success: number;
failed: number;
}
const CardFooterProcess: FC<CardFooterProcessProps> = ({
total,
completed,
success,
failed,
success = 0,
failed = 0,
}) => {
const { t } = useTranslation();
const successPrecentage = (success / total) * 100;
const failedPrecentage = (failed / total) * 100;
const successPrecentage = total ? (success / total) * 100 : 0;
const failedPrecentage = total ? (failed / total) * 100 : 0;
const completedPercentage = total ? ((success + failed) / total) * 100 : 0;
return (
<div className="flex items-center flex-col gap-2">
<div className="flex justify-between w-full text-sm text-text-secondary">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{success}
{success || 0}
<span>{t('knowledgeDetails.success')}</span>
</div>
<div className="flex items-center gap-1">
{failed}
{failed || 0}
<span>{t('knowledgeDetails.failed')}</span>
</div>
</div>
<div className="flex items-center gap-1">
{completed}
{toFixed(completedPercentage) as string}%
<span>{t('knowledgeDetails.completed')}</span>
</div>
</div>
<div className="w-full flex rounded-full h-3 bg-bg-card text-sm font-bold text-text-primary">
<div className="w-full flex rounded-full h-1.5 bg-bg-card text-sm font-bold text-text-primary">
<div
className=" rounded-full h-3 bg-accent-primary"
className=" rounded-full h-1.5 bg-accent-primary"
style={{ width: successPrecentage + '%' }}
></div>
<div
className=" rounded-full h-3 bg-state-error"
className=" rounded-full h-1.5 bg-state-error"
style={{ width: failedPrecentage + '%' }}
></div>
</div>
@ -86,24 +83,67 @@ const FileLogsPage: FC = () => {
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
LogTabs.FILE_LOGS,
);
const mockData = Array(30)
.fill(0)
.map((_, i) => ({
id: i === 0 ? '952734' : `14`,
fileName: 'PRD for DealBees 1.2 (1).txt',
source: 'GitHub',
pipeline: i === 0 ? 'data demo for...' : i === 1 ? 'test' : 'kikis demo',
startDate: '14/03/2025 14:53:39',
task: i === 0 ? 'chunck' : 'Parser',
status:
i === 0
? 'Success'
: i === 1
? 'Failed'
: i === 2
? 'Running'
: 'Pending',
}));
const topMockData = {
totalFiles: {
value: 2827,
precent: 12.5,
},
downloads: {
value: 28,
success: 8,
failed: 2,
},
processing: {
value: 156,
success: 8,
failed: 2,
},
};
const mockData = useMemo(() => {
if (active === LogTabs.FILE_LOGS) {
return Array(30)
.fill(0)
.map((_, i) => ({
id: i === 0 ? '952734' : `14`,
fileName: 'PRD for DealBees 1.2 (1).txt',
source: 'GitHub',
pipeline:
i === 0 ? 'data demo for...' : i === 1 ? 'test' : 'kikis demo',
startDate: '14/03/2025 14:53:39',
task: i === 0 ? 'chunck' : 'Parser',
status: i === 0 ? 3 : i === 1 ? 4 : i === 2 ? 1 : 0,
statusName:
i === 0
? 'Success'
: i === 1
? 'Failed'
: i === 2
? 'Running'
: 'Pending',
}));
}
if (active === LogTabs.DATASET_LOGS) {
return Array(8)
.fill(0)
.map((_, i) => ({
id: i === 0 ? '952734' : `14`,
fileName: 'PRD for DealBees 1.2 (1).txt',
source: 'GitHub',
startDate: '14/03/2025 14:53:39',
task: i === 0 ? 'chunck' : 'Parser',
pipeline:
i === 0 ? 'data demo for...' : i === 1 ? 'test' : 'kikis demo',
status:
i === 0
? 'Success'
: i === 1
? 'Failed'
: i === 2
? 'Running'
: 'Pending',
}));
}
}, [active]);
const pagination = {
current: 1,
@ -118,23 +158,63 @@ const FileLogsPage: FC = () => {
console.log('Pagination changed:', { page, pageSize });
};
const isDark = useIsDarkTheme();
return (
<div className="p-5 min-w-[880px] border-border border rounded-lg mr-5">
{/* Stats Cards */}
<div className="grid grid-cols-3 md:grid-cols-3 gap-4 mb-6">
<StatCard title="Total Files" value={2827} icon={<FileChartLine />}>
<div>+7% from last week</div>
<StatCard
title="Total Files"
value={topMockData.totalFiles.value}
icon={
isDark ? (
<SvgIcon name="data-flow/total-files-icon" width={40} />
) : (
<SvgIcon name="data-flow/total-files-icon-bri" width={40} />
)
}
>
<div>
<span className="text-accent-primary">
{topMockData.totalFiles.precent > 0 ? '+' : ''}
{topMockData.totalFiles.precent}%{' '}
</span>
from last week
</div>
</StatCard>
<StatCard title="Downloading" value={28} icon={<HardDriveDownload />}>
<StatCard
title="Downloading"
value={topMockData.downloads.value}
icon={
isDark ? (
<SvgIcon name="data-flow/data-icon" width={40} />
) : (
<SvgIcon name="data-flow/data-icon-bri" width={40} />
)
}
>
<CardFooterProcess
total={100}
success={8}
failed={2}
completed={15}
total={topMockData.downloads.value}
success={topMockData.downloads.success}
failed={topMockData.downloads.failed}
/>
</StatCard>
<StatCard title="Processing" value={156} icon={<Cpu />}>
<CardFooterProcess total={20} success={8} failed={2} completed={15} />
<StatCard
title="Processing"
value={topMockData.processing.value}
icon={
isDark ? (
<SvgIcon name="data-flow/processing-icon" width={40} />
) : (
<SvgIcon name="data-flow/processing-icon-bri" width={40} />
)
}
>
<CardFooterProcess
total={topMockData.processing.value}
success={topMockData.processing.success}
failed={topMockData.processing.failed}
/>
</StatCard>
</div>

View File

@ -14,10 +14,10 @@ import {
} from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import ProcessLogModal from '@/pages/datasets/process-log-modal';
import {
ColumnDef,
ColumnFiltersState,
Row,
SortingState,
flexRender,
getCoreRowModel,
@ -29,7 +29,8 @@ import {
import { TFunction } from 'i18next';
import { ClipboardList, Eye } from 'lucide-react';
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react';
import { LogTabs, processingType } from './dataset-common';
import ProcessLogModal from '../process-log-modal';
import { LogTabs, ProcessingType } from './dataset-common';
interface DocumentLog {
id: string;
@ -144,7 +145,12 @@ export const getFileLogsTableColumns = (
{
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => <FileStatusBadge status={row.original.status} />,
cell: ({ row }) => (
<FileStatusBadge
status={row.original.status}
name={row.original.statusName}
/>
),
},
{
id: 'operations',
@ -179,7 +185,7 @@ export const getFileLogsTableColumns = (
export const getDatasetLogsTableColumns = (
t: TFunction<'translation', string>,
setIsModalVisible: Dispatch<SetStateAction<boolean>>,
showLog: (row: Row<DocumentLog>, active: LogTabs) => void,
) => {
// const { t } = useTranslate('knowledgeDetails');
const columns: ColumnDef<DocumentLog>[] = [
@ -221,10 +227,10 @@ export const getDatasetLogsTableColumns = (
header: t('processingType'),
cell: ({ row }) => (
<div className="flex items-center gap-2 text-text-primary">
{processingType.knowledgeGraph === row.original.processingType && (
{ProcessingType.knowledgeGraph === row.original.processingType && (
<SvgIcon name={`data-flow/knowledgegraph`} width={24}></SvgIcon>
)}
{processingType.raptor === row.original.processingType && (
{ProcessingType.raptor === row.original.processingType && (
<SvgIcon name={`data-flow/raptor`} width={24}></SvgIcon>
)}
{row.original.processingType}
@ -234,7 +240,12 @@ export const getDatasetLogsTableColumns = (
{
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => <FileStatusBadge status={row.original.status} />,
cell: ({ row }) => (
<FileStatusBadge
status={row.original.status}
name={row.original.statusName}
/>
),
},
{
id: 'operations',
@ -246,7 +257,7 @@ export const getDatasetLogsTableColumns = (
size="sm"
className="p-1"
onClick={() => {
setIsModalVisible(true);
showLog(row, LogTabs.DATASET_LOGS);
}}
>
<Eye />
@ -259,6 +270,18 @@ export const getDatasetLogsTableColumns = (
return columns;
};
const taskInfo = {
taskId: '#9527',
fileName: 'PRD for DealBees 1.2 (1).text',
fileSize: '2.4G',
source: 'Github',
task: 'Parse',
state: 'Running',
startTime: '14/03/2025 14:53:39',
duration: '800',
details:
'\n17:43:21 Task has been received.\n17:43:25 Page(1~100000001): Start to parse.\n17:43:25 Page(1~100000001): Start to tag for every chunk ...\n17:43:45 Page(1~100000001): Tagging 2 chunks completed in 18.99s\n17:43:45 Page(1~100000001): Generate 2 chunks\n17:43:55 Page(1~100000001): Embedding chunks (10.60s)\n17:43:55 Page(1~100000001): Indexing done (0.07s). Task done (33.97s)\n17:43:58 created task raptor\n17:43:58 Task has been received.\n17:44:36 Cluster one layer: 2 -> 1\n17:44:36 Indexing done (0.05s). Task done (37.88s)\n17:44:40 created task graphrag\n17:44:41 Task has been received.\n17:50:57 Entities extraction of chunk 0 1/3 done, 25 nodes, 26 edges, 14893 tokens.\n17:56:01 [ERROR][Exception]: Operation timed out after 7200 seconds and 1 attempts.',
};
const FileLogsTable: FC<FileLogsTableProps> = ({
data,
pagination,
@ -272,11 +295,16 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
const { t } = useTranslate('knowledgeDetails');
const [isModalVisible, setIsModalVisible] = useState(false);
const { navigateToDataflowResult } = useNavigatePage();
const [logInfo, setLogInfo] = useState(taskInfo);
const showLog = (row: Row<DocumentLog>, active: LogTabs) => {
setLogInfo(row.original);
setIsModalVisible(true);
};
const columns = useMemo(() => {
console.log('columns', active);
return active === LogTabs.FILE_LOGS
? getFileLogsTableColumns(t, setIsModalVisible, navigateToDataflowResult)
: getDatasetLogsTableColumns(t, setIsModalVisible);
: getDatasetLogsTableColumns(t, showLog);
}, [active, t]);
const currentPagination = useMemo(
@ -308,18 +336,6 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
? Math.ceil(pagination.total / pagination.pageSize)
: 0,
});
const taskInfo = {
taskId: '#9527',
fileName: 'PRD for DealBees 1.2 (1).text',
fileSize: '2.4G',
source: 'Github',
task: 'Parse',
state: 'Running',
startTime: '14/03/2025 14:53:39',
duration: '800',
details:
'\n17:43:21 Task has been received.\n17:43:25 Page(1~100000001): Start to parse.\n17:43:25 Page(1~100000001): Start to tag for every chunk ...\n17:43:45 Page(1~100000001): Tagging 2 chunks completed in 18.99s\n17:43:45 Page(1~100000001): Generate 2 chunks\n17:43:55 Page(1~100000001): Embedding chunks (10.60s)\n17:43:55 Page(1~100000001): Indexing done (0.07s). Task done (33.97s)\n17:43:58 created task raptor\n17:43:58 Task has been received.\n17:44:36 Cluster one layer: 2 -> 1\n17:44:36 Indexing done (0.05s). Task done (37.88s)\n17:44:40 created task graphrag\n17:44:41 Task has been received.\n17:50:57 Entities extraction of chunk 0 1/3 done, 25 nodes, 26 edges, 14893 tokens.\n17:56:01 [ERROR][Exception]: Operation timed out after 7200 seconds and 1 attempts.',
};
return (
<div className="w-full h-[calc(100vh-350px)]">
<Table rootClassName="max-h-[calc(100vh-380px)]">
@ -364,7 +380,7 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
)}
</TableBody>
</Table>
<div className="flex items-center justify-end py-4 absolute bottom-3 right-12">
<div className="flex items-center justify-end absolute bottom-3 right-12">
<div className="space-x-2">
<RAGFlowPagination
{...{ current: pagination.current, pageSize: pagination.pageSize }}
@ -376,7 +392,7 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
<ProcessLogModal
visible={isModalVisible}
onCancel={() => setIsModalVisible(false)}
taskInfo={taskInfo}
logInfo={logInfo}
/>
</div>
);

View File

@ -29,6 +29,8 @@ import { useFetchDocumentList } from '@/hooks/use-document-request';
import { getExtension } from '@/utils/document-util';
import { pick } from 'lodash';
import { useMemo } from 'react';
import ProcessLogModal from '../process-log-modal';
import { useShowLog } from './hooks';
import { SetMetaDialog } from './set-meta-dialog';
import { useChangeDocumentParser } from './use-change-document-parser';
import { useDatasetTableColumns } from './use-dataset-table-columns';
@ -81,11 +83,13 @@ export function DatasetTable({
onSetMetaModalOk,
metaRecord,
} = useSaveMeta();
const { showLog, logInfo, logVisible, hideLog } = useShowLog(documents);
const columns = useDatasetTableColumns({
showChangeParserModal,
showRenameModal,
showSetMetaModal,
showLog,
});
const currentPagination = useMemo(() => {
@ -207,6 +211,13 @@ export function DatasetTable({
initialMetaData={metaRecord.meta_fields}
></SetMetaDialog>
)}
{logVisible && (
<ProcessLogModal
visible={logVisible}
onCancel={() => hideLog()}
logInfo={logInfo}
/>
)}
</div>
);
}

View File

@ -0,0 +1,113 @@
import { IconFontFill } from '@/components/icon-font';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { toFixed } from '@/utils/common-util';
import { t } from 'i18next';
import { lowerFirst } from 'lodash';
import { CirclePause, WandSparkles } from 'lucide-react';
import { useState } from 'react';
import { generateStatus, useFetchGenerateData } from './hook';
const MenuItem: React.FC<{ name: 'KnowledgeGraph' | 'Raptor' }> = ({
name,
}) => {
console.log(name, 'pppp');
const iconKeyMap = {
KnowledgeGraph: 'knowledgegraph',
Raptor: 'dataflow-01',
};
const {
data: { percent, type },
pauseGenerate,
} = useFetchGenerateData();
return (
<div className="flex items-start gap-2 flex-col w-full">
<div className="flex justify-start text-text-primary items-center gap-2">
<IconFontFill name={iconKeyMap[name]} className="text-accent-primary" />
{t(`knowledgeDetails.${lowerFirst(name)}`)}
</div>
{type === generateStatus.start && (
<div className="text-text-secondary text-sm">
{t(`knowledgeDetails.generate${name}`)}
</div>
)}
{type === generateStatus.running && (
<div className="flex justify-between items-center w-full">
<div className="w-[calc(100%-100px)] bg-border-button h-1 rounded-full">
<div
className="h-1 bg-accent-primary rounded-full"
style={{ width: `${toFixed(percent)}%` }}
></div>
</div>
<span>{toFixed(percent) as string}%</span>
<span
className="text-state-error"
onClick={() => {
pauseGenerate();
}}
>
<CirclePause />
</span>
</div>
)}
</div>
);
};
const Generate: React.FC = () => {
const [open, setOpen] = useState(false);
const handleOpenChange = (isOpen: boolean) => {
setOpen(isOpen);
console.log('Dropdown is now', isOpen ? 'open' : 'closed');
};
return (
<div className="generate">
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild>
<Button
variant={'transparent'}
onClick={() => {
handleOpenChange(!open);
}}
>
<WandSparkles className="mr-2" />
{t('knowledgeDetails.generate')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[380px] p-5 ">
<DropdownMenuItem
className="border cursor-pointer p-2 rounded-md hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
<MenuItem name="KnowledgeGraph" />
</DropdownMenuItem>
<DropdownMenuItem
className="border cursor-pointer p-2 rounded-md mt-3 hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
<MenuItem name="Raptor" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
export default Generate;

View File

@ -0,0 +1,32 @@
import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
export const generateStatus = {
running: 'running',
completed: 'completed',
start: 'start',
};
const useFetchGenerateData = () => {
let number = 10;
// TODO: 获取数据
const { data, isFetching: loading } = useQuery({
queryKey: ['generateData', 'id'],
initialData: { id: 0, percent: 0, type: 'running' },
gcTime: 0,
refetchInterval: 3000,
queryFn: async () => {
number += Math.random() * 10;
const data = {
id: Math.random(),
percent: number,
type: generateStatus.running,
};
return data;
},
});
const pauseGenerate = useCallback(() => {
// TODO: pause generate
console.log('pause generate');
}, []);
return { data, loading, pauseGenerate };
};
export { useFetchGenerateData };

View File

@ -1,68 +0,0 @@
import SvgIcon from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { t } from 'i18next';
import { lowerFirst, toLower } from 'lodash';
import { WandSparkles } from 'lucide-react';
const MenuItem: React.FC<{ name: 'KnowledgeGraph' | 'Raptor' }> = ({
name,
}) => {
console.log(name, 'pppp');
return (
<div className="flex items-start gap-2 flex-col">
<div className="flex justify-start text-text-primary items-center gap-2">
<SvgIcon name={`data-flow/${toLower(name)}`} width={24}></SvgIcon>
{t(`knowledgeDetails.${lowerFirst(name)}`)}
</div>
<div className="text-text-secondary text-sm">
{t(`knowledgeDetails.generate${name}`)}
</div>
</div>
);
};
const Generate: React.FC = () => {
return (
<div className="generate">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant={'transparent'}>
<WandSparkles className="mr-2" />
{t('knowledgeDetails.generate')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[380px] p-2 ">
<DropdownMenuItem className="border cursor-pointer p-2 rounded-md hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]">
<MenuItem name="KnowledgeGraph" />
</DropdownMenuItem>
<DropdownMenuItem
className="border cursor-pointer p-2 rounded-md mt-3 hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
<MenuItem name="Raptor" />
{/* <div className="flex items-start gap-2 flex-col">
<div className="flex items-center gap-2">
<SvgIcon name={`data-flow/raptor`} width={24}></SvgIcon>
{t('knowledgeDetails.raptor')}
</div>
<div>{t('knowledgeDetails.generateRaptor')}</div>
</div> */}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
export default Generate;

View File

@ -1,8 +1,10 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { useNextWebCrawl } from '@/hooks/document-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { useCallback, useState } from 'react';
import { IDocumentInfo } from '@/interfaces/database/document';
import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'umi';
import { ILogInfo } from '../process-log-modal';
export const useNavigateToOtherPage = () => {
const navigate = useNavigate();
@ -58,3 +60,41 @@ export const useHandleWebCrawl = () => {
showWebCrawlUploadModal,
};
};
export const useShowLog = (documents: IDocumentInfo[]) => {
const { showModal, hideModal, visible } = useSetModalState();
const [record, setRecord] = useState<IDocumentInfo>();
const logInfo = useMemo(() => {
const findRecord = documents.find(
(item: IDocumentInfo) => item.id === record?.id,
);
let log: ILogInfo = {
taskId: record?.id,
fileName: record?.name || '-',
details: record?.progress_msg || '-',
};
if (findRecord) {
log = {
taskId: findRecord.id,
fileName: findRecord.name,
fileSize: findRecord.size + '',
source: findRecord.source_type,
task: findRecord.status,
state: findRecord.run,
startTime: findRecord.process_begin_at,
endTime: findRecord.process_begin_at,
duration: findRecord.process_duration + 's',
details: findRecord.progress_msg,
};
}
return log;
}, [record, documents]);
const showLog = useCallback(
(data: IDocumentInfo) => {
setRecord(data);
showModal();
},
[showModal],
);
return { showLog, hideLog: hideModal, logVisible: visible, logInfo };
};

View File

@ -15,7 +15,7 @@ import { useFetchDocumentList } from '@/hooks/use-document-request';
import { Upload } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { DatasetTable } from './dataset-table';
import Generate from './generate';
import Generate from './generate-button/generate';
import { useBulkOperateDataset } from './use-bulk-operate-dataset';
import { useCreateEmptyDocument } from './use-create-empty-document';
import { useSelectDatasetFilters } from './use-select-filters';

View File

@ -11,6 +11,7 @@ import { RunningStatus, RunningStatusMap } from './constant';
interface IProps {
record: IDocumentInfo;
handleShowLog?: (record: IDocumentInfo) => void;
}
function Dot({ run }: { run: RunningStatus }) {
@ -85,11 +86,16 @@ export const PopoverContent = ({ record }: IProps) => {
);
};
export function ParsingCard({ record }: IProps) {
export function ParsingCard({ record, handleShowLog }: IProps) {
return (
<HoverCard>
<HoverCardTrigger asChild>
<Button variant={'transparent'} className="border-none" size={'sm'}>
<Button
variant={'transparent'}
className="border-none"
size={'sm'}
onClick={() => handleShowLog?.(record)}
>
<Dot run={record.run}></Dot>
</Button>
</HoverCardTrigger>

View File

@ -1,4 +1,5 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import { IconFontFill } from '@/components/icon-font';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
@ -14,7 +15,7 @@ import {
import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import { IDocumentInfo } from '@/interfaces/database/document';
import { CircleX, RefreshCw } from 'lucide-react';
import { CircleX } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { DocumentType, RunningStatus } from './constant';
@ -28,16 +29,26 @@ const IconMap = {
<div className="w-0 h-0 border-l-[10px] border-l-accent-primary border-t-8 border-r-4 border-b-8 border-transparent"></div>
),
[RunningStatus.RUNNING]: <CircleX size={14} color="var(--state-error)" />,
[RunningStatus.CANCEL]: <RefreshCw size={14} color="var(--accent-primary)" />,
[RunningStatus.DONE]: <RefreshCw size={14} color="var(--accent-primary)" />,
[RunningStatus.FAIL]: <RefreshCw size={14} color="var(--accent-primary)" />,
[RunningStatus.CANCEL]: (
<IconFontFill name="reparse" className="text-accent-primary" />
),
[RunningStatus.DONE]: (
<IconFontFill name="reparse" className="text-accent-primary" />
),
[RunningStatus.FAIL]: (
<IconFontFill name="reparse" className="text-accent-primary" />
),
};
export function ParsingStatusCell({
record,
showChangeParserModal,
showSetMetaModal,
}: { record: IDocumentInfo } & UseChangeDocumentParserShowType &
showLog,
}: {
record: IDocumentInfo;
showLog: (record: IDocumentInfo) => void;
} & UseChangeDocumentParserShowType &
UseSaveMetaShowType) {
const { t } = useTranslation();
const { run, parser_id, progress, chunk_num, id } = record;
@ -65,6 +76,9 @@ export function ParsingStatusCell({
return record.type !== DocumentType.Virtual;
}, [record]);
const handleShowLog = (record: IDocumentInfo) => {
showLog(record);
};
return (
<section className="flex gap-8 items-center">
<div className="w-[100px] text-ellipsis overflow-hidden flex items-center justify-between">
@ -85,41 +99,64 @@ export function ParsingStatusCell({
</DropdownMenu>
</div>
{showParse && (
<>
<ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
hidden={isZeroChunk || isRunning}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
<div className="flex items-center gap-3">
<Separator orientation="vertical" className="h-2.5" />
{!isParserRunning(run) && (
<ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
hidden={isZeroChunk || isRunning}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<Separator orientation="vertical" className="h-2.5" />
{operationIcon}
</div>
</ConfirmDeleteDialog>
{isParserRunning(run) ? (
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex items-center gap-1">
<Progress value={p} className="h-1 flex-1 min-w-10" />
{p}%
</div>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent record={record}></PopoverContent>
</HoverCardContent>
</HoverCard>
) : (
<ParsingCard record={record}></ParsingCard>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
>
{!isParserRunning(run) && operationIcon}
</div>
</ConfirmDeleteDialog>
)}
</>
{isParserRunning(run) ? (
<>
<HoverCard>
<HoverCardTrigger asChild>
<div
className="flex items-center gap-1"
onClick={() => handleShowLog(record)}
>
<Progress value={p} className="h-1 flex-1 min-w-10" />
{p}%
</div>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent
record={record}
handleShowLog={handleShowLog}
></PopoverContent>
</HoverCardContent>
</HoverCard>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
>
{operationIcon}
</div>
</>
) : (
<ParsingCard
record={record}
handleShowLog={handleShowLog}
></ParsingCard>
)}
</div>
)}
</section>
);

View File

@ -23,12 +23,13 @@ import { UseSaveMetaShowType } from './use-save-meta';
type UseDatasetTableColumnsType = UseChangeDocumentParserShowType &
UseRenameDocumentShowType &
UseSaveMetaShowType;
UseSaveMetaShowType & { showLog: (record: IDocumentInfo) => void };
export function useDatasetTableColumns({
showChangeParserModal,
showRenameModal,
showSetMetaModal,
showLog,
}: UseDatasetTableColumnsType) {
const { t } = useTranslation('translation', {
keyPrefix: 'knowledgeDetails',
@ -151,6 +152,7 @@ export function useDatasetTableColumns({
record={row.original}
showChangeParserModal={showChangeParserModal}
showSetMetaModal={showSetMetaModal}
showLog={showLog}
></ParsingStatusCell>
);
},

View File

@ -0,0 +1,132 @@
import FileStatusBadge from '@/components/file-status-badge';
import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import { useTranslate } from '@/hooks/common-hooks';
import React from 'react';
import reactStringReplace from 'react-string-replace';
export interface ILogInfo {
taskId?: string;
fileName: string;
fileSize?: string;
source?: string;
task?: string;
state?: 'Running' | 'Success' | 'Failed' | 'Pending';
startTime?: string;
endTime?: string;
duration?: string;
details: string;
}
interface ProcessLogModalProps {
visible: boolean;
onCancel: () => void;
logInfo: ILogInfo;
}
const InfoItem: React.FC<{
label: string;
value: string | React.ReactNode;
className?: string;
}> = ({ label, value, className = '' }) => {
return (
<div className={`flex flex-col mb-4 ${className}`}>
<span className="text-text-secondary text-sm">{label}</span>
<span className="text-text-primary mt-1">{value}</span>
</div>
);
};
const ProcessLogModal: React.FC<ProcessLogModalProps> = ({
visible,
onCancel,
logInfo,
}) => {
const { t } = useTranslate('knowledgeDetails');
const blackKeyList = [''];
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;
};
return (
<Modal
title={t('processLog')}
open={visible}
onCancel={onCancel}
footer={
<div className="flex justify-end">
<Button onClick={onCancel}>{t('close')}</Button>
</div>
}
className="process-log-modal"
>
<div className=" rounded-lg">
<div className="flex flex-wrap ">
{Object.keys(logInfo).map((key) => {
if (blackKeyList.includes(key)) {
return null;
}
if (key === 'details') {
return (
<div className="w-full" key={key}>
<InfoItem
label={t(key)}
value={
<div className="w-full whitespace-pre-line text-wrap bg-bg-card rounded-lg h-fit max-h-[350px] overflow-y-auto scrollbar-auto p-2.5">
{replaceText(logInfo.details)}
</div>
}
/>
</div>
);
}
if (key === 'Status') {
return (
<div className="flex flex-col" key={key}>
<span className="text-text-secondary text-sm">Status</span>
<div className="mt-1">
<FileStatusBadge status={logInfo.state} />
</div>
</div>
);
}
return (
<div className="w-1/2" key={key}>
<InfoItem
label={t(key)}
value={logInfo[key as keyof typeof logInfo]}
/>
</div>
);
})}
</div>
{/* <InfoItem label="Details" value={logInfo.details} /> */}
{/* <div>
<div>Details</div>
<div>
<ul className="space-y-2">
<div className={'w-full whitespace-pre-line text-wrap '}>
{replaceText(logInfo.details)}
</div>
</ul>
</div>
</div> */}
</div>
</Modal>
);
};
export default ProcessLogModal;