diff --git a/web/src/hooks/logic-hooks/use-build-operator-options.tsx b/web/src/hooks/logic-hooks/use-build-operator-options.tsx index 82e3ed79d..f1d2f3619 100644 --- a/web/src/hooks/logic-hooks/use-build-operator-options.tsx +++ b/web/src/hooks/logic-hooks/use-build-operator-options.tsx @@ -13,7 +13,7 @@ export const LogicalOperatorIcon = function OperatorIcon({ ', + 'rotate-180': value === ComparisonOperator.GreatThan, })} > ); diff --git a/web/src/pages/agent/canvas/node/dropdown/accordion-operators.tsx b/web/src/pages/agent/canvas/node/dropdown/accordion-operators.tsx index 232ab78ff..1ab0dde1f 100644 --- a/web/src/pages/agent/canvas/node/dropdown/accordion-operators.tsx +++ b/web/src/pages/agent/canvas/node/dropdown/accordion-operators.tsx @@ -79,7 +79,7 @@ export function AccordionOperators({ Operator.Code, Operator.StringTransform, Operator.DataOperations, - // Operator.VariableAssigner, + Operator.VariableAssigner, Operator.VariableAggregator, ]} isCustomDropdown={isCustomDropdown} diff --git a/web/src/pages/agent/constant/index.tsx b/web/src/pages/agent/constant/index.tsx index 45341abf4..53faccdb3 100644 --- a/web/src/pages/agent/constant/index.tsx +++ b/web/src/pages/agent/constant/index.tsx @@ -814,3 +814,29 @@ export enum JsonSchemaDataType { Array = 'array', Object = 'object', } + +export enum VariableAssignerLogicalOperator { + Overwrite = 'overwrite', + Clear = 'clear', + Set = 'set', +} + +export enum VariableAssignerLogicalNumberOperator { + Overwrite = VariableAssignerLogicalOperator.Overwrite, + Clear = VariableAssignerLogicalOperator.Clear, + Set = VariableAssignerLogicalOperator.Set, + Add = '+=', + Subtract = '-=', + Multiply = '*=', + Divide = '/=', +} + +export enum VariableAssignerLogicalArrayOperator { + Overwrite = VariableAssignerLogicalOperator.Overwrite, + Clear = VariableAssignerLogicalOperator.Clear, + Set = VariableAssignerLogicalOperator.Set, + Append = 'append', + Extend = 'extend', + RemoveFirst = 'remove_first', + RemoveLast = 'remove_last', +} diff --git a/web/src/pages/agent/form/components/structured-output-secondary-menu.tsx b/web/src/pages/agent/form/components/structured-output-secondary-menu.tsx index f2ecd9191..2e7a34390 100644 --- a/web/src/pages/agent/form/components/structured-output-secondary-menu.tsx +++ b/web/src/pages/agent/form/components/structured-output-secondary-menu.tsx @@ -10,10 +10,7 @@ import { PropsWithChildren, ReactNode, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { JsonSchemaDataType } from '../../constant'; import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output'; -import { - hasJsonSchemaChild, - hasSpecificTypeChild, -} from '../../utils/filter-agent-structured-output'; +import { hasSpecificTypeChild } from '../../utils/filter-agent-structured-output'; type DataItem = { label: ReactNode; value: string; parentLabel?: ReactNode }; @@ -101,8 +98,9 @@ export function StructuredOutputSecondaryMenu({ ); if ( - !hasJsonSchemaChild(structuredOutput) || - (!isEmpty(types) && !hasSpecificTypeChild(structuredOutput, types)) + !isEmpty(types) && + !hasSpecificTypeChild(structuredOutput, types) && + !types.some((x) => x === JsonSchemaDataType.Object) ) { return null; } diff --git a/web/src/pages/agent/form/variable-assigner-form/dynamic-variables.tsx b/web/src/pages/agent/form/variable-assigner-form/dynamic-variables.tsx new file mode 100644 index 000000000..c97c29cdd --- /dev/null +++ b/web/src/pages/agent/form/variable-assigner-form/dynamic-variables.tsx @@ -0,0 +1,177 @@ +import NumberInput from '@/components/originui/number-input'; +import { SelectWithSearch } from '@/components/originui/select-with-search'; +import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Separator } from '@/components/ui/separator'; +import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; +import { X } from 'lucide-react'; +import { ReactNode, useCallback } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import { + JsonSchemaDataType, + VariableAssignerLogicalNumberOperator, + VariableAssignerLogicalOperator, +} from '../../constant'; +import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query'; +import { DynamicFormHeader } from '../components/dynamic-fom-header'; +import { PromptEditor } from '../components/prompt-editor'; +import { QueryVariable } from '../components/query-variable'; +import { useBuildLogicalOptions } from './use-build-logical-options'; + +type SelectKeysProps = { + name: string; + label: ReactNode; + tooltip?: string; + keyField?: string; + valueField?: string; + operatorField?: string; +}; + +type RadioGroupProps = React.ComponentProps; + +type RadioButtonProps = Partial< + Omit & { + onChange: RadioGroupProps['onValueChange']; + } +>; + +function RadioButton({ value, onChange }: RadioButtonProps) { + return ( + +
+ + +
+
+ + +
+
+ ); +} + +export function DynamicVariables({ + name, + label, + tooltip, + keyField = 'variable', + valueField = 'parameter', + operatorField = 'operator', +}: SelectKeysProps) { + const form = useFormContext(); + const { getType } = useGetVariableLabelOrTypeByValue(); + + const { fields, remove, append } = useFieldArray({ + name: name, + control: form.control, + }); + + const { buildLogicalOptions } = useBuildLogicalOptions(); + + const getVariableType = useCallback( + (keyFieldName: string) => { + const key = form.getValues(keyFieldName); + return getType(key); + }, + [form, getType], + ); + + const renderParameter = useCallback( + (keyFieldName: string, operatorFieldName: string) => { + const logicalOperator = form.getValues(operatorFieldName); + + if (logicalOperator === VariableAssignerLogicalOperator.Clear) { + return null; + } else if ( + logicalOperator === VariableAssignerLogicalOperator.Overwrite + ) { + return ; + } else if (logicalOperator === VariableAssignerLogicalOperator.Set) { + const type = getVariableType(keyFieldName); + + if (type === JsonSchemaDataType.Boolean) { + return ; + } + } else if ( + Object.values(VariableAssignerLogicalNumberOperator).some( + (x) => logicalOperator === x, + ) + ) { + return ; + } + }, + [form, getVariableType], + ); + + const handleVariableChange = useCallback( + (operatorFieldAlias: string, valueFieldAlias: string) => () => { + form.setValue( + operatorFieldAlias, + VariableAssignerLogicalOperator.Overwrite, + ); + form.setValue(valueFieldAlias, undefined); + }, + [form], + ); + + return ( +
+ append({ [keyField]: '', [valueField]: '' })} + > + +
+ {fields.map((field, index) => { + const keyFieldAlias = `${name}.${index}.${keyField}`; + const valueFieldAlias = `${name}.${index}.${valueField}`; + const operatorFieldAlias = `${name}.${index}.${operatorField}`; + + return ( +
+
+
+ + + + + + + +
+ + {renderParameter(keyFieldAlias, operatorFieldAlias)} + +
+ + +
+ ); + })} +
+
+ ); +} diff --git a/web/src/pages/agent/form/variable-assigner-form/index.tsx b/web/src/pages/agent/form/variable-assigner-form/index.tsx index 97695f877..9ad4c3b8e 100644 --- a/web/src/pages/agent/form/variable-assigner-form/index.tsx +++ b/web/src/pages/agent/form/variable-assigner-form/index.tsx @@ -1,97 +1,48 @@ -import { SelectWithSearch } from '@/components/originui/select-with-search'; -import { RAGFlowFormItem } from '@/components/ragflow-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'; import { useForm } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { - JsonSchemaDataType, - Operations, - initialDataOperationsValues, -} from '../../constant'; +import { initialDataOperationsValues } from '../../constant'; import { useFormValues } from '../../hooks/use-form-values'; 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, OutputSchema } from '../components/output'; -import { QueryVariableList } from '../components/query-variable-list'; +import { DynamicVariables } from './dynamic-variables'; -export const RetrievalPartialSchema = { - 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(), - updates: z - .array( - z.object({ key: z.string().optional(), value: z.string().optional() }), - ) - .optional(), - rename_keys: z - .array( - z.object({ - old_key: z.string().optional(), - new_key: z.string().optional(), - }), - ) - .optional(), - filter_values: z - .array( - z.object({ - key: z.string().optional(), - value: z.string().optional(), - operator: z.string().optional(), - }), - ) - .optional(), - ...OutputSchema, +export const VariableAssignerSchema = { + variables: z.array( + z.object({ + variable: z.string().optional(), + operator: z.string().optional(), + parameter: z.string().or(z.number()).optional(), + }), + ), }; -export const FormSchema = z.object(RetrievalPartialSchema); +export const FormSchema = z.object(VariableAssignerSchema); -export type DataOperationsFormSchemaType = z.infer; +export type VariableAssignerFormSchemaType = z.infer; -const outputList = buildOutputList(initialDataOperationsValues.outputs); +// const outputList = buildOutputList(initialVariableAssignerValues.outputs); function VariableAssignerForm({ node }: INextOperatorForm) { - const { t } = useTranslation(); - const defaultValues = useFormValues(initialDataOperationsValues, node); - const form = useForm({ + const form = useForm({ defaultValues: defaultValues, mode: 'onChange', resolver: zodResolver(FormSchema), shouldUnregister: true, }); - const OperationsOptions = buildOptions( - Operations, - t, - `flow.operationsOptions`, - true, - ); - useWatchFormChange(node?.id, form, true); return (
- - - - - - - + + {/* */}
); diff --git a/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts b/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts new file mode 100644 index 000000000..a27ea20d5 --- /dev/null +++ b/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts @@ -0,0 +1,28 @@ +import { buildOptions } from '@/utils/form'; +import { useCallback } from 'react'; +import { + JsonSchemaDataType, + VariableAssignerLogicalArrayOperator, + VariableAssignerLogicalNumberOperator, + VariableAssignerLogicalOperator, +} from '../../constant'; + +export function useBuildLogicalOptions() { + const buildLogicalOptions = useCallback((type: string) => { + if ( + type?.toLowerCase().startsWith(JsonSchemaDataType.Array.toLowerCase()) + ) { + return buildOptions(VariableAssignerLogicalArrayOperator); + } + + if (type === JsonSchemaDataType.Number) { + return buildOptions(VariableAssignerLogicalNumberOperator); + } + + return buildOptions(VariableAssignerLogicalOperator); + }, []); + + return { + buildLogicalOptions, + }; +}