mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-26 00:46:52 +08:00
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:
@ -3,7 +3,7 @@ export enum LogTabs {
|
||||
DATASET_LOGS = 'datasetLogs',
|
||||
}
|
||||
|
||||
export enum processingType {
|
||||
export enum ProcessingType {
|
||||
knowledgeGraph = 'knowledgeGraph',
|
||||
raptor = 'raptor',
|
||||
}
|
||||
|
||||
@ -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' : 'kiki’s 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' : 'kiki’s 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' : 'kiki’s 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>
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
113
web/src/pages/dataset/dataset/generate-button/generate.tsx
Normal file
113
web/src/pages/dataset/dataset/generate-button/generate.tsx
Normal 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;
|
||||
32
web/src/pages/dataset/dataset/generate-button/hook.ts
Normal file
32
web/src/pages/dataset/dataset/generate-button/hook.ts
Normal 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 };
|
||||
@ -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;
|
||||
@ -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 };
|
||||
};
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
|
||||
132
web/src/pages/dataset/process-log-modal.tsx
Normal file
132
web/src/pages/dataset/process-log-modal.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user