fix: Added dataset generation logging functionality #9869 (#10180)

### What problem does this PR solve?

fix: Added dataset generation logging functionality #9869

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-09-22 10:01:34 +08:00
committed by GitHub
parent d050ef568d
commit d039d1e73d
15 changed files with 491 additions and 119 deletions

View File

@ -20,17 +20,10 @@ interface IProps {
isMult?: boolean; isMult?: boolean;
} }
const data = [
{ id: '1', name: 'data-pipeline-1' },
{ id: '2', name: 'data-pipeline-2' },
{ id: '3', name: 'data-pipeline-3' },
{ id: '4', name: 'data-pipeline-4' },
];
export function DataFlowSelect(props: IProps) { export function DataFlowSelect(props: IProps) {
const { toDataPipeline, formFieldName, isMult = true } = props; const { toDataPipeline, formFieldName, isMult = true } = props;
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext(); const form = useFormContext();
console.log('data-pipline form', form);
const toDataPipLine = () => { const toDataPipLine = () => {
toDataPipeline?.(); toDataPipeline?.();
}; };

View File

@ -1,6 +1,10 @@
import { DocumentParserType } from '@/constants/knowledge'; import { DocumentParserType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import {
GenerateLogButton,
GenerateType,
} from '@/pages/dataset/dataset/generate-button/generate';
import { upperFirst } from 'lodash'; import { upperFirst } from 'lodash';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useFormContext, useWatch } from 'react-hook-form'; import { useFormContext, useWatch } from 'react-hook-form';
@ -47,6 +51,7 @@ export const showGraphRagItems = (parserId: DocumentParserType | undefined) => {
type GraphRagItemsProps = { type GraphRagItemsProps = {
marginBottom?: boolean; marginBottom?: boolean;
className?: string; className?: string;
showGenerateItem?: boolean;
}; };
export function UseGraphRagFormField() { export function UseGraphRagFormField() {
@ -88,6 +93,7 @@ export function UseGraphRagFormField() {
// The three types "table", "resume" and "one" do not display this configuration. // The three types "table", "resume" and "one" do not display this configuration.
const GraphRagItems = ({ const GraphRagItems = ({
marginBottom = false, marginBottom = false,
showGenerateItem = false,
className = 'p-10', className = 'p-10',
}: GraphRagItemsProps) => { }: GraphRagItemsProps) => {
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
@ -210,6 +216,18 @@ const GraphRagItems = ({
</FormItem> </FormItem>
)} )}
/> />
{showGenerateItem && (
<div className="w-full flex items-center">
<div className="text-sm whitespace-nowrap w-1/4">
{t('extractKnowledgeGraph')}
</div>
<GenerateLogButton
className="w-3/4 text-text-secondary"
status={1}
type={GenerateType.KnowledgeGraph}
/>
</div>
)}
</> </>
)} )}
</FormContainer> </FormContainer>

View File

@ -1,6 +1,10 @@
import { FormLayout } from '@/constants/form'; import { FormLayout } from '@/constants/form';
import { DocumentParserType } from '@/constants/knowledge'; import { DocumentParserType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import {
GenerateLogButton,
GenerateType,
} from '@/pages/dataset/dataset/generate-button/generate';
import random from 'lodash/random'; import random from 'lodash/random';
import { Shuffle } from 'lucide-react'; import { Shuffle } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -52,7 +56,11 @@ const Prompt = 'parser_config.raptor.prompt';
// The three types "table", "resume" and "one" do not display this configuration. // The three types "table", "resume" and "one" do not display this configuration.
const RaptorFormFields = () => { const RaptorFormFields = ({
showGenerateItem = false,
}: {
showGenerateItem?: boolean;
}) => {
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
const useRaptor = useWatch({ name: UseRaptorField }); const useRaptor = useWatch({ name: UseRaptorField });
@ -211,6 +219,18 @@ const RaptorFormFields = () => {
</FormItem> </FormItem>
)} )}
/> />
{showGenerateItem && (
<div className="w-full flex items-center">
<div className="text-sm whitespace-nowrap w-1/4">
{t('extractRaptor')}
</div>
<GenerateLogButton
className="w-3/4 text-text-secondary"
status={1}
type={GenerateType.Raptor}
/>
</div>
)}
</div> </div>
)} )}
</> </>

