diff --git a/web/src/pages/agent/canvas/node/agent-node.tsx b/web/src/pages/agent/canvas/node/agent-node.tsx index 862fa239b..2a4057e31 100644 --- a/web/src/pages/agent/canvas/node/agent-node.tsx +++ b/web/src/pages/agent/canvas/node/agent-node.tsx @@ -63,6 +63,13 @@ function InnerAgentNode({ id="e" style={{ left: 180 }} > + diff --git a/web/src/pages/agent/canvas/node/tool-node.tsx b/web/src/pages/agent/canvas/node/tool-node.tsx index fc963efdf..bc45460f7 100644 --- a/web/src/pages/agent/canvas/node/tool-node.tsx +++ b/web/src/pages/agent/canvas/node/tool-node.tsx @@ -1,12 +1,8 @@ import { IToolNode } from '@/interfaces/database/agent'; -import { NodeProps, Position } from '@xyflow/react'; +import { Handle, NodeProps, Position } from '@xyflow/react'; import { memo } from 'react'; import { NodeHandleId } from '../../constant'; -import { CommonHandle } from './handle'; -import { LeftHandleStyle } from './handle-icon'; -import NodeHeader from './node-header'; import { NodeWrapper } from './node-wrapper'; -import { ToolBar } from './toolbar'; function InnerToolNode({ id, @@ -15,19 +11,14 @@ function InnerToolNode({ selected, }: NodeProps) { return ( - - - - - - + + + ); } diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index 95eb14abe..b90626061 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -84,6 +84,7 @@ export enum Operator { Code = 'Code', WaitingDialogue = 'WaitingDialogue', Agent = 'Agent', + Tool = 'Tool', } export const SwitchLogicOperatorOptions = ['and', 'or']; @@ -809,6 +810,7 @@ export const NodeMap = { [Operator.Code]: 'ragNode', [Operator.WaitingDialogue]: 'ragNode', [Operator.Agent]: 'agentNode', + [Operator.Tool]: 'toolNode', }; export const LanguageOptions = [ @@ -3012,4 +3014,5 @@ export const NoDebugOperatorsList = [ export enum NodeHandleId { Start = 'start', End = 'end', + Tool = 'tool', } diff --git a/web/src/pages/agent/form/agent-form/tool-popover/index.tsx b/web/src/pages/agent/form/agent-form/tool-popover/index.tsx index ef25e1b7a..fcb53d69a 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/index.tsx +++ b/web/src/pages/agent/form/agent-form/tool-popover/index.tsx @@ -3,15 +3,33 @@ import { PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; -import { PropsWithChildren } from 'react'; +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 { ToolCommand } from './tool-command'; export function ToolPopover({ children }: PropsWithChildren) { + const { addCanvasNode } = useContext(AgentInstanceContext); + const node = useContext(AgentFormContext); + + const handleChange = useCallback( + (value: string[]) => { + if (Array.isArray(value) && value.length > 0) { + addCanvasNode(Operator.Tool, { + position: Position.Bottom, + nodeId: node?.id, + })(); + } + }, + [addCanvasNode, node?.id], + ); + return ( {children} - + ); diff --git a/web/src/pages/agent/form/tavily-form/index.tsx b/web/src/pages/agent/form/tavily-form/index.tsx new file mode 100644 index 000000000..715df56db --- /dev/null +++ b/web/src/pages/agent/form/tavily-form/index.tsx @@ -0,0 +1,61 @@ +import { FormContainer } from '@/components/form-container'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { RAGFlowSelect } from '@/components/ui/select'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { INextOperatorForm } from '../../interface'; +import { useValues } from './use-values'; +import { useWatchFormChange } from './use-watch-change'; + +const MessageForm = ({ node }: INextOperatorForm) => { + const values = useValues(node); + + const FormSchema = z.object({ + query: z.string(), + }); + + const form = useForm({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); + + useWatchFormChange(node?.id, form); + + return ( +
+ { + e.preventDefault(); + }} + > + + ( + + Username + + + + + + )} + /> + +
+ + ); +}; + +export default MessageForm; diff --git a/web/src/pages/agent/form/tavily-form/use-values.ts b/web/src/pages/agent/form/tavily-form/use-values.ts new file mode 100644 index 000000000..43b8c3ae3 --- /dev/null +++ b/web/src/pages/agent/form/tavily-form/use-values.ts @@ -0,0 +1,23 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; +import { isEmpty } from 'lodash'; +import { useMemo } from 'react'; + +const defaultValues = { + content: [], +}; + +export function useValues(node?: RAGFlowNodeType) { + const values = useMemo(() => { + const formData = node?.data?.form; + + if (isEmpty(formData)) { + return defaultValues; + } + + return { + ...formData, + }; + }, [node]); + + return values; +} diff --git a/web/src/pages/agent/form/tavily-form/use-watch-change.ts b/web/src/pages/agent/form/tavily-form/use-watch-change.ts new file mode 100644 index 000000000..be52a1548 --- /dev/null +++ b/web/src/pages/agent/form/tavily-form/use-watch-change.ts @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import useGraphStore from '../../store'; + +export function useWatchFormChange(id?: string, form?: UseFormReturn) { + let values = useWatch({ control: form?.control }); + const updateNodeForm = useGraphStore((state) => state.updateNodeForm); + + useEffect(() => { + // Manually triggered form updates are synchronized to the canvas + if (id && form?.formState.isDirty) { + values = form?.getValues(); + let nextValues: any = values; + + nextValues = { + ...values, + }; + + updateNodeForm(id, nextValues); + } + }, [form?.formState.isDirty, id, updateNodeForm, values]); +} diff --git a/web/src/pages/agent/form/tool-form/index.tsx b/web/src/pages/agent/form/tool-form/index.tsx new file mode 100644 index 000000000..63f5d423a --- /dev/null +++ b/web/src/pages/agent/form/tool-form/index.tsx @@ -0,0 +1,7 @@ +import { INextOperatorForm } from '../../interface'; + +const ToolForm = ({ node }: INextOperatorForm) => { + return
xxx
; +}; + +export default ToolForm; diff --git a/web/src/pages/agent/hooks/use-add-node.ts b/web/src/pages/agent/hooks/use-add-node.ts index eb8e0e98e..bb047e9fb 100644 --- a/web/src/pages/agent/hooks/use-add-node.ts +++ b/web/src/pages/agent/hooks/use-add-node.ts @@ -103,6 +103,7 @@ export const useInitializeOperatorParams = () => { [Operator.Code]: initialCodeValues, [Operator.WaitingDialogue]: initialWaitingDialogueValues, [Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, + [Operator.Tool]: {}, }; }, [llmId]); @@ -183,6 +184,53 @@ function useAddChildEdge() { return { addChildEdge }; } +function useAddTooNode() { + const addNode = useGraphStore((state) => state.addNode); + const getNode = useGraphStore((state) => state.getNode); + const addEdge = useGraphStore((state) => state.addEdge); + const edges = useGraphStore((state) => state.edges); + const nodes = useGraphStore((state) => state.nodes); + + const addToolNode = useCallback( + (newNode: Node, nodeId?: string) => { + const agentNode = getNode(nodeId); + + if (agentNode) { + const childToolNodeIds = edges + .filter( + (x) => x.source === nodeId && x.sourceHandle === NodeHandleId.Tool, + ) + .map((x) => x.target); + + if ( + childToolNodeIds.length > 0 && + nodes.some((x) => x.id === childToolNodeIds[0]) + ) { + return; + } + + newNode.position = { + x: agentNode.position.x - 82, + y: agentNode.position.y + 140, + }; + + addNode(newNode); + if (nodeId) { + addEdge({ + source: nodeId, + target: newNode.id, + sourceHandle: NodeHandleId.Tool, + targetHandle: NodeHandleId.End, + }); + } + } + }, + [addEdge, addNode, edges, getNode, nodes], + ); + + return { addToolNode }; +} + export function useAddNode(reactFlowInstance?: ReactFlowInstance) { const addNode = useGraphStore((state) => state.addNode); const getNode = useGraphStore((state) => state.getNode); @@ -193,6 +241,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) { const initializeOperatorParams = useInitializeOperatorParams(); const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition(); const { addChildEdge } = useAddChildEdge(); + const { addToolNode } = useAddTooNode(); // const [reactFlowInstance, setReactFlowInstance] = // useState>(); @@ -203,15 +252,15 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) { position: Position.Right, }, ) => - (event: React.MouseEvent) => { + (event?: React.MouseEvent) => { const nodeId = params.nodeId; // reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition // and you don't need to subtract the reactFlowBounds.left/top anymore // details: https://@xyflow/react.dev/whats-new/2023-11-10 let position = reactFlowInstance?.screenToFlowPosition({ - x: event.clientX, - y: event.clientY, + x: event?.clientX || 0, + y: event?.clientY || 0, }); if (params.position === Position.Right) { @@ -287,6 +336,8 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) { targetHandle: 'f', }); } + } else if (type === Operator.Tool) { + addToolNode(newNode, params.nodeId); } else { const subNodeOfIteration = getRelativePositionToIterationNode( nodes, @@ -309,6 +360,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) { addChildEdge, addEdge, addNode, + addToolNode, calculateNewlyBackChildPosition, edges, getNode,