### What problem does this PR solve? Feat: Add AgentNode component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| export type IIterationStartNode = BaseNode; | export type IIterationStartNode = BaseNode; | ||||
| export type IKeywordNode = BaseNode; | export type IKeywordNode = BaseNode; | ||||
| export type ICodeNode = BaseNode<ICodeForm>; | export type ICodeNode = BaseNode<ICodeForm>; | ||||
| export type IAgentNode = BaseNode; | |||||
| export type RAGFlowNodeType = | export type RAGFlowNodeType = | ||||
| | IBeginNode | | IBeginNode | 
| import { ButtonEdge } from './edge'; | import { ButtonEdge } from './edge'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import { RagNode } from './node'; | import { RagNode } from './node'; | ||||
| import { AgentNode } from './node/agent-node'; | |||||
| import { BeginNode } from './node/begin-node'; | import { BeginNode } from './node/begin-node'; | ||||
| import { CategorizeNode } from './node/categorize-node'; | import { CategorizeNode } from './node/categorize-node'; | ||||
| import { EmailNode } from './node/email-node'; | import { EmailNode } from './node/email-node'; | ||||
| emailNode: EmailNode, | emailNode: EmailNode, | ||||
| group: IterationNode, | group: IterationNode, | ||||
| iterationStartNode: IterationStartNode, | iterationStartNode: IterationStartNode, | ||||
| agentNode: AgentNode, | |||||
| }; | }; | ||||
| const edgeTypes = { | const edgeTypes = { | 
| import { useTheme } from '@/components/theme-provider'; | |||||
| import { IAgentNode } from '@/interfaces/database/flow'; | |||||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | |||||
| import classNames from 'classnames'; | |||||
| import { memo } from 'react'; | |||||
| import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; | |||||
| import styles from './index.less'; | |||||
| import NodeHeader from './node-header'; | |||||
| function InnerAgentNode({ | |||||
| id, | |||||
| data, | |||||
| isConnectable = true, | |||||
| selected, | |||||
| }: NodeProps<IAgentNode>) { | |||||
| const { theme } = useTheme(); | |||||
| return ( | |||||
| <section | |||||
| className={classNames( | |||||
| styles.ragNode, | |||||
| theme === 'dark' ? styles.dark : '', | |||||
| { | |||||
| [styles.selectedNode]: selected, | |||||
| }, | |||||
| )} | |||||
| > | |||||
| <Handle | |||||
| id="c" | |||||
| type="source" | |||||
| position={Position.Left} | |||||
| isConnectable={isConnectable} | |||||
| className={styles.handle} | |||||
| style={LeftHandleStyle} | |||||
| ></Handle> | |||||
| <Handle | |||||
| type="source" | |||||
| position={Position.Right} | |||||
| isConnectable={isConnectable} | |||||
| className={styles.handle} | |||||
| id="b" | |||||
| style={RightHandleStyle} | |||||
| ></Handle> | |||||
| <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader> | |||||
| </section> | |||||
| ); | |||||
| } | |||||
| export const AgentNode = memo(InnerAgentNode); | 
| } from '@ant-design/icons'; | } from '@ant-design/icons'; | ||||
| import upperFirst from 'lodash/upperFirst'; | import upperFirst from 'lodash/upperFirst'; | ||||
| import { | import { | ||||
| Box, | |||||
| CirclePower, | CirclePower, | ||||
| CloudUpload, | CloudUpload, | ||||
| CodeXml, | CodeXml, | ||||
| IterationStart = 'IterationItem', | IterationStart = 'IterationItem', | ||||
| Code = 'Code', | Code = 'Code', | ||||
| WaitingDialogue = 'WaitingDialogue', | WaitingDialogue = 'WaitingDialogue', | ||||
| Agent = 'Agent', | |||||
| } | } | ||||
| export const CommonOperatorList = Object.values(Operator).filter( | export const CommonOperatorList = Object.values(Operator).filter( | ||||
| Operator.Iteration, | Operator.Iteration, | ||||
| Operator.WaitingDialogue, | Operator.WaitingDialogue, | ||||
| Operator.Note, | Operator.Note, | ||||
| Operator.Agent, | |||||
| ]; | ]; | ||||
| export const operatorIconMap = { | export const operatorIconMap = { | ||||
| [Operator.IterationStart]: CirclePower, | [Operator.IterationStart]: CirclePower, | ||||
| [Operator.Code]: CodeXml, | [Operator.Code]: CodeXml, | ||||
| [Operator.WaitingDialogue]: MessageSquareMore, | [Operator.WaitingDialogue]: MessageSquareMore, | ||||
| [Operator.Agent]: Box, | |||||
| }; | }; | ||||
| export const operatorMap: Record< | export const operatorMap: Record< | ||||
| [Operator.IterationStart]: { backgroundColor: '#e6f7ff' }, | [Operator.IterationStart]: { backgroundColor: '#e6f7ff' }, | ||||
| [Operator.Code]: { backgroundColor: '#4c5458' }, | [Operator.Code]: { backgroundColor: '#4c5458' }, | ||||
| [Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' }, | [Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' }, | ||||
| [Operator.Agent]: { backgroundColor: '#a5d65c' }, | |||||
| }; | }; | ||||
| export const componentMenuList = [ | export const componentMenuList = [ | ||||
| { | { | ||||
| name: Operator.WaitingDialogue, | name: Operator.WaitingDialogue, | ||||
| }, | }, | ||||
| { | |||||
| name: Operator.Agent, | |||||
| }, | |||||
| { | { | ||||
| name: Operator.Note, | name: Operator.Note, | ||||
| }, | }, | ||||
| export const initialWaitingDialogueValues = {}; | export const initialWaitingDialogueValues = {}; | ||||
| export const initialAgentValues = { | |||||
| ...initialLlmBaseValues, | |||||
| sys_prompt: ``, | |||||
| prompts: [], | |||||
| message_history_window_size: 12, | |||||
| tools: [], | |||||
| }; | |||||
| export const CategorizeAnchorPointPositions = [ | export const CategorizeAnchorPointPositions = [ | ||||
| { top: 1, right: 34 }, | { top: 1, right: 34 }, | ||||
| { top: 8, right: 18 }, | { top: 8, right: 18 }, | ||||
| [Operator.IterationStart]: 'iterationStartNode', | [Operator.IterationStart]: 'iterationStartNode', | ||||
| [Operator.Code]: 'ragNode', | [Operator.Code]: 'ragNode', | ||||
| [Operator.WaitingDialogue]: 'ragNode', | [Operator.WaitingDialogue]: 'ragNode', | ||||
| [Operator.Agent]: 'agentNode', | |||||
| }; | }; | ||||
| export const LanguageOptions = [ | export const LanguageOptions = [ | 
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { Operator } from '../constant'; | import { Operator } from '../constant'; | ||||
| import AgentForm from '../form/agent-form'; | |||||
| import AkShareForm from '../form/akshare-form'; | import AkShareForm from '../form/akshare-form'; | ||||
| import AnswerForm from '../form/answer-form'; | import AnswerForm from '../form/answer-form'; | ||||
| import ArXivForm from '../form/arxiv-form'; | import ArXivForm from '../form/arxiv-form'; | ||||
| ), | ), | ||||
| }), | }), | ||||
| }, | }, | ||||
| [Operator.Agent]: { | |||||
| component: AgentForm, | |||||
| defaultValues: {}, | |||||
| schema: z.object({}), | |||||
| }, | |||||
| [Operator.Baidu]: { | [Operator.Baidu]: { | ||||
| component: BaiduForm, | component: BaiduForm, | ||||
| defaultValues: { top_n: 10 }, | defaultValues: { top_n: 10 }, | 
| import { FormContainer } from '@/components/form-container'; | |||||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | |||||
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | |||||
| import { PromptEditor } from '@/components/prompt-editor'; | |||||
| import { Form, FormControl, FormField, FormItem } from '@/components/ui/form'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import { z } from 'zod'; | |||||
| import { initialAgentValues } from '../../constant'; | |||||
| import { useFormValues } from '../../hooks/use-form-values'; | |||||
| import { INextOperatorForm } from '../../interface'; | |||||
| const FormSchema = z.object({ | |||||
| sys_prompt: z.string(), | |||||
| prompts: z | |||||
| .array( | |||||
| z.object({ | |||||
| role: z.string(), | |||||
| content: z.string(), | |||||
| }), | |||||
| ) | |||||
| .optional(), | |||||
| message_history_window_size: z.coerce.number(), | |||||
| ...LlmSettingSchema, | |||||
| tools: z | |||||
| .array( | |||||
| z.object({ | |||||
| component_name: z.string(), | |||||
| }), | |||||
| ) | |||||
| .optional(), | |||||
| }); | |||||
| const AgentForm = ({ node }: INextOperatorForm) => { | |||||
| const { t } = useTranslation(); | |||||
| const defaultValues = useFormValues(initialAgentValues, node); | |||||
| const form = useForm({ | |||||
| defaultValues: defaultValues, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| return ( | |||||
| <Form {...form}> | |||||
| <form | |||||
| className="space-y-6 p-4" | |||||
| onSubmit={(e) => { | |||||
| e.preventDefault(); | |||||
| }} | |||||
| > | |||||
| <FormContainer> | |||||
| <LargeModelFormField></LargeModelFormField> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`sys_prompt`} | |||||
| render={({ field }) => ( | |||||
| <FormItem className="flex-1"> | |||||
| <FormControl> | |||||
| <PromptEditor | |||||
| {...field} | |||||
| placeholder={t('flow.messagePlaceholder')} | |||||
| ></PromptEditor> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| </FormContainer> | |||||
| </form> | |||||
| </Form> | |||||
| ); | |||||
| }; | |||||
| export default AgentForm; | 
| import { FormContainer } from '@/components/form-container'; | import { FormContainer } from '@/components/form-container'; | ||||
| import { KnowledgeBaseFormField } from '@/components/knowledge-base-item'; | import { KnowledgeBaseFormField } from '@/components/knowledge-base-item'; | ||||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | |||||
| import { RerankFormFields } from '@/components/rerank'; | import { RerankFormFields } from '@/components/rerank'; | ||||
| import { | import { | ||||
| initialKeywordsSimilarityWeightValue, | initialKeywordsSimilarityWeightValue, | ||||
| > | > | ||||
| <FormContainer> | <FormContainer> | ||||
| <QueryVariable></QueryVariable> | <QueryVariable></QueryVariable> | ||||
| <LargeModelFormField></LargeModelFormField> | |||||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||||
| </FormContainer> | </FormContainer> | ||||
| <FormContainer> | <FormContainer> | ||||
| <SimilaritySliderFormField | <SimilaritySliderFormField | ||||
| ></SimilaritySliderFormField> | ></SimilaritySliderFormField> | ||||
| <TopNFormField></TopNFormField> | <TopNFormField></TopNFormField> | ||||
| <RerankFormFields></RerankFormFields> | <RerankFormFields></RerankFormFields> | ||||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="empty_response" | name="empty_response" | 
| Operator, | Operator, | ||||
| RestrictedUpstreamMap, | RestrictedUpstreamMap, | ||||
| SwitchElseTo, | SwitchElseTo, | ||||
| initialAgentValues, | |||||
| initialAkShareValues, | initialAkShareValues, | ||||
| initialArXivValues, | initialArXivValues, | ||||
| initialBaiduFanyiValues, | initialBaiduFanyiValues, | ||||
| initialSwitchValues, | initialSwitchValues, | ||||
| initialTemplateValues, | initialTemplateValues, | ||||
| initialTuShareValues, | initialTuShareValues, | ||||
| initialWaitingDialogueValues, | |||||
| initialWenCaiValues, | initialWenCaiValues, | ||||
| initialWikipediaValues, | initialWikipediaValues, | ||||
| initialYahooFinanceValues, | initialYahooFinanceValues, | ||||
| [Operator.Iteration]: initialIterationValues, | [Operator.Iteration]: initialIterationValues, | ||||
| [Operator.IterationStart]: initialIterationValues, | [Operator.IterationStart]: initialIterationValues, | ||||
| [Operator.Code]: initialCodeValues, | [Operator.Code]: initialCodeValues, | ||||
| [Operator.WaitingDialogue]: initialWaitingDialogueValues, | |||||
| [Operator.Agent]: initialAgentValues, | |||||
| }; | }; | ||||
| }, [llmId]); | }, [llmId]); | ||||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||||
| import { isEmpty } from 'lodash'; | |||||
| import { useMemo } from 'react'; | |||||
| export function useFormValues( | |||||
| defaultValues: Record<string, any>, | |||||
| node?: RAGFlowNodeType, | |||||
| ) { | |||||
| const values = useMemo(() => { | |||||
| const formData = node?.data?.form; | |||||
| if (isEmpty(formData)) { | |||||
| return defaultValues; | |||||
| } | |||||
| return formData; | |||||
| }, [defaultValues, node?.data?.form]); | |||||
| return values; | |||||
| } | 
| import { PageHeader } from '@/components/page-header'; | import { PageHeader } from '@/components/page-header'; | ||||
| import { Button } from '@/components/ui/button'; | |||||
| import { Button, ButtonLoading } from '@/components/ui/button'; | |||||
| import { | import { | ||||
| DropdownMenu, | DropdownMenu, | ||||
| DropdownMenuContent, | DropdownMenuContent, | ||||
| import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | ||||
| import { useFetchDataOnMount } from './hooks/use-fetch-data'; | import { useFetchDataOnMount } from './hooks/use-fetch-data'; | ||||
| import { useOpenDocument } from './hooks/use-open-document'; | import { useOpenDocument } from './hooks/use-open-document'; | ||||
| import { useSaveGraph } from './hooks/use-save-graph'; | |||||
| import { UploadAgentDialog } from './upload-agent-dialog'; | import { UploadAgentDialog } from './upload-agent-dialog'; | ||||
| function AgentDropdownMenuItem({ | function AgentDropdownMenuItem({ | ||||
| onFileUploadOk, | onFileUploadOk, | ||||
| hideFileUploadModal, | hideFileUploadModal, | ||||
| } = useHandleExportOrImportJsonFile(); | } = useHandleExportOrImportJsonFile(); | ||||
| const { saveGraph, loading } = useSaveGraph(); | |||||
| const { flowDetail } = useFetchDataOnMount(); | const { flowDetail } = useFetchDataOnMount(); | ||||
| <section> | <section> | ||||
| <PageHeader back={navigateToAgentList} title={flowDetail.title}> | <PageHeader back={navigateToAgentList} title={flowDetail.title}> | ||||
| <div className="flex items-center gap-2"> | <div className="flex items-center gap-2"> | ||||
| <ButtonLoading | |||||
| variant={'outline'} | |||||
| onClick={() => saveGraph()} | |||||
| loading={loading} | |||||
| > | |||||
| Save | |||||
| </ButtonLoading> | |||||
| <Button variant={'outline'}>Run app</Button> | |||||
| <Button variant={'outline'}>Publish</Button> | |||||
| <DropdownMenu> | <DropdownMenu> | ||||
| <DropdownMenuTrigger asChild> | <DropdownMenuTrigger asChild> | ||||
| <Button variant={'icon'} size={'icon'}> | <Button variant={'icon'} size={'icon'}> | ||||
| </AgentDropdownMenuItem> | </AgentDropdownMenuItem> | ||||
| </DropdownMenuContent> | </DropdownMenuContent> | ||||
| </DropdownMenu> | </DropdownMenu> | ||||
| <Button variant={'outline'} size={'sm'}> | |||||
| Save | |||||
| </Button> | |||||
| <Button variant={'outline'} size={'sm'}> | |||||
| Run app | |||||
| </Button> | |||||
| <Button variant={'tertiary'} size={'sm'}> | |||||
| Publish | |||||
| </Button> | |||||
| </div> | </div> | ||||
| </PageHeader> | </PageHeader> | ||||
| <ReactFlowProvider> | <ReactFlowProvider> |