### 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
| @@ -152,6 +152,7 @@ export type IIterationNode = BaseNode; | |||
| export type IIterationStartNode = BaseNode; | |||
| export type IKeywordNode = BaseNode; | |||
| export type ICodeNode = BaseNode<ICodeForm>; | |||
| export type IAgentNode = BaseNode; | |||
| export type RAGFlowNodeType = | |||
| | IBeginNode | |||
| @@ -19,6 +19,7 @@ import { useShowDrawer } from '../hooks/use-show-drawer'; | |||
| import { ButtonEdge } from './edge'; | |||
| import styles from './index.less'; | |||
| import { RagNode } from './node'; | |||
| import { AgentNode } from './node/agent-node'; | |||
| import { BeginNode } from './node/begin-node'; | |||
| import { CategorizeNode } from './node/categorize-node'; | |||
| import { EmailNode } from './node/email-node'; | |||
| @@ -53,6 +54,7 @@ const nodeTypes: NodeTypes = { | |||
| emailNode: EmailNode, | |||
| group: IterationNode, | |||
| iterationStartNode: IterationStartNode, | |||
| agentNode: AgentNode, | |||
| }; | |||
| const edgeTypes = { | |||
| @@ -0,0 +1,48 @@ | |||
| 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); | |||
| @@ -59,6 +59,7 @@ import { | |||
| } from '@ant-design/icons'; | |||
| import upperFirst from 'lodash/upperFirst'; | |||
| import { | |||
| Box, | |||
| CirclePower, | |||
| CloudUpload, | |||
| CodeXml, | |||
| @@ -112,6 +113,7 @@ export enum Operator { | |||
| IterationStart = 'IterationItem', | |||
| Code = 'Code', | |||
| WaitingDialogue = 'WaitingDialogue', | |||
| Agent = 'Agent', | |||
| } | |||
| export const CommonOperatorList = Object.values(Operator).filter( | |||
| @@ -132,6 +134,7 @@ export const AgentOperatorList = [ | |||
| Operator.Iteration, | |||
| Operator.WaitingDialogue, | |||
| Operator.Note, | |||
| Operator.Agent, | |||
| ]; | |||
| export const operatorIconMap = { | |||
| @@ -173,6 +176,7 @@ export const operatorIconMap = { | |||
| [Operator.IterationStart]: CirclePower, | |||
| [Operator.Code]: CodeXml, | |||
| [Operator.WaitingDialogue]: MessageSquareMore, | |||
| [Operator.Agent]: Box, | |||
| }; | |||
| export const operatorMap: Record< | |||
| @@ -313,6 +317,7 @@ export const operatorMap: Record< | |||
| [Operator.IterationStart]: { backgroundColor: '#e6f7ff' }, | |||
| [Operator.Code]: { backgroundColor: '#4c5458' }, | |||
| [Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' }, | |||
| [Operator.Agent]: { backgroundColor: '#a5d65c' }, | |||
| }; | |||
| export const componentMenuList = [ | |||
| @@ -356,6 +361,9 @@ export const componentMenuList = [ | |||
| { | |||
| name: Operator.WaitingDialogue, | |||
| }, | |||
| { | |||
| name: Operator.Agent, | |||
| }, | |||
| { | |||
| name: Operator.Note, | |||
| }, | |||
| @@ -682,6 +690,14 @@ export const initialCodeValues = { | |||
| export const initialWaitingDialogueValues = {}; | |||
| export const initialAgentValues = { | |||
| ...initialLlmBaseValues, | |||
| sys_prompt: ``, | |||
| prompts: [], | |||
| message_history_window_size: 12, | |||
| tools: [], | |||
| }; | |||
| export const CategorizeAnchorPointPositions = [ | |||
| { top: 1, right: 34 }, | |||
| { top: 8, right: 18 }, | |||
| @@ -806,6 +822,7 @@ export const NodeMap = { | |||
| [Operator.IterationStart]: 'iterationStartNode', | |||
| [Operator.Code]: 'ragNode', | |||
| [Operator.WaitingDialogue]: 'ragNode', | |||
| [Operator.Agent]: 'agentNode', | |||
| }; | |||
| export const LanguageOptions = [ | |||
| @@ -3,6 +3,7 @@ import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { z } from 'zod'; | |||
| import { Operator } from '../constant'; | |||
| import AgentForm from '../form/agent-form'; | |||
| import AkShareForm from '../form/akshare-form'; | |||
| import AnswerForm from '../form/answer-form'; | |||
| import ArXivForm from '../form/arxiv-form'; | |||
| @@ -192,6 +193,11 @@ export function useFormConfigMap() { | |||
| ), | |||
| }), | |||
| }, | |||
| [Operator.Agent]: { | |||
| component: AgentForm, | |||
| defaultValues: {}, | |||
| schema: z.object({}), | |||
| }, | |||
| [Operator.Baidu]: { | |||
| component: BaiduForm, | |||
| defaultValues: { top_n: 10 }, | |||
| @@ -0,0 +1,74 @@ | |||
| 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; | |||
| @@ -1,6 +1,5 @@ | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { KnowledgeBaseFormField } from '@/components/knowledge-base-item'; | |||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | |||
| import { RerankFormFields } from '@/components/rerank'; | |||
| import { | |||
| initialKeywordsSimilarityWeightValue, | |||
| @@ -64,7 +63,7 @@ const RetrievalForm = ({ node }: INextOperatorForm) => { | |||
| > | |||
| <FormContainer> | |||
| <QueryVariable></QueryVariable> | |||
| <LargeModelFormField></LargeModelFormField> | |||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <SimilaritySliderFormField | |||
| @@ -73,7 +72,7 @@ const RetrievalForm = ({ node }: INextOperatorForm) => { | |||
| ></SimilaritySliderFormField> | |||
| <TopNFormField></TopNFormField> | |||
| <RerankFormFields></RerankFormFields> | |||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||
| <FormField | |||
| control={form.control} | |||
| name="empty_response" | |||
| @@ -33,6 +33,7 @@ import { | |||
| Operator, | |||
| RestrictedUpstreamMap, | |||
| SwitchElseTo, | |||
| initialAgentValues, | |||
| initialAkShareValues, | |||
| initialArXivValues, | |||
| initialBaiduFanyiValues, | |||
| @@ -65,6 +66,7 @@ import { | |||
| initialSwitchValues, | |||
| initialTemplateValues, | |||
| initialTuShareValues, | |||
| initialWaitingDialogueValues, | |||
| initialWenCaiValues, | |||
| initialWikipediaValues, | |||
| initialYahooFinanceValues, | |||
| @@ -143,6 +145,8 @@ export const useInitializeOperatorParams = () => { | |||
| [Operator.Iteration]: initialIterationValues, | |||
| [Operator.IterationStart]: initialIterationValues, | |||
| [Operator.Code]: initialCodeValues, | |||
| [Operator.WaitingDialogue]: initialWaitingDialogueValues, | |||
| [Operator.Agent]: initialAgentValues, | |||
| }; | |||
| }, [llmId]); | |||
| @@ -0,0 +1,20 @@ | |||
| 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; | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| import { PageHeader } from '@/components/page-header'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Button, ButtonLoading } from '@/components/ui/button'; | |||
| import { | |||
| DropdownMenu, | |||
| DropdownMenuContent, | |||
| @@ -19,6 +19,7 @@ import FlowCanvas from './canvas'; | |||
| import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | |||
| import { useFetchDataOnMount } from './hooks/use-fetch-data'; | |||
| import { useOpenDocument } from './hooks/use-open-document'; | |||
| import { useSaveGraph } from './hooks/use-save-graph'; | |||
| import { UploadAgentDialog } from './upload-agent-dialog'; | |||
| function AgentDropdownMenuItem({ | |||
| @@ -48,6 +49,7 @@ export default function Agent() { | |||
| onFileUploadOk, | |||
| hideFileUploadModal, | |||
| } = useHandleExportOrImportJsonFile(); | |||
| const { saveGraph, loading } = useSaveGraph(); | |||
| const { flowDetail } = useFetchDataOnMount(); | |||
| @@ -55,6 +57,16 @@ export default function Agent() { | |||
| <section> | |||
| <PageHeader back={navigateToAgentList} title={flowDetail.title}> | |||
| <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> | |||
| <DropdownMenuTrigger asChild> | |||
| <Button variant={'icon'} size={'icon'}> | |||
| @@ -83,17 +95,6 @@ export default function Agent() { | |||
| </AgentDropdownMenuItem> | |||
| </DropdownMenuContent> | |||
| </DropdownMenu> | |||
| <Button variant={'outline'} size={'sm'}> | |||
| Save | |||
| </Button> | |||
| <Button variant={'outline'} size={'sm'}> | |||
| Run app | |||
| </Button> | |||
| <Button variant={'tertiary'} size={'sm'}> | |||
| Publish | |||
| </Button> | |||
| </div> | |||
| </PageHeader> | |||
| <ReactFlowProvider> | |||