Fix: Improved knowledge base configuration and related logic #9869 (#10315)

### What problem does this PR solve?

Fix: Improved knowledge base configuration and related logic #9869
- Optimized the display logic of the Generate Log button to support
displaying completion time and task ID
- Implemented the ability to pause task generation and connect to the
data flow cancellation interface
- Fixed issues with type definitions and optional chaining calls in some
components
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-09-26 19:09:11 +08:00
committed by GitHub
parent c7efaab30e
commit 886d38620e
19 changed files with 374 additions and 231 deletions

View File

@ -23,7 +23,6 @@ import {
ParseTypeItem, ParseTypeItem,
} from '@/pages/dataset/dataset-setting/configuration/common-item'; } from '@/pages/dataset/dataset-setting/configuration/common-item';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import get from 'lodash/get';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import {} from 'module'; import {} from 'module';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
@ -41,13 +40,6 @@ import { ExcelToHtmlFormField } from '../excel-to-html-form-field';
import { FormContainer } from '../form-container'; import { FormContainer } from '../form-container';
import { LayoutRecognizeFormField } from '../layout-recognize-form-field'; import { LayoutRecognizeFormField } from '../layout-recognize-form-field';
import { MaxTokenNumberFormField } from '../max-token-number-from-field'; import { MaxTokenNumberFormField } from '../max-token-number-from-field';
import {
UseGraphRagFormField,
showGraphRagItems,
} from '../parse-configuration/graph-rag-form-fields';
import RaptorFormFields, {
showRaptorParseConfiguration,
} from '../parse-configuration/raptor-form-fields';
import { ButtonLoading } from '../ui/button'; import { ButtonLoading } from '../ui/button';
import { Input } from '../ui/input'; import { Input } from '../ui/input';
import { DynamicPageRange } from './dynamic-page-range'; import { DynamicPageRange } from './dynamic-page-range';
@ -121,19 +113,19 @@ export function ChunkMethodDialog({
auto_keywords: z.coerce.number().optional(), auto_keywords: z.coerce.number().optional(),
auto_questions: z.coerce.number().optional(), auto_questions: z.coerce.number().optional(),
html4excel: z.boolean().optional(), html4excel: z.boolean().optional(),
raptor: z // raptor: z
.object({ // .object({
use_raptor: z.boolean().optional(), // use_raptor: z.boolean().optional(),
prompt: z.string().optional().optional(), // prompt: z.string().optional().optional(),
max_token: z.coerce.number().optional(), // max_token: z.coerce.number().optional(),
threshold: z.coerce.number().optional(), // threshold: z.coerce.number().optional(),
max_cluster: z.coerce.number().optional(), // max_cluster: z.coerce.number().optional(),
random_seed: z.coerce.number().optional(), // random_seed: z.coerce.number().optional(),
}) // })
.optional(), // .optional(),
graphrag: z.object({ // graphrag: z.object({
use_graphrag: z.boolean().optional(), // use_graphrag: z.boolean().optional(),
}), // }),
entity_types: z.array(z.string()).optional(), entity_types: z.array(z.string()).optional(),
pages: z pages: z
.array(z.object({ from: z.coerce.number(), to: z.coerce.number() })) .array(z.object({ from: z.coerce.number(), to: z.coerce.number() }))
@ -223,13 +215,13 @@ export function ChunkMethodDialog({
parser_config: fillDefaultParserValue({ parser_config: fillDefaultParserValue({
pages: pages.length > 0 ? pages : [{ from: 1, to: 1024 }], pages: pages.length > 0 ? pages : [{ from: 1, to: 1024 }],
...omit(parserConfig, 'pages'), ...omit(parserConfig, 'pages'),
graphrag: { // graphrag: {
use_graphrag: get( // use_graphrag: get(
parserConfig, // parserConfig,
'graphrag.use_graphrag', // 'graphrag.use_graphrag',
useGraphRag, // useGraphRag,
), // ),
}, // },
}), }),
}); });
} }
@ -351,19 +343,19 @@ export function ChunkMethodDialog({
<ExcelToHtmlFormField></ExcelToHtmlFormField> <ExcelToHtmlFormField></ExcelToHtmlFormField>
)} )}
</FormContainer> </FormContainer>
{showRaptorParseConfiguration( {/* {showRaptorParseConfiguration(
selectedTag as DocumentParserType, selectedTag as DocumentParserType,
) && ( ) && (
<FormContainer> <FormContainer>
<RaptorFormFields></RaptorFormFields> <RaptorFormFields></RaptorFormFields>
</FormContainer> </FormContainer>
)} )} */}
{showGraphRagItems(selectedTag as DocumentParserType) && {/* {showGraphRagItems(selectedTag as DocumentParserType) &&
useGraphRag && ( useGraphRag && (
<FormContainer> <FormContainer>
<UseGraphRagFormField></UseGraphRagFormField> <UseGraphRagFormField></UseGraphRagFormField>
</FormContainer> </FormContainer>
)} )} */}
{showEntityTypes && ( {showEntityTypes && (
<EntityTypesFormField></EntityTypesFormField> <EntityTypesFormField></EntityTypesFormField>
)} )}

View File

@ -15,17 +15,17 @@ export function useDefaultParserValues() {
auto_keywords: 0, auto_keywords: 0,
auto_questions: 0, auto_questions: 0,
html4excel: false, html4excel: false,
raptor: { // raptor: {
use_raptor: false, // use_raptor: false,
prompt: t('knowledgeConfiguration.promptText'), // prompt: t('knowledgeConfiguration.promptText'),
max_token: 256, // max_token: 256,
threshold: 0.1, // threshold: 0.1,
max_cluster: 64, // max_cluster: 64,
random_seed: 0, // random_seed: 0,
}, // },
graphrag: { // graphrag: {
use_graphrag: false, // use_graphrag: false,
}, // },
entity_types: [], entity_types: [],
pages: [], pages: [],
}; };

View File

@ -2,7 +2,7 @@ import { useTranslate } from '@/hooks/common-hooks';
import { useFetchAgentList } from '@/hooks/use-agent-request'; import { useFetchAgentList } from '@/hooks/use-agent-request';
import { buildSelectOptions } from '@/utils/component-util'; import { buildSelectOptions } from '@/utils/component-util';
import { ArrowUpRight } from 'lucide-react'; import { ArrowUpRight } from 'lucide-react';
import { useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { SelectWithSearch } from '../originui/select-with-search'; import { SelectWithSearch } from '../originui/select-with-search';
import { import {
@ -13,15 +13,21 @@ import {
FormMessage, FormMessage,
} from '../ui/form'; } from '../ui/form';
import { MultiSelect } from '../ui/multi-select'; import { MultiSelect } from '../ui/multi-select';
export interface IDataPipelineSelectNode {
id?: string;
name?: string;
avatar?: string;
}
interface IProps { interface IProps {
toDataPipeline?: () => void; toDataPipeline?: () => void;
formFieldName: string; formFieldName: string;
isMult?: boolean; isMult?: boolean;
setDataList?: (data: IDataPipelineSelectNode[]) => void;
} }
export function DataFlowSelect(props: IProps) { export function DataFlowSelect(props: IProps) {
const { toDataPipeline, formFieldName, isMult = true } = props; const { toDataPipeline, formFieldName, isMult = false, setDataList } = props;
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext(); const form = useFormContext();
const toDataPipLine = () => { const toDataPipLine = () => {
@ -36,8 +42,26 @@ export function DataFlowSelect(props: IProps) {
'id', 'id',
'title', 'title',
); );
return option || []; return option || [];
}, [dataPipelineOptions]); }, [dataPipelineOptions]);
const nodes = useMemo(() => {
return (
dataPipelineOptions?.canvas?.map((item) => {
return {
id: item?.id,
name: item?.title,
avatar: item?.avatar,
};
}) || []
);
}, [dataPipelineOptions]);
useEffect(() => {
setDataList?.(nodes);
}, [nodes, setDataList]);
return ( return (
<FormField <FormField
control={form.control} control={form.control}

View File

@ -4,6 +4,7 @@ import { cn } from '@/lib/utils';
import { import {
GenerateLogButton, GenerateLogButton,
GenerateType, GenerateType,
IGenerateLogButtonProps,
} from '@/pages/dataset/dataset/generate-button/generate'; } 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';
@ -51,10 +52,14 @@ export const showGraphRagItems = (parserId: DocumentParserType | undefined) => {
type GraphRagItemsProps = { type GraphRagItemsProps = {
marginBottom?: boolean; marginBottom?: boolean;
className?: string; className?: string;
showGenerateItem?: boolean; data: IGenerateLogButtonProps;
}; };
export function UseGraphRagFormField() { export function UseGraphRagFormField({
data,
}: {
data: IGenerateLogButtonProps;
}) {
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
@ -73,10 +78,16 @@ export function UseGraphRagFormField() {
</FormLabel> </FormLabel>
<div className="w-3/4"> <div className="w-3/4">
<FormControl> <FormControl>
<Switch {/* <Switch
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
></Switch> ></Switch> */}
<GenerateLogButton
{...data}
className="w-full text-text-secondary"
status={1}
type={GenerateType.KnowledgeGraph}
/>
</FormControl> </FormControl>
</div> </div>
</div> </div>
@ -93,8 +104,8 @@ 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',
data,
}: GraphRagItemsProps) => { }: GraphRagItemsProps) => {
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext(); const form = useFormContext();
@ -120,7 +131,7 @@ const GraphRagItems = ({
return ( return (
<FormContainer className={cn({ 'mb-4': marginBottom }, className)}> <FormContainer className={cn({ 'mb-4': marginBottom }, className)}>
<UseGraphRagFormField></UseGraphRagFormField> <UseGraphRagFormField data={data}></UseGraphRagFormField>
{useRaptor && ( {useRaptor && (
<> <>
<EntityTypesFormField name="parser_config.graphrag.entity_types"></EntityTypesFormField> <EntityTypesFormField name="parser_config.graphrag.entity_types"></EntityTypesFormField>
@ -216,7 +227,7 @@ const GraphRagItems = ({
</FormItem> </FormItem>
)} )}
/> />
{showGenerateItem && ( {/* {showGenerateItem && (
<div className="w-full flex items-center"> <div className="w-full flex items-center">
<div className="text-sm whitespace-nowrap w-1/4"> <div className="text-sm whitespace-nowrap w-1/4">
{t('extractKnowledgeGraph')} {t('extractKnowledgeGraph')}
@ -227,7 +238,7 @@ const GraphRagItems = ({
type={GenerateType.KnowledgeGraph} type={GenerateType.KnowledgeGraph}
/> />
</div> </div>
)} )} */}
</> </>
)} )}
</FormContainer> </FormContainer>

View File

@ -4,6 +4,7 @@ import { useTranslate } from '@/hooks/common-hooks';
import { import {
GenerateLogButton, GenerateLogButton,
GenerateType, GenerateType,
IGenerateLogButtonProps,
} from '@/pages/dataset/dataset/generate-button/generate'; } 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';
@ -18,7 +19,6 @@ import {
FormMessage, FormMessage,
} from '../ui/form'; } from '../ui/form';
import { ExpandedInput } from '../ui/input'; import { ExpandedInput } from '../ui/input';
import { Switch } from '../ui/switch';
import { Textarea } from '../ui/textarea'; import { Textarea } from '../ui/textarea';
export const excludedParseMethods = [ export const excludedParseMethods = [
@ -56,11 +56,7 @@ 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 = ({ data }: { data: IGenerateLogButtonProps }) => {
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 });
@ -108,13 +104,12 @@ const RaptorFormFields = ({
</FormLabel> </FormLabel>
<div className="w-3/4"> <div className="w-3/4">
<FormControl> <FormControl>
<Switch <GenerateLogButton
checked={field.value} {...data}
onCheckedChange={(e) => { className="w-full text-text-secondary"
changeRaptor(e); status={1}
field.onChange(e); type={GenerateType.Raptor}
}} />
></Switch>
</FormControl> </FormControl>
</div> </div>
</div> </div>
@ -219,18 +214,6 @@ 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

@ -14,6 +14,9 @@ export interface IKnowledge {
name: string; name: string;
parser_config: ParserConfig; parser_config: ParserConfig;
parser_id: string; parser_id: string;
pipeline_id: string;
pipeline_name: string;
pipeline_avatar: string;
permission: string; permission: string;
similarity_threshold: number; similarity_threshold: number;
status: string; status: string;
@ -26,6 +29,10 @@ export interface IKnowledge {
nickname: string; nickname: string;
operator_permission: number; operator_permission: number;
size: number; size: number;
raptor_task_finish_at?: string;
raptor_task_id?: string;
mindmap_task_finish_at?: string;
mindmap_task_id?: string;
} }
export interface IKnowledgeResult { export interface IKnowledgeResult {

View File

@ -115,7 +115,7 @@ const FormatPreserveEditor = ({
) : ( ) : (
<> <>
{content.key === 'json' && */} {content.key === 'json' && */}
{content.value.map((item, index) => ( {content.value?.map((item, index) => (
<section <section
key={index} key={index}
className={ className={

View File

@ -45,11 +45,17 @@ const useFetchFileLogList = () => {
queryKey: [ queryKey: [
'fileLogList', 'fileLogList',
knowledgeBaseId, knowledgeBaseId,
pagination.current, pagination,
pagination.pageSize,
searchString, searchString,
active, active,
], ],
placeholderData: (previousData) => {
if (previousData === undefined) {
return { logs: [], total: 0 };
}
return previousData;
},
enabled: true,
queryFn: async () => { queryFn: async () => {
const { data: res = {} } = await fetchFunc({ const { data: res = {} } = await fetchFunc({
kb_id: knowledgeBaseId, kb_id: knowledgeBaseId,
@ -73,6 +79,7 @@ const useFetchFileLogList = () => {
searchString, searchString,
handleInputChange: onInputChange, handleInputChange: onInputChange,
pagination: { ...pagination, total: data?.total }, pagination: { ...pagination, total: data?.total },
setPagination,
active, active,
setActive, setActive,
}; };

View File

@ -1,5 +1,6 @@
import SvgIcon from '@/components/svg-icon'; import SvgIcon from '@/components/svg-icon';
import { useIsDarkTheme } from '@/components/theme-provider'; import { useIsDarkTheme } from '@/components/theme-provider';
import { useFetchDocumentList } from '@/hooks/use-document-request';
import { parseColorToRGBA } from '@/utils/common-util'; import { parseColorToRGBA } from '@/utils/common-util';
import { CircleQuestionMark } from 'lucide-react'; import { CircleQuestionMark } from 'lucide-react';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
@ -90,6 +91,9 @@ const FileLogsPage: FC = () => {
}); });
const { data: topData } = useFetchOverviewTital(); const { data: topData } = useFetchOverviewTital();
const {
pagination: { total: fileTotal },
} = useFetchDocumentList();
console.log('topData --> ', topData); console.log('topData --> ', topData);
useEffect(() => { useEffect(() => {
setTopAllData((prev) => { setTopAllData((prev) => {
@ -104,11 +108,24 @@ const FileLogsPage: FC = () => {
}); });
}, [topData]); }, [topData]);
useEffect(() => {
setTopAllData((prev) => {
return {
...prev,
totalFiles: {
value: fileTotal || 0,
precent: 0,
},
};
});
}, [fileTotal]);
const { const {
data: tableOriginData, data: tableOriginData,
searchString, searchString,
handleInputChange, handleInputChange,
pagination, pagination,
setPagination,
active, active,
setActive, setActive,
} = useFetchFileLogList(); } = useFetchFileLogList();
@ -131,6 +148,11 @@ const FileLogsPage: FC = () => {
}; };
const handlePaginationChange = (page: number, pageSize: number) => { const handlePaginationChange = (page: number, pageSize: number) => {
console.log('Pagination changed:', { page, pageSize }); console.log('Pagination changed:', { page, pageSize });
setPagination({
...pagination,
page,
pageSize: pageSize,
});
}; };
const isDark = useIsDarkTheme(); const isDark = useIsDarkTheme();

View File

@ -1,23 +1,26 @@
import { IDataPipelineSelectNode } from '@/components/data-pipeline-select';
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 { Modal } from '@/components/ui/modal/modal';
import { omit } from 'lodash'; import { Link } from 'lucide-react';
import { Link, Settings2, Unlink } from 'lucide-react'; import { useMemo, 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';
export interface IDataPipelineNodeProps extends IDataPipelineSelectNode {
interface DataPipelineItemProps {
id: string;
name: string;
avatar?: string;
isDefault?: boolean; isDefault?: boolean;
linked?: boolean; linked?: boolean;
}
export interface ILinkDataPipelineProps {
data?: IDataPipelineNodeProps;
handleLinkOrEditSubmit?: (data: IDataPipelineNodeProps | undefined) => void;
}
interface DataPipelineItemProps extends IDataPipelineNodeProps {
openLinkModalFunc?: (open: boolean, data?: IDataPipelineNodeProps) => 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;
@ -57,17 +60,17 @@ const DataPipelineItem = (props: DataPipelineItemProps) => {
}; };
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-md border">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<RAGFlowAvatar avatar={avatar} name={name} className="size-4" /> <RAGFlowAvatar avatar={avatar} name={name} className="size-4" />
<div>{name}</div> <div>{name}</div>
{isDefault && ( {/* {isDefault && (
<div className="text-xs bg-text-secondary text-bg-base px-2 py-1 rounded-md"> <div className="text-xs bg-text-secondary text-bg-base px-2 py-1 rounded-md">
{t('knowledgeConfiguration.default')} {t('knowledgeConfiguration.default')}
</div> </div>
)} )} */}
</div> </div>
<div className="flex gap-1 items-center"> {/* <div className="flex gap-1 items-center">
<Button <Button
variant={'transparent'} variant={'transparent'}
className="border-none" className="border-none"
@ -94,50 +97,29 @@ const DataPipelineItem = (props: DataPipelineItemProps) => {
)} )}
</> </>
)} )}
</div> </div> */}
</div> </div>
); );
}; };
export interface IDataPipelineNodeProps { const LinkDataPipeline = (props: ILinkDataPipelineProps) => {
id: string; const { data, handleLinkOrEditSubmit: submit } = props;
name: string;
avatar?: string;
isDefault?: boolean;
linked?: boolean;
}
const LinkDataPipeline = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [openLinkModal, setOpenLinkModal] = useState(false); const [openLinkModal, setOpenLinkModal] = useState(false);
const [currentDataPipeline, setCurrentDataPipeline] = const [currentDataPipeline, setCurrentDataPipeline] =
useState<IDataPipelineNodeProps>(); useState<IDataPipelineNodeProps>();
const testNode = [ const pipelineNode: IDataPipelineNodeProps[] = useMemo(
() => [
{ {
id: '1', id: data?.id,
name: 'Data Pipeline 1', name: data?.name,
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4', avatar: data?.avatar,
isDefault: true, isDefault: data?.isDefault,
linked: true, linked: true,
}, },
{ ],
id: '2', [data],
name: 'Data Pipeline 2', );
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
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, data?: IDataPipelineNodeProps) => { const openLinkModalFunc = (open: boolean, data?: IDataPipelineNodeProps) => {
console.log('open', open, data); console.log('open', open, data);
setOpenLinkModal(open); setOpenLinkModal(open);
@ -148,9 +130,11 @@ const LinkDataPipeline = () => {
} }
}; };
const handleLinkOrEditSubmit = ( const handleLinkOrEditSubmit = (
data: z.infer<typeof linkPiplineFormSchema>, data: IDataPipelineSelectNode | undefined,
) => { ) => {
console.log('handleLinkOrEditSubmit', data); console.log('handleLinkOrEditSubmit', data);
submit?.(data);
setOpenLinkModal(false);
}; };
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@ -178,13 +162,20 @@ const LinkDataPipeline = () => {
</div> </div>
</section> </section>
<section className="flex flex-col gap-2"> <section className="flex flex-col gap-2">
{testNode.map((item) => ( {pipelineNode.map(
(item) =>
item.id && (
<DataPipelineItem <DataPipelineItem
key={item.name} key={item.id}
openLinkModalFunc={openLinkModalFunc} openLinkModalFunc={openLinkModalFunc}
{...item} id={item.id}
name={item.name}
avatar={item.avatar}
isDefault={item.isDefault}
linked={item.linked}
/> />
))} ),
)}
</section> </section>
<LinkDataPipelineModal <LinkDataPipelineModal
data={currentDataPipeline} data={currentDataPipeline}

View File

@ -1,19 +1,14 @@
import { DataFlowSelect } from '@/components/data-pipeline-select';
import Input from '@/components/originui/input';
import { Button } from '@/components/ui/button';
import { import {
Form, DataFlowSelect,
FormControl, IDataPipelineSelectNode,
FormField, } from '@/components/data-pipeline-select';
FormItem, import { Button } from '@/components/ui/button';
FormLabel, import { Form } from '@/components/ui/form';
FormMessage,
} 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 { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { pipelineFormSchema } from '../form-schema'; import { pipelineFormSchema } from '../form-schema';
@ -28,13 +23,14 @@ const LinkDataPipelineModal = ({
data: IDataPipelineNodeProps | undefined; data: IDataPipelineNodeProps | undefined;
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
onSubmit?: (data: any) => void; onSubmit?: (pipeline: IDataPipelineSelectNode | undefined) => void;
}) => { }) => {
const isEdit = !!data; const isEdit = !!data;
const [list, setList] = useState<IDataPipelineSelectNode[]>();
const form = useForm<z.infer<typeof pipelineFormSchema>>({ const form = useForm<z.infer<typeof pipelineFormSchema>>({
resolver: zodResolver(pipelineFormSchema), resolver: zodResolver(pipelineFormSchema),
defaultValues: { defaultValues: {
data_flow: [], pipeline_id: '',
set_default: false, set_default: false,
file_filter: '', file_filter: '',
}, },
@ -43,11 +39,12 @@ const LinkDataPipelineModal = ({
const { navigateToAgents } = useNavigatePage(); const { navigateToAgents } = useNavigatePage();
const handleFormSubmit = (values: any) => { const handleFormSubmit = (values: any) => {
console.log(values, data); console.log(values, data);
const param = { // const param = {
...data, // ...data,
...values, // ...values,
}; // };
onSubmit?.(param); const pipeline = list?.find((item) => item.id === values.pipeline_id);
onSubmit?.(pipeline);
}; };
return ( return (
<Modal <Modal
@ -67,10 +64,11 @@ const LinkDataPipelineModal = ({
{!isEdit && ( {!isEdit && (
<DataFlowSelect <DataFlowSelect
toDataPipeline={navigateToAgents} toDataPipeline={navigateToAgents}
formFieldName="data_flow" formFieldName="pipeline_id"
setDataList={setList}
/> />
)} )}
<FormField {/* <FormField
control={form.control} control={form.control}
name={'file_filter'} name={'file_filter'}
render={({ field }) => ( render={({ field }) => (
@ -135,7 +133,7 @@ const LinkDataPipelineModal = ({
</FormItem> </FormItem>
)} )}
/> />
)} )} */}
<div className="flex justify-end gap-1"> <div className="flex justify-end gap-1">
<Button <Button
type="button" type="button"

View File

@ -11,6 +11,9 @@ export const formSchema = z.object({
avatar: z.any().nullish(), avatar: z.any().nullish(),
permission: z.string().optional(), permission: z.string().optional(),
parser_id: z.string(), parser_id: z.string(),
pipeline_id: z.string().optional(),
pipeline_name: z.string().optional(),
pipeline_avatar: z.string().optional(),
embd_id: z.string(), embd_id: z.string(),
parser_config: z parser_config: z
.object({ .object({
@ -73,16 +76,16 @@ export const formSchema = z.object({
}); });
export const pipelineFormSchema = z.object({ export const pipelineFormSchema = z.object({
data_flow: z.array(z.string()).optional(), pipeline_id: z.string().optional(),
set_default: z.boolean().optional(), set_default: z.boolean().optional(),
file_filter: z.string().optional(), file_filter: z.string().optional(),
}); });
export const linkPiplineFormSchema = pipelineFormSchema.pick({ // export const linkPiplineFormSchema = pipelineFormSchema.pick({
data_flow: true, // pipeline_id: true,
file_filter: true, // file_filter: true,
}); // });
export const editPiplineFormSchema = pipelineFormSchema.pick({ // export const editPiplineFormSchema = pipelineFormSchema.pick({
set_default: true, // set_default: true,
file_filter: true, // file_filter: true,
}); // });

View File

@ -1,4 +1,5 @@
import { AvatarUpload } from '@/components/avatar-upload'; import { AvatarUpload } from '@/components/avatar-upload';
import PageRankFormField from '@/components/page-rank-form-field';
import { import {
FormControl, FormControl,
FormField, FormField,
@ -9,6 +10,7 @@ import {
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TagItems } from './components/tag-item';
import { EmbeddingModelItem } from './configuration/common-item'; import { EmbeddingModelItem } from './configuration/common-item';
import { PermissionFormField } from './permission-form-field'; import { PermissionFormField } from './permission-form-field';
@ -87,6 +89,9 @@ export function GeneralForm() {
/> />
<PermissionFormField></PermissionFormField> <PermissionFormField></PermissionFormField>
<EmbeddingModelItem></EmbeddingModelItem> <EmbeddingModelItem></EmbeddingModelItem>
<PageRankFormField></PageRankFormField>
<TagItems></TagItems>
</> </>
); );
} }

View File

@ -41,6 +41,16 @@ export const useFetchKnowledgeConfigurationOnMount = (
const parser_config = { const parser_config = {
...form.formState?.defaultValues?.parser_config, ...form.formState?.defaultValues?.parser_config,
...knowledgeDetails.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 = { const formValues = {
...pick({ ...knowledgeDetails, parser_config: parser_config }, [ ...pick({ ...knowledgeDetails, parser_config: parser_config }, [

View File

@ -1,3 +1,4 @@
import { IDataPipelineSelectNode } from '@/components/data-pipeline-select';
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -6,11 +7,15 @@ import { Form } from '@/components/ui/form';
import { DocumentParserType } from '@/constants/knowledge'; import { DocumentParserType } from '@/constants/knowledge';
import { PermissionRole } from '@/constants/permission'; import { PermissionRole } from '@/constants/permission';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod'; import { z } from 'zod';
import { TopTitle } from '../dataset-title'; import { TopTitle } from '../dataset-title';
import LinkDataPipeline from './components/link-data-pipeline'; import { IGenerateLogButtonProps } from '../dataset/generate-button/generate';
import LinkDataPipeline, {
IDataPipelineNodeProps,
} from './components/link-data-pipeline';
import { MainContainer } from './configuration-form-container'; import { MainContainer } from './configuration-form-container';
import { formSchema } from './form-schema'; import { formSchema } from './form-schema';
import { GeneralForm } from './general-form'; import { GeneralForm } from './general-form';
@ -51,24 +56,70 @@ export default function DatasetSettings() {
html4excel: false, html4excel: false,
topn_tags: 3, topn_tags: 3,
raptor: { raptor: {
use_raptor: false, use_raptor: true,
max_token: 256,
threshold: 0.1,
max_cluster: 64,
random_seed: 0,
prompt: t('knowledgeConfiguration.promptText'),
}, },
graphrag: { graphrag: {
use_graphrag: false, use_graphrag: true,
entity_types: initialEntityTypes, entity_types: initialEntityTypes,
method: MethodValue.Light, method: MethodValue.Light,
}, },
}, },
pipeline_id: '',
pagerank: 0, 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>) { 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 || '');
}
};
return ( return (
<section className="p-5 h-full flex flex-col"> <section className="p-5 h-full flex flex-col">
<TopTitle <TopTitle
@ -88,12 +139,17 @@ export default function DatasetSettings() {
<GraphRagItems <GraphRagItems
className="border-none p-0" className="border-none p-0"
showGenerateItem={true} data={graphRagGenerateData as IGenerateLogButtonProps}
></GraphRagItems> ></GraphRagItems>
<Divider /> <Divider />
<RaptorFormFields showGenerateItem={true}></RaptorFormFields> <RaptorFormFields
data={raptorGenerateData as IGenerateLogButtonProps}
></RaptorFormFields>
<Divider /> <Divider />
<LinkDataPipeline /> <LinkDataPipeline
data={pipelineData}
handleLinkOrEditSubmit={handleLinkOrEditSubmit}
/>
</MainContainer> </MainContainer>
</div> </div>
<div className="text-right items-center flex justify-end gap-3 w-[768px]"> <div className="text-right items-center flex justify-end gap-3 w-[768px]">

View File

@ -62,7 +62,7 @@ export function SavingButton() {
if (beValid) { if (beValid) {
form.handleSubmit(async (values) => { form.handleSubmit(async (values) => {
console.log('saveKnowledgeConfiguration: ', values); console.log('saveKnowledgeConfiguration: ', values);
delete values['avatar']; // delete values['avatar'];
await saveKnowledgeConfiguration({ await saveKnowledgeConfiguration({
kb_id, kb_id,
...values, ...values,

View File

@ -9,6 +9,7 @@ import {
import { Modal } from '@/components/ui/modal/modal'; import { Modal } from '@/components/ui/modal/modal';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { toFixed } from '@/utils/common-util'; import { toFixed } from '@/utils/common-util';
import { formatDate } from '@/utils/date';
import { UseMutateAsyncFunction } from '@tanstack/react-query'; import { UseMutateAsyncFunction } from '@tanstack/react-query';
import { t } from 'i18next'; import { t } from 'i18next';
import { lowerFirst } from 'lodash'; import { lowerFirst } from 'lodash';
@ -29,7 +30,13 @@ export enum GenerateType {
const MenuItem: React.FC<{ const MenuItem: React.FC<{
name: GenerateType; name: GenerateType;
data: ITraceInfo; data: ITraceInfo;
pauseGenerate: () => void; pauseGenerate: ({
task_id,
type,
}: {
task_id: string;
type: GenerateType;
}) => void;
runGenerate: UseMutateAsyncFunction< runGenerate: UseMutateAsyncFunction<
any, any,
Error, Error,
@ -38,13 +45,12 @@ const MenuItem: React.FC<{
}, },
unknown unknown
>; >;
}> = ({ name, runGenerate, data, pauseGenerate }) => { }> = ({ name: type, runGenerate, data, pauseGenerate }) => {
console.log(name, 'pppp', data);
const iconKeyMap = { const iconKeyMap = {
KnowledgeGraph: 'knowledgegraph', KnowledgeGraph: 'knowledgegraph',
Raptor: 'dataflow-01', Raptor: 'dataflow-01',
}; };
const type = useMemo(() => { const status = useMemo(() => {
if (!data) { if (!data) {
return generateStatus.start; return generateStatus.start;
} }
@ -60,9 +66,9 @@ const MenuItem: React.FC<{
}, [data]); }, [data]);
const percent = const percent =
type === generateStatus.failed status === generateStatus.failed
? 100 ? 100
: type === generateStatus.running : status === generateStatus.running
? data.progress * 100 ? data.progress * 100
: 0; : 0;
@ -72,9 +78,9 @@ const MenuItem: React.FC<{
'border cursor-pointer p-2 rounded-md focus:bg-transparent', 'border cursor-pointer p-2 rounded-md focus:bg-transparent',
{ {
'hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]': 'hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]':
type === generateStatus.start, status === generateStatus.start,
'hover:border-border hover:bg-[rgba(59,160,92,0)]': 'hover:border-border hover:bg-[rgba(59,160,92,0)]':
type !== generateStatus.start, status !== generateStatus.start,
}, },
)} )}
onSelect={(e) => { onSelect={(e) => {
@ -87,56 +93,65 @@ const MenuItem: React.FC<{
<div <div
className="flex items-start gap-2 flex-col w-full" className="flex items-start gap-2 flex-col w-full"
onClick={() => { onClick={() => {
if (type === generateStatus.start) { if (status === generateStatus.start) {
runGenerate({ type: name }); runGenerate({ type });
} }
}} }}
> >
<div className="flex justify-start text-text-primary items-center gap-2"> <div className="flex justify-start text-text-primary items-center gap-2">
<IconFontFill <IconFontFill
name={iconKeyMap[name]} name={iconKeyMap[type]}
className="text-accent-primary" className="text-accent-primary"
/> />
{t(`knowledgeDetails.${lowerFirst(name)}`)} {t(`knowledgeDetails.${lowerFirst(type)}`)}
</div> </div>
{type === generateStatus.start && ( {status === generateStatus.start && (
<div className="text-text-secondary text-sm"> <div className="text-text-secondary text-sm">
{t(`knowledgeDetails.generate${name}`)} {t(`knowledgeDetails.generate${type}`)}
</div> </div>
)} )}
{(type === generateStatus.running || {(status === generateStatus.running ||
type === generateStatus.failed) && ( status === generateStatus.failed) && (
<div className="flex justify-between items-center w-full px-2.5 py-1"> <div className="flex justify-between items-center w-full px-2.5 py-1">
<div <div
className={cn(' bg-border-button h-1 rounded-full', { className={cn(' bg-border-button h-1 rounded-full', {
'w-[calc(100%-100px)]': type === generateStatus.running, 'w-[calc(100%-100px)]': status === generateStatus.running,
'w-[calc(100%-50px)]': type === generateStatus.failed, 'w-[calc(100%-50px)]': status === generateStatus.failed,
})} })}
> >
<div <div
className={cn('h-1 rounded-full', { className={cn('h-1 rounded-full', {
'bg-state-error': type === generateStatus.failed, 'bg-state-error': status === generateStatus.failed,
'bg-accent-primary': type === generateStatus.running, 'bg-accent-primary': status === generateStatus.running,
})} })}
style={{ width: `${toFixed(percent)}%` }} style={{ width: `${toFixed(percent)}%` }}
></div> ></div>
</div> </div>
{type === generateStatus.running && ( {status === generateStatus.running && (
<span>{(toFixed(percent) as string) + '%'}</span> <span>{(toFixed(percent) as string) + '%'}</span>
)} )}
{status === generateStatus.failed && (
<span <span
className="text-state-error" className="text-state-error"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
pauseGenerate(); runGenerate({ type });
}} }}
> >
{type === generateStatus.failed ? (
<IconFontFill name="reparse" className="text-accent-primary" /> <IconFontFill name="reparse" className="text-accent-primary" />
) : (
<CirclePause />
)}
</span> </span>
)}
{status !== generateStatus.failed && (
<span
className="text-state-error"
onClick={(e) => {
e.stopPropagation();
pauseGenerate({ task_id: data.id, type });
}}
>
<CirclePause />
</span>
)}
</div> </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"> <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">
@ -202,7 +217,12 @@ const Generate: React.FC = () => {
export default Generate; export default Generate;
export type IGenerateLogProps = { export type IGenerateLogButtonProps = {
finish_at: string;
task_id: string;
};
export type IGenerateLogProps = IGenerateLogButtonProps & {
id?: string; id?: string;
status: 0 | 1; status: 0 | 1;
message?: string; message?: string;
@ -214,16 +234,7 @@ export type IGenerateLogProps = {
}; };
export const GenerateLogButton = (props: IGenerateLogProps) => { export const GenerateLogButton = (props: IGenerateLogProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const { task_id, message, finish_at, type, onDelete } = props;
id,
status,
message,
created_at,
updated_at,
type,
className,
onDelete,
} = props;
const handleDelete = () => { const handleDelete = () => {
Modal.show({ Modal.show({
visible: true, visible: true,
@ -278,11 +289,11 @@ export const GenerateLogButton = (props: IGenerateLogProps) => {
className={cn('flex bg-bg-card rounded-md py-1 px-3', props.className)} className={cn('flex bg-bg-card rounded-md py-1 px-3', props.className)}
> >
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
{status === 1 && ( {finish_at && (
<> <>
<div> <div>
{message || t('knowledgeDetails.generatedOn')} {message || t('knowledgeDetails.generatedOn')}
{created_at} {formatDate(finish_at)}
</div> </div>
<Trash2 <Trash2
size={14} size={14}
@ -295,7 +306,7 @@ export const GenerateLogButton = (props: IGenerateLogProps) => {
/> />
</> </>
)} )}
{status === 0 && <div>{t('knowledgeDetails.notGenerated')}</div>} {!finish_at && <div>{t('knowledgeDetails.notGenerated')}</div>}
</div> </div>
</div> </div>
); );

View File

@ -1,8 +1,9 @@
import message from '@/components/ui/message'; import message from '@/components/ui/message';
import agentService from '@/services/agent-service';
import kbService from '@/services/knowledge-service'; import kbService from '@/services/knowledge-service';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { t } from 'i18next'; import { t } from 'i18next';
import { useCallback, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams } from 'umi'; import { useParams } from 'umi';
import { GenerateType } from './generate'; import { GenerateType } from './generate';
export const generateStatus = { export const generateStatus = {
@ -14,6 +15,7 @@ export const generateStatus = {
enum DatasetKey { enum DatasetKey {
generate = 'generate', generate = 'generate',
pauseGenerate = 'pauseGenerate',
} }
export interface ITraceInfo { export interface ITraceInfo {
@ -126,9 +128,28 @@ export const useDatasetGenerate = () => {
return data; return data;
}, },
}); });
const pauseGenerate = useCallback(() => { // const pauseGenerate = useCallback(() => {
// TODO: pause generate // // TODO: pause generate
console.log('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 }; return { runGenerate: mutateAsync, pauseGenerate, data, loading };
}; };

View File

@ -23,7 +23,9 @@ const IconMap = {
[RunningStatus.UNSTART]: ( [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> <div className="w-0 h-0 border-l-[10px] border-l-accent-primary border-t-8 border-r-4 border-b-8 border-transparent"></div>
), ),
[RunningStatus.RUNNING]: <CircleX size={14} color="var(--state-error)" />, [RunningStatus.RUNNING]: (
<CircleX size={14} color="rgba(var(--state-error))" />
),
[RunningStatus.CANCEL]: ( [RunningStatus.CANCEL]: (
<IconFontFill name="reparse" className="text-accent-primary" /> <IconFontFill name="reparse" className="text-accent-primary" />
), ),