Feat: Submit clean data operations form data to the backend. #10427 (#11030)

### What problem does this PR solve?

Feat: Submit clean data operations form data to the backend. #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-11-05 17:32:35 +08:00
committed by GitHub
parent 24335485bf
commit 4e76220e25
18 changed files with 268 additions and 155 deletions

View File

@ -15,10 +15,10 @@ import {
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { SwitchOperatorOptions } from '@/constants/agent';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
import { SwitchOperatorOptions } from '@/pages/agent/constant';
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
import { useBuildSwitchOperatorOptions } from '@/pages/agent/form/switch-form';
import { Plus, X } from 'lucide-react';
import { useCallback } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';

View File

@ -1,4 +1,5 @@
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
import { Circle, CircleSlash2 } from 'lucide-react';
import { ChatVariableEnabledField, variableEnabledFieldMap } from './chat';
export enum ProgrammingLanguage {
@ -117,3 +118,53 @@ export enum Operator {
HierarchicalMerger = 'HierarchicalMerger',
Extractor = 'Extractor',
}
export enum ComparisonOperator {
Equal = '=',
NotEqual = '≠',
GreatThan = '>',
GreatEqual = '≥',
LessThan = '<',
LessEqual = '≤',
Contains = 'contains',
NotContains = 'not contains',
StartWith = 'start with',
EndWith = 'end with',
Empty = 'empty',
NotEmpty = 'not empty',
}
export const SwitchOperatorOptions = [
{ value: ComparisonOperator.Equal, label: 'equal', icon: 'equal' },
{ value: ComparisonOperator.NotEqual, label: 'notEqual', icon: 'not-equals' },
{ value: ComparisonOperator.GreatThan, label: 'gt', icon: 'Less' },
{
value: ComparisonOperator.GreatEqual,
label: 'ge',
icon: 'Greater-or-equal',
},
{ value: ComparisonOperator.LessThan, label: 'lt', icon: 'Less' },
{ value: ComparisonOperator.LessEqual, label: 'le', icon: 'less-or-equal' },
{ value: ComparisonOperator.Contains, label: 'contains', icon: 'Contains' },
{
value: ComparisonOperator.NotContains,
label: 'notContains',
icon: 'not-contains',
},
{
value: ComparisonOperator.StartWith,
label: 'startWith',
icon: 'list-start',
},
{ value: ComparisonOperator.EndWith, label: 'endWith', icon: 'list-end' },
{
value: ComparisonOperator.Empty,
label: 'empty',
icon: <Circle className="size-4" />,
},
{
value: ComparisonOperator.NotEmpty,
label: 'notEmpty',
icon: <CircleSlash2 className="size-4" />,
},
];

View File

@ -0,0 +1,45 @@
import { IconFont } from '@/components/icon-font';
import { ComparisonOperator, SwitchOperatorOptions } from '@/constants/agent';
import { cn } from '@/lib/utils';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const LogicalOperatorIcon = function OperatorIcon({
icon,
value,
}: Omit<(typeof SwitchOperatorOptions)[0], 'label'>) {
if (typeof icon === 'string') {
return (
<IconFont
name={icon}
className={cn('size-4', {
'rotate-180': value === '>',
})}
></IconFont>
);
}
return icon;
};
export function useBuildSwitchOperatorOptions(
subset: ComparisonOperator[] = [],
) {
const { t } = useTranslation();
const switchOperatorOptions = useMemo(() => {
return SwitchOperatorOptions.filter((x) =>
subset.some((y) => y === x.value),
).map((x) => ({
value: x.value,
icon: (
<LogicalOperatorIcon
icon={x.icon}
value={x.value}
></LogicalOperatorIcon>
),
label: t(`flow.switchOperatorOptions.${x.label}`),
}));
}, [t]);
return switchOperatorOptions;
}

View File

@ -1,9 +1,9 @@
import { Card, CardContent } from '@/components/ui/card';
import { SwitchOperatorOptions } from '@/constants/agent';
import { LogicalOperatorIcon } from '@/hooks/logic-hooks/use-build-operator-options';
import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
import { NodeProps, Position } from '@xyflow/react';
import { memo, useCallback } from 'react';
import { SwitchOperatorOptions } from '../../constant';
import { LogicalOperatorIcon } from '../../form/switch-form';
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
import { CommonHandle, LeftEndHandle } from './handle';
import { RightHandleStyle } from './handle-icon';

View File

@ -6,8 +6,10 @@ import {
AgentGlobals,
AgentGlobalsSysQueryWithBrace,
CodeTemplateStrMap,
ComparisonOperator,
Operator,
ProgrammingLanguage,
SwitchOperatorOptions,
initialLlmBaseValues,
} from '@/constants/agent';
export { Operator } from '@/constants/agent';
@ -35,8 +37,6 @@ export enum PromptRole {
}
import {
Circle,
CircleSlash2,
CloudUpload,
ListOrdered,
OptionIcon,
@ -166,27 +166,12 @@ export const componentMenuList = [
},
];
export const SwitchOperatorOptions = [
{ value: '=', label: 'equal', icon: 'equal' },
{ value: '≠', label: 'notEqual', icon: 'not-equals' },
{ value: '>', label: 'gt', icon: 'Less' },
{ value: '≥', label: 'ge', icon: 'Greater-or-equal' },
{ value: '<', label: 'lt', icon: 'Less' },
{ value: '≤', label: 'le', icon: 'less-or-equal' },
{ value: 'contains', label: 'contains', icon: 'Contains' },
{ value: 'not contains', label: 'notContains', icon: 'not-contains' },
{ value: 'start with', label: 'startWith', icon: 'list-start' },
{ value: 'end with', label: 'endWith', icon: 'list-end' },
{
value: 'empty',
label: 'empty',
icon: <Circle className="size-4" />,
},
{
value: 'not empty',
label: 'notEmpty',
icon: <CircleSlash2 className="size-4" />,
},
export const DataOperationsOperatorOptions = [
ComparisonOperator.Equal,
ComparisonOperator.NotEqual,
ComparisonOperator.Contains,
ComparisonOperator.StartWith,
ComparisonOperator.EndWith,
];
export const SwitchElseTo = 'end_cpn_ids';
@ -716,16 +701,17 @@ export const initialPlaceholderValues = {
};
export enum Operations {
SelectKeys = 'select keys',
LiteralEval = 'literal eval',
SelectKeys = 'select_keys',
LiteralEval = 'literal_eval',
Combine = 'combine',
FilterValues = 'filter values',
AppendOrUpdate = 'append or update',
RemoveKeys = 'remove keys',
RenameKeys = 'rename keys',
FilterValues = 'filter_values',
AppendOrUpdate = 'append_or_update',
RemoveKeys = 'remove_keys',
RenameKeys = 'rename_keys',
}
export const initialDataOperationsValues = {
query: [],
operations: Operations.SelectKeys,
outputs: {
result: {

View File

@ -0,0 +1,25 @@
import { Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Plus } from 'lucide-react';
import { ReactNode } from 'react';
export type FormListHeaderProps = {
label: ReactNode;
tooltip?: string;
onClick?: () => void;
};
export function DynamicFormHeader({
label,
tooltip,
onClick,
}: FormListHeaderProps) {
return (
<div className="flex items-center justify-between">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<Button variant={'ghost'} type="button" onClick={onClick}>
<Plus />
</Button>
</div>
);
}

View File

@ -1,4 +1,7 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Input } from '@/components/ui/input';
import { t } from 'i18next';
import { z } from 'zod';
export type OutputType = {
title: string;
@ -7,6 +10,7 @@ export type OutputType = {
type OutputProps = {
list: Array<OutputType>;
isFormRequired?: boolean;
};
export function transferOutputs(outputs: Record<string, any>) {
@ -16,7 +20,11 @@ export function transferOutputs(outputs: Record<string, any>) {
}));
}
export function Output({ list }: OutputProps) {
export const OutputSchema = {
outputs: z.record(z.any()),
};
export function Output({ list, isFormRequired = false }: OutputProps) {
return (
<section className="space-y-2">
<div className="text-sm">{t('flow.output')}</div>
@ -30,6 +38,11 @@ export function Output({ list }: OutputProps) {
</li>
))}
</ul>
{isFormRequired && (
<RAGFlowFormItem name="outputs" className="hidden">
<Input></Input>
</RAGFlowFormItem>
)}
</section>
);
}

View File

@ -1,17 +1,20 @@
import { BlockButton, Button } from '@/components/ui/button';
import { Button } from '@/components/ui/button';
import { X } from 'lucide-react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { JsonSchemaDataType } from '../../constant';
import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header';
import { QueryVariable } from './query-variable';
type QueryVariableListProps = {
types?: JsonSchemaDataType[];
};
export function QueryVariableList({ types }: QueryVariableListProps) {
const { t } = useTranslation();
} & FormListHeaderProps;
export function QueryVariableList({
types,
label,
tooltip,
}: QueryVariableListProps) {
const form = useFormContext();
const name = 'inputs';
const name = 'query';
const { fields, remove, append } = useFieldArray({
name: name,
@ -19,6 +22,12 @@ export function QueryVariableList({ types }: QueryVariableListProps) {
});
return (
<section className="space-y-2">
<DynamicFormHeader
label={label}
tooltip={tooltip}
onClick={() => append({ input: '' })}
></DynamicFormHeader>
<div className="space-y-5">
{fields.map((field, index) => {
const nameField = `${name}.${index}.input`;
@ -37,10 +46,7 @@ export function QueryVariableList({ types }: QueryVariableListProps) {
</div>
);
})}
<BlockButton onClick={() => append({ input: '' })}>
{t('common.add')}
</BlockButton>
</div>
</section>
);
}

View File

@ -1,13 +1,14 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { DataOperationsOperatorOptions } from '../../constant';
import { DynamicFormHeader } from '../components/dynamic-fom-header';
type SelectKeysProps = {
name: string;
@ -25,7 +26,6 @@ export function FilterValues({
valueField = 'value',
operatorField = 'operator',
}: SelectKeysProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
@ -33,9 +33,18 @@ export function FilterValues({
control: form.control,
});
const operatorOptions = useBuildSwitchOperatorOptions(
DataOperationsOperatorOptions,
);
return (
<section className="space-y-2">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<DynamicFormHeader
label={label}
tooltip={tooltip}
onClick={() => append({ [keyField]: '', [valueField]: '' })}
></DynamicFormHeader>
<div className="space-y-5">
{fields.map((field, index) => {
const keyFieldAlias = `${name}.${index}.${keyField}`;
@ -47,12 +56,15 @@ export function FilterValues({
<RAGFlowFormItem name={keyFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<Separator orientation="vertical" className="h-2.5" />
<Separator className="w-2" />
<RAGFlowFormItem name={operatorFieldAlias} className="flex-1">
<SelectWithSearch {...field} options={[]}></SelectWithSearch>
<SelectWithSearch
{...field}
options={operatorOptions}
></SelectWithSearch>
</RAGFlowFormItem>
<Separator orientation="vertical" className="h-2.5" />
<Separator className="w-2" />
<RAGFlowFormItem name={valueFieldAlias} className="flex-1">
<Input></Input>
@ -64,10 +76,6 @@ export function FilterValues({
);
})}
</div>
<BlockButton onClick={() => append({ [keyField]: '', [valueField]: '' })}>
{t('common.add')}
</BlockButton>
</section>
);
}

View File

@ -1,6 +1,7 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Form, FormLabel } from '@/components/ui/form';
import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
@ -17,14 +18,14 @@ import { useWatchFormChange } from '../../hooks/use-watch-form-change';
import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import { Output, OutputSchema } from '../components/output';
import { QueryVariableList } from '../components/query-variable-list';
import { FilterValues } from './filter-values';
import { SelectKeys } from './select-keys';
import { Updates } from './updates';
export const RetrievalPartialSchema = {
inputs: z.array(z.object({ input: z.string().optional() })),
query: z.array(z.object({ input: z.string().optional() })),
operations: z.string(),
select_keys: z.array(z.object({ name: z.string().optional() })).optional(),
remove_keys: z.array(z.object({ name: z.string().optional() })).optional(),
@ -50,6 +51,7 @@ export const RetrievalPartialSchema = {
}),
)
.optional(),
...OutputSchema,
};
export const FormSchema = z.object(RetrievalPartialSchema);
@ -65,6 +67,7 @@ function DataOperationsForm({ node }: INextOperatorForm) {
const form = useForm<DataOperationsFormSchemaType>({
defaultValues: defaultValues,
mode: 'onChange',
resolver: zodResolver(FormSchema),
shouldUnregister: true,
});
@ -78,17 +81,17 @@ function DataOperationsForm({ node }: INextOperatorForm) {
true,
);
useWatchFormChange(node?.id, form);
useWatchFormChange(node?.id, form, true);
return (
<Form {...form}>
<FormWrapper>
<div className="space-y-2">
<FormLabel tooltip={t('flow.queryTip')}>{t('flow.query')}</FormLabel>
<QueryVariableList
tooltip={t('flow.queryTip')}
label={t('flow.query')}
types={[JsonSchemaDataType.Array, JsonSchemaDataType.Object]}
></QueryVariableList>
</div>
<Separator />
<RAGFlowFormItem name="operations" label={t('flow.operations')}>
<SelectWithSearch options={OperationsOptions} allowClear />
</RAGFlowFormItem>
@ -107,7 +110,7 @@ function DataOperationsForm({ node }: INextOperatorForm) {
{operations === Operations.AppendOrUpdate && (
<Updates
name="updates"
label={t('flow.operationsOptions.updates')}
label={t('flow.operationsOptions.appendOrUpdate')}
keyField="key"
valueField="value"
></Updates>
@ -126,7 +129,7 @@ function DataOperationsForm({ node }: INextOperatorForm) {
label={t('flow.operationsOptions.filterValues')}
></FilterValues>
)}
<Output list={outputList}></Output>
<Output list={outputList} isFormRequired></Output>
</FormWrapper>
</Form>
);

View File

@ -1,11 +1,10 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { DynamicFormHeader } from '../components/dynamic-fom-header';
type SelectKeysProps = {
name: string;
@ -13,7 +12,6 @@ type SelectKeysProps = {
tooltip?: string;
};
export function SelectKeys({ name, label, tooltip }: SelectKeysProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
@ -23,7 +21,11 @@ export function SelectKeys({ name, label, tooltip }: SelectKeysProps) {
return (
<section className="space-y-2">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<DynamicFormHeader
label={label}
tooltip={tooltip}
onClick={() => append({ name: '' })}
></DynamicFormHeader>
<div className="space-y-5">
{fields.map((field, index) => {
const nameField = `${name}.${index}.name`;
@ -40,10 +42,6 @@ export function SelectKeys({ name, label, tooltip }: SelectKeysProps) {
);
})}
</div>
<BlockButton onClick={() => append({ name: '' })}>
{t('common.add')}
</BlockButton>
</section>
);
}

View File

@ -1,11 +1,11 @@
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { DynamicFormHeader } from '../components/dynamic-fom-header';
type SelectKeysProps = {
name: string;
@ -21,7 +21,6 @@ export function Updates({
keyField,
valueField,
}: SelectKeysProps) {
const { t } = useTranslation();
const form = useFormContext();
const { fields, remove, append } = useFieldArray({
@ -31,7 +30,11 @@ export function Updates({
return (
<section className="space-y-2">
<FormLabel tooltip={tooltip}>{label}</FormLabel>
<DynamicFormHeader
label={label}
tooltip={tooltip}
onClick={() => append({ [keyField]: '', [valueField]: '' })}
></DynamicFormHeader>
<div className="space-y-5">
{fields.map((field, index) => {
const keyFieldAlias = `${name}.${index}.${keyField}`;
@ -42,6 +45,7 @@ export function Updates({
<RAGFlowFormItem name={keyFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
<Separator className="w-2" />
<RAGFlowFormItem name={valueFieldAlias} className="flex-1">
<Input></Input>
</RAGFlowFormItem>
@ -52,10 +56,6 @@ export function Updates({
);
})}
</div>
<BlockButton onClick={() => append({ [keyField]: '', [valueField]: '' })}>
{t('common.add')}
</BlockButton>
</section>
);
}

View File

@ -1,5 +1,4 @@
import { FormContainer } from '@/components/form-container';
import { IconFont } from '@/components/icon-font';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { BlockButton, Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
@ -13,6 +12,7 @@ import {
import { RAGFlowSelect } from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { cn } from '@/lib/utils';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
@ -22,11 +22,7 @@ import { memo, useCallback, useMemo } from 'react';
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
SwitchLogicOperatorOptions,
SwitchOperatorOptions,
VariableType,
} from '../../constant';
import { SwitchLogicOperatorOptions, VariableType } from '../../constant';
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
import { IOperatorForm } from '../../interface';
import { FormWrapper } from '../components/form-wrapper';
@ -43,42 +39,6 @@ type ConditionCardsProps = {
parentLength: number;
} & IOperatorForm;
export const LogicalOperatorIcon = function OperatorIcon({
icon,
value,
}: Omit<(typeof SwitchOperatorOptions)[0], 'label'>) {
if (typeof icon === 'string') {
return (
<IconFont
name={icon}
className={cn('size-4', {
'rotate-180': value === '>',
})}
></IconFont>
);
}
return icon;
};
export function useBuildSwitchOperatorOptions() {
const { t } = useTranslation();
const switchOperatorOptions = useMemo(() => {
return SwitchOperatorOptions.map((x) => ({
value: x.value,
icon: (
<LogicalOperatorIcon
icon={x.icon}
value={x.value}
></LogicalOperatorIcon>
),
label: t(`flow.switchOperatorOptions.${x.label}`),
}));
}, [t]);
return switchOperatorOptions;
}
function ConditionCards({
name: parentName,
parentIndex,

View File

@ -2,9 +2,13 @@ import { useEffect } from 'react';
import { UseFormReturn, useWatch } from 'react-hook-form';
import useGraphStore from '../store';
export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
export function useWatchFormChange(
id?: string,
form?: UseFormReturn<any>,
enableReplacement = false,
) {
let values = useWatch({ control: form?.control });
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
const { updateNodeForm, replaceNodeForm } = useGraphStore((state) => state);
useEffect(() => {
// Manually triggered form updates are synchronized to the canvas
@ -12,7 +16,7 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
values = form?.getValues() || {};
let nextValues: any = values;
updateNodeForm(id, nextValues);
(enableReplacement ? replaceNodeForm : updateNodeForm)(id, nextValues);
}
}, [form?.formState.isDirty, id, updateNodeForm, values]);
}

View File

@ -14,7 +14,7 @@ import {
applyEdgeChanges,
applyNodeChanges,
} from '@xyflow/react';
import { omit } from 'lodash';
import { cloneDeep, omit } from 'lodash';
import differenceWith from 'lodash/differenceWith';
import intersectionWith from 'lodash/intersectionWith';
import lodashSet from 'lodash/set';
@ -53,6 +53,7 @@ export type RFState = {
values: any,
path?: (string | number)[],
) => RAGFlowNodeType[];
replaceNodeForm: (nodeId: string, values: any) => void;
onSelectionChange: OnSelectionChangeFunc;
addNode: (nodes: RAGFlowNodeType) => void;
getNode: (id?: string | null) => RAGFlowNodeType | undefined;
@ -433,6 +434,19 @@ const useGraphStore = create<RFState>()(
return nextNodes;
},
replaceNodeForm(nodeId, values) {
if (nodeId) {
set((state) => {
for (const node of state.nodes) {
if (node.id === nodeId) {
//cloneDeep Solving the issue of react-hook-form errors
node.data.form = cloneDeep(values); // TypeError: Cannot assign to read only property '0' of object '[object Array]'
break;
}
}
});
}
},
updateSwitchFormData: (source, sourceHandle, target, isConnecting) => {
const { updateNodeForm, edges } = get();
if (sourceHandle) {

View File

@ -273,7 +273,7 @@ function transformDataOperationsParams(params: DataOperationsFormSchemaType) {
...params,
select_keys: params?.select_keys?.map((x) => x.name),
remove_keys: params?.remove_keys?.map((x) => x.name),
inputs: params.inputs.map((x) => x.input),
query: params.query.map((x) => x.input),
};
}

View File

@ -1,6 +1,6 @@
import { SwitchOperatorOptions } from '@/constants/agent';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
import { SwitchOperatorOptions } from '@/pages/agent/constant';
import { useBuildSwitchOperatorOptions } from '@/pages/agent/form/switch-form';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import {
Button,

View File

@ -15,9 +15,9 @@ import {
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { SwitchOperatorOptions } from '@/constants/agent';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
import { SwitchOperatorOptions } from '@/pages/agent/constant';
import { useBuildSwitchOperatorOptions } from '@/pages/agent/form/switch-form';
import { Plus, X } from 'lucide-react';
import { useCallback } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';