mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### 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:
@ -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';
|
||||
|
||||
@ -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" />,
|
||||
},
|
||||
];
|
||||
45
web/src/hooks/logic-hooks/use-build-operator-options.tsx
Normal file
45
web/src/hooks/logic-hooks/use-build-operator-options.tsx
Normal 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;
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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: {
|
||||
|
||||
25
web/src/pages/agent/form/components/dynamic-fom-header.tsx
Normal file
25
web/src/pages/agent/form/components/dynamic-fom-header.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,28 +22,31 @@ export function QueryVariableList({ types }: QueryVariableListProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{fields.map((field, index) => {
|
||||
const nameField = `${name}.${index}.input`;
|
||||
<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`;
|
||||
|
||||
return (
|
||||
<div key={field.id} className="flex items-center gap-2">
|
||||
<QueryVariable
|
||||
name={nameField}
|
||||
hideLabel
|
||||
className="flex-1"
|
||||
types={types}
|
||||
></QueryVariable>
|
||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||
<X className="text-text-sub-title-invert " />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<BlockButton onClick={() => append({ input: '' })}>
|
||||
{t('common.add')}
|
||||
</BlockButton>
|
||||
</div>
|
||||
return (
|
||||
<div key={field.id} className="flex items-center gap-2">
|
||||
<QueryVariable
|
||||
name={nameField}
|
||||
hideLabel
|
||||
className="flex-1"
|
||||
types={types}
|
||||
></QueryVariable>
|
||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||
<X className="text-text-sub-title-invert " />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
types={[JsonSchemaDataType.Array, JsonSchemaDataType.Object]}
|
||||
></QueryVariableList>
|
||||
</div>
|
||||
<QueryVariableList
|
||||
tooltip={t('flow.queryTip')}
|
||||
label={t('flow.query')}
|
||||
types={[JsonSchemaDataType.Array, JsonSchemaDataType.Object]}
|
||||
></QueryVariableList>
|
||||
<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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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';
|
||||
|
||||
Reference in New Issue
Block a user