diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index b90626061..3f1d192e1 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -85,6 +85,7 @@ export enum Operator { WaitingDialogue = 'WaitingDialogue', Agent = 'Agent', Tool = 'Tool', + Tavily = 'Tavily', } export const SwitchLogicOperatorOptions = ['and', 'or']; @@ -249,6 +250,7 @@ export const operatorMap: Record< [Operator.Code]: { backgroundColor: '#4c5458' }, [Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' }, [Operator.Agent]: { backgroundColor: '#a5d65c' }, + [Operator.Tavily]: { backgroundColor: '#a5d65c' }, }; export const componentMenuList = [ diff --git a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx index 507444098..5d948fe47 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx +++ b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx @@ -9,13 +9,14 @@ import { CommandList, } from '@/components/ui/command'; import { cn } from '@/lib/utils'; -import { Operator } from '@/pages/flow/constant'; +import { Operator } from '@/pages/agent/constant'; import { useCallback, useEffect, useState } from 'react'; const Menus = [ { label: 'Search', list: [ + Operator.Tavily, Operator.Google, Operator.Bing, Operator.DuckDuckGo, diff --git a/web/src/pages/agent/form/retrieval-form/index.tsx b/web/src/pages/agent/form/retrieval-form/index.tsx deleted file mode 100644 index 4a92a7f94..000000000 --- a/web/src/pages/agent/form/retrieval-form/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import KnowledgeBaseItem from '@/components/knowledge-base-item'; -import Rerank from '@/components/rerank'; -import SimilaritySlider from '@/components/similarity-slider'; -import TopNItem from '@/components/top-n-item'; -import { useTranslate } from '@/hooks/common-hooks'; -import type { FormProps } from 'antd'; -import { Form, Input } from 'antd'; -import { IOperatorForm } from '../../interface'; -import DynamicInputVariable from '../components/dynamic-input-variable'; - -type FieldType = { - top_n?: number; -}; - -const onFinish: FormProps['onFinish'] = (values) => { - console.log('Success:', values); -}; - -const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { - console.log('Failed:', errorInfo); -}; - -const RetrievalForm = ({ onValuesChange, form, node }: IOperatorForm) => { - const { t } = useTranslate('flow'); - return ( -
- - - - - - - - -
- ); -}; - -export default RetrievalForm; diff --git a/web/src/pages/agent/form/tavily-form/index.tsx b/web/src/pages/agent/form/tavily-form/index.tsx index d9b9a800f..89b19d2f7 100644 --- a/web/src/pages/agent/form/tavily-form/index.tsx +++ b/web/src/pages/agent/form/tavily-form/index.tsx @@ -7,19 +7,31 @@ import { FormLabel, FormMessage, } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; import { RAGFlowSelect } from '@/components/ui/select'; +import { buildOptions } from '@/utils/form'; 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 { QueryVariable } from '../components/query-variable'; +import { SearchDepth, Topic, useValues } from './use-values'; import { useWatchFormChange } from './use-watch-change'; -const TavilyForm = ({ node }: INextOperatorForm) => { - const values = useValues(node); +const TavilyForm = () => { + const values = useValues(); const FormSchema = z.object({ query: z.string(), + search_depth: z.enum([SearchDepth.Advanced, SearchDepth.Basic]), + topic: z.enum([Topic.News, Topic.General]), + max_results: z.coerce.number(), + days: z.coerce.number(), + include_answer: z.boolean(), + include_raw_content: z.boolean(), + include_images: z.boolean(), + include_image_descriptions: z.boolean(), + include_domains: z.array(z.string()), + exclude_domains: z.array(z.string()), }); const form = useForm({ @@ -27,7 +39,7 @@ const TavilyForm = ({ node }: INextOperatorForm) => { resolver: zodResolver(FormSchema), }); - useWatchFormChange(node?.id, form); + useWatchFormChange(form); return (
@@ -39,14 +51,50 @@ const TavilyForm = ({ node }: INextOperatorForm) => { }} > + + ( - Username + Search Depth - + + + + + )} + /> + ( + + Topic + + + + + + )} + /> + ( + + Max Results + + diff --git a/web/src/pages/agent/form/tavily-form/use-values.ts b/web/src/pages/agent/form/tavily-form/use-values.ts index 43b8c3ae3..677e08962 100644 --- a/web/src/pages/agent/form/tavily-form/use-values.ts +++ b/web/src/pages/agent/form/tavily-form/use-values.ts @@ -1,14 +1,44 @@ -import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { isEmpty } from 'lodash'; import { useMemo } from 'react'; +import useGraphStore from '../../store'; +import { getAgentNodeTools } from '../../utils'; + +export enum SearchDepth { + Basic = 'basic', + Advanced = 'advanced', +} + +export enum Topic { + News = 'news', + General = 'general', +} const defaultValues = { - content: [], + query: '', + search_depth: SearchDepth.Basic, + topic: Topic.General, + max_results: 5, + days: 7, + include_answer: false, + include_raw_content: true, + include_images: false, + include_image_descriptions: false, + include_domains: [], + exclude_domains: [], }; -export function useValues(node?: RAGFlowNodeType) { +export function useValues() { + const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore( + (state) => state, + ); + const values = useMemo(() => { - const formData = node?.data?.form; + const agentNode = findUpstreamNodeById(clickedNodeId); + const tools = getAgentNodeTools(agentNode); + + const formData = tools.find( + (x) => x.component_name === clickedToolId, + )?.params; if (isEmpty(formData)) { return defaultValues; @@ -17,7 +47,7 @@ export function useValues(node?: RAGFlowNodeType) { return { ...formData, }; - }, [node]); + }, [clickedNodeId, clickedToolId, findUpstreamNodeById]); 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 index be52a1548..39ae2fef8 100644 --- a/web/src/pages/agent/form/tavily-form/use-watch-change.ts +++ b/web/src/pages/agent/form/tavily-form/use-watch-change.ts @@ -1,22 +1,34 @@ import { useEffect } from 'react'; import { UseFormReturn, useWatch } from 'react-hook-form'; import useGraphStore from '../../store'; +import { getAgentNodeTools } from '../../utils'; -export function useWatchFormChange(id?: string, form?: UseFormReturn) { +export function useWatchFormChange(form?: UseFormReturn) { let values = useWatch({ control: form?.control }); - const updateNodeForm = useGraphStore((state) => state.updateNodeForm); + const { clickedToolId, clickedNodeId, findUpstreamNodeById, updateNodeForm } = + useGraphStore((state) => state); useEffect(() => { + const agentNode = findUpstreamNodeById(clickedNodeId); // Manually triggered form updates are synchronized to the canvas - if (id && form?.formState.isDirty) { - values = form?.getValues(); - let nextValues: any = values; + if (agentNode && form?.formState.isDirty) { + const agentNodeId = agentNode?.id; + const tools = getAgentNodeTools(agentNode); - nextValues = { - ...values, + values = form?.getValues(); + const nextTools = tools.map((x) => { + if (x.component_name === clickedToolId) { + return { ...x, params: { ...values } }; + } + return x; + }); + + const nextValues = { + ...(agentNode?.data?.form ?? {}), + tools: nextTools, }; - updateNodeForm(id, nextValues); + updateNodeForm(agentNodeId, nextValues); } - }, [form?.formState.isDirty, id, updateNodeForm, values]); + }, [form?.formState.isDirty, updateNodeForm, values]); } diff --git a/web/src/pages/agent/form/tool-form/constant.ts b/web/src/pages/agent/form/tool-form/constant.ts index e2622cccb..a22aa8a2d 100644 --- a/web/src/pages/agent/form/tool-form/constant.ts +++ b/web/src/pages/agent/form/tool-form/constant.ts @@ -13,6 +13,7 @@ import GoogleForm from '../google-form'; import GoogleScholarForm from '../google-scholar-form'; import PubMedForm from '../pubmed-form'; import RetrievalForm from '../retrieval-form/next'; +import TavilyForm from '../tavily-form'; import WikipediaForm from '../wikipedia-form'; import YahooFinanceForm from '../yahoo-finance-form'; @@ -33,4 +34,5 @@ export const ToolFormConfigMap = { [Operator.YahooFinance]: YahooFinanceForm, [Operator.Crawler]: CrawlerForm, [Operator.Email]: EmailForm, + [Operator.Tavily]: TavilyForm, }; diff --git a/web/src/pages/agent/store.ts b/web/src/pages/agent/store.ts index 6db489fa9..c49d8753d 100644 --- a/web/src/pages/agent/store.ts +++ b/web/src/pages/agent/store.ts @@ -75,6 +75,7 @@ export type RFState = { generateNodeName: (name: string) => string; setClickedNodeId: (id?: string) => void; setClickedToolId: (id?: string) => void; + findUpstreamNodeById: (id?: string | null) => RAGFlowNodeType | undefined; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions @@ -471,6 +472,11 @@ const useGraphStore = create()( setClickedToolId: (id?: string) => { set({ clickedToolId: id }); }, + findUpstreamNodeById: (id) => { + const { edges, getNode } = get(); + const edge = edges.find((x) => x.target === id); + return getNode(edge?.source); + }, })), { name: 'graph', trace: true }, ), diff --git a/web/src/pages/agent/utils.ts b/web/src/pages/agent/utils.ts index 9d5306f01..50c6420cb 100644 --- a/web/src/pages/agent/utils.ts +++ b/web/src/pages/agent/utils.ts @@ -1,4 +1,5 @@ import { + IAgentForm, ICategorizeItem, ICategorizeItemResult, } from '@/interfaces/database/agent'; @@ -460,3 +461,8 @@ export const buildCategorizeObjectFromList = (list: Array) => { return pre; }, {}); }; + +export function getAgentNodeTools(agentNode?: RAGFlowNodeType) { + const tools: IAgentForm['tools'] = get(agentNode, 'data.form.tools', []); + return tools; +} diff --git a/web/src/utils/form.ts b/web/src/utils/form.ts index 7b33fd1f0..9487e5f60 100644 --- a/web/src/utils/form.ts +++ b/web/src/utils/form.ts @@ -26,3 +26,7 @@ export const removeUselessFieldsFromValues = (values: any, prefix?: string) => { return nextValues; }; + +export function buildOptions(data: Record) { + return Object.values(data).map((val) => ({ label: val, value: val })); +}