Feat: Add splitter node component #9869 (#10114)

### What problem does this PR solve?

Feat: Add splitter node component #9869
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-09-16 17:53:48 +08:00
committed by GitHub
parent b79fef1ca8
commit 93cf0258c3
10 changed files with 201 additions and 19 deletions

View File

@ -0,0 +1,127 @@
import message from '@/components/ui/message';
import { IFlow } from '@/interfaces/database/agent';
import { Operator } from '@/pages/data-flow/constant';
import dataflowService from '@/services/dataflow-service';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useParams } from 'umi';
export const enum DataflowApiAction {
ListDataflow = 'listDataflow',
RemoveDataflow = 'removeDataflow',
FetchDataflow = 'fetchDataflow',
RunDataflow = 'runDataflow',
SetDataflow = 'setDataflow',
}
export const EmptyDsl = {
graph: {
nodes: [
{
id: Operator.Begin,
type: 'beginNode',
position: {
x: 50,
y: 200,
},
data: {
label: 'Begin',
name: Operator.Begin,
},
sourcePosition: 'left',
targetPosition: 'right',
},
],
edges: [],
},
components: {
begin: {
obj: {
component_name: 'Begin',
params: {},
},
downstream: [], // other edge target is downstream, edge source is current node id
upstream: [], // edge source is upstream, edge target is current node id
},
},
retrieval: [], // reference
history: [],
path: [],
};
export const useRemoveDataflow = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [DataflowApiAction.RemoveDataflow],
mutationFn: async (ids: string[]) => {
const { data } = await dataflowService.removeDataflow({
canvas_ids: ids,
});
if (data.code === 0) {
queryClient.invalidateQueries({
queryKey: [DataflowApiAction.ListDataflow],
});
message.success(t('message.deleted'));
}
return data.code;
},
});
return { data, loading, removeDataflow: mutateAsync };
};
export const useSetDataflow = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [DataflowApiAction.SetDataflow],
mutationFn: async (params: Partial<IFlow>) => {
const { data } = await dataflowService.setDataflow(params);
if (data.code === 0) {
queryClient.invalidateQueries({
queryKey: [DataflowApiAction.FetchDataflow],
});
message.success(t(`message.${params.id ? 'modified' : 'created'}`));
}
return data?.code;
},
});
return { data, loading, setDataflow: mutateAsync };
};
export const useFetchDataflow = () => {
const { id } = useParams();
const {
data,
isFetching: loading,
refetch,
} = useQuery<IFlow>({
queryKey: [DataflowApiAction.FetchDataflow, id],
gcTime: 0,
initialData: {} as IFlow,
enabled: !!id,
refetchOnWindowFocus: false,
queryFn: async () => {
const { data } = await dataflowService.fetchDataflow(id);
return data?.data ?? ({} as IFlow);
},
});
return { data, loading, refetch };
};

View File

