mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### What problem does this PR solve? Feat: Construct a dynamic variable assignment form #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
1077
web/package-lock.json
generated
1077
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -119,6 +119,7 @@
|
|||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@hookform/devtools": "^4.4.0",
|
||||||
"@react-dev-inspector/umi4-plugin": "^2.0.1",
|
"@react-dev-inspector/umi4-plugin": "^2.0.1",
|
||||||
"@redux-devtools/extension": "^3.3.0",
|
"@redux-devtools/extension": "^3.3.0",
|
||||||
"@storybook/addon-docs": "^9.1.4",
|
"@storybook/addon-docs": "^9.1.4",
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const LogicalOperatorIcon = function OperatorIcon({
|
|||||||
<IconFont
|
<IconFont
|
||||||
name={icon}
|
name={icon}
|
||||||
className={cn('size-4', {
|
className={cn('size-4', {
|
||||||
'rotate-180': value === '>',
|
'rotate-180': value === ComparisonOperator.GreatThan,
|
||||||
})}
|
})}
|
||||||
></IconFont>
|
></IconFont>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -79,8 +79,9 @@ export function AccordionOperators({
|
|||||||
Operator.Code,
|
Operator.Code,
|
||||||
Operator.StringTransform,
|
Operator.StringTransform,
|
||||||
Operator.DataOperations,
|
Operator.DataOperations,
|
||||||
|
Operator.VariableAssigner,
|
||||||
Operator.ListOperations,
|
Operator.ListOperations,
|
||||||
// Operator.VariableAssigner,
|
Operator.VariableAssigner,
|
||||||
Operator.VariableAggregator,
|
Operator.VariableAggregator,
|
||||||
]}
|
]}
|
||||||
isCustomDropdown={isCustomDropdown}
|
isCustomDropdown={isCustomDropdown}
|
||||||
|
|||||||
@ -55,8 +55,8 @@ function InnerRetrievalNode({
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
className="size-6 rounded-lg"
|
className="size-6 rounded-lg"
|
||||||
avatar={id}
|
avatar={item?.avatar}
|
||||||
name={item?.name || (label as string) || 'CN'}
|
name={item ? item?.name : id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={'truncate flex-1'}>{label || item?.name}</div>
|
<div className={'truncate flex-1'}>{label || item?.name}</div>
|
||||||
|
|||||||
@ -847,6 +847,31 @@ export enum JsonSchemaDataType {
|
|||||||
Object = 'object',
|
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,
|
||||||
|
Append = 'append',
|
||||||
|
Extend = 'extend',
|
||||||
|
RemoveFirst = 'remove_first',
|
||||||
|
RemoveLast = 'remove_last',
|
||||||
|
}
|
||||||
|
|
||||||
export enum ExportFileType {
|
export enum ExportFileType {
|
||||||
PDF = 'pdf',
|
PDF = 'pdf',
|
||||||
HTML = 'html',
|
HTML = 'html',
|
||||||
|
|||||||
@ -19,6 +19,8 @@ type QueryVariableProps = {
|
|||||||
hideLabel?: boolean;
|
hideLabel?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
|
pureQuery?: boolean;
|
||||||
|
value?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function QueryVariable({
|
export function QueryVariable({
|
||||||
@ -28,12 +30,34 @@ export function QueryVariable({
|
|||||||
hideLabel = false,
|
hideLabel = false,
|
||||||
className,
|
className,
|
||||||
onChange,
|
onChange,
|
||||||
|
pureQuery = false,
|
||||||
|
value,
|
||||||
}: QueryVariableProps) {
|
}: QueryVariableProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
|
|
||||||
const finalOptions = useFilterQueryVariableOptionsByTypes(types);
|
const finalOptions = useFilterQueryVariableOptionsByTypes(types);
|
||||||
|
|
||||||
|
const renderWidget = (
|
||||||
|
value?: string,
|
||||||
|
handleChange?: (value: string) => void,
|
||||||
|
) => (
|
||||||
|
<GroupedSelectWithSecondaryMenu
|
||||||
|
options={finalOptions}
|
||||||
|
value={value}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleChange?.(val);
|
||||||
|
onChange?.(val);
|
||||||
|
}}
|
||||||
|
// allowClear
|
||||||
|
types={types}
|
||||||
|
></GroupedSelectWithSecondaryMenu>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pureQuery) {
|
||||||
|
renderWidget(value, onChange);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@ -45,18 +69,7 @@ export function QueryVariable({
|
|||||||
{t('flow.query')}
|
{t('flow.query')}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
)}
|
)}
|
||||||
<FormControl>
|
<FormControl>{renderWidget(field.value, field.onChange)}</FormControl>
|
||||||
<GroupedSelectWithSecondaryMenu
|
|
||||||
options={finalOptions}
|
|
||||||
value={field.value}
|
|
||||||
onChange={(val) => {
|
|
||||||
field.onChange(val);
|
|
||||||
onChange?.(val);
|
|
||||||
}}
|
|
||||||
// allowClear
|
|
||||||
types={types}
|
|
||||||
></GroupedSelectWithSecondaryMenu>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -10,10 +10,7 @@ import { PropsWithChildren, ReactNode, useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { JsonSchemaDataType } from '../../constant';
|
import { JsonSchemaDataType } from '../../constant';
|
||||||
import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output';
|
import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output';
|
||||||
import {
|
import { hasSpecificTypeChild } from '../../utils/filter-agent-structured-output';
|
||||||
hasJsonSchemaChild,
|
|
||||||
hasSpecificTypeChild,
|
|
||||||
} from '../../utils/filter-agent-structured-output';
|
|
||||||
|
|
||||||
type DataItem = { label: ReactNode; value: string; parentLabel?: ReactNode };
|
type DataItem = { label: ReactNode; value: string; parentLabel?: ReactNode };
|
||||||
|
|
||||||
@ -101,8 +98,9 @@ export function StructuredOutputSecondaryMenu({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasJsonSchemaChild(structuredOutput) ||
|
!isEmpty(types) &&
|
||||||
(!isEmpty(types) && !hasSpecificTypeChild(structuredOutput, types))
|
!hasSpecificTypeChild(structuredOutput, types) &&
|
||||||
|
!types.some((x) => x === JsonSchemaDataType.Object)
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,290 @@
|
|||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { Editor } from '@monaco-editor/react';
|
||||||
|
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,
|
||||||
|
VariableAssignerLogicalArrayOperator,
|
||||||
|
VariableAssignerLogicalNumberOperator,
|
||||||
|
VariableAssignerLogicalOperator,
|
||||||
|
} from '../../constant';
|
||||||
|
import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query';
|
||||||
|
import { DynamicFormHeader } from '../components/dynamic-fom-header';
|
||||||
|
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<typeof RadioGroupPrimitive.Root>;
|
||||||
|
|
||||||
|
type RadioButtonProps = Partial<
|
||||||
|
Omit<RadioGroupProps, 'onValueChange'> & {
|
||||||
|
onChange: RadioGroupProps['onValueChange'];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
function RadioButton({ value, onChange }: RadioButtonProps) {
|
||||||
|
return (
|
||||||
|
<RadioGroup
|
||||||
|
defaultValue="yes"
|
||||||
|
className="flex"
|
||||||
|
value={value}
|
||||||
|
onValueChange={onChange}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="yes" id="r1" />
|
||||||
|
<Label htmlFor="r1">Yes</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="no" id="r2" />
|
||||||
|
<Label htmlFor="r2">No</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmptyFields = [
|
||||||
|
VariableAssignerLogicalOperator.Clear,
|
||||||
|
VariableAssignerLogicalArrayOperator.RemoveFirst,
|
||||||
|
VariableAssignerLogicalArrayOperator.RemoveLast,
|
||||||
|
];
|
||||||
|
|
||||||
|
const EmptyValueMap = {
|
||||||
|
[JsonSchemaDataType.String]: '',
|
||||||
|
[JsonSchemaDataType.Number]: 0,
|
||||||
|
[JsonSchemaDataType.Boolean]: 'yes',
|
||||||
|
[JsonSchemaDataType.Object]: {},
|
||||||
|
[JsonSchemaDataType.Array]: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DynamicVariables({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
tooltip,
|
||||||
|
keyField = 'variable',
|
||||||
|
valueField = 'parameter',
|
||||||
|
operatorField = 'operator',
|
||||||
|
}: SelectKeysProps) {
|
||||||
|
const form = useFormContext();
|
||||||
|
const { getType } = useGetVariableLabelOrTypeByValue();
|
||||||
|
const isDarkTheme = useIsDarkTheme();
|
||||||
|
|
||||||
|
const { fields, remove, append, update } = 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,
|
||||||
|
valueFieldAlias: string,
|
||||||
|
) => {
|
||||||
|
console.log(
|
||||||
|
'🚀 ~ DynamicVariables ~ valueFieldAlias:',
|
||||||
|
form.getValues(valueFieldAlias),
|
||||||
|
);
|
||||||
|
const logicalOperator = form.getValues(operatorFieldName);
|
||||||
|
const type = getVariableType(keyFieldName);
|
||||||
|
|
||||||
|
if (EmptyFields.includes(logicalOperator)) {
|
||||||
|
return null;
|
||||||
|
} else if (
|
||||||
|
logicalOperator === VariableAssignerLogicalOperator.Overwrite ||
|
||||||
|
VariableAssignerLogicalArrayOperator.Extend === logicalOperator
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<QueryVariable types={[type]} hideLabel pureQuery></QueryVariable>
|
||||||
|
);
|
||||||
|
} else if (logicalOperator === VariableAssignerLogicalOperator.Set) {
|
||||||
|
if (type === JsonSchemaDataType.Boolean) {
|
||||||
|
return <RadioButton></RadioButton>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === JsonSchemaDataType.Number) {
|
||||||
|
return <Input className="w-full" type="number"></Input>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === JsonSchemaDataType.Object) {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
height={300}
|
||||||
|
theme={isDarkTheme ? 'vs-dark' : 'vs'}
|
||||||
|
language={'json'}
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === JsonSchemaDataType.String) {
|
||||||
|
return <Textarea></Textarea>;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
Object.values(VariableAssignerLogicalNumberOperator).some(
|
||||||
|
(x) => logicalOperator === x,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return <Input className="w-full" type="number"></Input>;
|
||||||
|
} else if (
|
||||||
|
logicalOperator === VariableAssignerLogicalArrayOperator.Append
|
||||||
|
) {
|
||||||
|
const subType = type.match(/<([^>]+)>/).at(1);
|
||||||
|
return (
|
||||||
|
<QueryVariable types={[subType]} hideLabel pureQuery></QueryVariable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form, getVariableType, isDarkTheme],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleVariableChange = useCallback(
|
||||||
|
(operatorFieldAlias: string, valueFieldAlias: string) => {
|
||||||
|
console.log(
|
||||||
|
'🚀 ~ DynamicVariables ~ operatorFieldAlias:',
|
||||||
|
operatorFieldAlias,
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
form.setValue(
|
||||||
|
operatorFieldAlias,
|
||||||
|
VariableAssignerLogicalOperator.Overwrite,
|
||||||
|
{ shouldDirty: true, shouldValidate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
form.setValue(valueFieldAlias, '', {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldValidate: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOperatorChange = useCallback(
|
||||||
|
(
|
||||||
|
valueFieldAlias: string,
|
||||||
|
keyFieldAlias: string,
|
||||||
|
value: string,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
const type = getVariableType(keyFieldAlias);
|
||||||
|
console.log('🚀 ~ DynamicVariables ~ type:', type);
|
||||||
|
|
||||||
|
let parameter = EmptyValueMap[type as keyof typeof EmptyValueMap];
|
||||||
|
|
||||||
|
if (value === VariableAssignerLogicalOperator.Overwrite) {
|
||||||
|
parameter = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== VariableAssignerLogicalOperator.Clear) {
|
||||||
|
form.setValue(valueFieldAlias, parameter, {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldValidate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// form.trigger(valueFieldAlias);
|
||||||
|
|
||||||
|
// update(index, { [valueField]: parameter });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form, getVariableType],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="space-y-2">
|
||||||
|
<DynamicFormHeader
|
||||||
|
label={label}
|
||||||
|
tooltip={tooltip}
|
||||||
|
onClick={() => append({ [keyField]: '', [valueField]: '' })}
|
||||||
|
></DynamicFormHeader>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
{fields.map((field, index) => {
|
||||||
|
const keyFieldAlias = `${name}.${index}.${keyField}`;
|
||||||
|
const valueFieldAlias = `${name}.${index}.${valueField}`;
|
||||||
|
const operatorFieldAlias = `${name}.${index}.${operatorField}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section key={field.id} className="flex gap-2">
|
||||||
|
<div className="flex-1 space-y-3 min-w-0">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<QueryVariable
|
||||||
|
name={keyFieldAlias}
|
||||||
|
hideLabel
|
||||||
|
className="flex-1 min-w-0"
|
||||||
|
onChange={handleVariableChange(
|
||||||
|
operatorFieldAlias,
|
||||||
|
valueFieldAlias,
|
||||||
|
)}
|
||||||
|
></QueryVariable>
|
||||||
|
|
||||||
|
<Separator className="w-2" />
|
||||||
|
|
||||||
|
<RAGFlowFormItem name={operatorFieldAlias} className="w-1/3">
|
||||||
|
{({ onChange, value }) => (
|
||||||
|
<SelectWithSearch
|
||||||
|
value={value}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleOperatorChange(
|
||||||
|
valueFieldAlias,
|
||||||
|
keyFieldAlias,
|
||||||
|
val,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
onChange(val);
|
||||||
|
}}
|
||||||
|
options={buildLogicalOptions(
|
||||||
|
getVariableType(keyFieldAlias),
|
||||||
|
)}
|
||||||
|
></SelectWithSearch>
|
||||||
|
)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</div>
|
||||||
|
<RAGFlowFormItem name={valueFieldAlias} className="w-full">
|
||||||
|
{renderParameter(
|
||||||
|
keyFieldAlias,
|
||||||
|
operatorFieldAlias,
|
||||||
|
valueFieldAlias,
|
||||||
|
)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||||
|
<X />
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,98 +1,51 @@
|
|||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
|
||||||
import { Form } 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 { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { initialDataOperationsValues } from '../../constant';
|
||||||
JsonSchemaDataType,
|
|
||||||
Operations,
|
|
||||||
initialDataOperationsValues,
|
|
||||||
} from '../../constant';
|
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
import { FormWrapper } from '../components/form-wrapper';
|
||||||
import { Output, OutputSchema } from '../components/output';
|
import { DynamicVariables } from './dynamic-variables';
|
||||||
import { QueryVariableList } from '../components/query-variable-list';
|
|
||||||
|
|
||||||
export const RetrievalPartialSchema = {
|
export const VariableAssignerSchema = {
|
||||||
query: z.array(z.object({ input: z.string().optional() })),
|
variables: z.array(
|
||||||
operations: z.string(),
|
z.object({
|
||||||
select_keys: z.array(z.object({ name: z.string().optional() })).optional(),
|
variable: z.string().optional(),
|
||||||
remove_keys: z.array(z.object({ name: z.string().optional() })).optional(),
|
operator: z.string().optional(),
|
||||||
updates: z
|
parameter: z.string().or(z.number()).or(z.boolean()).optional(),
|
||||||
.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 FormSchema = z.object(RetrievalPartialSchema);
|
export const FormSchema = z.object(VariableAssignerSchema);
|
||||||
|
|
||||||
export type DataOperationsFormSchemaType = z.infer<typeof FormSchema>;
|
export type VariableAssignerFormSchemaType = z.infer<typeof FormSchema>;
|
||||||
|
|
||||||
const outputList = buildOutputList(initialDataOperationsValues.outputs);
|
// const outputList = buildOutputList(initialVariableAssignerValues.outputs);
|
||||||
|
|
||||||
function VariableAssignerForm({ node }: INextOperatorForm) {
|
function VariableAssignerForm({ node }: INextOperatorForm) {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const defaultValues = useFormValues(initialDataOperationsValues, node);
|
const defaultValues = useFormValues(initialDataOperationsValues, node);
|
||||||
|
|
||||||
const form = useForm<DataOperationsFormSchemaType>({
|
const form = useForm<VariableAssignerFormSchemaType>({
|
||||||
defaultValues: defaultValues,
|
defaultValues: defaultValues,
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
shouldUnregister: true,
|
shouldUnregister: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const OperationsOptions = buildOptions(
|
|
||||||
Operations,
|
|
||||||
t,
|
|
||||||
`flow.operationsOptions`,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form, true);
|
useWatchFormChange(node?.id, form, true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<QueryVariableList
|
<DynamicVariables name="variables" label="Variables"></DynamicVariables>
|
||||||
tooltip={t('flow.queryTip')}
|
{/* <Output list={outputList} isFormRequired></Output> */}
|
||||||
label={t('flow.query')}
|
|
||||||
types={[JsonSchemaDataType.Array, JsonSchemaDataType.Object]}
|
|
||||||
></QueryVariableList>
|
|
||||||
<Separator />
|
|
||||||
<RAGFlowFormItem name="operations" label={t('flow.operations')}>
|
|
||||||
<SelectWithSearch options={OperationsOptions} allowClear />
|
|
||||||
</RAGFlowFormItem>
|
|
||||||
|
|
||||||
<Output list={outputList} isFormRequired></Output>
|
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
|
{/* <DevTool control={form.control} placement="top-left" /> */}
|
||||||
|
{/* set up the dev tool */}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -7,8 +7,11 @@ import {
|
|||||||
} from '../constant';
|
} from '../constant';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
|
|
||||||
|
function splitValue(value?: string) {
|
||||||
|
return typeof value === 'string' ? value?.split('@') : [];
|
||||||
|
}
|
||||||
function getNodeId(value: string) {
|
function getNodeId(value: string) {
|
||||||
return value.split('@').at(0);
|
return splitValue(value).at(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useShowSecondaryMenu() {
|
export function useShowSecondaryMenu() {
|
||||||
@ -63,7 +66,7 @@ export function useFindAgentStructuredOutputLabel() {
|
|||||||
}>,
|
}>,
|
||||||
) => {
|
) => {
|
||||||
// agent structured output
|
// agent structured output
|
||||||
const fields = value.split('@');
|
const fields = splitValue(value);
|
||||||
if (
|
if (
|
||||||
getOperatorTypeFromId(fields.at(0)) === Operator.Agent &&
|
getOperatorTypeFromId(fields.at(0)) === Operator.Agent &&
|
||||||
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
||||||
@ -130,7 +133,7 @@ export function useFindAgentStructuredOutputTypeByValue() {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fields = value.split('@');
|
const fields = splitValue(value);
|
||||||
const nodeId = fields.at(0);
|
const nodeId = fields.at(0);
|
||||||
const jsonSchema = filterStructuredOutput(value);
|
const jsonSchema = filterStructuredOutput(value);
|
||||||
|
|
||||||
@ -163,7 +166,7 @@ export function useFindAgentStructuredOutputLabelByValue() {
|
|||||||
const operatorName = getNode(getNodeId(value ?? ''))?.data.name;
|
const operatorName = getNode(getNodeId(value ?? ''))?.data.name;
|
||||||
|
|
||||||
if (operatorName) {
|
if (operatorName) {
|
||||||
return operatorName + ' / ' + value?.split('@').at(1);
|
return operatorName + ' / ' + splitValue(value).at(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -232,8 +232,11 @@ export function useFilterQueryVariableOptionsByTypes(
|
|||||||
...x,
|
...x,
|
||||||
options: x.options.filter(
|
options: x.options.filter(
|
||||||
(y) =>
|
(y) =>
|
||||||
types?.some((x) => toLower(y.type).includes(x)) ||
|
types?.some((x) =>
|
||||||
y.type === undefined, // agent structured output
|
toLower(x).startsWith('array')
|
||||||
|
? toLower(y.type).includes(toLower(x))
|
||||||
|
: toLower(y.type) === toLower(x),
|
||||||
|
) || y.type === undefined, // agent structured output
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
import { JSONSchema } from '@/components/jsonjoy-builder';
|
import { JSONSchema } from '@/components/jsonjoy-builder';
|
||||||
import { get, isPlainObject } from 'lodash';
|
import { get, isPlainObject, toLower } from 'lodash';
|
||||||
import { JsonSchemaDataType } from '../constant';
|
import { JsonSchemaDataType } from '../constant';
|
||||||
|
|
||||||
|
function predicate(types: string[], type: string) {
|
||||||
|
return types.some((x) => toLower(x) === toLower(type));
|
||||||
|
}
|
||||||
|
|
||||||
export function hasSpecificTypeChild(
|
export function hasSpecificTypeChild(
|
||||||
data: Record<string, any> | Array<any>,
|
data: Record<string, any> | Array<any>,
|
||||||
types: string[] = [],
|
types: string[] = [],
|
||||||
) {
|
) {
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
for (const value of data) {
|
for (const value of data) {
|
||||||
if (isPlainObject(value) && types.some((x) => x === value.type)) {
|
if (isPlainObject(value) && predicate(types, value.type)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (hasSpecificTypeChild(value, types)) {
|
if (hasSpecificTypeChild(value, types)) {
|
||||||
@ -19,7 +23,7 @@ export function hasSpecificTypeChild(
|
|||||||
|
|
||||||
if (isPlainObject(data)) {
|
if (isPlainObject(data)) {
|
||||||
for (const value of Object.values(data)) {
|
for (const value of Object.values(data)) {
|
||||||
if (isPlainObject(value) && types.some((x) => x === value.type)) {
|
if (isPlainObject(value) && predicate(types, value.type)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user