### What problem does this PR solve? Feat: Save the agent tool data to the node #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| @@ -139,6 +139,29 @@ export interface ICodeForm { | |||
| script?: string; | |||
| } | |||
| export interface IAgentForm { | |||
| sys_prompt: string; | |||
| prompts: Array<{ | |||
| role: string; | |||
| content: string; | |||
| }>; | |||
| max_retries: number; | |||
| delay_after_error: number; | |||
| visual_files_var: string; | |||
| max_rounds: number; | |||
| exception_method: Nullable<'comment' | 'go'>; | |||
| exception_comment: any; | |||
| exception_goto: any; | |||
| tools: Array<{ | |||
| component_name: string; | |||
| params: Record<string, any>; | |||
| }>; | |||
| outputs: { | |||
| structured_output: Record<string, Record<string, any>>; | |||
| content: Record<string, any>; | |||
| }; | |||
| } | |||
| export type BaseNodeData<TForm extends any> = { | |||
| label: string; // operator type | |||
| name: string; // operator name | |||
| @@ -167,7 +190,7 @@ export type IIterationStartNode = BaseNode; | |||
| export type IKeywordNode = BaseNode; | |||
| export type ICodeNode = BaseNode<ICodeForm>; | |||
| export type IAgentNode = BaseNode; | |||
| export type IToolNode = BaseNode; | |||
| export type IToolNode = BaseNode<IAgentForm>; | |||
| export type RAGFlowNodeType = | |||
| | IBeginNode | |||
| @@ -1,7 +1,9 @@ | |||
| import { IToolNode } from '@/interfaces/database/agent'; | |||
| import { IAgentForm, IToolNode } from '@/interfaces/database/agent'; | |||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | |||
| import { get } from 'lodash'; | |||
| import { memo } from 'react'; | |||
| import { NodeHandleId } from '../../constant'; | |||
| import useGraphStore from '../../store'; | |||
| import { NodeWrapper } from './node-wrapper'; | |||
| function InnerToolNode({ | |||
| @@ -10,6 +12,16 @@ function InnerToolNode({ | |||
| isConnectable = true, | |||
| selected, | |||
| }: NodeProps<IToolNode>) { | |||
| const { edges, getNode } = useGraphStore((state) => state); | |||
| const upstreamAgentNodeId = edges.find((x) => x.target === id)?.source; | |||
| const upstreamAgentNode = getNode(upstreamAgentNodeId); | |||
| const tools: IAgentForm['tools'] = get( | |||
| upstreamAgentNode, | |||
| 'data.form.tools', | |||
| [], | |||
| ); | |||
| return ( | |||
| <NodeWrapper> | |||
| <Handle | |||
| @@ -18,6 +30,11 @@ function InnerToolNode({ | |||
| position={Position.Top} | |||
| isConnectable={isConnectable} | |||
| ></Handle> | |||
| <ul className="space-y-1"> | |||
| {tools.map((x) => ( | |||
| <li key={x.component_name}>{x.component_name}</li> | |||
| ))} | |||
| </ul> | |||
| </NodeWrapper> | |||
| ); | |||
| } | |||
| @@ -34,6 +34,7 @@ import RetrievalForm from '../form/retrieval-form/next'; | |||
| import RewriteQuestionForm from '../form/rewrite-question-form'; | |||
| import SwitchForm from '../form/switch-form'; | |||
| import TemplateForm from '../form/template-form'; | |||
| import ToolForm from '../form/tool-form'; | |||
| import TuShareForm from '../form/tushare-form'; | |||
| import WenCaiForm from '../form/wencai-form'; | |||
| import WikipediaForm from '../form/wikipedia-form'; | |||
| @@ -369,6 +370,11 @@ export function useFormConfigMap() { | |||
| defaultValues: {}, | |||
| schema: z.object({}), | |||
| }, | |||
| [Operator.Tool]: { | |||
| component: ToolForm, | |||
| defaultValues: {}, | |||
| schema: z.object({}), | |||
| }, | |||
| }; | |||
| return FormConfigMap; | |||
| @@ -3,33 +3,43 @@ import { | |||
| PopoverContent, | |||
| PopoverTrigger, | |||
| } from '@/components/ui/popover'; | |||
| import { IAgentForm } from '@/interfaces/database/agent'; | |||
| import { Operator } from '@/pages/agent/constant'; | |||
| import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context'; | |||
| import { Position } from '@xyflow/react'; | |||
| import { PropsWithChildren, useCallback, useContext } from 'react'; | |||
| import { get } from 'lodash'; | |||
| import { PropsWithChildren, useCallback, useContext, useMemo } from 'react'; | |||
| import { ToolCommand } from './tool-command'; | |||
| import { useUpdateAgentNodeTools } from './use-update-tools'; | |||
| export function ToolPopover({ children }: PropsWithChildren) { | |||
| const { addCanvasNode } = useContext(AgentInstanceContext); | |||
| const node = useContext(AgentFormContext); | |||
| const { updateNodeTools } = useUpdateAgentNodeTools(); | |||
| const toolNames = useMemo(() => { | |||
| const tools: IAgentForm['tools'] = get(node, 'data.form.tools', []); | |||
| return tools.map((x) => x.component_name); | |||
| }, [node]); | |||
| const handleChange = useCallback( | |||
| (value: string[]) => { | |||
| if (Array.isArray(value) && value.length > 0) { | |||
| if (Array.isArray(value) && value.length > 0 && node?.id) { | |||
| updateNodeTools(value); | |||
| addCanvasNode(Operator.Tool, { | |||
| position: Position.Bottom, | |||
| nodeId: node?.id, | |||
| })(); | |||
| } | |||
| }, | |||
| [addCanvasNode, node?.id], | |||
| [addCanvasNode, node?.id, updateNodeTools], | |||
| ); | |||
| return ( | |||
| <Popover> | |||
| <PopoverTrigger asChild>{children}</PopoverTrigger> | |||
| <PopoverContent className="w-80 p-0"> | |||
| <ToolCommand onChange={handleChange}></ToolCommand> | |||
| <ToolCommand onChange={handleChange} value={toolNames}></ToolCommand> | |||
| </PopoverContent> | |||
| </Popover> | |||
| ); | |||
| @@ -45,11 +45,6 @@ const Menus = [ | |||
| }, | |||
| ]; | |||
| const Options = Menus.reduce<string[]>((pre, cur) => { | |||
| pre.push(...cur.list); | |||
| return pre; | |||
| }, []); | |||
| type ToolCommandProps = { | |||
| value?: string[]; | |||
| onChange?(values: string[]): void; | |||
| @@ -57,7 +52,6 @@ type ToolCommandProps = { | |||
| export function ToolCommand({ value, onChange }: ToolCommandProps) { | |||
| const [currentValue, setCurrentValue] = useState<string[]>([]); | |||
| console.log('🚀 ~ ToolCommand ~ currentValue:', currentValue); | |||
| const toggleOption = useCallback( | |||
| (option: string) => { | |||
| @@ -0,0 +1,29 @@ | |||
| import { IAgentForm } from '@/interfaces/database/agent'; | |||
| import { AgentFormContext } from '@/pages/agent/context'; | |||
| import useGraphStore from '@/pages/agent/store'; | |||
| import { get } from 'lodash'; | |||
| import { useCallback, useContext } from 'react'; | |||
| export function useUpdateAgentNodeTools() { | |||
| const { updateNodeForm } = useGraphStore((state) => state); | |||
| const node = useContext(AgentFormContext); | |||
| const updateNodeTools = useCallback( | |||
| (value: string[]) => { | |||
| if (node?.id) { | |||
| const tools: IAgentForm['tools'] = get(node, 'data.form.tools'); | |||
| const nextValue = value.reduce<IAgentForm['tools']>((pre, cur) => { | |||
| const tool = tools.find((x) => x.component_name === cur); | |||
| pre.push(tool ? tool : { component_name: cur, params: {} }); | |||
| return pre; | |||
| }, []); | |||
| updateNodeForm(node?.id, nextValue, ['tools']); | |||
| } | |||
| }, | |||
| [node, updateNodeForm], | |||
| ); | |||
| return { updateNodeTools }; | |||
| } | |||