### What problem does this PR solve? Feat: Add retrieval tool #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| FormItem, | FormItem, | ||||
| FormLabel, | FormLabel, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { Textarea } from '@/components/ui/textarea'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { Position } from '@xyflow/react'; | import { Position } from '@xyflow/react'; | ||||
| import { useContext, useMemo } from 'react'; | import { useContext, useMemo } from 'react'; | ||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| import { isBottomSubAgent } from '../../utils'; | import { isBottomSubAgent } from '../../utils'; | ||||
| import { DescriptionField } from '../components/description-field'; | |||||
| import { Output } from '../components/output'; | import { Output } from '../components/output'; | ||||
| import { PromptEditor } from '../components/prompt-editor'; | import { PromptEditor } from '../components/prompt-editor'; | ||||
| import { AgentTools } from './agent-tools'; | import { AgentTools } from './agent-tools'; | ||||
| }} | }} | ||||
| > | > | ||||
| <FormContainer> | <FormContainer> | ||||
| {isSubAgent && ( | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`description`} | |||||
| render={({ field }) => ( | |||||
| <FormItem className="flex-1"> | |||||
| <FormLabel>Description</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea {...field}></Textarea> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| )} | |||||
| {isSubAgent && <DescriptionField></DescriptionField>} | |||||
| <LargeModelFormField></LargeModelFormField> | <LargeModelFormField></LargeModelFormField> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} |
| Operator.Invoke, | Operator.Invoke, | ||||
| Operator.Crawler, | Operator.Crawler, | ||||
| Operator.Code, | Operator.Code, | ||||
| Operator.Retrieval, | |||||
| ], | ], | ||||
| }, | }, | ||||
| ]; | ]; |
| import { IAgentForm } from '@/interfaces/database/agent'; | import { IAgentForm } from '@/interfaces/database/agent'; | ||||
| import { DefaultAgentToolValuesMap } from '@/pages/agent/constant'; | |||||
| import { AgentFormContext } from '@/pages/agent/context'; | import { AgentFormContext } from '@/pages/agent/context'; | ||||
| import useGraphStore from '@/pages/agent/store'; | import useGraphStore from '@/pages/agent/store'; | ||||
| import { get } from 'lodash'; | import { get } from 'lodash'; | ||||
| if (node?.id) { | if (node?.id) { | ||||
| const nextValue = value.reduce<IAgentForm['tools']>((pre, cur) => { | const nextValue = value.reduce<IAgentForm['tools']>((pre, cur) => { | ||||
| const tool = tools.find((x) => x.component_name === cur); | const tool = tools.find((x) => x.component_name === cur); | ||||
| pre.push(tool ? tool : { component_name: cur, params: {} }); | |||||
| pre.push( | |||||
| tool | |||||
| ? tool | |||||
| : { | |||||
| component_name: cur, | |||||
| params: | |||||
| DefaultAgentToolValuesMap[ | |||||
| cur as keyof typeof DefaultAgentToolValuesMap | |||||
| ] || {}, | |||||
| }, | |||||
| ); | |||||
| return pre; | return pre; | ||||
| }, []); | }, []); | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Input, Select } from 'antd'; | import { Form, Input, Select } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { IOperatorForm } from '../../interface'; | |||||
| import { | import { | ||||
| BaiduFanyiDomainOptions, | BaiduFanyiDomainOptions, | ||||
| BaiduFanyiSourceLangOptions, | BaiduFanyiSourceLangOptions, | ||||
| } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | |||||
| } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => { | const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => { |
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Input, Select } from 'antd'; | import { Form, Input, Select } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { BingCountryOptions, BingLanguageOptions } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { BingCountryOptions, BingLanguageOptions } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const BingForm = ({ onValuesChange, form, node }: IOperatorForm) => { | const BingForm = ({ onValuesChange, form, node }: IOperatorForm) => { |
| import { | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| } from '@/components/ui/form'; | |||||
| import { Textarea } from '@/components/ui/textarea'; | |||||
| import { useFormContext } from 'react-hook-form'; | |||||
| export function DescriptionField() { | |||||
| const form = useFormContext(); | |||||
| return ( | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={`description`} | |||||
| render={({ field }) => ( | |||||
| <FormItem className="flex-1"> | |||||
| <FormLabel>Description</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea {...field}></Textarea> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| } |
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Input, Select } from 'antd'; | import { Form, Input, Select } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { CrawlerResultOptions } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { CrawlerResultOptions } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const CrawlerForm = ({ onValuesChange, form, node }: IOperatorForm) => { | const CrawlerForm = ({ onValuesChange, form, node }: IOperatorForm) => { | ||||
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); |
| import TopNItem from '@/components/top-n-item'; | import TopNItem from '@/components/top-n-item'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Select } from 'antd'; | import { Form, Select } from 'antd'; | ||||
| import { DeepLSourceLangOptions, DeepLTargetLangOptions } from '../../constant'; | |||||
| import { useBuildSortOptions } from '../../form-hooks'; | import { useBuildSortOptions } from '../../form-hooks'; | ||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { DeepLSourceLangOptions, DeepLTargetLangOptions } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const DeepLForm = ({ onValuesChange, form, node }: IOperatorForm) => { | const DeepLForm = ({ onValuesChange, form, node }: IOperatorForm) => { |
| import { useTestDbConnect } from '@/hooks/flow-hooks'; | import { useTestDbConnect } from '@/hooks/flow-hooks'; | ||||
| import { Button, Flex, Form, Input, InputNumber, Select } from 'antd'; | import { Button, Flex, Form, Input, InputNumber, Select } from 'antd'; | ||||
| import { useCallback } from 'react'; | import { useCallback } from 'react'; | ||||
| import { ExeSQLOptions } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { ExeSQLOptions } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const ExeSQLForm = ({ onValuesChange, form, node }: IOperatorForm) => { | const ExeSQLForm = ({ onValuesChange, form, node }: IOperatorForm) => { |
| import TopNItem from '@/components/top-n-item'; | import TopNItem from '@/components/top-n-item'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Input, Select } from 'antd'; | import { Form, Input, Select } from 'antd'; | ||||
| import { GoogleCountryOptions, GoogleLanguageOptions } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { GoogleCountryOptions, GoogleLanguageOptions } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const GoogleForm = ({ onValuesChange, form, node }: IOperatorForm) => { | const GoogleForm = ({ onValuesChange, form, node }: IOperatorForm) => { |
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Input, Select } from 'antd'; | import { Form, Input, Select } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { IOperatorForm } from '../../interface'; | |||||
| import { | import { | ||||
| Jin10CalendarDatashapeOptions, | Jin10CalendarDatashapeOptions, | ||||
| Jin10CalendarTypeOptions, | Jin10CalendarTypeOptions, | ||||
| Jin10SymbolsDatatypeOptions, | Jin10SymbolsDatatypeOptions, | ||||
| Jin10SymbolsTypeOptions, | Jin10SymbolsTypeOptions, | ||||
| Jin10TypeOptions, | Jin10TypeOptions, | ||||
| } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | |||||
| } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const Jin10Form = ({ onValuesChange, form, node }: IOperatorForm) => { | const Jin10Form = ({ onValuesChange, form, node }: IOperatorForm) => { |
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { useCallback, useMemo } from 'react'; | import { useCallback, useMemo } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { INextOperatorForm } from '../../interface'; | |||||
| import { | import { | ||||
| QWeatherLangOptions, | QWeatherLangOptions, | ||||
| QWeatherTimePeriodOptions, | QWeatherTimePeriodOptions, | ||||
| QWeatherTypeOptions, | QWeatherTypeOptions, | ||||
| QWeatherUserTypeOptions, | QWeatherUserTypeOptions, | ||||
| } from '../../constant'; | |||||
| import { INextOperatorForm } from '../../interface'; | |||||
| } from '../../options'; | |||||
| import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | ||||
| enum FormFieldName { | enum FormFieldName { |
| import { Textarea } from '@/components/ui/textarea'; | import { Textarea } from '@/components/ui/textarea'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { useForm } from 'react-hook-form'; | |||||
| import { useForm, useFormContext } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { initialRetrievalValues } from '../../constant'; | import { initialRetrievalValues } from '../../constant'; | ||||
| import { QueryVariable } from '../components/query-variable'; | import { QueryVariable } from '../components/query-variable'; | ||||
| import { useValues } from './use-values'; | import { useValues } from './use-values'; | ||||
| const FormSchema = z.object({ | |||||
| query: z.string().optional(), | |||||
| export const RetrievalPartialSchema = { | |||||
| similarity_threshold: z.coerce.number(), | similarity_threshold: z.coerce.number(), | ||||
| keywords_similarity_weight: z.coerce.number(), | keywords_similarity_weight: z.coerce.number(), | ||||
| top_n: z.coerce.number(), | top_n: z.coerce.number(), | ||||
| kb_ids: z.array(z.string()), | kb_ids: z.array(z.string()), | ||||
| rerank_id: z.string(), | rerank_id: z.string(), | ||||
| empty_response: z.string(), | empty_response: z.string(), | ||||
| }; | |||||
| export const FormSchema = z.object({ | |||||
| query: z.string().optional(), | |||||
| ...RetrievalPartialSchema, | |||||
| }); | }); | ||||
| const RetrievalForm = ({ node }: INextOperatorForm) => { | |||||
| export function EmptyResponseField() { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const form = useFormContext(); | |||||
| return ( | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="empty_response" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('chat.emptyResponse')}</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea | |||||
| placeholder={t('common.namePlaceholder')} | |||||
| {...field} | |||||
| autoComplete="off" | |||||
| rows={4} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| } | |||||
| const RetrievalForm = ({ node }: INextOperatorForm) => { | |||||
| const outputList = useMemo(() => { | const outputList = useMemo(() => { | ||||
| return [ | return [ | ||||
| { | { | ||||
| ></SimilaritySliderFormField> | ></SimilaritySliderFormField> | ||||
| <TopNFormField></TopNFormField> | <TopNFormField></TopNFormField> | ||||
| <RerankFormFields></RerankFormFields> | <RerankFormFields></RerankFormFields> | ||||
| <FormField | |||||
| control={form.control} | |||||
| name="empty_response" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('chat.emptyResponse')}</FormLabel> | |||||
| <FormControl> | |||||
| <Textarea | |||||
| placeholder={t('common.namePlaceholder')} | |||||
| {...field} | |||||
| autoComplete="off" | |||||
| rows={4} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <EmptyResponseField></EmptyResponseField> | |||||
| </FormContainer> | </FormContainer> | ||||
| <Output list={outputList}></Output> | <Output list={outputList}></Output> | ||||
| </form> | </form> |
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { GoogleLanguageOptions } from '../../constant'; | |||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import { GoogleLanguageOptions } from '../../options'; | |||||
| const RewriteQuestionForm = ({ form }: INextOperatorForm) => { | const RewriteQuestionForm = ({ form }: INextOperatorForm) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); |
| import GoogleForm from '../google-form'; | 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 WikipediaForm from '../wikipedia-form'; | import WikipediaForm from '../wikipedia-form'; | ||||
| import YahooFinanceForm from '../yahoo-finance-form'; | import YahooFinanceForm from '../yahoo-finance-form'; | ||||
| import RetrievalForm from './retrieval-form'; | |||||
| import TavilyForm from './tavily-form'; | import TavilyForm from './tavily-form'; | ||||
| export const ToolFormConfigMap = { | export const ToolFormConfigMap = { |
| import { FormContainer } from '@/components/form-container'; | |||||
| import { KnowledgeBaseFormField } from '@/components/knowledge-base-item'; | |||||
| import { RerankFormFields } from '@/components/rerank'; | |||||
| import { SimilaritySliderFormField } from '@/components/similarity-slider'; | |||||
| import { TopNFormField } from '@/components/top-n-item'; | |||||
| import { Form } from '@/components/ui/form'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { z } from 'zod'; | |||||
| import { DescriptionField } from '../../components/description-field'; | |||||
| import { | |||||
| EmptyResponseField, | |||||
| RetrievalPartialSchema, | |||||
| } from '../../retrieval-form/next'; | |||||
| import { useValues } from '../use-values'; | |||||
| import { useWatchFormChange } from '../use-watch-change'; | |||||
| export const FormSchema = z.object({ | |||||
| ...RetrievalPartialSchema, | |||||
| description: z.string().optional(), | |||||
| }); | |||||
| const RetrievalForm = () => { | |||||
| const defaultValues = useValues(); | |||||
| const form = useForm({ | |||||
| defaultValues: defaultValues, | |||||
| resolver: zodResolver(FormSchema), | |||||
| }); | |||||
| useWatchFormChange(form); | |||||
| return ( | |||||
| <Form {...form}> | |||||
| <form | |||||
| className="space-y-6 p-4" | |||||
| onSubmit={(e) => { | |||||
| e.preventDefault(); | |||||
| }} | |||||
| > | |||||
| <FormContainer> | |||||
| <DescriptionField></DescriptionField> | |||||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||||
| </FormContainer> | |||||
| <FormContainer> | |||||
| <SimilaritySliderFormField | |||||
| vectorSimilarityWeightName="keywords_similarity_weight" | |||||
| isTooltipShown | |||||
| ></SimilaritySliderFormField> | |||||
| <TopNFormField></TopNFormField> | |||||
| <RerankFormFields></RerankFormFields> | |||||
| <EmptyResponseField></EmptyResponseField> | |||||
| </FormContainer> | |||||
| </form> | |||||
| </Form> | |||||
| ); | |||||
| }; | |||||
| export default RetrievalForm; |
| import { isEmpty } from 'lodash'; | import { isEmpty } from 'lodash'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { DefaultAgentToolValuesMap } from '../../constant'; | |||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| import { getAgentNodeTools } from '../../utils'; | import { getAgentNodeTools } from '../../utils'; | ||||
| General = 'general', | General = 'general', | ||||
| } | } | ||||
| export const defaultValues = { | |||||
| api_key: '', | |||||
| }; | |||||
| export function useValues() { | export function useValues() { | ||||
| const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore( | const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore( | ||||
| (state) => state, | (state) => state, | ||||
| )?.params; | )?.params; | ||||
| if (isEmpty(formData)) { | if (isEmpty(formData)) { | ||||
| const defaultValues = | |||||
| DefaultAgentToolValuesMap[ | |||||
| clickedToolId as keyof typeof DefaultAgentToolValuesMap | |||||
| ]; | |||||
| return defaultValues; | return defaultValues; | ||||
| } | } | ||||
| import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd'; | import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import { useCallback, useMemo } from 'react'; | import { useCallback, useMemo } from 'react'; | ||||
| import { TuShareSrcOptions } from '../../constant'; | |||||
| import { IOperatorForm } from '../../interface'; | import { IOperatorForm } from '../../interface'; | ||||
| import { TuShareSrcOptions } from '../../options'; | |||||
| import DynamicInputVariable from '../components/dynamic-input-variable'; | import DynamicInputVariable from '../components/dynamic-input-variable'; | ||||
| const DateTimePicker = ({ | const DateTimePicker = ({ |
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { WenCaiQueryTypeOptions } from '../../constant'; | |||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import { WenCaiQueryTypeOptions } from '../../options'; | |||||
| import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | ||||
| const WenCaiForm = ({ form, node }: INextOperatorForm) => { | const WenCaiForm = ({ form, node }: INextOperatorForm) => { |
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { LanguageOptions } from '../../constant'; | |||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import { LanguageOptions } from '../../options'; | |||||
| import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; | ||||
| const WikipediaForm = ({ form, node }: INextOperatorForm) => { | const WikipediaForm = ({ form, node }: INextOperatorForm) => { |
| } | } | ||||
| function useAddToolNode() { | function useAddToolNode() { | ||||
| const addNode = useGraphStore((state) => state.addNode); | |||||
| const getNode = useGraphStore((state) => state.getNode); | |||||
| const addEdge = useGraphStore((state) => state.addEdge); | |||||
| const edges = useGraphStore((state) => state.edges); | |||||
| const nodes = useGraphStore((state) => state.nodes); | |||||
| const { nodes, edges, addEdge, getNode, addNode } = useGraphStore( | |||||
| (state) => state, | |||||
| ); | |||||
| const addToolNode = useCallback( | const addToolNode = useCallback( | ||||
| (newNode: Node<any>, nodeId?: string) => { | (newNode: Node<any>, nodeId?: string) => { |