diff --git a/web/src/pages/agent/canvas/node/tool-node.tsx b/web/src/pages/agent/canvas/node/tool-node.tsx
index 0305d15e9..ba3f621be 100644
--- a/web/src/pages/agent/canvas/node/tool-node.tsx
+++ b/web/src/pages/agent/canvas/node/tool-node.tsx
@@ -1,8 +1,9 @@
import { IAgentForm, IToolNode } from '@/interfaces/database/agent';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { get } from 'lodash';
-import { memo } from 'react';
+import { memo, useCallback } from 'react';
import { NodeHandleId } from '../../constant';
+import { ToolCard } from '../../form/agent-form/agent-tools';
import useGraphStore from '../../store';
import { NodeWrapper } from './node-wrapper';
@@ -16,6 +17,8 @@ function InnerToolNode({
const upstreamAgentNodeId = edges.find((x) => x.target === id)?.source;
const upstreamAgentNode = getNode(upstreamAgentNodeId);
+ const handleClick = useCallback(() => {}, []);
+
const tools: IAgentForm['tools'] = get(
upstreamAgentNode,
'data.form.tools',
@@ -30,9 +33,16 @@ function InnerToolNode({
position={Position.Top}
isConnectable={isConnectable}
>
-
+
{tools.map((x) => (
- - {x.component_name}
+
+ {x.component_name}
+
))}
diff --git a/web/src/pages/agent/form/agent-form/agent-tools.tsx b/web/src/pages/agent/form/agent-form/agent-tools.tsx
new file mode 100644
index 000000000..25bbead6d
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/agent-tools.tsx
@@ -0,0 +1,53 @@
+import { BlockButton } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+import { PencilLine, X } from 'lucide-react';
+import { PropsWithChildren } from 'react';
+import { ToolPopover } from './tool-popover';
+import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
+import { useGetAgentToolNames } from './use-get-tools';
+
+export function ToolCard({
+ children,
+ className,
+ ...props
+}: PropsWithChildren & React.HTMLAttributes) {
+ return (
+ -
+ {children}
+
+ );
+}
+
+export function AgentTools() {
+ const { toolNames } = useGetAgentToolNames();
+ const { deleteNodeTool } = useDeleteAgentNodeTools();
+
+ return (
+
+ Tools
+
+ {toolNames.map((x) => (
+
+ {x}
+
+
+ ))}
+
+
+ Add Tool
+
+
+ );
+}
diff --git a/web/src/pages/agent/form/agent-form/index.tsx b/web/src/pages/agent/form/agent-form/index.tsx
index 99d18425c..4825f20c9 100644
--- a/web/src/pages/agent/form/agent-form/index.tsx
+++ b/web/src/pages/agent/form/agent-form/index.tsx
@@ -21,8 +21,8 @@ import { AgentInstanceContext } from '../../context';
import { INextOperatorForm } from '../../interface';
import { Output } from '../components/output';
import { PromptEditor } from '../components/prompt-editor';
-import { ToolPopover } from './tool-popover';
-import { useToolOptions, useValues } from './use-values';
+import { AgentTools } from './agent-tools';
+import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-change';
const FormSchema = z.object({
@@ -67,8 +67,6 @@ const AgentForm = ({ node }: INextOperatorForm) => {
const { addCanvasNode } = useContext(AgentInstanceContext);
- const toolOptions = useToolOptions();
-
return (
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 d903c3168..7c6bbda79 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,12 +3,12 @@ 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 { get } from 'lodash';
-import { PropsWithChildren, useCallback, useContext, useMemo } from 'react';
+import { PropsWithChildren, useCallback, useContext } from 'react';
+import { useDeleteToolNode } from '../use-delete-tool-node';
+import { useGetAgentToolNames } from '../use-get-tools';
import { ToolCommand } from './tool-command';
import { useUpdateAgentNodeTools } from './use-update-tools';
@@ -16,23 +16,24 @@ 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 { toolNames } = useGetAgentToolNames();
+ const { deleteToolNode } = useDeleteToolNode();
const handleChange = useCallback(
(value: string[]) => {
- if (Array.isArray(value) && value.length > 0 && node?.id) {
+ if (Array.isArray(value) && node?.id) {
updateNodeTools(value);
- addCanvasNode(Operator.Tool, {
- position: Position.Bottom,
- nodeId: node?.id,
- })();
+ if (value.length > 0) {
+ addCanvasNode(Operator.Tool, {
+ position: Position.Bottom,
+ nodeId: node?.id,
+ })();
+ } else {
+ deleteToolNode(node.id); // TODO: The tool node should be derived from the agent tools data
+ }
}
},
- [addCanvasNode, node?.id, updateNodeTools],
+ [addCanvasNode, deleteToolNode, node?.id, updateNodeTools],
);
return (
diff --git a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts
index 2c55ce1ac..3bcf84484 100644
--- a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts
+++ b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts
@@ -2,17 +2,26 @@ 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';
+import { useCallback, useContext, useMemo } from 'react';
+import { useDeleteToolNode } from '../use-delete-tool-node';
+
+export function useGetNodeTools() {
+ const node = useContext(AgentFormContext);
+
+ return useMemo(() => {
+ const tools: IAgentForm['tools'] = get(node, 'data.form.tools');
+ return tools;
+ }, [node]);
+}
export function useUpdateAgentNodeTools() {
const { updateNodeForm } = useGraphStore((state) => state);
const node = useContext(AgentFormContext);
+ const tools = useGetNodeTools();
const updateNodeTools = useCallback(
(value: string[]) => {
if (node?.id) {
- const tools: IAgentForm['tools'] = get(node, 'data.form.tools');
-
const nextValue = value.reduce((pre, cur) => {
const tool = tools.find((x) => x.component_name === cur);
pre.push(tool ? tool : { component_name: cur, params: {} });
@@ -22,8 +31,37 @@ export function useUpdateAgentNodeTools() {
updateNodeForm(node?.id, nextValue, ['tools']);
}
},
- [node, updateNodeForm],
+ [node?.id, tools, updateNodeForm],
);
- return { updateNodeTools };
+ const deleteNodeTool = useCallback(
+ (value: string) => {
+ updateNodeTools([value]);
+ },
+ [updateNodeTools],
+ );
+
+ return { updateNodeTools, deleteNodeTool };
+}
+
+export function useDeleteAgentNodeTools() {
+ const { updateNodeForm } = useGraphStore((state) => state);
+ const tools = useGetNodeTools();
+ const node = useContext(AgentFormContext);
+ const { deleteToolNode } = useDeleteToolNode();
+
+ const deleteNodeTool = useCallback(
+ (value: string) => () => {
+ const nextTools = tools.filter((x) => x.component_name !== value);
+ if (node?.id) {
+ updateNodeForm(node?.id, nextTools, ['tools']);
+ if (nextTools.length === 0) {
+ deleteToolNode(node?.id);
+ }
+ }
+ },
+ [deleteToolNode, node?.id, tools, updateNodeForm],
+ );
+
+ return { deleteNodeTool };
}
diff --git a/web/src/pages/agent/form/agent-form/use-delete-tool-node.ts b/web/src/pages/agent/form/agent-form/use-delete-tool-node.ts
new file mode 100644
index 000000000..b32274595
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/use-delete-tool-node.ts
@@ -0,0 +1,24 @@
+import { useCallback } from 'react';
+import { NodeHandleId } from '../../constant';
+import useGraphStore from '../../store';
+
+export function useDeleteToolNode() {
+ const { edges, deleteEdgeById, deleteNodeById } = useGraphStore(
+ (state) => state,
+ );
+ const deleteToolNode = useCallback(
+ (agentNodeId: string) => {
+ const edge = edges.find(
+ (x) => x.source === agentNodeId && x.sourceHandle === NodeHandleId.Tool,
+ );
+
+ if (edge) {
+ deleteEdgeById(edge.id);
+ deleteNodeById(edge.target);
+ }
+ },
+ [deleteEdgeById, deleteNodeById, edges],
+ );
+
+ return { deleteToolNode };
+}
diff --git a/web/src/pages/agent/form/agent-form/use-get-tools.ts b/web/src/pages/agent/form/agent-form/use-get-tools.ts
new file mode 100644
index 000000000..c9f37113d
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/use-get-tools.ts
@@ -0,0 +1,15 @@
+import { IAgentForm } from '@/interfaces/database/agent';
+import { get } from 'lodash';
+import { useContext, useMemo } from 'react';
+import { AgentFormContext } from '../../context';
+
+export function useGetAgentToolNames() {
+ const node = useContext(AgentFormContext);
+
+ const toolNames = useMemo(() => {
+ const tools: IAgentForm['tools'] = get(node, 'data.form.tools', []);
+ return tools.map((x) => x.component_name);
+ }, [node]);
+
+ return { toolNames };
+}
diff --git a/web/src/pages/agent/form/agent-form/use-values.ts b/web/src/pages/agent/form/agent-form/use-values.ts
index 5a854bcd0..3e6b057a6 100644
--- a/web/src/pages/agent/form/agent-form/use-values.ts
+++ b/web/src/pages/agent/form/agent-form/use-values.ts
@@ -2,7 +2,7 @@ import { useFetchModelId } from '@/hooks/logic-hooks';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { get, isEmpty } from 'lodash';
import { useMemo } from 'react';
-import { Operator, initialAgentValues } from '../../constant';
+import { initialAgentValues } from '../../constant';
export function useValues(node?: RAGFlowNodeType) {
const llmId = useFetchModelId();
@@ -28,48 +28,3 @@ export function useValues(node?: RAGFlowNodeType) {
return values;
}
-
-function buildOptions(list: string[]) {
- return list.map((x) => ({ label: x, value: x }));
-}
-
-export function useToolOptions() {
- const options = useMemo(() => {
- const options = [
- {
- label: 'Search',
- options: buildOptions([
- Operator.Google,
- Operator.Bing,
- Operator.DuckDuckGo,
- Operator.Wikipedia,
- Operator.YahooFinance,
- Operator.PubMed,
- Operator.GoogleScholar,
- ]),
- },
- {
- label: 'Communication',
- options: buildOptions([Operator.Email]),
- },
- {
- label: 'Productivity',
- options: [],
- },
- {
- label: 'Developer',
- options: buildOptions([
- Operator.GitHub,
- Operator.ExeSQL,
- Operator.Invoke,
- Operator.Crawler,
- Operator.Code,
- ]),
- },
- ];
-
- return options;
- }, []);
-
- return options;
-}
diff --git a/web/src/pages/agent/form/tavily-form/index.tsx b/web/src/pages/agent/form/tavily-form/index.tsx
index 715df56db..d9b9a800f 100644
--- a/web/src/pages/agent/form/tavily-form/index.tsx
+++ b/web/src/pages/agent/form/tavily-form/index.tsx
@@ -15,7 +15,7 @@ import { INextOperatorForm } from '../../interface';
import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-change';
-const MessageForm = ({ node }: INextOperatorForm) => {
+const TavilyForm = ({ node }: INextOperatorForm) => {
const values = useValues(node);
const FormSchema = z.object({
@@ -58,4 +58,4 @@ const MessageForm = ({ node }: INextOperatorForm) => {
);
};
-export default MessageForm;
+export default TavilyForm;
diff --git a/web/src/pages/agent/form/tool-form/constant.ts b/web/src/pages/agent/form/tool-form/constant.ts
new file mode 100644
index 000000000..e2622cccb
--- /dev/null
+++ b/web/src/pages/agent/form/tool-form/constant.ts
@@ -0,0 +1,36 @@
+import { Operator } from '../../constant';
+import AkShareForm from '../akshare-form';
+import ArXivForm from '../arxiv-form';
+import BingForm from '../bing-form';
+import CodeForm from '../code-form';
+import CrawlerForm from '../crawler-form';
+import DeepLForm from '../deepl-form';
+import DuckDuckGoForm from '../duckduckgo-form';
+import EmailForm from '../email-form';
+import ExeSQLForm from '../exesql-form';
+import GithubForm from '../github-form';
+import GoogleForm from '../google-form';
+import GoogleScholarForm from '../google-scholar-form';
+import PubMedForm from '../pubmed-form';
+import RetrievalForm from '../retrieval-form/next';
+import WikipediaForm from '../wikipedia-form';
+import YahooFinanceForm from '../yahoo-finance-form';
+
+export const ToolFormConfigMap = {
+ [Operator.Retrieval]: RetrievalForm,
+ [Operator.Code]: CodeForm,
+ [Operator.DuckDuckGo]: DuckDuckGoForm,
+ [Operator.Wikipedia]: WikipediaForm,
+ [Operator.PubMed]: PubMedForm,
+ [Operator.ArXiv]: ArXivForm,
+ [Operator.Google]: GoogleForm,
+ [Operator.Bing]: BingForm,
+ [Operator.GoogleScholar]: GoogleScholarForm,
+ [Operator.DeepL]: DeepLForm,
+ [Operator.GitHub]: GithubForm,
+ [Operator.ExeSQL]: ExeSQLForm,
+ [Operator.AkShare]: AkShareForm,
+ [Operator.YahooFinance]: YahooFinanceForm,
+ [Operator.Crawler]: CrawlerForm,
+ [Operator.Email]: EmailForm,
+};
diff --git a/web/src/pages/agent/form/tool-form/index.tsx b/web/src/pages/agent/form/tool-form/index.tsx
index 63f5d423a..c23ab05e4 100644
--- a/web/src/pages/agent/form/tool-form/index.tsx
+++ b/web/src/pages/agent/form/tool-form/index.tsx
@@ -1,7 +1,20 @@
-import { INextOperatorForm } from '../../interface';
+import useGraphStore from '../../store';
+import { ToolFormConfigMap } from './constant';
-const ToolForm = ({ node }: INextOperatorForm) => {
- return ;
+const EmptyContent = () => ;
+
+const ToolForm = () => {
+ const clickedToolId = useGraphStore((state) => state.clickedToolId);
+
+ const ToolForm =
+ ToolFormConfigMap[clickedToolId as keyof typeof ToolFormConfigMap] ??
+ EmptyContent;
+
+ return (
+
+ );
};
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 bb047e9fb..074e1b201 100644
--- a/web/src/pages/agent/hooks/use-add-node.ts
+++ b/web/src/pages/agent/hooks/use-add-node.ts
@@ -184,7 +184,7 @@ function useAddChildEdge() {
return { addChildEdge };
}
-function useAddTooNode() {
+function useAddToolNode() {
const addNode = useGraphStore((state) => state.addNode);
const getNode = useGraphStore((state) => state.getNode);
const addEdge = useGraphStore((state) => state.addEdge);
@@ -241,7 +241,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) {
const initializeOperatorParams = useInitializeOperatorParams();
const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition();
const { addChildEdge } = useAddChildEdge();
- const { addToolNode } = useAddTooNode();
+ const { addToolNode } = useAddToolNode();
// const [reactFlowInstance, setReactFlowInstance] =
// useState>();
diff --git a/web/src/pages/agent/hooks/use-show-drawer.tsx b/web/src/pages/agent/hooks/use-show-drawer.tsx
index 4bdf25459..990ea6f92 100644
--- a/web/src/pages/agent/hooks/use-show-drawer.tsx
+++ b/web/src/pages/agent/hooks/use-show-drawer.tsx
@@ -14,6 +14,7 @@ export const useShowFormDrawer = () => {
clickedNodeId: clickNodeId,
setClickedNodeId,
getNode,
+ setClickedToolId,
} = useGraphStore((state) => state);
const {
visible: formDrawerVisible,
@@ -21,12 +22,13 @@ export const useShowFormDrawer = () => {
showModal: showFormDrawer,
} = useSetModalState();
- const handleShow = useCallback(
- (node: Node) => {
+ const handleShow: NodeMouseHandler = useCallback(
+ (e, node: Node) => {
setClickedNodeId(node.id);
+ setClickedToolId(get(e.target, 'dataset.tool'));
showFormDrawer();
},
- [showFormDrawer, setClickedNodeId],
+ [setClickedNodeId, setClickedToolId, showFormDrawer],
);
return {
@@ -118,7 +120,7 @@ export function useShowDrawer({
if (!ExcludedNodes.some((x) => x === node.data.label)) {
hideSingleDebugDrawer();
hideRunOrChatDrawer();
- showFormDrawer(node);
+ showFormDrawer(e, node);
}
// handle single debug icon click
if (
diff --git a/web/src/pages/agent/store.ts b/web/src/pages/agent/store.ts
index 8b6d3842e..6db489fa9 100644
--- a/web/src/pages/agent/store.ts
+++ b/web/src/pages/agent/store.ts
@@ -35,6 +35,7 @@ export type RFState = {
selectedNodeIds: string[];
selectedEdgeIds: string[];
clickedNodeId: string; // currently selected node
+ clickedToolId: string; // currently selected tool id
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
onConnect: OnConnect;
@@ -73,6 +74,7 @@ export type RFState = {
updateNodeName: (id: string, name: string) => void;
generateNodeName: (name: string) => string;
setClickedNodeId: (id?: string) => void;
+ setClickedToolId: (id?: string) => void;
};
// this is our useStore hook that we can use in our components to get parts of the store and call actions
@@ -84,6 +86,7 @@ const useGraphStore = create()(
selectedNodeIds: [] as string[],
selectedEdgeIds: [] as string[],
clickedNodeId: '',
+ clickedToolId: '',
onNodesChange: (changes) => {
set({
nodes: applyNodeChanges(changes, get().nodes),
@@ -465,6 +468,9 @@ const useGraphStore = create()(
return generateNodeNamesWithIncreasingIndex(name, nodes);
},
+ setClickedToolId: (id?: string) => {
+ set({ clickedToolId: id });
+ },
})),
{ name: 'graph', trace: true },
),