### 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
| @@ -116,6 +116,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| runVisible, | |||
| hideRunOrChatDrawer, | |||
| showChatModal, | |||
| showFormDrawer, | |||
| } = useShowDrawer({ | |||
| drawerVisible, | |||
| hideDrawer, | |||
| @@ -175,7 +176,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| </marker> | |||
| </defs> | |||
| </svg> | |||
| <AgentInstanceContext.Provider value={{ addCanvasNode }}> | |||
| <AgentInstanceContext.Provider value={{ addCanvasNode, showFormDrawer }}> | |||
| <ReactFlow | |||
| connectionMode={ConnectionMode.Loose} | |||
| nodes={nodes} | |||
| @@ -228,7 +229,9 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| ref={ref} | |||
| ></NotebookPen> | |||
| {formDrawerVisible && ( | |||
| <AgentInstanceContext.Provider value={{ addCanvasNode }}> | |||
| <AgentInstanceContext.Provider | |||
| value={{ addCanvasNode, showFormDrawer }} | |||
| > | |||
| <FormSheet | |||
| node={clickedNode} | |||
| visible={formDrawerVisible} | |||
| @@ -3,7 +3,7 @@ import { HandleType, Position } from '@xyflow/react'; | |||
| import { createContext } from 'react'; | |||
| import { useAddNode } from './hooks/use-add-node'; | |||
| 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>( | |||
| undefined, | |||
| @@ -12,7 +12,8 @@ export const AgentFormContext = createContext<RAGFlowNodeType | undefined>( | |||
| type AgentInstanceContextType = Pick< | |||
| ReturnType<typeof useAddNode>, | |||
| 'addCanvasNode' | |||
| >; | |||
| > & | |||
| Pick<ReturnType<typeof useShowFormDrawer>, 'showFormDrawer'>; | |||
| export const AgentInstanceContext = createContext<AgentInstanceContextType>( | |||
| {} as AgentInstanceContextType, | |||
| @@ -2,7 +2,13 @@ import { BlockButton } from '@/components/ui/button'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Position } from '@xyflow/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 { AgentInstanceContext } from '../../context'; | |||
| import { useFindMcpById } from '../../hooks/use-find-mcp-by-id'; | |||
| @@ -35,23 +41,20 @@ export function ToolCard({ | |||
| type ActionButtonProps<T> = { | |||
| record: T; | |||
| 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(() => { | |||
| deleteRecord(record); | |||
| }, [deleteRecord, record]); | |||
| const handleEdit = useCallback(() => { | |||
| edit(record); | |||
| }, [edit, record]); | |||
| return ( | |||
| <div className="flex items-center gap-2 text-text-sub-title"> | |||
| <PencilLine | |||
| className="size-4 cursor-pointer" | |||
| data-tool={record} | |||
| onClick={handleEdit} | |||
| onClick={edit} | |||
| /> | |||
| <X className="size-4 cursor-pointer" onClick={handleDelete} /> | |||
| </div> | |||
| @@ -64,6 +67,21 @@ export function AgentTools() { | |||
| const { mcpIds } = useGetAgentMCPIds(); | |||
| const { findMcpById } = useFindMcpById(); | |||
| 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 ( | |||
| <section className="space-y-2.5"> | |||
| @@ -74,8 +92,8 @@ export function AgentTools() { | |||
| {x} | |||
| <ActionButton | |||
| record={x} | |||
| edit={() => {}} | |||
| deleteRecord={deleteNodeTool(x)} | |||
| edit={handleEdit} | |||
| ></ActionButton> | |||
| </ToolCard> | |||
| ))} | |||
| @@ -84,8 +102,8 @@ export function AgentTools() { | |||
| {findMcpById(id)?.name} | |||
| <ActionButton | |||
| record={id} | |||
| edit={() => {}} | |||
| deleteRecord={deleteNodeMCP(id)} | |||
| edit={handleEdit} | |||
| ></ActionButton> | |||
| </ToolCard> | |||
| ))} | |||
| @@ -99,8 +117,17 @@ export function AgentTools() { | |||
| export function Agents({ node }: INextOperatorForm) { | |||
| 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(() => { | |||
| @@ -119,8 +146,8 @@ export function Agents({ node }: INextOperatorForm) { | |||
| {currentNode?.data.name} | |||
| <ActionButton | |||
| record={id} | |||
| edit={() => {}} | |||
| deleteRecord={deleteAgentDownstreamNodesById} | |||
| edit={handleEdit(id)} | |||
| ></ActionButton> | |||
| </ToolCard> | |||
| ); | |||
| @@ -40,7 +40,7 @@ export function useUpdateAgentNodeMCP() { | |||
| } else if (tools) { | |||
| pre.push({ | |||
| mcp_id: cur, | |||
| tools, | |||
| tools: {}, | |||
| }); | |||
| } | |||
| return pre; | |||
| @@ -1,7 +1,7 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { Node, NodeMouseHandler } from '@xyflow/react'; | |||
| import { NodeMouseHandler } from '@xyflow/react'; | |||
| import get from 'lodash/get'; | |||
| import { useCallback, useEffect } from 'react'; | |||
| import React, { useCallback, useEffect } from 'react'; | |||
| import { Operator } from '../constant'; | |||
| import useGraphStore from '../store'; | |||
| import { useCacheChatLog } from './use-cache-chat-log'; | |||
| @@ -21,9 +21,9 @@ export const useShowFormDrawer = () => { | |||
| showModal: showFormDrawer, | |||
| } = 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')); | |||
| showFormDrawer(); | |||
| }, | |||
| @@ -118,7 +118,7 @@ export function useShowDrawer({ | |||
| if (!ExcludedNodes.some((x) => x === node.data.label)) { | |||
| hideSingleDebugDrawer(); | |||
| // hideRunOrChatDrawer(); | |||
| showFormDrawer(e, node); | |||
| showFormDrawer(e, node.id); | |||
| } | |||
| // handle single debug icon click | |||
| if ( | |||
| @@ -85,6 +85,8 @@ export type RFState = { | |||
| setClickedToolId: (id?: string) => void; | |||
| findUpstreamNodeById: (id?: string | null) => RAGFlowNodeType | undefined; | |||
| 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 | |||
| @@ -518,6 +520,22 @@ const useGraphStore = create<RFState>()( | |||
| ), | |||
| ); | |||
| }, | |||
| 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 }, | |||
| ), | |||