### 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
| script?: string; | 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> = { | export type BaseNodeData<TForm extends any> = { | ||||
| label: string; // operator type | label: string; // operator type | ||||
| name: string; // operator name | name: string; // operator name | ||||
| export type IKeywordNode = BaseNode; | export type IKeywordNode = BaseNode; | ||||
| export type ICodeNode = BaseNode<ICodeForm>; | export type ICodeNode = BaseNode<ICodeForm>; | ||||
| export type IAgentNode = BaseNode; | export type IAgentNode = BaseNode; | ||||
| export type IToolNode = BaseNode; | |||||
| export type IToolNode = BaseNode<IAgentForm>; | |||||
| export type RAGFlowNodeType = | export type RAGFlowNodeType = | ||||
| | IBeginNode | | IBeginNode |
| import { IToolNode } from '@/interfaces/database/agent'; | |||||
| import { IAgentForm, IToolNode } from '@/interfaces/database/agent'; | |||||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | import { Handle, NodeProps, Position } from '@xyflow/react'; | ||||
| import { get } from 'lodash'; | |||||
| import { memo } from 'react'; | import { memo } from 'react'; | ||||
| import { NodeHandleId } from '../../constant'; | import { NodeHandleId } from '../../constant'; | ||||
| import useGraphStore from '../../store'; | |||||
| import { NodeWrapper } from './node-wrapper'; | import { NodeWrapper } from './node-wrapper'; | ||||
| function InnerToolNode({ | function InnerToolNode({ | ||||
| isConnectable = true, | isConnectable = true, | ||||
| selected, | selected, | ||||
| }: NodeProps<IToolNode>) { | }: 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 ( | return ( | ||||
| <NodeWrapper> | <NodeWrapper> | ||||
| <Handle | <Handle | ||||
| position={Position.Top} | position={Position.Top} | ||||
| isConnectable={isConnectable} | isConnectable={isConnectable} | ||||
| ></Handle> | ></Handle> | ||||
| <ul className="space-y-1"> | |||||
| {tools.map((x) => ( | |||||
| <li key={x.component_name}>{x.component_name}</li> | |||||
| ))} | |||||
| </ul> | |||||
| </NodeWrapper> | </NodeWrapper> | ||||
| ); | ); | ||||
| } | } |
| import RewriteQuestionForm from '../form/rewrite-question-form'; | import RewriteQuestionForm from '../form/rewrite-question-form'; | ||||
| import SwitchForm from '../form/switch-form'; | import SwitchForm from '../form/switch-form'; | ||||
| import TemplateForm from '../form/template-form'; | import TemplateForm from '../form/template-form'; | ||||
| import ToolForm from '../form/tool-form'; | |||||
| import TuShareForm from '../form/tushare-form'; | import TuShareForm from '../form/tushare-form'; | ||||
| import WenCaiForm from '../form/wencai-form'; | import WenCaiForm from '../form/wencai-form'; | ||||
| import WikipediaForm from '../form/wikipedia-form'; | import WikipediaForm from '../form/wikipedia-form'; | ||||
| defaultValues: {}, | defaultValues: {}, | ||||
| schema: z.object({}), | schema: z.object({}), | ||||
| }, | }, | ||||
| [Operator.Tool]: { | |||||
| component: ToolForm, | |||||
| defaultValues: {}, | |||||
| schema: z.object({}), | |||||
| }, | |||||
| }; | }; | ||||
| return FormConfigMap; | return FormConfigMap; |
| PopoverContent, | PopoverContent, | ||||
| PopoverTrigger, | PopoverTrigger, | ||||
| } from '@/components/ui/popover'; | } from '@/components/ui/popover'; | ||||
| import { IAgentForm } from '@/interfaces/database/agent'; | |||||
| import { Operator } from '@/pages/agent/constant'; | import { Operator } from '@/pages/agent/constant'; | ||||
| import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context'; | import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context'; | ||||
| import { Position } from '@xyflow/react'; | 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 { ToolCommand } from './tool-command'; | ||||
| import { useUpdateAgentNodeTools } from './use-update-tools'; | |||||
| export function ToolPopover({ children }: PropsWithChildren) { | export function ToolPopover({ children }: PropsWithChildren) { | ||||
| const { addCanvasNode } = useContext(AgentInstanceContext); | const { addCanvasNode } = useContext(AgentInstanceContext); | ||||
| const node = useContext(AgentFormContext); | 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( | const handleChange = useCallback( | ||||
| (value: string[]) => { | (value: string[]) => { | ||||
| if (Array.isArray(value) && value.length > 0) { | |||||
| if (Array.isArray(value) && value.length > 0 && node?.id) { | |||||
| updateNodeTools(value); | |||||
| addCanvasNode(Operator.Tool, { | addCanvasNode(Operator.Tool, { | ||||
| position: Position.Bottom, | position: Position.Bottom, | ||||
| nodeId: node?.id, | nodeId: node?.id, | ||||
| })(); | })(); | ||||
| } | } | ||||
| }, | }, | ||||
| [addCanvasNode, node?.id], | |||||
| [addCanvasNode, node?.id, updateNodeTools], | |||||
| ); | ); | ||||
| return ( | return ( | ||||
| <Popover> | <Popover> | ||||
| <PopoverTrigger asChild>{children}</PopoverTrigger> | <PopoverTrigger asChild>{children}</PopoverTrigger> | ||||
| <PopoverContent className="w-80 p-0"> | <PopoverContent className="w-80 p-0"> | ||||
| <ToolCommand onChange={handleChange}></ToolCommand> | |||||
| <ToolCommand onChange={handleChange} value={toolNames}></ToolCommand> | |||||
| </PopoverContent> | </PopoverContent> | ||||
| </Popover> | </Popover> | ||||
| ); | ); |
| }, | }, | ||||
| ]; | ]; | ||||
| const Options = Menus.reduce<string[]>((pre, cur) => { | |||||
| pre.push(...cur.list); | |||||
| return pre; | |||||
| }, []); | |||||
| type ToolCommandProps = { | type ToolCommandProps = { | ||||
| value?: string[]; | value?: string[]; | ||||
| onChange?(values: string[]): void; | onChange?(values: string[]): void; | ||||
| export function ToolCommand({ value, onChange }: ToolCommandProps) { | export function ToolCommand({ value, onChange }: ToolCommandProps) { | ||||
| const [currentValue, setCurrentValue] = useState<string[]>([]); | const [currentValue, setCurrentValue] = useState<string[]>([]); | ||||
| console.log('🚀 ~ ToolCommand ~ currentValue:', currentValue); | |||||
| const toggleOption = useCallback( | const toggleOption = useCallback( | ||||
| (option: string) => { | (option: string) => { |
| 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 }; | |||||
| } |