mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Synchronize the data of the tavily form to the canvas node #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -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 = [
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<FieldType>['onFinish'] = (values) => {
|
||||
console.log('Success:', values);
|
||||
};
|
||||
|
||||
const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
const RetrievalForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
return (
|
||||
<Form
|
||||
name="basic"
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
onValuesChange={onValuesChange}
|
||||
form={form}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<SimilaritySlider
|
||||
isTooltipShown
|
||||
vectorSimilarityWeightName="keywords_similarity_weight"
|
||||
></SimilaritySlider>
|
||||
<TopNItem></TopNItem>
|
||||
<Rerank></Rerank>
|
||||
<KnowledgeBaseItem></KnowledgeBaseItem>
|
||||
<Form.Item
|
||||
name={'empty_response'}
|
||||
label={t('emptyResponse', { keyPrefix: 'chat' })}
|
||||
tooltip={t('emptyResponseTip', { keyPrefix: 'chat' })}
|
||||
>
|
||||
<Input.TextArea placeholder="" rows={4} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RetrievalForm;
|
||||
@ -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 (
|
||||
<Form {...form}>
|
||||
@ -39,14 +51,50 @@ const TavilyForm = ({ node }: INextOperatorForm) => {
|
||||
}}
|
||||
>
|
||||
<FormContainer>
|
||||
<QueryVariable></QueryVariable>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="query"
|
||||
name="search_depth"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormLabel>Search Depth</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect placeholder="shadcn" {...field} options={[]} />
|
||||
<RAGFlowSelect
|
||||
placeholder="shadcn"
|
||||
{...field}
|
||||
options={buildOptions(SearchDepth)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="topic"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Topic</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
placeholder="shadcn"
|
||||
{...field}
|
||||
options={buildOptions(Topic)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="max_results"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max Results</FormLabel>
|
||||
<FormControl>
|
||||
<Input type={'number'} {...field}></Input>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<any>) {
|
||||
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]);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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<RFState>()(
|
||||
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 },
|
||||
),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {
|
||||
IAgentForm,
|
||||
ICategorizeItem,
|
||||
ICategorizeItemResult,
|
||||
} from '@/interfaces/database/agent';
|
||||
@ -460,3 +461,8 @@ export const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => {
|
||||
return pre;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export function getAgentNodeTools(agentNode?: RAGFlowNodeType) {
|
||||
const tools: IAgentForm['tools'] = get(agentNode, 'data.form.tools', []);
|
||||
return tools;
|
||||
}
|
||||
|
||||
@ -26,3 +26,7 @@ export const removeUselessFieldsFromValues = (values: any, prefix?: string) => {
|
||||
|
||||
return nextValues;
|
||||
};
|
||||
|
||||
export function buildOptions(data: Record<string, any>) {
|
||||
return Object.values(data).map((val) => ({ label: val, value: val }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user