ソースを参照

Feat: Synchronize the data of the tavily form to the canvas node #3221 (#8377)

### 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)
tags/v0.19.1
balibabu 4ヶ月前
コミット
c4e081d4c6
コミッターのメールアドレスに関連付けられたアカウントが存在しません

+ 2
- 0
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 = [

+ 2
- 1
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,

+ 0
- 54
web/src/pages/agent/form/retrieval-form/index.tsx ファイルの表示

@@ -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;

+ 56
- 8
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 (
<Form {...form}>
@@ -39,14 +51,50 @@ const TavilyForm = ({ node }: INextOperatorForm) => {
}}
>
<FormContainer>
<QueryVariable></QueryVariable>

<FormField
control={form.control}
name="search_depth"
render={({ field }) => (
<FormItem>
<FormLabel>Search Depth</FormLabel>
<FormControl>
<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="query"
name="max_results"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormLabel>Max Results</FormLabel>
<FormControl>
<RAGFlowSelect placeholder="shadcn" {...field} options={[]} />
<Input type={'number'} {...field}></Input>
</FormControl>
<FormMessage />
</FormItem>

+ 35
- 5
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;
}

+ 20
- 8
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<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) {
if (agentNode && form?.formState.isDirty) {
const agentNodeId = agentNode?.id;
const tools = getAgentNodeTools(agentNode);

values = form?.getValues();
let nextValues: any = values;
const nextTools = tools.map((x) => {
if (x.component_name === clickedToolId) {
return { ...x, params: { ...values } };
}
return x;
});

nextValues = {
...values,
const nextValues = {
...(agentNode?.data?.form ?? {}),
tools: nextTools,
};

updateNodeForm(id, nextValues);
updateNodeForm(agentNodeId, nextValues);
}
}, [form?.formState.isDirty, id, updateNodeForm, values]);
}, [form?.formState.isDirty, updateNodeForm, values]);
}

+ 2
- 0
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,
};

+ 6
- 0
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<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 },
),

+ 6
- 0
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<ICategorizeItem>) => {
return pre;
}, {});
};

export function getAgentNodeTools(agentNode?: RAGFlowNodeType) {
const tools: IAgentForm['tools'] = get(agentNode, 'data.form.tools', []);
return tools;
}

+ 4
- 0
web/src/utils/form.ts ファイルの表示

@@ -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 }));
}

読み込み中…
キャンセル
保存