@ -1,12 +1,13 @@
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { EmptyDsl, useSetAgent } from '@/hooks/use-agent-request'; import { useSetAgent } from '@/hooks/use-agent-request';
import { DSL } from '@/interfaces/database/agent'; import { EmptyDsl, useSetDataflow } from '@/hooks/use-dataflow-request';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { FlowType } from '../constant'; import { FlowType } from '../constant';
import { FormSchemaType } from '../create-agent-form'; import { FormSchemaType } from '../create-agent-form';
export function useCreateAgentOrPipeline() { export function useCreateAgentOrPipeline() {
const { loading, setAgent } = useSetAgent(); const { loading, setAgent } = useSetAgent();
const { loading: dataflowLoading, setDataflow } = useSetDataflow();
const { const {
visible: creatingVisible, visible: creatingVisible,
hideModal: hideCreatingModal, hideModal: hideCreatingModal,
@ -15,7 +16,7 @@ export function useCreateAgentOrPipeline() {
const createAgent = useCallback( const createAgent = useCallback(
async (name: string) => { async (name: string) => {
return setAgent({ title: name, dsl: EmptyDsl as DSL }); return setAgent({ title: name, dsl: EmptyDsl });
}, },
[setAgent], [setAgent],
); );
@ -27,13 +28,18 @@ export function useCreateAgentOrPipeline() {
if (ret.code === 0) { if (ret.code === 0) {
hideCreatingModal(); hideCreatingModal();
} }
} else {
setDataflow({
title: data.name,
dsl: EmptyDsl,
});
} }
}, },
[createAgent, hideCreatingModal], [createAgent, hideCreatingModal, setDataflow],
); );
return { return {
loading, loading: loading || dataflowLoading,
creatingVisible, creatingVisible,
hideCreatingModal, hideCreatingModal,
showCreatingModal, showCreatingModal,

View File

@ -54,6 +54,7 @@ import ParserNode from './node/parser-node';
import { RelevantNode } from './node/relevant-node'; import { RelevantNode } from './node/relevant-node';
import { RetrievalNode } from './node/retrieval-node'; import { RetrievalNode } from './node/retrieval-node';
import { RewriteNode } from './node/rewrite-node'; import { RewriteNode } from './node/rewrite-node';
import { SplitterNode } from './node/splitter-node';
import { SwitchNode } from './node/switch-node'; import { SwitchNode } from './node/switch-node';
import { TemplateNode } from './node/template-node'; import { TemplateNode } from './node/template-node';
import TokenizerNode from './node/tokenizer-node'; import TokenizerNode from './node/tokenizer-node';
@ -82,6 +83,7 @@ export const nodeTypes: NodeTypes = {
parserNode: ParserNode, parserNode: ParserNode,
chunkerNode: ChunkerNode, chunkerNode: ChunkerNode,
tokenizerNode: TokenizerNode, tokenizerNode: TokenizerNode,
splitterNode: SplitterNode,
}; };
const edgeTypes = { const edgeTypes = {

View File

@ -141,6 +141,8 @@ function AccordionOperators({
Operator.Parser, Operator.Parser,
Operator.Chunker, Operator.Chunker,
Operator.Tokenizer, Operator.Tokenizer,
Operator.Splitter,
Operator.HierarchicalMerger,
]} ]}
isCustomDropdown={isCustomDropdown} isCustomDropdown={isCustomDropdown}
mousePosition={mousePosition} mousePosition={mousePosition}

View File

@ -0,0 +1 @@
export { RagNode as HierarchicalMergerNode } from './index';

View File

@ -0,0 +1 @@
export { RagNode as SplitterNode } from './index';

View File

@ -66,6 +66,8 @@ export enum Operator {
Parser = 'Parser', Parser = 'Parser',
Chunker = 'Chunker', Chunker = 'Chunker',
Tokenizer = 'Tokenizer', Tokenizer = 'Tokenizer',
Splitter = 'Splitter',
HierarchicalMerger = 'HierarchicalMerger',
} }
export const SwitchLogicOperatorOptions = ['and', 'or']; export const SwitchLogicOperatorOptions = ['and', 'or'];
@ -74,20 +76,6 @@ export const CommonOperatorList = Object.values(Operator).filter(
(x) => x !== Operator.Note, (x) => x !== Operator.Note,
); );
export const AgentOperatorList = [
Operator.Retrieval,
Operator.Categorize,
Operator.Message,
Operator.RewriteQuestion,
Operator.KeywordExtract,
Operator.Switch,
Operator.Concentrator,
Operator.Iteration,
Operator.WaitingDialogue,
Operator.Note,
Operator.Agent,
];
export const SwitchOperatorOptions = [ export const SwitchOperatorOptions = [
{ value: '=', label: 'equal', icon: 'equal' }, { value: '=', label: 'equal', icon: 'equal' },
{ value: '≠', label: 'notEqual', icon: 'not-equals' }, { value: '≠', label: 'notEqual', icon: 'not-equals' },
@ -390,6 +378,10 @@ export const initialStringTransformValues = {
export const initialParserValues = { outputs: {}, parser: [] }; export const initialParserValues = { outputs: {}, parser: [] };
export const initialSplitterValues = {};
export const initialHierarchicalMergerValues = {};
export const CategorizeAnchorPointPositions = [ export const CategorizeAnchorPointPositions = [
{ top: 1, right: 34 }, { top: 1, right: 34 },
{ top: 8, right: 18 }, { top: 8, right: 18 },
@ -473,6 +465,8 @@ export const NodeMap = {
[Operator.Parser]: 'parserNode', [Operator.Parser]: 'parserNode',
[Operator.Chunker]: 'chunkerNode', [Operator.Chunker]: 'chunkerNode',
[Operator.Tokenizer]: 'tokenizerNode', [Operator.Tokenizer]: 'tokenizerNode',
[Operator.Splitter]: 'splitterNode',
[Operator.HierarchicalMerger]: 'hierarchicalMergerrNode',
}; };
export enum BeginQueryType { export enum BeginQueryType {

View File

@ -18,6 +18,7 @@ import {
initialCrawlerValues, initialCrawlerValues,
initialEmailValues, initialEmailValues,
initialExeSqlValues, initialExeSqlValues,
initialHierarchicalMergerValues,
initialInvokeValues, initialInvokeValues,
initialIterationStartValues, initialIterationStartValues,
initialIterationValues, initialIterationValues,
@ -28,6 +29,7 @@ import {
initialRelevantValues, initialRelevantValues,
initialRetrievalValues, initialRetrievalValues,
initialRewriteQuestionValues, initialRewriteQuestionValues,
initialSplitterValues,
initialStringTransformValues, initialStringTransformValues,
initialSwitchValues, initialSwitchValues,
initialTokenizerValues, initialTokenizerValues,
@ -82,6 +84,8 @@ export const useInitializeOperatorParams = () => {
[Operator.Parser]: initialParserValues, [Operator.Parser]: initialParserValues,
[Operator.Chunker]: initialChunkerValues, [Operator.Chunker]: initialChunkerValues,
[Operator.Tokenizer]: initialTokenizerValues, [Operator.Tokenizer]: initialTokenizerValues,
[Operator.Splitter]: initialSplitterValues,
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
}; };
}, [llmId]); }, [llmId]);

View File

@ -0,0 +1,37 @@
import api from '@/utils/api';
import { registerNextServer } from '@/utils/register-server';
const {
listDataflow,
removeDataflow,
fetchDataflow,
runDataflow,
setDataflow,
} = api;
const methods = {
listDataflow: {
url: listDataflow,
method: 'get',
},
removeDataflow: {
url: removeDataflow,
method: 'post',
},
fetchDataflow: {
url: fetchDataflow,
method: 'get',
},
runDataflow: {
url: runDataflow,
method: 'post',
},
setDataflow: {
url: setDataflow,
method: 'post',
},
} as const;
const dataflowService = registerNextServer<keyof typeof methods>(methods);
export default dataflowService;

View File

@ -190,4 +190,12 @@ export default {
mindmapShare: `${ExternalApi}${api_host}/searchbots/mindmap`, mindmapShare: `${ExternalApi}${api_host}/searchbots/mindmap`,
getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`, getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`,
retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`, retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`,
// data pipeline
fetchDataflow: (id: string) => `${api_host}/dataflow/get/${id}`,
setDataflow: `${api_host}/dataflow/set`,
removeDataflow: `${api_host}/dataflow/rm`,
listDataflow: `${api_host}/dataflow/list`,
runDataflow: `${api_host}/dataflow/run`,
}; };