From e47186cc42f558ab63c9ceb1df3373c21b3e07a8 Mon Sep 17 00:00:00 2001 From: balibabu Date: Tue, 3 Jun 2025 17:42:30 +0800 Subject: [PATCH] Feat: Add AgentNode component #3221 (#8019) ### What problem does this PR solve? Feat: Add AgentNode component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/interfaces/database/flow.ts | 1 + web/src/pages/agent/canvas/index.tsx | 2 + .../pages/agent/canvas/node/agent-node.tsx | 48 ++++++++++++ web/src/pages/agent/constant.tsx | 17 +++++ .../agent/form-sheet/use-form-config-map.tsx | 6 ++ web/src/pages/agent/form/agent-form/index.tsx | 74 +++++++++++++++++++ .../pages/agent/form/retrieval-form/next.tsx | 5 +- web/src/pages/agent/hooks.tsx | 4 + web/src/pages/agent/hooks/use-form-values.ts | 20 +++++ web/src/pages/agent/index.tsx | 25 ++++--- 10 files changed, 187 insertions(+), 15 deletions(-) create mode 100644 web/src/pages/agent/canvas/node/agent-node.tsx create mode 100644 web/src/pages/agent/form/agent-form/index.tsx create mode 100644 web/src/pages/agent/hooks/use-form-values.ts diff --git a/web/src/interfaces/database/flow.ts b/web/src/interfaces/database/flow.ts index 8d324c373..dd07f5847 100644 --- a/web/src/interfaces/database/flow.ts +++ b/web/src/interfaces/database/flow.ts @@ -152,6 +152,7 @@ export type IIterationNode = BaseNode; export type IIterationStartNode = BaseNode; export type IKeywordNode = BaseNode; export type ICodeNode = BaseNode; +export type IAgentNode = BaseNode; export type RAGFlowNodeType = | IBeginNode diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index c7acb9df1..2e1f6c9a7 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -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 = { diff --git a/web/src/pages/agent/canvas/node/agent-node.tsx b/web/src/pages/agent/canvas/node/agent-node.tsx new file mode 100644 index 000000000..059da7642 --- /dev/null +++ b/web/src/pages/agent/canvas/node/agent-node.tsx @@ -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) { + const { theme } = useTheme(); + return ( +
+ + + +
+ ); +} + +export const AgentNode = memo(InnerAgentNode); diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index 8c1f30a27..9f0cfe3f9 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -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 = [ diff --git a/web/src/pages/agent/form-sheet/use-form-config-map.tsx b/web/src/pages/agent/form-sheet/use-form-config-map.tsx index 042f9f16d..2530d4ed9 100644 --- a/web/src/pages/agent/form-sheet/use-form-config-map.tsx +++ b/web/src/pages/agent/form-sheet/use-form-config-map.tsx @@ -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 }, diff --git a/web/src/pages/agent/form/agent-form/index.tsx b/web/src/pages/agent/form/agent-form/index.tsx new file mode 100644 index 000000000..44baf0aae --- /dev/null +++ b/web/src/pages/agent/form/agent-form/index.tsx @@ -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 ( +
+ { + e.preventDefault(); + }} + > + + + ( + + + + + + )} + /> + +
+ + ); +}; + +export default AgentForm; diff --git a/web/src/pages/agent/form/retrieval-form/next.tsx b/web/src/pages/agent/form/retrieval-form/next.tsx index 323685845..4ae499e33 100644 --- a/web/src/pages/agent/form/retrieval-form/next.tsx +++ b/web/src/pages/agent/form/retrieval-form/next.tsx @@ -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) => { > - + { > - + { [Operator.Iteration]: initialIterationValues, [Operator.IterationStart]: initialIterationValues, [Operator.Code]: initialCodeValues, + [Operator.WaitingDialogue]: initialWaitingDialogueValues, + [Operator.Agent]: initialAgentValues, }; }, [llmId]); diff --git a/web/src/pages/agent/hooks/use-form-values.ts b/web/src/pages/agent/hooks/use-form-values.ts new file mode 100644 index 000000000..edb2abbbd --- /dev/null +++ b/web/src/pages/agent/hooks/use-form-values.ts @@ -0,0 +1,20 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; +import { isEmpty } from 'lodash'; +import { useMemo } from 'react'; + +export function useFormValues( + defaultValues: Record, + node?: RAGFlowNodeType, +) { + const values = useMemo(() => { + const formData = node?.data?.form; + + if (isEmpty(formData)) { + return defaultValues; + } + + return formData; + }, [defaultValues, node?.data?.form]); + + return values; +} diff --git a/web/src/pages/agent/index.tsx b/web/src/pages/agent/index.tsx index a50b0f3f6..4da19640c 100644 --- a/web/src/pages/agent/index.tsx +++ b/web/src/pages/agent/index.tsx @@ -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() {
+ saveGraph()} + loading={loading} + > + Save + + + + - - -