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',
|
WaitingDialogue = 'WaitingDialogue',
|
||||||
Agent = 'Agent',
|
Agent = 'Agent',
|
||||||
Tool = 'Tool',
|
Tool = 'Tool',
|
||||||
|
Tavily = 'Tavily',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
||||||
@ -249,6 +250,7 @@ export const operatorMap: Record<
|
|||||||
[Operator.Code]: { backgroundColor: '#4c5458' },
|
[Operator.Code]: { backgroundColor: '#4c5458' },
|
||||||
[Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' },
|
[Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' },
|
||||||
[Operator.Agent]: { backgroundColor: '#a5d65c' },
|
[Operator.Agent]: { backgroundColor: '#a5d65c' },
|
||||||
|
[Operator.Tavily]: { backgroundColor: '#a5d65c' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMenuList = [
|
export const componentMenuList = [
|
||||||
|
|||||||
@ -9,13 +9,14 @@ import {
|
|||||||
CommandList,
|
CommandList,
|
||||||
} from '@/components/ui/command';
|
} from '@/components/ui/command';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Operator } from '@/pages/flow/constant';
|
import { Operator } from '@/pages/agent/constant';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
const Menus = [
|
const Menus = [
|
||||||
{
|
{
|
||||||
label: 'Search',
|
label: 'Search',
|
||||||
list: [
|
list: [
|
||||||
|
Operator.Tavily,
|
||||||
Operator.Google,
|
Operator.Google,
|
||||||
Operator.Bing,
|
Operator.Bing,
|
||||||
Operator.DuckDuckGo,
|
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,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
import { RAGFlowSelect } from '@/components/ui/select';
|
||||||
|
import { buildOptions } from '@/utils/form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { QueryVariable } from '../components/query-variable';
|
||||||
import { useValues } from './use-values';
|
import { SearchDepth, Topic, useValues } from './use-values';
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
import { useWatchFormChange } from './use-watch-change';
|
||||||
|
|
||||||
const TavilyForm = ({ node }: INextOperatorForm) => {
|
const TavilyForm = () => {
|
||||||
const values = useValues(node);
|
const values = useValues();
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
query: z.string(),
|
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({
|
const form = useForm({
|
||||||
@ -27,7 +39,7 @@ const TavilyForm = ({ node }: INextOperatorForm) => {
|
|||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
useWatchFormChange(form);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
@ -39,14 +51,50 @@ const TavilyForm = ({ node }: INextOperatorForm) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
|
<QueryVariable></QueryVariable>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="query"
|
name="search_depth"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Username</FormLabel>
|
<FormLabel>Search Depth</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -1,14 +1,44 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { useMemo } from 'react';
|
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 = {
|
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 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)) {
|
if (isEmpty(formData)) {
|
||||||
return defaultValues;
|
return defaultValues;
|
||||||
@ -17,7 +47,7 @@ export function useValues(node?: RAGFlowNodeType) {
|
|||||||
return {
|
return {
|
||||||
...formData,
|
...formData,
|
||||||
};
|
};
|
||||||
}, [node]);
|
}, [clickedNodeId, clickedToolId, findUpstreamNodeById]);
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,34 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||||
import useGraphStore from '../../store';
|
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 });
|
let values = useWatch({ control: form?.control });
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
const { clickedToolId, clickedNodeId, findUpstreamNodeById, updateNodeForm } =
|
||||||
|
useGraphStore((state) => state);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const agentNode = findUpstreamNodeById(clickedNodeId);
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
// Manually triggered form updates are synchronized to the canvas
|
||||||
if (id && form?.formState.isDirty) {
|
if (agentNode && form?.formState.isDirty) {
|
||||||
values = form?.getValues();
|
const agentNodeId = agentNode?.id;
|
||||||
let nextValues: any = values;
|
const tools = getAgentNodeTools(agentNode);
|
||||||
|
|
||||||
nextValues = {
|
values = form?.getValues();
|
||||||
...values,
|
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 GoogleScholarForm from '../google-scholar-form';
|
||||||
import PubMedForm from '../pubmed-form';
|
import PubMedForm from '../pubmed-form';
|
||||||
import RetrievalForm from '../retrieval-form/next';
|
import RetrievalForm from '../retrieval-form/next';
|
||||||
|
import TavilyForm from '../tavily-form';
|
||||||
import WikipediaForm from '../wikipedia-form';
|
import WikipediaForm from '../wikipedia-form';
|
||||||
import YahooFinanceForm from '../yahoo-finance-form';
|
import YahooFinanceForm from '../yahoo-finance-form';
|
||||||
|
|
||||||
@ -33,4 +34,5 @@ export const ToolFormConfigMap = {
|
|||||||
[Operator.YahooFinance]: YahooFinanceForm,
|
[Operator.YahooFinance]: YahooFinanceForm,
|
||||||
[Operator.Crawler]: CrawlerForm,
|
[Operator.Crawler]: CrawlerForm,
|
||||||
[Operator.Email]: EmailForm,
|
[Operator.Email]: EmailForm,
|
||||||
|
[Operator.Tavily]: TavilyForm,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export type RFState = {
|
|||||||
generateNodeName: (name: string) => string;
|
generateNodeName: (name: string) => string;
|
||||||
setClickedNodeId: (id?: string) => void;
|
setClickedNodeId: (id?: string) => void;
|
||||||
setClickedToolId: (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
|
// 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) => {
|
setClickedToolId: (id?: string) => {
|
||||||
set({ clickedToolId: id });
|
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 },
|
{ name: 'graph', trace: true },
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
IAgentForm,
|
||||||
ICategorizeItem,
|
ICategorizeItem,
|
||||||
ICategorizeItemResult,
|
ICategorizeItemResult,
|
||||||
} from '@/interfaces/database/agent';
|
} from '@/interfaces/database/agent';
|
||||||
@ -460,3 +461,8 @@ export const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => {
|
|||||||
return pre;
|
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;
|
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