mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-26 08:56:47 +08:00
Feat: Use data pipeline to visualize the parsing configuration of the knowledge base (#10423)
### What problem does this PR solve? #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: jinhai <haijin.chn@gmail.com> Signed-off-by: Jin Hai <haijin.chn@gmail.com> Co-authored-by: chanx <1243304602@qq.com> Co-authored-by: balibabu <cike8899@users.noreply.github.com> Co-authored-by: Lynn <lynn_inf@hotmail.com> Co-authored-by: 纷繁下的无奈 <zhileihuang@126.com> Co-authored-by: huangzl <huangzl@shinemo.com> Co-authored-by: writinwaters <93570324+writinwaters@users.noreply.github.com> Co-authored-by: Wilmer <33392318@qq.com> Co-authored-by: Adrian Weidig <adrianweidig@gmx.net> Co-authored-by: Zhichang Yu <yuzhichang@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Yongteng Lei <yongtengrey@outlook.com> Co-authored-by: Liu An <asiro@qq.com> Co-authored-by: buua436 <66937541+buua436@users.noreply.github.com> Co-authored-by: BadwomanCraZY <511528396@qq.com> Co-authored-by: cucusenok <31804608+cucusenok@users.noreply.github.com> Co-authored-by: Russell Valentine <russ@coldstonelabs.org> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Billy Bao <newyorkupperbay@gmail.com> Co-authored-by: Zhedong Cen <cenzhedong2@126.com> Co-authored-by: TensorNull <129579691+TensorNull@users.noreply.github.com> Co-authored-by: TensorNull <tensor.null@gmail.com> Co-authored-by: TeslaZY <TeslaZY@outlook.com> Co-authored-by: Ajay <160579663+aybanda@users.noreply.github.com> Co-authored-by: AB <aj@Ajays-MacBook-Air.local> Co-authored-by: 天海蒼灆 <huangaoqin@tecpie.com> Co-authored-by: He Wang <wanghechn@qq.com> Co-authored-by: Atsushi Hatakeyama <atu729@icloud.com> Co-authored-by: Jin Hai <haijin.chn@gmail.com> Co-authored-by: Mohamed Mathari <155896313+melmathari@users.noreply.github.com> Co-authored-by: Mohamed Mathari <nocodeventure@Mac-mini-van-Mohamed.fritz.box> Co-authored-by: Stephen Hu <stephenhu@seismic.com> Co-authored-by: Shaun Zhang <zhangwfjh@users.noreply.github.com> Co-authored-by: zhimeng123 <60221886+zhimeng123@users.noreply.github.com> Co-authored-by: mxc <mxc@example.com> Co-authored-by: Dominik Novotný <50611433+SgtMarmite@users.noreply.github.com> Co-authored-by: EVGENY M <168018528+rjohny55@users.noreply.github.com> Co-authored-by: mcoder6425 <mcoder64@gmail.com> Co-authored-by: lemsn <lemsn@msn.com> Co-authored-by: lemsn <lemsn@126.com> Co-authored-by: Adrian Gora <47756404+adagora@users.noreply.github.com> Co-authored-by: Womsxd <45663319+Womsxd@users.noreply.github.com> Co-authored-by: FatMii <39074672+FatMii@users.noreply.github.com>
This commit is contained in:
@ -3,7 +3,12 @@ export enum LogTabs {
|
||||
DATASET_LOGS = 'datasetLogs',
|
||||
}
|
||||
|
||||
export enum processingType {
|
||||
knowledgeGraph = 'knowledgeGraph',
|
||||
raptor = 'raptor',
|
||||
export enum ProcessingType {
|
||||
knowledgeGraph = 'GraphRAG',
|
||||
raptor = 'RAPTOR',
|
||||
}
|
||||
|
||||
export const ProcessingTypeMap = {
|
||||
[ProcessingType.knowledgeGraph]: 'Knowledge Graph',
|
||||
[ProcessingType.raptor]: 'Raptor',
|
||||
};
|
||||
|
||||
96
web/src/pages/dataset/dataset-overview/hook.ts
Normal file
96
web/src/pages/dataset/dataset-overview/hook.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from '@/hooks/logic-hooks';
|
||||
import kbService, {
|
||||
listDataPipelineLogDocument,
|
||||
listPipelineDatasetLogs,
|
||||
} from '@/services/knowledge-service';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { LogTabs } from './dataset-common';
|
||||
import { IFileLogList, IOverviewTital } from './interface';
|
||||
|
||||
const useFetchOverviewTital = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { id } = useParams();
|
||||
const knowledgeBaseId = searchParams.get('id') || id;
|
||||
const { data } = useQuery<IOverviewTital>({
|
||||
queryKey: ['overviewTital'],
|
||||
queryFn: async () => {
|
||||
const { data: res = {} } = await kbService.getKnowledgeBasicInfo({
|
||||
kb_id: knowledgeBaseId,
|
||||
});
|
||||
return res.data || [];
|
||||
},
|
||||
});
|
||||
return { data };
|
||||
};
|
||||
|
||||
const useFetchFileLogList = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const { filterValue, handleFilterSubmit } = useHandleFilterSubmit();
|
||||
const { id } = useParams();
|
||||
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
|
||||
LogTabs.FILE_LOGS,
|
||||
);
|
||||
const knowledgeBaseId = searchParams.get('id') || id;
|
||||
const fetchFunc =
|
||||
active === LogTabs.DATASET_LOGS
|
||||
? listPipelineDatasetLogs
|
||||
: listDataPipelineLogDocument;
|
||||
const { data } = useQuery<IFileLogList>({
|
||||
queryKey: [
|
||||
'fileLogList',
|
||||
knowledgeBaseId,
|
||||
pagination,
|
||||
searchString,
|
||||
active,
|
||||
filterValue,
|
||||
],
|
||||
placeholderData: (previousData) => {
|
||||
if (previousData === undefined) {
|
||||
return { logs: [], total: 0 };
|
||||
}
|
||||
return previousData;
|
||||
},
|
||||
enabled: true,
|
||||
queryFn: async () => {
|
||||
const { data: res = {} } = await fetchFunc(
|
||||
{
|
||||
kb_id: knowledgeBaseId,
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
keywords: searchString,
|
||||
// order_by: '',
|
||||
},
|
||||
{ ...filterValue },
|
||||
);
|
||||
return res.data || [];
|
||||
},
|
||||
});
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
return {
|
||||
data,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
setPagination,
|
||||
active,
|
||||
setActive,
|
||||
filterValue,
|
||||
handleFilterSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export { useFetchFileLogList, useFetchOverviewTital };
|
||||
@ -1,13 +1,16 @@
|
||||
import {
|
||||
CircleQuestionMark,
|
||||
Cpu,
|
||||
FileChartLine,
|
||||
HardDriveDownload,
|
||||
} from 'lucide-react';
|
||||
import { FC, useState } from 'react';
|
||||
import { FilterCollection } from '@/components/list-filter-bar/interface';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||
import { AntToolTip } from '@/components/ui/tooltip';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { t } from 'i18next';
|
||||
import { CircleQuestionMark } from 'lucide-react';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RunningStatus, RunningStatusMap } from '../dataset/constant';
|
||||
import { LogTabs } from './dataset-common';
|
||||
import { DatasetFilter } from './dataset-filter';
|
||||
import { useFetchFileLogList, useFetchOverviewTital } from './hook';
|
||||
import FileLogsTable from './overview-table';
|
||||
|
||||
interface StatCardProps {
|
||||
@ -15,15 +18,30 @@ interface StatCardProps {
|
||||
value: number;
|
||||
icon: JSX.Element;
|
||||
children?: JSX.Element;
|
||||
tooltip?: string;
|
||||
}
|
||||
interface CardFooterProcessProps {
|
||||
success: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => {
|
||||
const StatCard: FC<StatCardProps> = ({
|
||||
title,
|
||||
value,
|
||||
children,
|
||||
icon,
|
||||
tooltip,
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-bg-card p-4 rounded-lg border border-border flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="flex items-center gap-1 text-sm font-medium text-text-secondary">
|
||||
{title}
|
||||
<CircleQuestionMark size={12} />
|
||||
{tooltip && (
|
||||
<AntToolTip title={tooltip} trigger="hover">
|
||||
<CircleQuestionMark size={12} />
|
||||
</AntToolTip>
|
||||
)}
|
||||
</h3>
|
||||
{icon}
|
||||
</div>
|
||||
@ -35,115 +53,228 @@ 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;
|
||||
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}
|
||||
<span>{t('knowledgeDetails.success')}</span>
|
||||
<div className="w-full flex justify-between gap-4 rounded-lg text-sm font-bold text-text-primary">
|
||||
<div className="flex items-center justify-between rounded-md w-1/2 p-2 bg-state-success-5">
|
||||
<div className="flex items-center rounded-lg gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-state-success "></div>
|
||||
<div className="font-normal text-text-secondary text-xs">
|
||||
{t('knowledgeDetails.success')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{failed}
|
||||
<span>{t('knowledgeDetails.failed')}</span>
|
||||
<div>{success || 0}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md w-1/2 bg-state-error-5 p-2">
|
||||
<div className="flex items-center rounded-lg gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-state-error"></div>
|
||||
<div className="font-normal text-text-secondary text-xs">
|
||||
{t('knowledgeDetails.failed')}
|
||||
</div>
|
||||
</div>
|
||||
<div>{failed || 0}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{completed}
|
||||
<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=" rounded-full h-3 bg-accent-primary"
|
||||
style={{ width: successPrecentage + '%' }}
|
||||
></div>
|
||||
<div
|
||||
className=" rounded-full h-3 bg-state-error"
|
||||
style={{ width: failedPrecentage + '%' }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const filters = [
|
||||
{
|
||||
field: 'operation_status',
|
||||
label: t('knowledgeDetails.status'),
|
||||
list: Object.values(RunningStatus).map((value) => {
|
||||
// const value = key as RunningStatus;
|
||||
console.log(value);
|
||||
return {
|
||||
id: value,
|
||||
label: RunningStatusMap[value].label,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'types',
|
||||
label: t('knowledgeDetails.task'),
|
||||
list: [
|
||||
{
|
||||
id: 'Parse',
|
||||
label: 'Parse',
|
||||
},
|
||||
{
|
||||
id: 'Download',
|
||||
label: 'Download',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const FileLogsPage: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
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 ? 'Parse' : 'Parser',
|
||||
status:
|
||||
i === 0
|
||||
? 'Success'
|
||||
: i === 1
|
||||
? 'Failed'
|
||||
: i === 2
|
||||
? 'Running'
|
||||
: 'Pending',
|
||||
}));
|
||||
|
||||
const pagination = {
|
||||
current: 1,
|
||||
pageSize: 30,
|
||||
total: 100,
|
||||
};
|
||||
const [topAllData, setTopAllData] = useState({
|
||||
totalFiles: {
|
||||
value: 0,
|
||||
precent: 0,
|
||||
},
|
||||
downloads: {
|
||||
value: 0,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
},
|
||||
processing: {
|
||||
value: 0,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
},
|
||||
});
|
||||
const { data: topData } = useFetchOverviewTital();
|
||||
const {
|
||||
pagination: { total: fileTotal },
|
||||
} = useFetchDocumentList();
|
||||
|
||||
useEffect(() => {
|
||||
setTopAllData((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
processing: {
|
||||
value: topData?.processing || 0,
|
||||
success: topData?.finished || 0,
|
||||
failed: topData?.failed || 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [topData]);
|
||||
|
||||
useEffect(() => {
|
||||
setTopAllData((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
totalFiles: {
|
||||
value: fileTotal || 0,
|
||||
precent: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [fileTotal]);
|
||||
|
||||
const {
|
||||
data: tableOriginData,
|
||||
searchString,
|
||||
handleInputChange,
|
||||
pagination,
|
||||
setPagination,
|
||||
active,
|
||||
filterValue,
|
||||
handleFilterSubmit,
|
||||
setActive,
|
||||
} = useFetchFileLogList();
|
||||
|
||||
const tableList = useMemo(() => {
|
||||
console.log('tableList', tableOriginData);
|
||||
if (tableOriginData && tableOriginData.logs?.length) {
|
||||
return tableOriginData.logs.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
fileName: item.document_name,
|
||||
statusName: item.operation_status,
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [tableOriginData]);
|
||||
|
||||
const changeActiveLogs = (active: (typeof LogTabs)[keyof typeof LogTabs]) => {
|
||||
setActive(active);
|
||||
};
|
||||
const handlePaginationChange = (page: number, pageSize: number) => {
|
||||
console.log('Pagination changed:', { page, pageSize });
|
||||
setPagination({
|
||||
...pagination,
|
||||
page,
|
||||
pageSize: 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={t('datasetOverview.totalFiles')}
|
||||
value={topAllData.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">
|
||||
{topAllData.totalFiles.precent > 0 ? '+' : ''}
|
||||
{topAllData.totalFiles.precent}%{' '}
|
||||
</span>
|
||||
<span className="font-normal text-text-secondary text-xs">
|
||||
from last week
|
||||
</span>
|
||||
</div>
|
||||
</StatCard>
|
||||
<StatCard title="Downloading" value={28} icon={<HardDriveDownload />}>
|
||||
<StatCard
|
||||
title={t('datasetOverview.downloading')}
|
||||
value={topAllData.downloads.value}
|
||||
icon={
|
||||
isDark ? (
|
||||
<SvgIcon name="data-flow/data-icon" width={40} />
|
||||
) : (
|
||||
<SvgIcon name="data-flow/data-icon-bri" width={40} />
|
||||
)
|
||||
}
|
||||
tooltip={t('datasetOverview.downloadTip')}
|
||||
>
|
||||
<CardFooterProcess
|
||||
total={100}
|
||||
success={8}
|
||||
failed={2}
|
||||
completed={15}
|
||||
success={topAllData.downloads.success}
|
||||
failed={topAllData.downloads.failed}
|
||||
/>
|
||||
</StatCard>
|
||||
<StatCard title="Processing" value={156} icon={<Cpu />}>
|
||||
<CardFooterProcess total={20} success={8} failed={2} completed={15} />
|
||||
<StatCard
|
||||
title={t('datasetOverview.processing')}
|
||||
value={topAllData.processing.value}
|
||||
icon={
|
||||
isDark ? (
|
||||
<SvgIcon name="data-flow/processing-icon" width={40} />
|
||||
) : (
|
||||
<SvgIcon name="data-flow/processing-icon-bri" width={40} />
|
||||
)
|
||||
}
|
||||
tooltip={t('datasetOverview.processingTip')}
|
||||
>
|
||||
<CardFooterProcess
|
||||
success={topAllData.processing.success}
|
||||
failed={topAllData.processing.failed}
|
||||
/>
|
||||
</StatCard>
|
||||
</div>
|
||||
|
||||
{/* Tabs & Search */}
|
||||
<DatasetFilter active={active} setActive={changeActiveLogs} />
|
||||
<DatasetFilter
|
||||
filters={filters as FilterCollection[]}
|
||||
value={filterValue}
|
||||
active={active}
|
||||
setActive={changeActiveLogs}
|
||||
searchString={searchString}
|
||||
onSearchChange={handleInputChange}
|
||||
onChange={handleFilterSubmit}
|
||||
/>
|
||||
|
||||
{/* Table */}
|
||||
<FileLogsTable
|
||||
data={mockData}
|
||||
data={tableList}
|
||||
pagination={pagination}
|
||||
setPagination={handlePaginationChange}
|
||||
pageCount={10}
|
||||
|
||||
62
web/src/pages/dataset/dataset-overview/interface.ts
Normal file
62
web/src/pages/dataset/dataset-overview/interface.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { RunningStatus, RunningStatusMap } from '../dataset/constant';
|
||||
import { LogTabs } from './dataset-common';
|
||||
|
||||
export interface DocumentLog {
|
||||
fileName: string;
|
||||
status: RunningStatus;
|
||||
statusName: typeof RunningStatusMap;
|
||||
}
|
||||
|
||||
export interface FileLogsTableProps {
|
||||
data: Array<IFileLogItem & DocumentLog>;
|
||||
pageCount: number;
|
||||
pagination: {
|
||||
current: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
setPagination: (pagination: { page: number; pageSize: number }) => void;
|
||||
loading?: boolean;
|
||||
active: (typeof LogTabs)[keyof typeof LogTabs];
|
||||
}
|
||||
|
||||
export interface IOverviewTital {
|
||||
cancelled: number;
|
||||
failed: number;
|
||||
finished: number;
|
||||
processing: number;
|
||||
}
|
||||
|
||||
export interface IFileLogItem {
|
||||
create_date: string;
|
||||
create_time: number;
|
||||
document_id: string;
|
||||
document_name: string;
|
||||
document_suffix: string;
|
||||
document_type: string;
|
||||
dsl: any;
|
||||
path: string[];
|
||||
task_id: string;
|
||||
id: string;
|
||||
name: string;
|
||||
kb_id: string;
|
||||
operation_status: string;
|
||||
parser_id: string;
|
||||
pipeline_id: string;
|
||||
pipeline_title: string;
|
||||
avatar: string;
|
||||
process_begin_at: null | string;
|
||||
process_duration: number;
|
||||
progress: number;
|
||||
progress_msg: string;
|
||||
source_from: string;
|
||||
status: string;
|
||||
task_type: string;
|
||||
tenant_id: string;
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
}
|
||||
export interface IFileLogList {
|
||||
logs: IFileLogItem[];
|
||||
total: number;
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import FileStatusBadge from '@/components/file-status-badge';
|
||||
import { FileIcon } from '@/components/icon-font';
|
||||
import { FileIcon, IconFontFill } from '@/components/icon-font';
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import {
|
||||
@ -12,12 +11,16 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { RunningStatusMap } from '@/constants/knowledge';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import ProcessLogModal from '@/pages/datasets/process-log-modal';
|
||||
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
|
||||
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
|
||||
import { formatDate, formatSecondsToHumanReadable } from '@/utils/date';
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
Row,
|
||||
SortingState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
@ -27,62 +30,43 @@ import {
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import { TFunction } from 'i18next';
|
||||
import { ClipboardList, Eye } from 'lucide-react';
|
||||
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react';
|
||||
import { LogTabs, processingType } from './dataset-common';
|
||||
|
||||
interface DocumentLog {
|
||||
id: string;
|
||||
fileName: string;
|
||||
source: string;
|
||||
pipeline: string;
|
||||
startDate: string;
|
||||
task: string;
|
||||
status: 'Success' | 'Failed' | 'Running' | 'Pending';
|
||||
}
|
||||
|
||||
interface FileLogsTableProps {
|
||||
data: DocumentLog[];
|
||||
pageCount: number;
|
||||
pagination: {
|
||||
current: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
setPagination: (pagination: { page: number; pageSize: number }) => void;
|
||||
loading?: boolean;
|
||||
active: (typeof LogTabs)[keyof typeof LogTabs];
|
||||
}
|
||||
import { ArrowUpDown, ClipboardList, Eye } from 'lucide-react';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { RunningStatus } from '../dataset/constant';
|
||||
import ProcessLogModal from '../process-log-modal';
|
||||
import { LogTabs, ProcessingType, ProcessingTypeMap } from './dataset-common';
|
||||
import { DocumentLog, FileLogsTableProps, IFileLogItem } from './interface';
|
||||
|
||||
export const getFileLogsTableColumns = (
|
||||
t: TFunction<'translation', string>,
|
||||
setIsModalVisible: Dispatch<SetStateAction<boolean>>,
|
||||
showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void,
|
||||
kowledgeId: string,
|
||||
navigateToDataflowResult: (
|
||||
id: string,
|
||||
knowledgeId?: string | undefined,
|
||||
props: NavigateToDataflowResultProps,
|
||||
) => () => void,
|
||||
) => {
|
||||
// const { t } = useTranslate('knowledgeDetails');
|
||||
const columns: ColumnDef<DocumentLog>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={table.getIsAllRowsSelected()}
|
||||
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={row.getIsSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
),
|
||||
},
|
||||
const columns: ColumnDef<IFileLogItem & DocumentLog>[] = [
|
||||
// {
|
||||
// id: 'select',
|
||||
// header: ({ table }) => (
|
||||
// <input
|
||||
// type="checkbox"
|
||||
// checked={table.getIsAllRowsSelected()}
|
||||
// onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
// className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
// />
|
||||
// ),
|
||||
// cell: ({ row }) => (
|
||||
// <input
|
||||
// type="checkbox"
|
||||
// checked={row.getIsSelected()}
|
||||
// onChange={row.getToggleSelectedHandler()}
|
||||
// className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
// />
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
@ -96,10 +80,10 @@ export const getFileLogsTableColumns = (
|
||||
cell: ({ row }) => (
|
||||
<div
|
||||
className="flex items-center gap-2 text-text-primary"
|
||||
onClick={navigateToDataflowResult(
|
||||
row.original.id,
|
||||
row.original.kb_id,
|
||||
)}
|
||||
// onClick={navigateToDataflowResult(
|
||||
// row.original.id,
|
||||
// row.original.kb_id,
|
||||
// )}
|
||||
>
|
||||
<FileIcon name={row.original.fileName}></FileIcon>
|
||||
{row.original.fileName}
|
||||
@ -107,68 +91,97 @@ export const getFileLogsTableColumns = (
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'source',
|
||||
accessorKey: 'source_from',
|
||||
header: t('source'),
|
||||
cell: ({ row }) => (
|
||||
<div className="text-text-primary">{row.original.source}</div>
|
||||
<div className="text-text-primary">{row.original.source_from}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'pipeline',
|
||||
accessorKey: 'pipeline_title',
|
||||
header: t('dataPipeline'),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<RAGFlowAvatar
|
||||
avatar={null}
|
||||
name={row.original.pipeline}
|
||||
avatar={row.original.avatar}
|
||||
name={row.original.pipeline_title}
|
||||
className="size-4"
|
||||
/>
|
||||
{row.original.pipeline}
|
||||
{row.original.pipeline_title}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'startDate',
|
||||
header: t('startDate'),
|
||||
accessorKey: 'process_begin_at',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
{t('startDate')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="text-text-primary">{row.original.startDate}</div>
|
||||
<div className="text-text-primary">
|
||||
{formatDate(row.original.process_begin_at)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'task',
|
||||
accessorKey: 'task_type',
|
||||
header: t('task'),
|
||||
cell: ({ row }) => (
|
||||
<div className="text-text-primary">{row.original.task}</div>
|
||||
<div className="text-text-primary">{row.original.task_type}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
accessorKey: 'operation_status',
|
||||
header: t('status'),
|
||||
cell: ({ row }) => <FileStatusBadge status={row.original.status} />,
|
||||
cell: ({ row }) => (
|
||||
<FileStatusBadge
|
||||
status={row.original.operation_status as RunningStatus}
|
||||
name={
|
||||
RunningStatusMap[row.original.operation_status as RunningStatus]
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'operations',
|
||||
header: t('operations'),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-start space-x-2">
|
||||
<div className="flex justify-start space-x-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="p-1"
|
||||
onClick={() => {
|
||||
setIsModalVisible(true);
|
||||
showLog(row, LogTabs.FILE_LOGS);
|
||||
}}
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="p-1"
|
||||
onClick={navigateToDataflowResult(row.original.id)}
|
||||
>
|
||||
<ClipboardList />
|
||||
</Button>
|
||||
{row.original.pipeline_id && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="p-1"
|
||||
onClick={navigateToDataflowResult({
|
||||
id: row.original.id,
|
||||
[PipelineResultSearchParams.KnowledgeId]: kowledgeId,
|
||||
[PipelineResultSearchParams.DocumentId]:
|
||||
row.original.document_id,
|
||||
[PipelineResultSearchParams.IsReadOnly]: 'false',
|
||||
[PipelineResultSearchParams.Type]: 'dataflow',
|
||||
})}
|
||||
>
|
||||
<ClipboardList />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@ -179,29 +192,29 @@ export const getFileLogsTableColumns = (
|
||||
|
||||
export const getDatasetLogsTableColumns = (
|
||||
t: TFunction<'translation', string>,
|
||||
setIsModalVisible: Dispatch<SetStateAction<boolean>>,
|
||||
showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void,
|
||||
) => {
|
||||
// const { t } = useTranslate('knowledgeDetails');
|
||||
const columns: ColumnDef<DocumentLog>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={table.getIsAllRowsSelected()}
|
||||
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={row.getIsSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
),
|
||||
},
|
||||
const columns: ColumnDef<IFileLogItem & DocumentLog>[] = [
|
||||
// {
|
||||
// id: 'select',
|
||||
// header: ({ table }) => (
|
||||
// <input
|
||||
// type="checkbox"
|
||||
// checked={table.getIsAllRowsSelected()}
|
||||
// onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
// className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
// />
|
||||
// ),
|
||||
// cell: ({ row }) => (
|
||||
// <input
|
||||
// type="checkbox"
|
||||
// checked={row.getIsSelected()}
|
||||
// onChange={row.getToggleSelectedHandler()}
|
||||
// className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
|
||||
// />
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
@ -210,43 +223,74 @@ export const getDatasetLogsTableColumns = (
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'startDate',
|
||||
header: t('startDate'),
|
||||
accessorKey: 'process_begin_at',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
{t('startDate')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="text-text-primary">{row.original.startDate}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'processingType',
|
||||
header: t('processingType'),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
{processingType.knowledgeGraph === row.original.processingType && (
|
||||
<SvgIcon name={`data-flow/knowledgegraph`} width={24}></SvgIcon>
|
||||
)}
|
||||
{processingType.raptor === row.original.processingType && (
|
||||
<SvgIcon name={`data-flow/raptor`} width={24}></SvgIcon>
|
||||
)}
|
||||
{row.original.processingType}
|
||||
<div className="text-text-primary">
|
||||
{formatDate(row.original.process_begin_at)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
accessorKey: 'task_type',
|
||||
header: t('processingType'),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
{ProcessingType.knowledgeGraph === row.original.task_type && (
|
||||
<IconFontFill
|
||||
name={`knowledgegraph`}
|
||||
className="text-text-secondary"
|
||||
></IconFontFill>
|
||||
)}
|
||||
{ProcessingType.raptor === row.original.task_type && (
|
||||
<IconFontFill
|
||||
name={`dataflow-01`}
|
||||
className="text-text-secondary"
|
||||
></IconFontFill>
|
||||
)}
|
||||
{ProcessingTypeMap[row.original.task_type as ProcessingType] ||
|
||||
row.original.task_type}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'operation_status',
|
||||
header: t('status'),
|
||||
cell: ({ row }) => <FileStatusBadge status={row.original.status} />,
|
||||
cell: ({ row }) => (
|
||||
// <FileStatusBadge
|
||||
// status={row.original.status}
|
||||
// name={row.original.statusName}
|
||||
// />
|
||||
<FileStatusBadge
|
||||
status={row.original.operation_status as RunningStatus}
|
||||
name={
|
||||
RunningStatusMap[row.original.operation_status as RunningStatus]
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'operations',
|
||||
header: t('operations'),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-start space-x-2">
|
||||
<div className="flex justify-start space-x-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="p-1"
|
||||
onClick={() => {
|
||||
setIsModalVisible(true);
|
||||
showLog(row, LogTabs.DATASET_LOGS);
|
||||
}}
|
||||
>
|
||||
<Eye />
|
||||
@ -272,11 +316,35 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const { navigateToDataflowResult } = useNavigatePage();
|
||||
const [logInfo, setLogInfo] = useState<IFileLogItem>();
|
||||
const kowledgeId = useParams().id;
|
||||
const showLog = (row: Row<IFileLogItem & DocumentLog>) => {
|
||||
const logDetail = {
|
||||
taskId: row.original?.dsl?.task_id,
|
||||
fileName: row.original.document_name,
|
||||
source: row.original.source_from,
|
||||
task: row.original?.task_type,
|
||||
status: row.original.statusName,
|
||||
startDate: formatDate(row.original.process_begin_at),
|
||||
duration: formatSecondsToHumanReadable(
|
||||
row.original.process_duration || 0,
|
||||
),
|
||||
details: row.original.progress_msg,
|
||||
};
|
||||
console.log('logDetail', logDetail);
|
||||
setLogInfo(logDetail);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const columns = useMemo(() => {
|
||||
console.log('columns', active);
|
||||
return active === LogTabs.FILE_LOGS
|
||||
? getFileLogsTableColumns(t, setIsModalVisible, navigateToDataflowResult)
|
||||
: getDatasetLogsTableColumns(t, setIsModalVisible);
|
||||
? getFileLogsTableColumns(
|
||||
t,
|
||||
showLog,
|
||||
kowledgeId || '',
|
||||
navigateToDataflowResult,
|
||||
)
|
||||
: getDatasetLogsTableColumns(t, showLog);
|
||||
}, [active, t]);
|
||||
|
||||
const currentPagination = useMemo(
|
||||
@ -287,8 +355,8 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
||||
[pagination],
|
||||
);
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
const table = useReactTable<IFileLogItem & DocumentLog>({
|
||||
data: data || [],
|
||||
columns,
|
||||
manualPagination: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
@ -308,19 +376,9 @@ 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: 'PRD for DealBees 1.2 (1).text',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-[calc(100vh-350px)]">
|
||||
<div className="w-full h-[calc(100vh-360px)]">
|
||||
<Table rootClassName="max-h-[calc(100vh-380px)]">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
@ -337,7 +395,7 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody className="relative">
|
||||
{table.getRowModel().rows.length ? (
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
@ -363,7 +421,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 }}
|
||||
@ -372,11 +430,14 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ProcessLogModal
|
||||
visible={isModalVisible}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
taskInfo={taskInfo}
|
||||
/>
|
||||
{isModalVisible && (
|
||||
<ProcessLogModal
|
||||
title={active === LogTabs.FILE_LOGS ? t('fileLogs') : t('datasetLog')}
|
||||
visible={isModalVisible}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
logInfo={logInfo}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { NaiveConfiguration } from './naive';
|
||||
import { SavingButton } from './saving-button';
|
||||
|
||||
export function ChunkMethodForm() {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col">
|
||||
<div className="overflow-auto flex-1 min-h-0">
|
||||
<NaiveConfiguration></NaiveConfiguration>
|
||||
</div>
|
||||
<div className="text-right pt-4 flex justify-end gap-3">
|
||||
<Button
|
||||
type="reset"
|
||||
className="bg-transparent text-color-white hover:bg-transparent border-gray-500 border-[1px]"
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
{t('knowledgeConfiguration.cancel')}
|
||||
</Button>
|
||||
<SavingButton></SavingButton>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
import { IDataPipelineSelectNode } from '@/components/data-pipeline-select';
|
||||
import { IconFont } from '@/components/icon-font';
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { Link } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LinkDataPipelineModal from './link-data-pipline-modal';
|
||||
export interface IDataPipelineNodeProps extends IDataPipelineSelectNode {
|
||||
isDefault?: boolean;
|
||||
linked?: boolean;
|
||||
}
|
||||
|
||||
export interface ILinkDataPipelineProps {
|
||||
data?: IDataPipelineNodeProps;
|
||||
handleLinkOrEditSubmit?: (data: IDataPipelineNodeProps | undefined) => void;
|
||||
}
|
||||
|
||||
interface DataPipelineItemProps extends IDataPipelineNodeProps {
|
||||
openLinkModalFunc?: (open: boolean, data?: IDataPipelineNodeProps) => void;
|
||||
}
|
||||
|
||||
const DataPipelineItem = (props: DataPipelineItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { name, avatar, isDefault, linked, openLinkModalFunc } = props;
|
||||
const openUnlinkModal = () => {
|
||||
Modal.show({
|
||||
visible: true,
|
||||
className: '!w-[560px]',
|
||||
title: t('dataflowParser.unlinkPipelineModalTitle'),
|
||||
children: (
|
||||
<div
|
||||
className="text-sm text-text-secondary"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('dataflowParser.unlinkPipelineModalContent'),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
onVisibleChange: () => {
|
||||
Modal.hide();
|
||||
},
|
||||
footer: (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant={'outline'} onClick={() => Modal.hide()}>
|
||||
{t('dataflowParser.changeStepModalCancelText')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
className="!bg-state-error text-bg-base"
|
||||
onClick={() => {
|
||||
Modal.hide();
|
||||
}}
|
||||
>
|
||||
{t('dataflowParser.unlinkPipelineModalConfirmText')}
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-1 px-2 rounded-md border">
|
||||
<div className="flex items-center gap-1">
|
||||
<RAGFlowAvatar avatar={avatar} name={name} className="size-4" />
|
||||
<div>{name}</div>
|
||||
{/* {isDefault && (
|
||||
<div className="text-xs bg-text-secondary text-bg-base px-2 py-1 rounded-md">
|
||||
{t('knowledgeConfiguration.default')}
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
{/* <div className="flex gap-1 items-center">
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
className="border-none"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
openLinkModalFunc?.(true, { ...omit(props, ['openLinkModalFunc']) })
|
||||
}
|
||||
>
|
||||
<Settings2 />
|
||||
</Button>
|
||||
{!isDefault && (
|
||||
<>
|
||||
{linked && (
|
||||
<Button
|
||||
type="button"
|
||||
variant={'transparent'}
|
||||
className="border-none"
|
||||
onClick={() => {
|
||||
openUnlinkModal();
|
||||
}}
|
||||
>
|
||||
<Unlink />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LinkDataPipeline = (props: ILinkDataPipelineProps) => {
|
||||
const { data, handleLinkOrEditSubmit: submit } = props;
|
||||
const { t } = useTranslation();
|
||||
const [openLinkModal, setOpenLinkModal] = useState(false);
|
||||
const [currentDataPipeline, setCurrentDataPipeline] =
|
||||
useState<IDataPipelineNodeProps>();
|
||||
const pipelineNode: IDataPipelineNodeProps[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: data?.id,
|
||||
name: data?.name,
|
||||
avatar: data?.avatar,
|
||||
isDefault: data?.isDefault,
|
||||
linked: true,
|
||||
},
|
||||
],
|
||||
[data],
|
||||
);
|
||||
const openLinkModalFunc = (open: boolean, data?: IDataPipelineNodeProps) => {
|
||||
console.log('open', open, data);
|
||||
setOpenLinkModal(open);
|
||||
if (data) {
|
||||
setCurrentDataPipeline(data);
|
||||
} else {
|
||||
setCurrentDataPipeline(undefined);
|
||||
}
|
||||
};
|
||||
const handleLinkOrEditSubmit = (
|
||||
data: IDataPipelineSelectNode | undefined,
|
||||
) => {
|
||||
console.log('handleLinkOrEditSubmit', data);
|
||||
submit?.(data);
|
||||
setOpenLinkModal(false);
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<section className="flex flex-col">
|
||||
<div className="flex items-center gap-1 text-text-primary text-sm">
|
||||
<IconFont name="Pipeline" />
|
||||
{t('knowledgeConfiguration.dataPipeline')}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-center text-xs text-text-secondary">
|
||||
{t('knowledgeConfiguration.linkPipelineSetTip')}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant={'transparent'}
|
||||
onClick={() => {
|
||||
openLinkModalFunc?.(true);
|
||||
}}
|
||||
>
|
||||
<Link />
|
||||
<span className="text-xs text-text-primary">
|
||||
{t('knowledgeConfiguration.linkDataPipeline')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
{pipelineNode.map(
|
||||
(item) =>
|
||||
item.id && (
|
||||
<DataPipelineItem
|
||||
key={item.id}
|
||||
openLinkModalFunc={openLinkModalFunc}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
avatar={item.avatar}
|
||||
isDefault={item.isDefault}
|
||||
linked={item.linked}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</section>
|
||||
<LinkDataPipelineModal
|
||||
data={currentDataPipeline}
|
||||
open={openLinkModal}
|
||||
setOpen={(open: boolean) => {
|
||||
openLinkModalFunc(open);
|
||||
}}
|
||||
onSubmit={handleLinkOrEditSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default LinkDataPipeline;
|
||||
@ -0,0 +1,163 @@
|
||||
import {
|
||||
DataFlowSelect,
|
||||
IDataPipelineSelectNode,
|
||||
} from '@/components/data-pipeline-select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from 'i18next';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { pipelineFormSchema } from '../form-schema';
|
||||
import { IDataPipelineNodeProps } from './link-data-pipeline';
|
||||
|
||||
const LinkDataPipelineModal = ({
|
||||
data,
|
||||
open,
|
||||
setOpen,
|
||||
onSubmit,
|
||||
}: {
|
||||
data: IDataPipelineNodeProps | undefined;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
onSubmit?: (pipeline: IDataPipelineSelectNode | undefined) => void;
|
||||
}) => {
|
||||
const isEdit = !!data;
|
||||
const [list, setList] = useState<IDataPipelineSelectNode[]>();
|
||||
const form = useForm<z.infer<typeof pipelineFormSchema>>({
|
||||
resolver: zodResolver(pipelineFormSchema),
|
||||
defaultValues: {
|
||||
pipeline_id: '',
|
||||
set_default: false,
|
||||
file_filter: '',
|
||||
},
|
||||
});
|
||||
// const [open, setOpen] = useState(false);
|
||||
const { navigateToAgents } = useNavigatePage();
|
||||
const handleFormSubmit = (values: any) => {
|
||||
console.log(values, data);
|
||||
// const param = {
|
||||
// ...data,
|
||||
// ...values,
|
||||
// };
|
||||
const pipeline = list?.find((item) => item.id === values.pipeline_id);
|
||||
onSubmit?.(pipeline);
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
className="!w-[560px]"
|
||||
title={
|
||||
!isEdit
|
||||
? t('knowledgeConfiguration.linkDataPipeline')
|
||||
: t('knowledgeConfiguration.eidtLinkDataPipeline')
|
||||
}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
showfooter={false}
|
||||
>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleFormSubmit)}>
|
||||
<div className="flex flex-col gap-4 ">
|
||||
{!isEdit && (
|
||||
<DataFlowSelect
|
||||
toDataPipeline={navigateToAgents}
|
||||
formFieldName="pipeline_id"
|
||||
setDataList={setList}
|
||||
/>
|
||||
)}
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name={'file_filter'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-2 justify-between ">
|
||||
<FormLabel
|
||||
tooltip={t('knowledgeConfiguration.fileFilterTip')}
|
||||
className="text-sm text-text-primary whitespace-wrap "
|
||||
>
|
||||
{t('knowledgeConfiguration.fileFilter')}
|
||||
</FormLabel>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground">
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t(
|
||||
'knowledgeConfiguration.filterPlaceholder',
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-full"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{isEdit && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'set_default'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-2 justify-between ">
|
||||
<FormLabel
|
||||
tooltip={t('knowledgeConfiguration.setDefaultTip')}
|
||||
className="text-sm text-text-primary whitespace-wrap "
|
||||
>
|
||||
{t('knowledgeConfiguration.setDefault')}
|
||||
</FormLabel>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground">
|
||||
<FormControl>
|
||||
<Switch
|
||||
value={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-full"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)} */}
|
||||
<div className="flex justify-end gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant={'outline'}
|
||||
className="btn-primary"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{t('modal.cancelText')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={'default'}
|
||||
className="btn-primary"
|
||||
onClick={form.handleSubmit(handleFormSubmit)}
|
||||
>
|
||||
{t('modal.okText')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
export default LinkDataPipelineModal;
|
||||
@ -9,6 +9,9 @@ export function ConfigurationFormContainer({
|
||||
return <section className={cn('space-y-4', className)}>{children}</section>;
|
||||
}
|
||||
|
||||
export function MainContainer({ children }: PropsWithChildren) {
|
||||
return <section className="space-y-5">{children}</section>;
|
||||
export function MainContainer({
|
||||
children,
|
||||
className,
|
||||
}: PropsWithChildren & { className?: string }) {
|
||||
return <section className={cn('space-y-5', className)}>{children}</section>;
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
@ -10,15 +11,18 @@ import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import {
|
||||
useHasParsedDocument,
|
||||
useSelectChunkMethodList,
|
||||
useSelectEmbeddingModelOptions,
|
||||
} from '../hooks';
|
||||
|
||||
export function ChunkMethodItem() {
|
||||
interface IProps {
|
||||
line?: 1 | 2;
|
||||
isEdit?: boolean;
|
||||
}
|
||||
export function ChunkMethodItem(props: IProps) {
|
||||
const { line } = props;
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
// const handleChunkMethodSelectChange = useHandleChunkMethodSelectChange(form);
|
||||
@ -29,28 +33,29 @@ export function ChunkMethodItem() {
|
||||
control={form.control}
|
||||
name={'parser_id'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="flex items-center">
|
||||
<FormItem className=" items-center space-y-1">
|
||||
<div className={line === 1 ? 'flex items-center' : ''}>
|
||||
<FormLabel
|
||||
required
|
||||
tooltip={t('chunkMethodTip')}
|
||||
className="text-sm text-muted-foreground whitespace-wrap w-1/4"
|
||||
className={cn('text-sm', {
|
||||
'w-1/4 whitespace-pre-wrap': line === 1,
|
||||
})}
|
||||
>
|
||||
{t('chunkMethod')}
|
||||
{t('dataPipeline')}
|
||||
</FormLabel>
|
||||
<div className="w-3/4 ">
|
||||
<div className={line === 1 ? 'w-3/4 ' : 'w-full'}>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={parserList}
|
||||
placeholder={t('chunkMethodPlaceholder')}
|
||||
// onChange={handleChunkMethodSelectChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-1/4"></div>
|
||||
<div className={line === 1 ? 'w-1/4' : ''}></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
@ -58,49 +63,55 @@ export function ChunkMethodItem() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EmbeddingModelItem({ line = 1 }: { line?: 1 | 2 }) {
|
||||
export function EmbeddingModelItem({ line = 1, isEdit = true }: IProps) {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
const embeddingModelOptions = useSelectEmbeddingModelOptions();
|
||||
const disabled = useHasParsedDocument();
|
||||
|
||||
const disabled = useHasParsedDocument(isEdit);
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'embd_id'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className={cn({ 'flex items-center': line === 1 })}>
|
||||
<FormLabel
|
||||
required
|
||||
tooltip={t('embeddingModelTip')}
|
||||
className={cn('text-sm whitespace-wrap ', {
|
||||
'w-1/4': line === 1,
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'embd_id'}
|
||||
render={({ field }) => (
|
||||
<FormItem className={cn(' items-center space-y-0 ')}>
|
||||
<div
|
||||
className={cn('flex', {
|
||||
' items-center': line === 1,
|
||||
'flex-col gap-1': line === 2,
|
||||
})}
|
||||
>
|
||||
{t('embeddingModel')}
|
||||
</FormLabel>
|
||||
<div
|
||||
className={cn('text-muted-foreground', { 'w-3/4': line === 1 })}
|
||||
>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={embeddingModelOptions}
|
||||
disabled={disabled}
|
||||
placeholder={t('embeddingModelPlaceholder')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel
|
||||
required
|
||||
tooltip={t('embeddingModelTip')}
|
||||
className={cn('text-sm whitespace-wrap ', {
|
||||
'w-1/4': line === 1,
|
||||
})}
|
||||
>
|
||||
{t('embeddingModel')}
|
||||
</FormLabel>
|
||||
<div
|
||||
className={cn('text-muted-foreground', { 'w-3/4': line === 1 })}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectWithSearch
|
||||
onChange={field.onChange}
|
||||
value={field.value}
|
||||
options={embeddingModelOptions}
|
||||
disabled={isEdit ? disabled : false}
|
||||
placeholder={t('embeddingModelPlaceholder')}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-1/4"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex pt-1">
|
||||
<div className={line === 1 ? 'w-1/4' : ''}></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -142,155 +153,6 @@ export function ParseTypeItem() {
|
||||
);
|
||||
}
|
||||
|
||||
export function DataFlowItem() {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'data_flow'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="">
|
||||
<div className="flex gap-2 justify-between ">
|
||||
<FormLabel
|
||||
tooltip={t('dataFlowTip')}
|
||||
className="text-sm text-text-primary whitespace-wrap "
|
||||
>
|
||||
{t('dataFlow')}
|
||||
</FormLabel>
|
||||
<div className="text-sm flex text-text-primary">
|
||||
{t('buildItFromScratch')}
|
||||
<ArrowUpRight size={14} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground">
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
placeholder={t('dataFlowPlaceholder')}
|
||||
options={[{ value: '0', label: t('dataFlowDefault') }]}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-1/4"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DataExtractKnowledgeItem() {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'extractKnowledgeGraph'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="">
|
||||
<FormLabel
|
||||
tooltip={t('extractKnowledgeGraphTip')}
|
||||
className="text-sm whitespace-wrap "
|
||||
>
|
||||
{t('extractKnowledgeGraph')}
|
||||
</FormLabel>
|
||||
<div className="text-muted-foreground">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-1/4"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>{' '}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'useRAPTORToEnhanceRetrieval'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="">
|
||||
<FormLabel
|
||||
tooltip={t('useRAPTORToEnhanceRetrievalTip')}
|
||||
className="text-sm whitespace-wrap "
|
||||
>
|
||||
{t('useRAPTORToEnhanceRetrieval')}
|
||||
</FormLabel>
|
||||
<div className="text-muted-foreground">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-1/4"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function TeamItem() {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'team'}
|
||||
render={({ field }) => (
|
||||
<FormItem className=" items-center space-y-0 ">
|
||||
<div className="">
|
||||
<FormLabel
|
||||
tooltip={t('teamTip')}
|
||||
className="text-sm whitespace-wrap "
|
||||
>
|
||||
<span className="text-destructive mr-1"> *</span>
|
||||
{t('team')}
|
||||
</FormLabel>
|
||||
<div className="text-muted-foreground">
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
placeholder={t('teamPlaceholder')}
|
||||
options={[{ value: '0', label: t('teamDefault') }]}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-1">
|
||||
<div className="w-1/4"></div>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EnableAutoGenerateItem() {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
|
||||
@ -11,6 +11,9 @@ export const formSchema = z.object({
|
||||
avatar: z.any().nullish(),
|
||||
permission: z.string().optional(),
|
||||
parser_id: z.string(),
|
||||
pipeline_id: z.string().optional(),
|
||||
pipeline_name: z.string().optional(),
|
||||
pipeline_avatar: z.string().optional(),
|
||||
embd_id: z.string(),
|
||||
parser_config: z
|
||||
.object({
|
||||
@ -71,3 +74,18 @@ export const formSchema = z.object({
|
||||
pagerank: z.number(),
|
||||
// icon: z.array(z.instanceof(File)),
|
||||
});
|
||||
|
||||
export const pipelineFormSchema = z.object({
|
||||
pipeline_id: z.string().optional(),
|
||||
set_default: z.boolean().optional(),
|
||||
file_filter: z.string().optional(),
|
||||
});
|
||||
|
||||
// export const linkPiplineFormSchema = pipelineFormSchema.pick({
|
||||
// pipeline_id: true,
|
||||
// file_filter: true,
|
||||
// });
|
||||
// export const editPiplineFormSchema = pipelineFormSchema.pick({
|
||||
// set_default: true,
|
||||
// file_filter: true,
|
||||
// });
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { AvatarUpload } from '@/components/avatar-upload';
|
||||
import PageRankFormField from '@/components/page-rank-form-field';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
@ -9,6 +10,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TagItems } from './components/tag-item';
|
||||
import { EmbeddingModelItem } from './configuration/common-item';
|
||||
import { PermissionFormField } from './permission-form-field';
|
||||
|
||||
@ -17,14 +19,14 @@ export function GeneralForm() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="items-center space-y-0">
|
||||
<div className="flex">
|
||||
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
|
||||
<FormLabel className="text-sm whitespace-nowrap w-1/4">
|
||||
<span className="text-red-600">*</span>
|
||||
{t('common.name')}
|
||||
</FormLabel>
|
||||
@ -45,7 +47,7 @@ export function GeneralForm() {
|
||||
render={({ field }) => (
|
||||
<FormItem className="items-center space-y-0">
|
||||
<div className="flex">
|
||||
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
|
||||
<FormLabel className="text-sm whitespace-nowrap w-1/4">
|
||||
{t('setting.avatar')}
|
||||
</FormLabel>
|
||||
<FormControl className="w-3/4">
|
||||
@ -70,7 +72,7 @@ export function GeneralForm() {
|
||||
return (
|
||||
<FormItem className="items-center space-y-0">
|
||||
<div className="flex">
|
||||
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
|
||||
<FormLabel className="text-sm whitespace-nowrap w-1/4">
|
||||
{t('flow.description')}
|
||||
</FormLabel>
|
||||
<FormControl className="w-3/4">
|
||||
@ -87,6 +89,9 @@ export function GeneralForm() {
|
||||
/>
|
||||
<PermissionFormField></PermissionFormField>
|
||||
<EmbeddingModelItem></EmbeddingModelItem>
|
||||
</section>
|
||||
<PageRankFormField></PageRankFormField>
|
||||
|
||||
<TagItems></TagItems>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -25,8 +25,10 @@ export function useSelectEmbeddingModelOptions() {
|
||||
return allOptions[LlmModelType.Embedding];
|
||||
}
|
||||
|
||||
export function useHasParsedDocument() {
|
||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
|
||||
export function useHasParsedDocument(isEdit?: boolean) {
|
||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration({
|
||||
isEdit,
|
||||
});
|
||||
return knowledgeDetails.chunk_num > 0;
|
||||
}
|
||||
|
||||
@ -39,6 +41,16 @@ export const useFetchKnowledgeConfigurationOnMount = (
|
||||
const parser_config = {
|
||||
...form.formState?.defaultValues?.parser_config,
|
||||
...knowledgeDetails.parser_config,
|
||||
raptor: {
|
||||
...form.formState?.defaultValues?.parser_config?.raptor,
|
||||
...knowledgeDetails.parser_config?.raptor,
|
||||
use_raptor: true,
|
||||
},
|
||||
graphrag: {
|
||||
...form.formState?.defaultValues?.parser_config?.graphrag,
|
||||
...knowledgeDetails.parser_config?.graphrag,
|
||||
use_graphrag: true,
|
||||
},
|
||||
};
|
||||
const formValues = {
|
||||
...pick({ ...knowledgeDetails, parser_config: parser_config }, [
|
||||
@ -52,7 +64,7 @@ export const useFetchKnowledgeConfigurationOnMount = (
|
||||
'pagerank',
|
||||
'avatar',
|
||||
]),
|
||||
};
|
||||
} as z.infer<typeof formSchema>;
|
||||
form.reset(formValues);
|
||||
}, [form, knowledgeDetails]);
|
||||
|
||||
|
||||
@ -1,16 +1,29 @@
|
||||
import { IDataPipelineSelectNode } from '@/components/data-pipeline-select';
|
||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
|
||||
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Divider from '@/components/ui/divider';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { DocumentParserType } from '@/constants/knowledge';
|
||||
import { PermissionRole } from '@/constants/permission';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import { TopTitle } from '../dataset-title';
|
||||
import { ChunkMethodForm } from './chunk-method-form';
|
||||
import {
|
||||
GenerateType,
|
||||
IGenerateLogButtonProps,
|
||||
} from '../dataset/generate-button/generate';
|
||||
import LinkDataPipeline, {
|
||||
IDataPipelineNodeProps,
|
||||
} from './components/link-data-pipeline';
|
||||
import { MainContainer } from './configuration-form-container';
|
||||
import { formSchema } from './form-schema';
|
||||
import { GeneralForm } from './general-form';
|
||||
import { useFetchKnowledgeConfigurationOnMount } from './hooks';
|
||||
|
||||
import { SavingButton } from './saving-button';
|
||||
const enum DocumentType {
|
||||
DeepDOC = 'DeepDOC',
|
||||
PlainText = 'Plain Text',
|
||||
@ -46,24 +59,84 @@ export default function DatasetSettings() {
|
||||
html4excel: false,
|
||||
topn_tags: 3,
|
||||
raptor: {
|
||||
use_raptor: false,
|
||||
use_raptor: true,
|
||||
max_token: 256,
|
||||
threshold: 0.1,
|
||||
max_cluster: 64,
|
||||
random_seed: 0,
|
||||
prompt: t('knowledgeConfiguration.promptText'),
|
||||
},
|
||||
graphrag: {
|
||||
use_graphrag: false,
|
||||
use_graphrag: true,
|
||||
entity_types: initialEntityTypes,
|
||||
method: MethodValue.Light,
|
||||
},
|
||||
},
|
||||
pipeline_id: '',
|
||||
pagerank: 0,
|
||||
},
|
||||
});
|
||||
|
||||
useFetchKnowledgeConfigurationOnMount(form);
|
||||
const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form);
|
||||
|
||||
const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>();
|
||||
const [graphRagGenerateData, setGraphRagGenerateData] =
|
||||
useState<IGenerateLogButtonProps>();
|
||||
const [raptorGenerateData, setRaptorGenerateData] =
|
||||
useState<IGenerateLogButtonProps>();
|
||||
useEffect(() => {
|
||||
console.log('🚀 ~ DatasetSettings ~ knowledgeDetails:', knowledgeDetails);
|
||||
if (knowledgeDetails) {
|
||||
const data: IDataPipelineNodeProps = {
|
||||
id: knowledgeDetails.pipeline_id,
|
||||
name: knowledgeDetails.pipeline_name,
|
||||
avatar: knowledgeDetails.pipeline_avatar,
|
||||
linked: true,
|
||||
};
|
||||
setPipelineData(data);
|
||||
setGraphRagGenerateData({
|
||||
finish_at: knowledgeDetails.mindmap_task_finish_at,
|
||||
task_id: knowledgeDetails.mindmap_task_id,
|
||||
} as IGenerateLogButtonProps);
|
||||
setRaptorGenerateData({
|
||||
finish_at: knowledgeDetails.raptor_task_finish_at,
|
||||
task_id: knowledgeDetails.raptor_task_id,
|
||||
} as IGenerateLogButtonProps);
|
||||
}
|
||||
}, [knowledgeDetails]);
|
||||
|
||||
async function onSubmit(data: z.infer<typeof formSchema>) {
|
||||
console.log('🚀 ~ DatasetSettings ~ data:', data);
|
||||
try {
|
||||
console.log('Form validation passed, submit data', data);
|
||||
} catch (error) {
|
||||
console.error('An error occurred during submission:', error);
|
||||
}
|
||||
}
|
||||
const handleLinkOrEditSubmit = (
|
||||
data: IDataPipelineSelectNode | undefined,
|
||||
) => {
|
||||
console.log('🚀 ~ DatasetSettings ~ data:', data);
|
||||
if (data) {
|
||||
setPipelineData(data);
|
||||
form.setValue('pipeline_id', data.id || '');
|
||||
// form.setValue('pipeline_name', data.name || '');
|
||||
// form.setValue('pipeline_avatar', data.avatar || '');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeletePipelineTask = (type: GenerateType) => {
|
||||
if (type === GenerateType.KnowledgeGraph) {
|
||||
setGraphRagGenerateData({
|
||||
finish_at: '',
|
||||
task_id: '',
|
||||
} as IGenerateLogButtonProps);
|
||||
} else if (type === GenerateType.Raptor) {
|
||||
setRaptorGenerateData({
|
||||
finish_at: '',
|
||||
task_id: '',
|
||||
} as IGenerateLogButtonProps);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<section className="p-5 h-full flex flex-col">
|
||||
<TopTitle
|
||||
@ -76,9 +149,41 @@ export default function DatasetSettings() {
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 flex-1"
|
||||
>
|
||||
<div className="w-[768px]">
|
||||
<GeneralForm></GeneralForm>
|
||||
<ChunkMethodForm></ChunkMethodForm>
|
||||
<div className="w-[768px] h-[calc(100vh-240px)] pr-1 overflow-y-auto scrollbar-auto">
|
||||
<MainContainer className="text-text-secondary">
|
||||
<GeneralForm></GeneralForm>
|
||||
<Divider />
|
||||
|
||||
<GraphRagItems
|
||||
className="border-none p-0"
|
||||
data={graphRagGenerateData as IGenerateLogButtonProps}
|
||||
onDelete={() =>
|
||||
handleDeletePipelineTask(GenerateType.KnowledgeGraph)
|
||||
}
|
||||
></GraphRagItems>
|
||||
<Divider />
|
||||
<RaptorFormFields
|
||||
data={raptorGenerateData as IGenerateLogButtonProps}
|
||||
onDelete={() => handleDeletePipelineTask(GenerateType.Raptor)}
|
||||
></RaptorFormFields>
|
||||
<Divider />
|
||||
<LinkDataPipeline
|
||||
data={pipelineData}
|
||||
handleLinkOrEditSubmit={handleLinkOrEditSubmit}
|
||||
/>
|
||||
</MainContainer>
|
||||
</div>
|
||||
<div className="text-right items-center flex justify-end gap-3 w-[768px]">
|
||||
<Button
|
||||
type="reset"
|
||||
className="bg-transparent text-color-white hover:bg-transparent border-gray-500 border-[1px]"
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
{t('knowledgeConfiguration.cancel')}
|
||||
</Button>
|
||||
<SavingButton></SavingButton>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
|
||||
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
|
||||
import {
|
||||
ConfigurationFormContainer,
|
||||
MainContainer,
|
||||
} from './configuration-form-container';
|
||||
import { EnableAutoGenerateItem } from './configuration/common-item';
|
||||
|
||||
export function NaiveConfiguration() {
|
||||
return (
|
||||
<MainContainer>
|
||||
<GraphRagItems className="border-none p-0"></GraphRagItems>
|
||||
<ConfigurationFormContainer>
|
||||
<RaptorFormFields></RaptorFormFields>
|
||||
</ConfigurationFormContainer>
|
||||
<EnableAutoGenerateItem />
|
||||
{/* <ConfigurationFormContainer>
|
||||
<ChunkMethodItem></ChunkMethodItem>
|
||||
<LayoutRecognizeFormField></LayoutRecognizeFormField>
|
||||
|
||||
<MaxTokenNumberFormField initialValue={512}></MaxTokenNumberFormField>
|
||||
<DelimiterFormField></DelimiterFormField>
|
||||
</ConfigurationFormContainer>
|
||||
<ConfigurationFormContainer>
|
||||
<PageRankFormField></PageRankFormField>
|
||||
<AutoKeywordsFormField></AutoKeywordsFormField>
|
||||
<AutoQuestionsFormField></AutoQuestionsFormField>
|
||||
<ExcelToHtmlFormField></ExcelToHtmlFormField>
|
||||
<TagItems></TagItems>
|
||||
</ConfigurationFormContainer> */}
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
@ -62,7 +62,7 @@ export function SavingButton() {
|
||||
if (beValid) {
|
||||
form.handleSubmit(async (values) => {
|
||||
console.log('saveKnowledgeConfiguration: ', values);
|
||||
delete values['avatar'];
|
||||
// delete values['avatar'];
|
||||
await saveKnowledgeConfiguration({
|
||||
kb_id,
|
||||
...values,
|
||||
|
||||
@ -3,15 +3,21 @@ import { RunningStatus } from '@/constants/knowledge';
|
||||
export const RunningStatusMap = {
|
||||
[RunningStatus.UNSTART]: {
|
||||
label: 'UNSTART',
|
||||
color: 'var(--accent-primary)',
|
||||
color: 'rgba(var(--accent-primary))',
|
||||
},
|
||||
[RunningStatus.RUNNING]: {
|
||||
label: 'Parsing',
|
||||
color: 'var(--team-member)',
|
||||
},
|
||||
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'var(--state-warning)' },
|
||||
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'var(--state-success)' },
|
||||
[RunningStatus.FAIL]: { label: 'FAIL', color: 'var(--state-error' },
|
||||
[RunningStatus.CANCEL]: {
|
||||
label: 'CANCEL',
|
||||
color: 'rgba(var(--state-warning))',
|
||||
},
|
||||
[RunningStatus.DONE]: {
|
||||
label: 'SUCCESS',
|
||||
color: 'rgba(var(--state-success))',
|
||||
},
|
||||
[RunningStatus.FAIL]: { label: 'FAIL', color: 'rgba(var(--state-error))' },
|
||||
};
|
||||
|
||||
export * from '@/constants/knowledge';
|
||||
|
||||
@ -27,8 +27,11 @@ import {
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { t } from 'i18next';
|
||||
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 +84,13 @@ export function DatasetTable({
|
||||
onSetMetaModalOk,
|
||||
metaRecord,
|
||||
} = useSaveMeta();
|
||||
const { showLog, logInfo, logVisible, hideLog } = useShowLog(documents);
|
||||
|
||||
const columns = useDatasetTableColumns({
|
||||
showChangeParserModal,
|
||||
showRenameModal,
|
||||
showSetMetaModal,
|
||||
showLog,
|
||||
});
|
||||
|
||||
const currentPagination = useMemo(() => {
|
||||
@ -180,6 +185,7 @@ export function DatasetTable({
|
||||
<ChunkMethodDialog
|
||||
documentId={changeParserRecord.id}
|
||||
parserId={changeParserRecord.parser_id}
|
||||
pipelineId={changeParserRecord.pipeline_id}
|
||||
parserConfig={changeParserRecord.parser_config}
|
||||
documentExtension={getExtension(changeParserRecord.name)}
|
||||
onOk={onChangeParserOk}
|
||||
@ -207,6 +213,14 @@ export function DatasetTable({
|
||||
initialMetaData={metaRecord.meta_fields}
|
||||
></SetMetaDialog>
|
||||
)}
|
||||
{logVisible && (
|
||||
<ProcessLogModal
|
||||
title={t('knowledgeDetails.fileLogs')}
|
||||
visible={logVisible}
|
||||
onCancel={() => hideLog()}
|
||||
logInfo={logInfo}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
339
web/src/pages/dataset/dataset/generate-button/generate.tsx
Normal file
339
web/src/pages/dataset/dataset/generate-button/generate.tsx
Normal file
@ -0,0 +1,339 @@
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toFixed } from '@/utils/common-util';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { UseMutateAsyncFunction } from '@tanstack/react-query';
|
||||
import { t } from 'i18next';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import { CirclePause, Trash2, WandSparkles } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ProcessingType } from '../../dataset-overview/dataset-common';
|
||||
import { replaceText } from '../../process-log-modal';
|
||||
import {
|
||||
ITraceInfo,
|
||||
generateStatus,
|
||||
useDatasetGenerate,
|
||||
useTraceGenerate,
|
||||
useUnBindTask,
|
||||
} from './hook';
|
||||
export enum GenerateType {
|
||||
KnowledgeGraph = 'KnowledgeGraph',
|
||||
Raptor = 'Raptor',
|
||||
}
|
||||
export const GenerateTypeMap = {
|
||||
[GenerateType.KnowledgeGraph]: ProcessingType.knowledgeGraph,
|
||||
[GenerateType.Raptor]: ProcessingType.raptor,
|
||||
};
|
||||
const MenuItem: React.FC<{
|
||||
name: GenerateType;
|
||||
data: ITraceInfo;
|
||||
pauseGenerate: ({
|
||||
task_id,
|
||||
type,
|
||||
}: {
|
||||
task_id: string;
|
||||
type: GenerateType;
|
||||
}) => void;
|
||||
runGenerate: UseMutateAsyncFunction<
|
||||
any,
|
||||
Error,
|
||||
{
|
||||
type: GenerateType;
|
||||
},
|
||||
unknown
|
||||
>;
|
||||
}> = ({ name: type, runGenerate, data, pauseGenerate }) => {
|
||||
const iconKeyMap = {
|
||||
KnowledgeGraph: 'knowledgegraph',
|
||||
Raptor: 'dataflow-01',
|
||||
};
|
||||
const status = useMemo(() => {
|
||||
if (!data) {
|
||||
return generateStatus.start;
|
||||
}
|
||||
if (data.progress >= 1) {
|
||||
return generateStatus.completed;
|
||||
} else if (!data.progress && data.progress !== 0) {
|
||||
return generateStatus.start;
|
||||
} else if (data.progress < 0) {
|
||||
return generateStatus.failed;
|
||||
} else if (data.progress < 1) {
|
||||
return generateStatus.running;
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const percent =
|
||||
status === generateStatus.failed
|
||||
? 100
|
||||
: status === generateStatus.running
|
||||
? data.progress * 100
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
className={cn(
|
||||
'border cursor-pointer p-2 rounded-md focus:bg-transparent',
|
||||
{
|
||||
'hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]':
|
||||
status === generateStatus.start ||
|
||||
status === generateStatus.completed,
|
||||
'hover:border-border hover:bg-[rgba(59,160,92,0)]':
|
||||
status !== generateStatus.start &&
|
||||
status !== generateStatus.completed,
|
||||
},
|
||||
)}
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex items-start gap-2 flex-col w-full"
|
||||
onClick={() => {
|
||||
if (
|
||||
status === generateStatus.start ||
|
||||
status === generateStatus.completed
|
||||
) {
|
||||
runGenerate({ type });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-start text-text-primary items-center gap-2">
|
||||
<IconFontFill
|
||||
name={iconKeyMap[type]}
|
||||
className="text-accent-primary"
|
||||
/>
|
||||
{t(`knowledgeDetails.${lowerFirst(type)}`)}
|
||||
</div>
|
||||
{status === generateStatus.start && (
|
||||
<div className="text-text-secondary text-sm">
|
||||
{t(`knowledgeDetails.generate${type}`)}
|
||||
</div>
|
||||
)}
|
||||
{(status === generateStatus.running ||
|
||||
status === generateStatus.failed) && (
|
||||
<div className="flex justify-between items-center w-full px-2.5 py-1">
|
||||
<div
|
||||
className={cn(' bg-border-button h-1 rounded-full', {
|
||||
'w-[calc(100%-100px)]': status === generateStatus.running,
|
||||
'w-[calc(100%-50px)]': status === generateStatus.failed,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn('h-1 rounded-full', {
|
||||
'bg-state-error': status === generateStatus.failed,
|
||||
'bg-accent-primary': status === generateStatus.running,
|
||||
})}
|
||||
style={{ width: `${toFixed(percent)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
{status === generateStatus.running && (
|
||||
<span>{(toFixed(percent) as string) + '%'}</span>
|
||||
)}
|
||||
{status === generateStatus.failed && (
|
||||
<span
|
||||
className="text-state-error"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
runGenerate({ type });
|
||||
}}
|
||||
>
|
||||
<IconFontFill name="reparse" className="text-accent-primary" />
|
||||
</span>
|
||||
)}
|
||||
{status !== generateStatus.failed && (
|
||||
<span
|
||||
className="text-state-error"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
pauseGenerate({ task_id: data.id, type });
|
||||
}}
|
||||
>
|
||||
<CirclePause />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full whitespace-pre-line text-wrap rounded-lg h-fit max-h-[350px] overflow-y-auto scrollbar-auto px-2.5 py-1">
|
||||
{replaceText(data?.progress_msg || '')}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
const Generate: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { graphRunData, raptorRunData } = useTraceGenerate({ open });
|
||||
const { runGenerate, pauseGenerate } = useDatasetGenerate();
|
||||
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 flex flex-col gap-2 ">
|
||||
{Object.values(GenerateType).map((name) => {
|
||||
const data = (
|
||||
name === GenerateType.KnowledgeGraph
|
||||
? graphRunData
|
||||
: raptorRunData
|
||||
) as ITraceInfo;
|
||||
console.log(
|
||||
name,
|
||||
'data',
|
||||
data,
|
||||
!data || (!data.progress && data.progress !== 0),
|
||||
);
|
||||
return (
|
||||
<div key={name}>
|
||||
<MenuItem
|
||||
name={name}
|
||||
runGenerate={runGenerate}
|
||||
data={data}
|
||||
pauseGenerate={pauseGenerate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Generate;
|
||||
|
||||
export type IGenerateLogButtonProps = {
|
||||
finish_at: string;
|
||||
task_id: string;
|
||||
};
|
||||
|
||||
export type IGenerateLogProps = IGenerateLogButtonProps & {
|
||||
id?: string;
|
||||
status: 0 | 1;
|
||||
message?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
type?: GenerateType;
|
||||
className?: string;
|
||||
onDelete?: () => void;
|
||||
};
|
||||
export const GenerateLogButton = (props: IGenerateLogProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { message, finish_at, type, onDelete } = props;
|
||||
|
||||
const { handleUnbindTask } = useUnBindTask();
|
||||
|
||||
const handleDeleteFunc = async () => {
|
||||
const data = await handleUnbindTask({
|
||||
type: GenerateTypeMap[type as GenerateType],
|
||||
});
|
||||
Modal.destroy();
|
||||
console.log('handleUnbindTask', data);
|
||||
if (data.code === 0) {
|
||||
onDelete?.();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
Modal.show({
|
||||
visible: true,
|
||||
className: '!w-[560px]',
|
||||
title:
|
||||
t('common.delete') +
|
||||
' ' +
|
||||
(type === GenerateType.KnowledgeGraph
|
||||
? t('knowledgeDetails.knowledgeGraph')
|
||||
: t('knowledgeDetails.raptor')),
|
||||
children: (
|
||||
<div
|
||||
className="text-sm text-text-secondary"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('knowledgeConfiguration.deleteGenerateModalContent', {
|
||||
type:
|
||||
type === GenerateType.KnowledgeGraph
|
||||
? t('knowledgeDetails.knowledgeGraph')
|
||||
: t('knowledgeDetails.raptor'),
|
||||
}),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
onVisibleChange: () => {
|
||||
Modal.destroy();
|
||||
},
|
||||
footer: (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant={'outline'}
|
||||
onClick={() => Modal.destroy()}
|
||||
>
|
||||
{t('dataflowParser.changeStepModalCancelText')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={'secondary'}
|
||||
className="!bg-state-error text-text-primary"
|
||||
onClick={() => {
|
||||
handleDeleteFunc();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('flex bg-bg-card rounded-md py-1 px-3', props.className)}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
{finish_at && (
|
||||
<>
|
||||
<div>
|
||||
{message || t('knowledgeDetails.generatedOn')}
|
||||
{formatDate(finish_at)}
|
||||
</div>
|
||||
<Trash2
|
||||
size={14}
|
||||
className="cursor-pointer"
|
||||
onClick={(e) => {
|
||||
console.log('delete');
|
||||
handleDelete();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!finish_at && <div>{t('knowledgeDetails.notGenerated')}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
174
web/src/pages/dataset/dataset/generate-button/hook.ts
Normal file
174
web/src/pages/dataset/dataset/generate-button/hook.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import message from '@/components/ui/message';
|
||||
import agentService from '@/services/agent-service';
|
||||
import kbService, { deletePipelineTask } from '@/services/knowledge-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { t } from 'i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { ProcessingType } from '../../dataset-overview/dataset-common';
|
||||
import { GenerateType } from './generate';
|
||||
export const generateStatus = {
|
||||
running: 'running',
|
||||
completed: 'completed',
|
||||
start: 'start',
|
||||
failed: 'failed',
|
||||
};
|
||||
|
||||
enum DatasetKey {
|
||||
generate = 'generate',
|
||||
pauseGenerate = 'pauseGenerate',
|
||||
}
|
||||
|
||||
export interface ITraceInfo {
|
||||
begin_at: string;
|
||||
chunk_ids: string;
|
||||
create_date: string;
|
||||
create_time: number;
|
||||
digest: string;
|
||||
doc_id: string;
|
||||
from_page: number;
|
||||
id: string;
|
||||
priority: number;
|
||||
process_duration: number;
|
||||
progress: number;
|
||||
progress_msg: string;
|
||||
retry_count: number;
|
||||
task_type: string;
|
||||
to_page: number;
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
}
|
||||
|
||||
export const useTraceGenerate = ({ open }: { open: boolean }) => {
|
||||
const { id } = useParams();
|
||||
const [isLoopGraphRun, setLoopGraphRun] = useState(false);
|
||||
const [isLoopRaptorRun, setLoopRaptorRun] = useState(false);
|
||||
const { data: graphRunData, isFetching: graphRunloading } =
|
||||
useQuery<ITraceInfo>({
|
||||
queryKey: [GenerateType.KnowledgeGraph, id, open],
|
||||
// initialData: {},
|
||||
gcTime: 0,
|
||||
refetchInterval: isLoopGraphRun ? 5000 : false,
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
enabled: open,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.traceGraphRag({
|
||||
kb_id: id,
|
||||
});
|
||||
return data?.data || {};
|
||||
},
|
||||
});
|
||||
|
||||
const { data: raptorRunData, isFetching: raptorRunloading } =
|
||||
useQuery<ITraceInfo>({
|
||||
queryKey: [GenerateType.Raptor, id, open],
|
||||
// initialData: {},
|
||||
gcTime: 0,
|
||||
refetchInterval: isLoopRaptorRun ? 5000 : false,
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
enabled: open,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.traceRaptor({
|
||||
kb_id: id,
|
||||
});
|
||||
return data?.data || {};
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setLoopGraphRun(
|
||||
!!(
|
||||
(graphRunData?.progress || graphRunData?.progress === 0) &&
|
||||
graphRunData?.progress < 1 &&
|
||||
graphRunData?.progress >= 0
|
||||
),
|
||||
);
|
||||
}, [graphRunData?.progress]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoopRaptorRun(
|
||||
!!(
|
||||
(raptorRunData?.progress || raptorRunData?.progress === 0) &&
|
||||
raptorRunData?.progress < 1 &&
|
||||
raptorRunData?.progress >= 0
|
||||
),
|
||||
);
|
||||
}, [raptorRunData?.progress]);
|
||||
return {
|
||||
graphRunData,
|
||||
graphRunloading,
|
||||
raptorRunData,
|
||||
raptorRunloading,
|
||||
};
|
||||
};
|
||||
export const useDatasetGenerate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DatasetKey.generate],
|
||||
mutationFn: async ({ type }: { type: GenerateType }) => {
|
||||
const func =
|
||||
type === GenerateType.KnowledgeGraph
|
||||
? kbService.runGraphRag
|
||||
: kbService.runRaptor;
|
||||
const { data } = await func({
|
||||
kb_id: id,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [type],
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
// const pauseGenerate = useCallback(() => {
|
||||
// // TODO: pause generate
|
||||
// console.log('pause generate');
|
||||
// }, []);
|
||||
const { mutateAsync: pauseGenerate } = useMutation({
|
||||
mutationKey: [DatasetKey.pauseGenerate],
|
||||
mutationFn: async ({
|
||||
task_id,
|
||||
type,
|
||||
}: {
|
||||
task_id: string;
|
||||
type: GenerateType;
|
||||
}) => {
|
||||
const { data } = await agentService.cancelDataflow(task_id);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [type],
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
return { runGenerate: mutateAsync, pauseGenerate, data, loading };
|
||||
};
|
||||
|
||||
export const useUnBindTask = () => {
|
||||
const { id } = useParams();
|
||||
const { mutateAsync: handleUnbindTask } = useMutation({
|
||||
mutationKey: [DatasetKey.pauseGenerate],
|
||||
mutationFn: async ({ type }: { type: ProcessingType }) => {
|
||||
const { data } = await deletePipelineTask({ kb_id: id as string, type });
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
// queryClient.invalidateQueries({
|
||||
// queryKey: [type],
|
||||
// });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
return { handleUnbindTask };
|
||||
};
|
||||
@ -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,13 @@
|
||||
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 { formatDate, formatSecondsToHumanReadable } from '@/utils/date';
|
||||
import { formatBytes } from '@/utils/file-util';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'umi';
|
||||
import { ILogInfo } from '../process-log-modal';
|
||||
import { RunningStatus } from './constant';
|
||||
|
||||
export const useNavigateToOtherPage = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -58,3 +63,43 @@ 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 = {
|
||||
fileType: findRecord?.suffix,
|
||||
uploadedBy: findRecord?.nickname,
|
||||
fileName: findRecord?.name,
|
||||
uploadDate: formatDate(findRecord.create_date),
|
||||
fileSize: formatBytes(findRecord.size || 0),
|
||||
processBeginAt: formatDate(findRecord.process_begin_at),
|
||||
chunkNumber: findRecord.chunk_num,
|
||||
duration: formatSecondsToHumanReadable(
|
||||
findRecord.process_duration || 0,
|
||||
),
|
||||
status: findRecord.run as RunningStatus,
|
||||
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';
|
||||
@ -75,7 +75,7 @@ export default function Dataset() {
|
||||
filters={filters}
|
||||
leftPanel={
|
||||
<div className="items-start">
|
||||
<div className="pb-1">{t('knowledgeDetails.dataset')}</div>
|
||||
<div className="pb-1">{t('knowledgeDetails.subbarFiles')}</div>
|
||||
<div className="text-text-sub-title-invert text-sm">
|
||||
{t('knowledgeDetails.datasetDescription')}
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
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';
|
||||
@ -11,6 +6,7 @@ import { RunningStatus, RunningStatusMap } from './constant';
|
||||
|
||||
interface IProps {
|
||||
record: IDocumentInfo;
|
||||
handleShowLog?: (record: IDocumentInfo) => void;
|
||||
}
|
||||
|
||||
function Dot({ run }: { run: RunningStatus }) {
|
||||
@ -85,17 +81,15 @@ 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'}>
|
||||
<Dot run={record.run}></Dot>
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-[40vw]">
|
||||
<PopoverContent record={record}></PopoverContent>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
className="border-none"
|
||||
size={'sm'}
|
||||
onClick={() => handleShowLog?.(record)}
|
||||
>
|
||||
<Dot run={record.run}></Dot>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
@ -6,41 +7,56 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card';
|
||||
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';
|
||||
import { ParsingCard, PopoverContent } from './parsing-card';
|
||||
import { ParsingCard } from './parsing-card';
|
||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||
import { useHandleRunDocumentByIds } from './use-run-document';
|
||||
import { UseSaveMetaShowType } from './use-save-meta';
|
||||
import { isParserRunning } from './utils';
|
||||
const IconMap = {
|
||||
[RunningStatus.UNSTART]: (
|
||||
<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>
|
||||
<IconFontFill name="play" className="text-accent-primary" />
|
||||
),
|
||||
[RunningStatus.RUNNING]: (
|
||||
<CircleX size={14} color="rgba(var(--state-error))" />
|
||||
),
|
||||
[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" />
|
||||
),
|
||||
[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)" />,
|
||||
};
|
||||
|
||||
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;
|
||||
const {
|
||||
run,
|
||||
parser_id,
|
||||
pipeline_id,
|
||||
pipeline_name,
|
||||
progress,
|
||||
chunk_num,
|
||||
id,
|
||||
} = record;
|
||||
const operationIcon = IconMap[run];
|
||||
const p = Number((progress * 100).toFixed(2));
|
||||
const { handleRunDocumentByIds } = useHandleRunDocumentByIds(id);
|
||||
@ -65,18 +81,25 @@ 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">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'transparent'} className="border-none" size={'sm'}>
|
||||
{parser_id === 'naive' ? 'general' : parser_id}
|
||||
{pipeline_id
|
||||
? pipeline_name || pipeline_id
|
||||
: parser_id === 'naive'
|
||||
? 'general'
|
||||
: parser_id}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={handleShowChangeParserModal}>
|
||||
{t('knowledgeDetails.chunkMethod')}
|
||||
{t('knowledgeDetails.dataPipeline')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleShowSetMetaModal}>
|
||||
{t('knowledgeDetails.setMetaData')}
|
||||
@ -85,41 +108,54 @@ 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) ? (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center gap-1 cursor-pointer"
|
||||
onClick={() => handleShowLog(record)}
|
||||
>
|
||||
<Progress value={p} className="h-1 flex-1 min-w-10" />
|
||||
{p}%
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer flex items-center gap-3"
|
||||
onClick={
|
||||
isZeroChunk || isRunning
|
||||
? handleOperationIconClick(false)
|
||||
: () => {}
|
||||
}
|
||||
>
|
||||
{operationIcon}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<ParsingCard
|
||||
record={record}
|
||||
handleShowLog={handleShowLog}
|
||||
></ParsingCard>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -19,6 +19,7 @@ export const useChangeDocumentParser = () => {
|
||||
if (record?.id) {
|
||||
const ret = await setDocumentParser({
|
||||
parserId: parserConfigInfo.parser_id,
|
||||
pipelineId: parserConfigInfo.pipeline_id,
|
||||
documentId: record?.id,
|
||||
parserConfig: parserConfigInfo.parser_config,
|
||||
});
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
|
||||
155
web/src/pages/dataset/process-log-modal.tsx
Normal file
155
web/src/pages/dataset/process-log-modal.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import FileStatusBadge from '@/components/file-status-badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { RunningStatusMap } from '@/constants/knowledge';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import React, { useMemo } from 'react';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { RunningStatus } from './dataset/constant';
|
||||
export interface ILogInfo {
|
||||
fileType?: string;
|
||||
uploadedBy?: string;
|
||||
uploadDate?: string;
|
||||
processBeginAt?: string;
|
||||
chunkNumber?: number;
|
||||
|
||||
taskId?: string;
|
||||
fileName: string;
|
||||
fileSize?: string;
|
||||
source?: string;
|
||||
task?: string;
|
||||
status?: RunningStatus;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
duration?: string;
|
||||
details: string;
|
||||
}
|
||||
|
||||
interface ProcessLogModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
logInfo: ILogInfo;
|
||||
title: string;
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
export 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 ProcessLogModal: React.FC<ProcessLogModalProps> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
logInfo: initData,
|
||||
title,
|
||||
}) => {
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const blackKeyList = [''];
|
||||
console.log('logInfo', initData);
|
||||
const logInfo = useMemo(() => {
|
||||
console.log('logInfo', initData);
|
||||
return initData;
|
||||
}, [initData]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={title || 'log'}
|
||||
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) ||
|
||||
!logInfo[key as keyof typeof logInfo]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (key === 'details') {
|
||||
return (
|
||||
<div className="w-full mt-2" 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 w-1/2" key={key}>
|
||||
<span className="text-text-secondary text-sm">
|
||||
{t('status')}
|
||||
</span>
|
||||
<div className="mt-1">
|
||||
<FileStatusBadge
|
||||
status={logInfo.status as RunningStatus}
|
||||
name={RunningStatusMap[logInfo.status as RunningStatus]}
|
||||
/>
|
||||
</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;
|
||||
@ -9,7 +9,7 @@ import { cn, formatBytes } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { formatPureDate } from '@/utils/date';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Banknote, Database, FileSearch2, GitGraph } from 'lucide-react';
|
||||
import { Banknote, DatabaseZap, FileSearch2, FolderOpen } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleMenuClick } from './hooks';
|
||||
@ -22,36 +22,36 @@ export function SideBar({ refreshCount }: PropType) {
|
||||
const pathName = useSecondPathName();
|
||||
const { handleMenuClick } = useHandleMenuClick();
|
||||
// refreshCount: be for avatar img sync update on top left
|
||||
const { data } = useFetchKnowledgeBaseConfiguration(refreshCount);
|
||||
const { data } = useFetchKnowledgeBaseConfiguration({ refreshCount });
|
||||
const { data: routerData } = useFetchKnowledgeGraph();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const items = useMemo(() => {
|
||||
const list = [
|
||||
// {
|
||||
// icon: DatabaseZap,
|
||||
// label: t(`knowledgeDetails.overview`),
|
||||
// key: Routes.DataSetOverview,
|
||||
// },
|
||||
{
|
||||
icon: Database,
|
||||
label: t(`knowledgeDetails.dataset`),
|
||||
icon: <DatabaseZap className="size-4" />,
|
||||
label: t(`knowledgeDetails.overview`),
|
||||
key: Routes.DataSetOverview,
|
||||
},
|
||||
{
|
||||
icon: <FolderOpen className="size-4" />,
|
||||
label: t(`knowledgeDetails.subbarFiles`),
|
||||
key: Routes.DatasetBase,
|
||||
},
|
||||
{
|
||||
icon: FileSearch2,
|
||||
icon: <FileSearch2 className="size-4" />,
|
||||
label: t(`knowledgeDetails.testing`),
|
||||
key: Routes.DatasetTesting,
|
||||
},
|
||||
{
|
||||
icon: Banknote,
|
||||
icon: <Banknote className="size-4" />,
|
||||
label: t(`knowledgeDetails.configuration`),
|
||||
key: Routes.DatasetSetting,
|
||||
key: Routes.DataSetSetting,
|
||||
},
|
||||
];
|
||||
if (!isEmpty(routerData?.graph)) {
|
||||
list.push({
|
||||
icon: GitGraph,
|
||||
icon: <IconFontFill name="knowledgegraph" className="size-4" />,
|
||||
label: t(`knowledgeDetails.knowledgeGraph`),
|
||||
key: Routes.KnowledgeGraph,
|
||||
});
|
||||
@ -99,7 +99,7 @@ export function SideBar({ refreshCount }: PropType) {
|
||||
)}
|
||||
onClick={handleMenuClick(item.key)}
|
||||
>
|
||||
<item.icon className="size-4" />
|
||||
{item.icon}
|
||||
<span>{item.label}</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user