### What problem does this PR solve? Feat: Click the edit tool button of the agent form to open the corresponding form #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| runVisible, | runVisible, | ||||
| hideRunOrChatDrawer, | hideRunOrChatDrawer, | ||||
| showChatModal, | showChatModal, | ||||
| showFormDrawer, | |||||
| } = useShowDrawer({ | } = useShowDrawer({ | ||||
| drawerVisible, | drawerVisible, | ||||
| hideDrawer, | hideDrawer, | ||||
| </marker> | </marker> | ||||
| </defs> | </defs> | ||||
| </svg> | </svg> | ||||
| <AgentInstanceContext.Provider value={{ addCanvasNode }}> | |||||
| <AgentInstanceContext.Provider value={{ addCanvasNode, showFormDrawer }}> | |||||
| <ReactFlow | <ReactFlow | ||||
| connectionMode={ConnectionMode.Loose} | connectionMode={ConnectionMode.Loose} | ||||
| nodes={nodes} | nodes={nodes} | ||||
| ref={ref} | ref={ref} | ||||
| ></NotebookPen> | ></NotebookPen> | ||||
| {formDrawerVisible && ( | {formDrawerVisible && ( | ||||
| <AgentInstanceContext.Provider value={{ addCanvasNode }}> | |||||
| <AgentInstanceContext.Provider | |||||
| value={{ addCanvasNode, showFormDrawer }} | |||||
| > | |||||
| <FormSheet | <FormSheet | ||||
| node={clickedNode} | node={clickedNode} | ||||
| visible={formDrawerVisible} | visible={formDrawerVisible} |
| import { createContext } from 'react'; | import { createContext } from 'react'; | ||||
| import { useAddNode } from './hooks/use-add-node'; | import { useAddNode } from './hooks/use-add-node'; | ||||
| import { useCacheChatLog } from './hooks/use-cache-chat-log'; | import { useCacheChatLog } from './hooks/use-cache-chat-log'; | ||||
| import { useShowLogSheet } from './hooks/use-show-drawer'; | |||||
| import { useShowFormDrawer, useShowLogSheet } from './hooks/use-show-drawer'; | |||||
| export const AgentFormContext = createContext<RAGFlowNodeType | undefined>( | export const AgentFormContext = createContext<RAGFlowNodeType | undefined>( | ||||
| undefined, | undefined, | ||||
| type AgentInstanceContextType = Pick< | type AgentInstanceContextType = Pick< | ||||
| ReturnType<typeof useAddNode>, | ReturnType<typeof useAddNode>, | ||||
| 'addCanvasNode' | 'addCanvasNode' | ||||
| >; | |||||
| > & | |||||
| Pick<ReturnType<typeof useShowFormDrawer>, 'showFormDrawer'>; | |||||
| export const AgentInstanceContext = createContext<AgentInstanceContextType>( | export const AgentInstanceContext = createContext<AgentInstanceContextType>( | ||||
| {} as AgentInstanceContextType, | {} as AgentInstanceContextType, |
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { Position } from '@xyflow/react'; | import { Position } from '@xyflow/react'; | ||||
| import { PencilLine, X } from 'lucide-react'; | import { PencilLine, X } from 'lucide-react'; | ||||
| import { PropsWithChildren, useCallback, useContext, useMemo } from 'react'; | |||||
| import { | |||||
| MouseEventHandler, | |||||
| PropsWithChildren, | |||||
| useCallback, | |||||
| useContext, | |||||
| useMemo, | |||||
| } from 'react'; | |||||
| import { Operator } from '../../constant'; | import { Operator } from '../../constant'; | ||||
| import { AgentInstanceContext } from '../../context'; | import { AgentInstanceContext } from '../../context'; | ||||
| import { useFindMcpById } from '../../hooks/use-find-mcp-by-id'; | import { useFindMcpById } from '../../hooks/use-find-mcp-by-id'; | ||||
| type ActionButtonProps<T> = { | type ActionButtonProps<T> = { | ||||
| record: T; | record: T; | ||||
| deleteRecord(record: T): void; | deleteRecord(record: T): void; | ||||
| edit(record: T): void; | |||||
| edit: MouseEventHandler<HTMLOrSVGElement>; | |||||
| }; | }; | ||||
| function ActionButton<T>({ edit, deleteRecord, record }: ActionButtonProps<T>) { | |||||
| function ActionButton<T>({ deleteRecord, record, edit }: ActionButtonProps<T>) { | |||||
| const handleDelete = useCallback(() => { | const handleDelete = useCallback(() => { | ||||
| deleteRecord(record); | deleteRecord(record); | ||||
| }, [deleteRecord, record]); | }, [deleteRecord, record]); | ||||
| const handleEdit = useCallback(() => { | |||||
| edit(record); | |||||
| }, [edit, record]); | |||||
| return ( | return ( | ||||
| <div className="flex items-center gap-2 text-text-sub-title"> | <div className="flex items-center gap-2 text-text-sub-title"> | ||||
| <PencilLine | <PencilLine | ||||
| className="size-4 cursor-pointer" | className="size-4 cursor-pointer" | ||||
| data-tool={record} | data-tool={record} | ||||
| onClick={handleEdit} | |||||
| onClick={edit} | |||||
| /> | /> | ||||
| <X className="size-4 cursor-pointer" onClick={handleDelete} /> | <X className="size-4 cursor-pointer" onClick={handleDelete} /> | ||||
| </div> | </div> | ||||
| const { mcpIds } = useGetAgentMCPIds(); | const { mcpIds } = useGetAgentMCPIds(); | ||||
| const { findMcpById } = useFindMcpById(); | const { findMcpById } = useFindMcpById(); | ||||
| const { deleteNodeMCP } = useDeleteAgentNodeMCP(); | const { deleteNodeMCP } = useDeleteAgentNodeMCP(); | ||||
| const { showFormDrawer } = useContext(AgentInstanceContext); | |||||
| const { clickedNodeId, findAgentToolNodeById, selectNodeIds } = useGraphStore( | |||||
| (state) => state, | |||||
| ); | |||||
| const handleEdit: MouseEventHandler<SVGSVGElement> = useCallback( | |||||
| (e) => { | |||||
| const toolNodeId = findAgentToolNodeById(clickedNodeId); | |||||
| if (toolNodeId) { | |||||
| selectNodeIds([toolNodeId]); | |||||
| showFormDrawer(e, toolNodeId); | |||||
| } | |||||
| }, | |||||
| [clickedNodeId, findAgentToolNodeById, selectNodeIds, showFormDrawer], | |||||
| ); | |||||
| return ( | return ( | ||||
| <section className="space-y-2.5"> | <section className="space-y-2.5"> | ||||
| {x} | {x} | ||||
| <ActionButton | <ActionButton | ||||
| record={x} | record={x} | ||||
| edit={() => {}} | |||||
| deleteRecord={deleteNodeTool(x)} | deleteRecord={deleteNodeTool(x)} | ||||
| edit={handleEdit} | |||||
| ></ActionButton> | ></ActionButton> | ||||
| </ToolCard> | </ToolCard> | ||||
| ))} | ))} | ||||
| {findMcpById(id)?.name} | {findMcpById(id)?.name} | ||||
| <ActionButton | <ActionButton | ||||
| record={id} | record={id} | ||||
| edit={() => {}} | |||||
| deleteRecord={deleteNodeMCP(id)} | deleteRecord={deleteNodeMCP(id)} | ||||
| edit={handleEdit} | |||||
| ></ActionButton> | ></ActionButton> | ||||
| </ToolCard> | </ToolCard> | ||||
| ))} | ))} | ||||
| export function Agents({ node }: INextOperatorForm) { | export function Agents({ node }: INextOperatorForm) { | ||||
| const { addCanvasNode } = useContext(AgentInstanceContext); | const { addCanvasNode } = useContext(AgentInstanceContext); | ||||
| const { deleteAgentDownstreamNodesById, edges, getNode } = useGraphStore( | |||||
| (state) => state, | |||||
| const { deleteAgentDownstreamNodesById, edges, getNode, selectNodeIds } = | |||||
| useGraphStore((state) => state); | |||||
| const { showFormDrawer } = useContext(AgentInstanceContext); | |||||
| const handleEdit = useCallback( | |||||
| (nodeId: string): MouseEventHandler<SVGSVGElement> => | |||||
| (e) => { | |||||
| selectNodeIds([nodeId]); | |||||
| showFormDrawer(e, nodeId); | |||||
| }, | |||||
| [selectNodeIds, showFormDrawer], | |||||
| ); | ); | ||||
| const subBottomAgentNodeIds = useMemo(() => { | const subBottomAgentNodeIds = useMemo(() => { | ||||
| {currentNode?.data.name} | {currentNode?.data.name} | ||||
| <ActionButton | <ActionButton | ||||
| record={id} | record={id} | ||||
| edit={() => {}} | |||||
| deleteRecord={deleteAgentDownstreamNodesById} | deleteRecord={deleteAgentDownstreamNodesById} | ||||
| edit={handleEdit(id)} | |||||
| ></ActionButton> | ></ActionButton> | ||||
| </ToolCard> | </ToolCard> | ||||
| ); | ); |
| } else if (tools) { | } else if (tools) { | ||||
| pre.push({ | pre.push({ | ||||
| mcp_id: cur, | mcp_id: cur, | ||||
| tools, | |||||
| tools: {}, | |||||
| }); | }); | ||||
| } | } | ||||
| return pre; | return pre; |
| import { useSetModalState } from '@/hooks/common-hooks'; | import { useSetModalState } from '@/hooks/common-hooks'; | ||||
| import { Node, NodeMouseHandler } from '@xyflow/react'; | |||||
| import { NodeMouseHandler } from '@xyflow/react'; | |||||
| import get from 'lodash/get'; | import get from 'lodash/get'; | ||||
| import { useCallback, useEffect } from 'react'; | |||||
| import React, { useCallback, useEffect } from 'react'; | |||||
| import { Operator } from '../constant'; | import { Operator } from '../constant'; | ||||
| import useGraphStore from '../store'; | import useGraphStore from '../store'; | ||||
| import { useCacheChatLog } from './use-cache-chat-log'; | import { useCacheChatLog } from './use-cache-chat-log'; | ||||
| showModal: showFormDrawer, | showModal: showFormDrawer, | ||||
| } = useSetModalState(); | } = useSetModalState(); | ||||
| const handleShow: NodeMouseHandler = useCallback( | |||||
| (e, node: Node) => { | |||||
| setClickedNodeId(node.id); | |||||
| const handleShow = useCallback( | |||||
| (e: React.MouseEvent<Element>, nodeId: string) => { | |||||
| setClickedNodeId(nodeId); | |||||
| setClickedToolId(get(e.target, 'dataset.tool')); | setClickedToolId(get(e.target, 'dataset.tool')); | ||||
| showFormDrawer(); | showFormDrawer(); | ||||
| }, | }, | ||||
| if (!ExcludedNodes.some((x) => x === node.data.label)) { | if (!ExcludedNodes.some((x) => x === node.data.label)) { | ||||
| hideSingleDebugDrawer(); | hideSingleDebugDrawer(); | ||||
| // hideRunOrChatDrawer(); | // hideRunOrChatDrawer(); | ||||
| showFormDrawer(e, node); | |||||
| showFormDrawer(e, node.id); | |||||
| } | } | ||||
| // handle single debug icon click | // handle single debug icon click | ||||
| if ( | if ( |
| setClickedToolId: (id?: string) => void; | setClickedToolId: (id?: string) => void; | ||||
| findUpstreamNodeById: (id?: string | null) => RAGFlowNodeType | undefined; | findUpstreamNodeById: (id?: string | null) => RAGFlowNodeType | undefined; | ||||
| deleteCategorizeCaseEdges: (source: string, sourceHandle: string) => void; // Deleting a condition of a classification operator will delete the related edge | deleteCategorizeCaseEdges: (source: string, sourceHandle: string) => void; // Deleting a condition of a classification operator will delete the related edge | ||||
| findAgentToolNodeById: (id: string | null) => string | undefined; | |||||
| selectNodeIds: (nodeIds: string[]) => void; | |||||
| }; | }; | ||||
| // this is our useStore hook that we can use in our components to get parts of the store and call actions | // this is our useStore hook that we can use in our components to get parts of the store and call actions | ||||
| ), | ), | ||||
| ); | ); | ||||
| }, | }, | ||||
| findAgentToolNodeById: (id) => { | |||||
| const { edges } = get(); | |||||
| return edges.find( | |||||
| (edge) => | |||||
| edge.source === id && edge.sourceHandle === NodeHandleId.Tool, | |||||
| )?.target; | |||||
| }, | |||||
| selectNodeIds: (nodeIds) => { | |||||
| const { nodes, setNodes } = get(); | |||||
| setNodes( | |||||
| nodes.map((node) => ({ | |||||
| ...node, | |||||
| selected: nodeIds.includes(node.id), | |||||
| })), | |||||
| ); | |||||
| }, | |||||
| })), | })), | ||||
| { name: 'graph', trace: true }, | { name: 'graph', trace: true }, | ||||
| ), | ), |