View File

@ -75,21 +75,21 @@ const Modal: ModalType = ({
const handleCancel = useCallback(() => { const handleCancel = useCallback(() => {
onOpenChange?.(false); onOpenChange?.(false);
onCancel?.(); // onCancel?.();
}, [onOpenChange, onCancel]); }, [onOpenChange]);
const handleOk = useCallback(() => { const handleOk = useCallback(() => {
onOpenChange?.(true); onOpenChange?.(true);
onOk?.(); // onOk?.();
}, [onOpenChange, onOk]); }, [onOpenChange]);
const handleChange = (open: boolean) => { const handleChange = (open: boolean) => {
onOpenChange?.(open); onOpenChange?.(open);
console.log('open', open, onOpenChange); console.log('open', open, onOpenChange);
if (open) { if (open) {
handleOk(); onOk?.();
} }
if (!open) { if (!open) {
handleCancel(); onCancel?.();
} }
}; };
const footEl = useMemo(() => { const footEl = useMemo(() => {

View File

@ -102,13 +102,15 @@ export default {
noMoreData: `That's all. Nothing more.`, noMoreData: `That's all. Nothing more.`,
}, },
knowledgeDetails: { knowledgeDetails: {
notGenerated: 'Not generated',
generatedOn: 'Generated on',
subbarFiles: 'Files',
generateKnowledgeGraph: generateKnowledgeGraph:
'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.', 'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.',
generateRaptor: generateRaptor:
'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.', 'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.',
generate: 'Generate', generate: 'Generate',
raptor: 'Raptor', raptor: 'Raptor',
knowledgeGraph: 'Knowledge Graph',
processingType: 'Processing Type', processingType: 'Processing Type',
dataPipeline: 'Data Pipeline', dataPipeline: 'Data Pipeline',
operations: 'Operations', operations: 'Operations',
@ -138,12 +140,12 @@ export default {
testing: 'Retrieval testing', testing: 'Retrieval testing',
files: 'files', files: 'files',
configuration: 'Configuration', configuration: 'Configuration',
knowledgeGraph: 'Knowledge graph', knowledgeGraph: 'Knowledge Graph',
name: 'Name', name: 'Name',
namePlaceholder: 'Please input name!', namePlaceholder: 'Please input name!',
doc: 'Docs', doc: 'Docs',
datasetDescription: datasetDescription:
'😉 Please wait for your files to finish parsing before starting an AI-powered chat.', 'Please wait for your files to finish parsing before starting an AI-powered chat.',
addFile: 'Add file', addFile: 'Add file',
searchFiles: 'Search your files', searchFiles: 'Search your files',
localFiles: 'Local files', localFiles: 'Local files',
@ -261,6 +263,22 @@ export default {
reRankModelWaring: 'Re-rank model is very time consuming.', reRankModelWaring: 'Re-rank model is very time consuming.',
}, },
knowledgeConfiguration: { knowledgeConfiguration: {
deleteGenerateModalContent: `
<p>Deleting the generated <strong class='text-text-primary'>{{type}}</strong> results
will remove all derived entities and relationships from this dataset.
Your original files will remain intact.<p>
<br/>
Do you want to continue?
`,
extractRaptor: 'Extract Raptor',
extractKnowledgeGraph: 'Extract Knowledge Graph',
filterPlaceholder: 'please input filter',
fileFilterTip: '',
fileFilter: 'File Filter',
setDefaultTip: '',
setDefault: 'Set as Default',
eidtLinkDataPipeline: 'Edit Data Pipeline',
linkPipelineSetTip: 'Manage data pipeline linkage with this dataset',
default: 'Default', default: 'Default',
dataPipeline: 'Data Pipeline', dataPipeline: 'Data Pipeline',
linkDataPipeline: 'Link Data Pipeline', linkDataPipeline: 'Link Data Pipeline',
@ -1646,6 +1664,13 @@ This delimiter is used to split the input text into several text pieces echo of
<p>To keep them, please click Rerun to re-run the current stage.</p> `, <p>To keep them, please click Rerun to re-run the current stage.</p> `,
changeStepModalConfirmText: 'Switch Anyway', changeStepModalConfirmText: 'Switch Anyway',
changeStepModalCancelText: 'Cancel', changeStepModalCancelText: 'Cancel',
unlinkPipelineModalTitle: 'Unlink data pipeline',
unlinkPipelineModalContent: `
<p>Once unlinked, this Dataset will no longer be connected to the current Data Pipeline.</p>
<p>Files that are already being parsed will continue until completion</p>
<p>Files that are not yet parsed will no longer be processed</p> <br/>
<p>Are you sure you want to proceed?</p> `,
unlinkPipelineModalConfirmText: 'Unlink',
}, },
dataflow: { dataflow: {
parser: 'Parser', parser: 'Parser',

View File

@ -94,9 +94,11 @@ export default {
noMoreData: '没有更多数据了', noMoreData: '没有更多数据了',
}, },
knowledgeDetails: { knowledgeDetails: {
notGenerated: '未生成',
generatedOn: '生成于',
subbarFiles: '文件列表',
generate: '生成', generate: '生成',
raptor: 'Raptor', raptor: 'Raptor',
knowledgeGraph: '知识图谱',
processingType: '处理类型', processingType: '处理类型',
dataPipeline: '数据管道', dataPipeline: '数据管道',
operations: '操作', operations: '操作',
@ -130,7 +132,7 @@ export default {
name: '名称', name: '名称',
namePlaceholder: '请输入名称', namePlaceholder: '请输入名称',
doc: '文档', doc: '文档',
datasetDescription: '😉 解析成功后才能问答哦。', datasetDescription: '解析成功后才能问答哦。',
addFile: '新增文件', addFile: '新增文件',
searchFiles: '搜索文件', searchFiles: '搜索文件',
localFiles: '本地文件', localFiles: '本地文件',
@ -246,6 +248,22 @@ export default {
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除', theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
}, },
knowledgeConfiguration: { knowledgeConfiguration: {
deleteGenerateModalContent: `
<p>删除生成的 <strong class='text-text-primary'>{{type}}</strong> 结果
将从此数据集中移除所有派生实体和关系。
您的原始文件将保持不变。<p>
<br/>
是否要继续?
`,
extractRaptor: '从文档中提取Raptor',
extractKnowledgeGraph: '从文档中提取知识图谱',
filterPlaceholder: '请输入',
fileFilterTip: '',
fileFilter: '正则匹配表达式',
setDefaultTip: '',
setDefault: '设置默认',
eidtLinkDataPipeline: '编辑数据流',
linkPipelineSetTip: '管理与此数据集的数据管道链接',
default: '默认', default: '默认',
dataPipeline: '数据流', dataPipeline: '数据流',
linkDataPipeline: '关联数据流', linkDataPipeline: '关联数据流',
@ -1556,6 +1574,13 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
<p>要保留这些更改,请点击“重新运行”以重新运行当前阶段。</p> `, <p>要保留这些更改,请点击“重新运行”以重新运行当前阶段。</p> `,
changeStepModalConfirmText: '继续切换', changeStepModalConfirmText: '继续切换',
changeStepModalCancelText: '取消', changeStepModalCancelText: '取消',
unlinkPipelineModalTitle: '解绑数据流',
unlinkPipelineModalContent: `
<p>一旦取消链接,该数据集将不再连接到当前数据管道。</p>
<p>正在解析的文件将继续解析,直到完成。</p>
<p>尚未解析的文件将不再被处理。</p> <br/>
<p>你确定要继续吗?</p> `,
unlinkPipelineModalConfirmText: '解绑',
}, },
dataflow: { dataflow: {
parser: '解析器', parser: '解析器',

View File

@ -2,7 +2,7 @@ import SvgIcon from '@/components/svg-icon';
import { useIsDarkTheme } from '@/components/theme-provider'; import { useIsDarkTheme } from '@/components/theme-provider';
import { parseColorToRGBA } from '@/utils/common-util'; import { parseColorToRGBA } from '@/utils/common-util';
import { CircleQuestionMark } from 'lucide-react'; import { CircleQuestionMark } from 'lucide-react';
import { FC, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LogTabs } from './dataset-common'; import { LogTabs } from './dataset-common';
import { DatasetFilter } from './dataset-filter'; import { DatasetFilter } from './dataset-filter';
@ -74,25 +74,35 @@ const FileLogsPage: FC = () => {
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>( const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
LogTabs.FILE_LOGS, LogTabs.FILE_LOGS,
); );
const topMockData = { const [topAllData, setTopAllData] = useState({
totalFiles: { totalFiles: {
value: 2827, value: 0,
precent: 12.5, precent: 0,
}, },
downloads: { downloads: {
value: 28, value: 0,
success: 8, success: 0,
failed: 2, failed: 0,
}, },
processing: { processing: {
value: 156, value: 0,
success: 8, success: 0,
failed: 2, failed: 0,
}, },
}; });
const { data: topData } = useFetchOverviewTital(); const { data: topData } = useFetchOverviewTital();
console.log('topData --> ', topData); console.log('topData --> ', topData);
useEffect(() => {
setTopAllData({
...topAllData,
processing: {
value: topData?.processing || 0,
success: topData?.finished || 0,
failed: topData?.failed || 0,
},
});
}, [topData, topAllData]);
const mockData = useMemo(() => { const mockData = useMemo(() => {
if (active === LogTabs.FILE_LOGS) { if (active === LogTabs.FILE_LOGS) {
@ -161,7 +171,7 @@ const FileLogsPage: FC = () => {
<div className="grid grid-cols-3 md:grid-cols-3 gap-4 mb-6"> <div className="grid grid-cols-3 md:grid-cols-3 gap-4 mb-6">
<StatCard <StatCard
title="Total Files" title="Total Files"
value={topMockData.totalFiles.value} value={topAllData.totalFiles.value}
icon={ icon={
isDark ? ( isDark ? (
<SvgIcon name="data-flow/total-files-icon" width={40} /> <SvgIcon name="data-flow/total-files-icon" width={40} />
@ -172,15 +182,15 @@ const FileLogsPage: FC = () => {
> >
<div> <div>
<span className="text-accent-primary"> <span className="text-accent-primary">
{topMockData.totalFiles.precent > 0 ? '+' : ''} {topAllData.totalFiles.precent > 0 ? '+' : ''}
{topMockData.totalFiles.precent}%{' '} {topAllData.totalFiles.precent}%{' '}
</span> </span>
from last week from last week
</div> </div>
</StatCard> </StatCard>
<StatCard <StatCard
title="Downloading" title="Downloading"
value={topMockData.downloads.value} value={topAllData.downloads.value}
icon={ icon={
isDark ? ( isDark ? (
<SvgIcon name="data-flow/data-icon" width={40} /> <SvgIcon name="data-flow/data-icon" width={40} />
@ -190,13 +200,13 @@ const FileLogsPage: FC = () => {
} }
> >
<CardFooterProcess <CardFooterProcess
success={topMockData.downloads.success} success={topAllData.downloads.success}
failed={topMockData.downloads.failed} failed={topAllData.downloads.failed}
/> />
</StatCard> </StatCard>
<StatCard <StatCard
title="Processing" title="Processing"
value={topMockData.processing.value} value={topAllData.processing.value}
icon={ icon={
isDark ? ( isDark ? (
<SvgIcon name="data-flow/processing-icon" width={40} /> <SvgIcon name="data-flow/processing-icon" width={40} />
@ -206,8 +216,8 @@ const FileLogsPage: FC = () => {
} }
> >
<CardFooterProcess <CardFooterProcess
success={topMockData.processing.success} success={topAllData.processing.success}
failed={topMockData.processing.failed} failed={topAllData.processing.failed}
/> />
</StatCard> </StatCard>
</div> </div>

View File

@ -65,25 +65,25 @@ export const getFileLogsTableColumns = (
) => { ) => {
// const { t } = useTranslate('knowledgeDetails'); // const { t } = useTranslate('knowledgeDetails');
const columns: ColumnDef<DocumentLog>[] = [ const columns: ColumnDef<DocumentLog>[] = [
{ // {
id: 'select', // id: 'select',
header: ({ table }) => ( // header: ({ table }) => (
<input // <input
type="checkbox" // type="checkbox"
checked={table.getIsAllRowsSelected()} // checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()} // onChange={table.getToggleAllRowsSelectedHandler()}
className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500" // className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
/> // />
), // ),
cell: ({ row }) => ( // cell: ({ row }) => (
<input // <input
type="checkbox" // type="checkbox"
checked={row.getIsSelected()} // checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()} // onChange={row.getToggleSelectedHandler()}
className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500" // className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
/> // />
), // ),
}, // },
{ {
accessorKey: 'id', accessorKey: 'id',
header: 'ID', header: 'ID',
@ -156,7 +156,7 @@ export const getFileLogsTableColumns = (
id: 'operations', id: 'operations',
header: t('operations'), header: t('operations'),
cell: ({ row }) => ( 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 <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -189,25 +189,25 @@ export const getDatasetLogsTableColumns = (
) => { ) => {
// const { t } = useTranslate('knowledgeDetails'); // const { t } = useTranslate('knowledgeDetails');
const columns: ColumnDef<DocumentLog>[] = [ const columns: ColumnDef<DocumentLog>[] = [
{ // {
id: 'select', // id: 'select',
header: ({ table }) => ( // header: ({ table }) => (
<input // <input
type="checkbox" // type="checkbox"
checked={table.getIsAllRowsSelected()} // checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()} // onChange={table.getToggleAllRowsSelectedHandler()}
className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500" // className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
/> // />
), // ),
cell: ({ row }) => ( // cell: ({ row }) => (
<input // <input
type="checkbox" // type="checkbox"
checked={row.getIsSelected()} // checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()} // onChange={row.getToggleSelectedHandler()}
className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500" // className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
/> // />
), // ),
}, // },
{ {
accessorKey: 'id', accessorKey: 'id',
header: 'ID', header: 'ID',
@ -251,7 +251,7 @@ export const getDatasetLogsTableColumns = (
id: 'operations', id: 'operations',
header: t('operations'), header: t('operations'),
cell: ({ row }) => ( 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 <Button
variant="ghost" variant="ghost"
size="sm" size="sm"

View File

@ -1,21 +1,61 @@
import { IconFont } from '@/components/icon-font'; import { IconFont } from '@/components/icon-font';
import { RAGFlowAvatar } from '@/components/ragflow-avatar'; import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import { omit } from 'lodash';
import { Link, Settings2, Unlink } from 'lucide-react'; import { Link, Settings2, Unlink } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { linkPiplineFormSchema } from '../form-schema';
import LinkDataPipelineModal from './link-data-pipline-modal'; import LinkDataPipelineModal from './link-data-pipline-modal';
interface DataPipelineItemProps { interface DataPipelineItemProps {
id: string;
name: string; name: string;
avatar?: string; avatar?: string;
isDefault?: boolean; isDefault?: boolean;
linked?: boolean; linked?: boolean;
openLinkModalFunc?: (open: boolean) => void; openLinkModalFunc?: (open: boolean, data?: IDataPipelineNodeProps) => void;
} }
const DataPipelineItem = (props: DataPipelineItemProps) => { const DataPipelineItem = (props: DataPipelineItemProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { name, avatar, isDefault, linked, openLinkModalFunc } = props; 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 ( return (
<div className="flex items-center justify-between gap-1 px-2 rounded-lg border"> <div className="flex items-center justify-between gap-1 px-2 rounded-lg border">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -28,42 +68,89 @@ const DataPipelineItem = (props: DataPipelineItemProps) => {
)} )}
</div> </div>
<div className="flex gap-1 items-center"> <div className="flex gap-1 items-center">
<Button variant={'transparent'} className="border-none">
<Settings2 />
</Button>
{!isDefault && (
<Button <Button
variant={'transparent'}
className="border-none"
type="button"
onClick={() =>
openLinkModalFunc?.(true, { ...omit(props, ['openLinkModalFunc']) })
}
>
<Settings2 />
</Button>
{!isDefault && (
<>
{linked && (
<Button
type="button"
variant={'transparent'} variant={'transparent'}
className="border-none" className="border-none"
onClick={() => { onClick={() => {
openLinkModalFunc?.(true); openUnlinkModal();
}} }}
> >
{linked ? <Link /> : <Unlink />} <Unlink />
</Button> </Button>
)} )}
</>
)}
</div> </div>
</div> </div>
); );
}; };
export interface IDataPipelineNodeProps {
id: string;
name: string;
avatar?: string;
isDefault?: boolean;
linked?: boolean;
}
const LinkDataPipeline = () => { const LinkDataPipeline = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [openLinkModal, setOpenLinkModal] = useState(false); const [openLinkModal, setOpenLinkModal] = useState(false);
const [currentDataPipeline, setCurrentDataPipeline] =
useState<IDataPipelineNodeProps>();
const testNode = [ const testNode = [
{ {
id: '1',
name: 'Data Pipeline 1', name: 'Data Pipeline 1',
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4', avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
isDefault: true, isDefault: true,
linked: true, linked: true,
}, },
{ {
id: '2',
name: 'Data Pipeline 2', name: 'Data Pipeline 2',
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4', avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
linked: false, linked: false,
}, },
{
id: '3',
name: 'Data Pipeline 3',
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
linked: false,
},
{
id: '4',
name: 'Data Pipeline 4',
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
linked: true,
},
]; ];
const openLinkModalFunc = (open: boolean) => { const openLinkModalFunc = (open: boolean, data?: IDataPipelineNodeProps) => {
console.log('open', open, data);
setOpenLinkModal(open); setOpenLinkModal(open);
if (data) {
setCurrentDataPipeline(data);
} else {
setCurrentDataPipeline(undefined);
}
};
const handleLinkOrEditSubmit = (
data: z.infer<typeof linkPiplineFormSchema>,
) => {
console.log('handleLinkOrEditSubmit', data);
}; };
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@ -74,9 +161,15 @@ const LinkDataPipeline = () => {
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="text-center text-xs text-text-secondary"> <div className="text-center text-xs text-text-secondary">
Manage data pipeline linkage with this dataset {t('knowledgeConfiguration.linkPipelineSetTip')}
</div> </div>
<Button variant={'transparent'}> <Button
type="button"
variant={'transparent'}
onClick={() => {
openLinkModalFunc?.(true);
}}
>
<Link /> <Link />
<span className="text-xs text-text-primary"> <span className="text-xs text-text-primary">
{t('knowledgeConfiguration.linkDataPipeline')} {t('knowledgeConfiguration.linkDataPipeline')}
@ -94,10 +187,12 @@ const LinkDataPipeline = () => {
))} ))}
</section> </section>
<LinkDataPipelineModal <LinkDataPipelineModal
data={currentDataPipeline}
open={openLinkModal} open={openLinkModal}
setOpen={(open: boolean) => { setOpen={(open: boolean) => {
openLinkModalFunc(open); openLinkModalFunc(open);
}} }}
onSubmit={handleLinkOrEditSubmit}
/> />
</div> </div>
); );

View File

@ -10,32 +10,53 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Modal } from '@/components/ui/modal/modal'; import { Modal } from '@/components/ui/modal/modal';
import { Switch } from '@/components/ui/switch';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next'; import { t } from 'i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { linkPiplineFormSchema } from '../form-schema'; import { pipelineFormSchema } from '../form-schema';
import { IDataPipelineNodeProps } from './link-data-pipeline';
const LinkDataPipelineModal = ({ const LinkDataPipelineModal = ({
data,
open, open,
setOpen, setOpen,
onSubmit,
}: { }: {
data: IDataPipelineNodeProps | undefined;
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
onSubmit?: (data: any) => void;
}) => { }) => {
const form = useForm<z.infer<typeof linkPiplineFormSchema>>({ const isEdit = !!data;
resolver: zodResolver(linkPiplineFormSchema), const form = useForm<z.infer<typeof pipelineFormSchema>>({
defaultValues: { data_flow: ['888'], file_filter: '' }, resolver: zodResolver(pipelineFormSchema),
defaultValues: {
data_flow: [],
set_default: false,
file_filter: '',
},
}); });
// const [open, setOpen] = useState(false); // const [open, setOpen] = useState(false);
const { navigateToAgents } = useNavigatePage(); const { navigateToAgents } = useNavigatePage();
const handleFormSubmit = (values: any) => { const handleFormSubmit = (values: any) => {
console.log(values); console.log(values, data);
const param = {
...data,
...values,
};
onSubmit?.(param);
}; };
return ( return (
<Modal <Modal
title={t('knowledgeConfiguration.linkDataPipeline')} className="!w-[560px]"
title={
!isEdit
? t('knowledgeConfiguration.linkDataPipeline')
: t('knowledgeConfiguration.eidtLinkDataPipeline')
}
open={open} open={open}
onOpenChange={setOpen} onOpenChange={setOpen}
showfooter={false} showfooter={false}
@ -43,10 +64,12 @@ const LinkDataPipelineModal = ({
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(handleFormSubmit)}> <form onSubmit={form.handleSubmit(handleFormSubmit)}>
<div className="flex flex-col gap-4 "> <div className="flex flex-col gap-4 ">
{!isEdit && (
<DataFlowSelect <DataFlowSelect
toDataPipeline={navigateToAgents} toDataPipeline={navigateToAgents}
formFieldName="data_flow" formFieldName="data_flow"
/> />
)}
<FormField <FormField
control={form.control} control={form.control}
name={'file_filter'} name={'file_filter'}
@ -65,7 +88,9 @@ const LinkDataPipelineModal = ({
<div className="text-muted-foreground"> <div className="text-muted-foreground">
<FormControl> <FormControl>
<Input <Input
placeholder={t('dataFlowPlaceholder')} placeholder={t(
'knowledgeConfiguration.filterPlaceholder',
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -78,11 +103,56 @@ const LinkDataPipelineModal = ({
</FormItem> </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"> <div className="flex justify-end gap-1">
<Button type="reset" variant={'outline'} className="btn-primary"> <Button
type="button"
variant={'outline'}
className="btn-primary"
onClick={() => {
setOpen(false);
}}
>
{t('modal.cancelText')} {t('modal.cancelText')}
</Button> </Button>
<Button type="submit" variant={'default'} className="btn-primary"> <Button
type="button"
variant={'default'}
className="btn-primary"
onClick={form.handleSubmit(handleFormSubmit)}
>
{t('modal.okText')} {t('modal.okText')}
</Button> </Button>
</div> </div>

View File

@ -72,7 +72,17 @@ export const formSchema = z.object({
// icon: z.array(z.instanceof(File)), // icon: z.array(z.instanceof(File)),
}); });
export const linkPiplineFormSchema = z.object({ export const pipelineFormSchema = z.object({
data_flow: z.array(z.string()), data_flow: z.array(z.string()).optional(),
set_default: z.boolean().optional(),
file_filter: z.string().optional(), file_filter: z.string().optional(),
}); });
export const linkPiplineFormSchema = pipelineFormSchema.pick({
data_flow: true,
file_filter: true,
});
export const editPiplineFormSchema = pipelineFormSchema.pick({
set_default: true,
file_filter: true,
});

View File

@ -86,9 +86,12 @@ export default function DatasetSettings() {
<GeneralForm></GeneralForm> <GeneralForm></GeneralForm>
<Divider /> <Divider />
<GraphRagItems className="border-none p-0"></GraphRagItems> <GraphRagItems
className="border-none p-0"
showGenerateItem={true}
></GraphRagItems>
<Divider /> <Divider />
<RaptorFormFields></RaptorFormFields> <RaptorFormFields showGenerateItem={true}></RaptorFormFields>
<Divider /> <Divider />
<LinkDataPipeline /> <LinkDataPipeline />
</MainContainer> </MainContainer>

View File

@ -6,16 +6,20 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { Modal } from '@/components/ui/modal/modal';
import { cn } from '@/lib/utils';
import { toFixed } from '@/utils/common-util'; import { toFixed } from '@/utils/common-util';
import { t } from 'i18next'; import { t } from 'i18next';
import { lowerFirst } from 'lodash'; import { lowerFirst } from 'lodash';
import { CirclePause, WandSparkles } from 'lucide-react'; import { CirclePause, Trash2, WandSparkles } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generateStatus, useFetchGenerateData } from './hook'; import { generateStatus, useFetchGenerateData } from './hook';
export enum GenerateType {
const MenuItem: React.FC<{ name: 'KnowledgeGraph' | 'Raptor' }> = ({ KnowledgeGraph = 'KnowledgeGraph',
name, Raptor = 'Raptor',
}) => { }
const MenuItem: React.FC<{ name: GenerateType }> = ({ name }) => {
console.log(name, 'pppp'); console.log(name, 'pppp');
const iconKeyMap = { const iconKeyMap = {
KnowledgeGraph: 'knowledgegraph', KnowledgeGraph: 'knowledgegraph',
@ -111,3 +115,102 @@ const Generate: React.FC = () => {
}; };
export default Generate; export default Generate;
export type IGenerateLogProps = {
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 {
id,
status,
message,
created_at,
updated_at,
type,
className,
onDelete,
} = props;
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.hide();
},
footer: (
<div className="flex justify-end gap-2">
<Button
type="button"
variant={'outline'}
onClick={() => Modal.hide()}
>
{t('dataflowParser.changeStepModalCancelText')}
</Button>
<Button
type="button"
variant={'secondary'}
className="!bg-state-error text-text-primary"
onClick={() => {
Modal.hide();
}}
>
{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">
{status === 1 && (
<>
<div>
{message || t('knowledgeDetails.generatedOn')}
{created_at}
</div>
<Trash2
size={14}
className="cursor-pointer"
onClick={(e) => {
console.log('delete');
handleDelete();
e.stopPropagation();
}}
/>
</>
)}
{status === 0 && <div>{t('knowledgeDetails.notGenerated')}</div>}
</div>
</div>
);
};

View File

@ -75,7 +75,7 @@ export default function Dataset() {
filters={filters} filters={filters}
leftPanel={ leftPanel={
<div className="items-start"> <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"> <div className="text-text-sub-title-invert text-sm">
{t('knowledgeDetails.datasetDescription')} {t('knowledgeDetails.datasetDescription')}
</div> </div>

View File

@ -9,7 +9,7 @@ import { cn, formatBytes } from '@/lib/utils';
import { Routes } from '@/routes'; import { Routes } from '@/routes';
import { formatPureDate } from '@/utils/date'; import { formatPureDate } from '@/utils/date';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { Banknote, Database, FileSearch2, GitGraph } from 'lucide-react'; import { Banknote, FileSearch2, FolderOpen, GitGraph } from 'lucide-react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHandleMenuClick } from './hooks'; import { useHandleMenuClick } from './hooks';
@ -34,8 +34,8 @@ export function SideBar({ refreshCount }: PropType) {
// key: Routes.DataSetOverview, // key: Routes.DataSetOverview,
// }, // },
{ {
icon: Database, icon: FolderOpen,
label: t(`knowledgeDetails.dataset`), label: t(`knowledgeDetails.subbarFiles`),
key: Routes.DatasetBase, key: Routes.DatasetBase,
}, },
{ {