mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Set the outputs type of list operation. #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -6,6 +6,7 @@ interface NumberInputProps {
|
|||||||
value?: number;
|
value?: number;
|
||||||
onChange?: (value: number) => void;
|
onChange?: (value: number) => void;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
|
min?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberInput: React.FC<NumberInputProps> = ({
|
const NumberInput: React.FC<NumberInputProps> = ({
|
||||||
@ -13,6 +14,7 @@ const NumberInput: React.FC<NumberInputProps> = ({
|
|||||||
value: initialValue,
|
value: initialValue,
|
||||||
onChange,
|
onChange,
|
||||||
height,
|
height,
|
||||||
|
min = 0,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState<number>(() => {
|
const [value, setValue] = useState<number>(() => {
|
||||||
return initialValue ?? 0;
|
return initialValue ?? 0;
|
||||||
@ -76,6 +78,7 @@ const NumberInput: React.FC<NumberInputProps> = ({
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full flex-1 text-center bg-transparent focus:outline-none"
|
className="w-full flex-1 text-center bg-transparent focus:outline-none"
|
||||||
style={style}
|
style={style}
|
||||||
|
min={min}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -1062,7 +1062,7 @@ Example: https://fsn1.your-objectstorage.com`,
|
|||||||
apiKeyPlaceholder:
|
apiKeyPlaceholder:
|
||||||
'YOUR_API_KEY (obtained from https://serpapi.com/manage-api-key)',
|
'YOUR_API_KEY (obtained from https://serpapi.com/manage-api-key)',
|
||||||
flowStart: 'Start',
|
flowStart: 'Start',
|
||||||
flowNum: 'Num',
|
flowNum: 'N',
|
||||||
test: 'Test',
|
test: 'Test',
|
||||||
extractDepth: 'Extract Depth',
|
extractDepth: 'Extract Depth',
|
||||||
format: 'Format',
|
format: 'Format',
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
AgentStructuredOutputField,
|
AgentStructuredOutputField,
|
||||||
CodeTemplateStrMap,
|
CodeTemplateStrMap,
|
||||||
ComparisonOperator,
|
ComparisonOperator,
|
||||||
|
JsonSchemaDataType,
|
||||||
Operator,
|
Operator,
|
||||||
ProgrammingLanguage,
|
ProgrammingLanguage,
|
||||||
SwitchOperatorOptions,
|
SwitchOperatorOptions,
|
||||||
@ -610,15 +611,15 @@ export const initialListOperationsValues = {
|
|||||||
query: '',
|
query: '',
|
||||||
operations: ListOperations.TopN,
|
operations: ListOperations.TopN,
|
||||||
outputs: {
|
outputs: {
|
||||||
result: {
|
// result: {
|
||||||
type: 'Array<?>',
|
// type: 'Array<?>',
|
||||||
},
|
// },
|
||||||
first: {
|
// first: {
|
||||||
type: '?',
|
// type: '?',
|
||||||
},
|
// },
|
||||||
last: {
|
// last: {
|
||||||
type: '?',
|
// type: '?',
|
||||||
},
|
// },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -874,3 +875,22 @@ export enum ExportFileType {
|
|||||||
Markdown = 'md',
|
Markdown = 'md',
|
||||||
DOCX = 'docx',
|
DOCX = 'docx',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TypesWithArray {
|
||||||
|
String = 'string',
|
||||||
|
Number = 'number',
|
||||||
|
Boolean = 'boolean',
|
||||||
|
Object = 'object',
|
||||||
|
ArrayString = 'array<string>',
|
||||||
|
ArrayNumber = 'array<number>',
|
||||||
|
ArrayBoolean = 'array<boolean>',
|
||||||
|
ArrayObject = 'array<object>',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArrayFields = [
|
||||||
|
JsonSchemaDataType.Array,
|
||||||
|
TypesWithArray.ArrayBoolean,
|
||||||
|
TypesWithArray.ArrayNumber,
|
||||||
|
TypesWithArray.ArrayString,
|
||||||
|
TypesWithArray.ArrayObject,
|
||||||
|
];
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { JsonSchemaDataType } from '../../constant';
|
import { ArrayFields } from '../../constant';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
import { FormWrapper } from '../components/form-wrapper';
|
||||||
import { Output } from '../components/output';
|
import { Output } from '../components/output';
|
||||||
@ -44,7 +44,7 @@ function IterationForm({ node }: INextOperatorForm) {
|
|||||||
<FormContainer>
|
<FormContainer>
|
||||||
<QueryVariable
|
<QueryVariable
|
||||||
name="items_ref"
|
name="items_ref"
|
||||||
types={[JsonSchemaDataType.Array]}
|
types={ArrayFields as any[]}
|
||||||
></QueryVariable>
|
></QueryVariable>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
<DynamicOutput node={node}></DynamicOutput>
|
<DynamicOutput node={node}></DynamicOutput>
|
||||||
|
|||||||
@ -13,20 +13,22 @@ import { Separator } from '@/components/ui/separator';
|
|||||||
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
|
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
|
ArrayFields,
|
||||||
DataOperationsOperatorOptions,
|
DataOperationsOperatorOptions,
|
||||||
JsonSchemaDataType,
|
|
||||||
ListOperations,
|
ListOperations,
|
||||||
SortMethod,
|
SortMethod,
|
||||||
initialListOperationsValues,
|
initialListOperationsValues,
|
||||||
} from '../../constant';
|
} from '../../constant';
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
|
import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query';
|
||||||
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 { getArrayElementType } from '../../utils';
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
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 { Output, OutputSchema } from '../components/output';
|
||||||
@ -36,7 +38,7 @@ import { QueryVariable } from '../components/query-variable';
|
|||||||
export const RetrievalPartialSchema = {
|
export const RetrievalPartialSchema = {
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
operations: z.string(),
|
operations: z.string(),
|
||||||
n: z.number().int().min(0).optional(),
|
n: z.number().int().min(1).optional(),
|
||||||
sort_method: z.string().optional(),
|
sort_method: z.string().optional(),
|
||||||
filter: z
|
filter: z
|
||||||
.object({
|
.object({
|
||||||
@ -47,26 +49,68 @@ export const RetrievalPartialSchema = {
|
|||||||
...OutputSchema,
|
...OutputSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NumFields = [
|
||||||
|
ListOperations.TopN,
|
||||||
|
ListOperations.Head,
|
||||||
|
ListOperations.Tail,
|
||||||
|
];
|
||||||
|
|
||||||
|
function showField(operations: string) {
|
||||||
|
const showNum = NumFields.includes(operations as ListOperations);
|
||||||
|
const showSortMethod = [ListOperations.Sort].includes(
|
||||||
|
operations as ListOperations,
|
||||||
|
);
|
||||||
|
const showFilter = [ListOperations.Filter].includes(
|
||||||
|
operations as ListOperations,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
showNum,
|
||||||
|
showSortMethod,
|
||||||
|
showFilter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const FormSchema = z.object(RetrievalPartialSchema);
|
export const FormSchema = z.object(RetrievalPartialSchema);
|
||||||
|
|
||||||
export type ListOperationsFormSchemaType = z.infer<typeof FormSchema>;
|
export type ListOperationsFormSchemaType = z.infer<typeof FormSchema>;
|
||||||
|
|
||||||
const outputList = buildOutputList(initialListOperationsValues.outputs);
|
|
||||||
|
|
||||||
function ListOperationsForm({ node }: INextOperatorForm) {
|
function ListOperationsForm({ node }: INextOperatorForm) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { getType } = useGetVariableLabelOrTypeByValue();
|
||||||
|
|
||||||
const defaultValues = useFormValues(initialListOperationsValues, node);
|
const defaultValues = useFormValues(initialListOperationsValues, node);
|
||||||
|
|
||||||
const form = useForm<ListOperationsFormSchemaType>({
|
const form = useForm<ListOperationsFormSchemaType>({
|
||||||
defaultValues: defaultValues,
|
defaultValues: defaultValues,
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
shouldUnregister: true,
|
// shouldUnregister: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const operations = useWatch({ control: form.control, name: 'operations' });
|
const operations = useWatch({ control: form.control, name: 'operations' });
|
||||||
|
|
||||||
|
const query = useWatch({ control: form.control, name: 'query' });
|
||||||
|
|
||||||
|
const subType = getArrayElementType(getType(query));
|
||||||
|
|
||||||
|
const currentOutputs = useMemo(() => {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
type: `Array<${subType}>`,
|
||||||
|
},
|
||||||
|
first: {
|
||||||
|
type: subType,
|
||||||
|
},
|
||||||
|
last: {
|
||||||
|
type: subType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [subType]);
|
||||||
|
|
||||||
|
const outputList = buildOutputList(currentOutputs);
|
||||||
|
|
||||||
const ListOperationsOptions = buildOptions(
|
const ListOperationsOptions = buildOptions(
|
||||||
ListOperations,
|
ListOperations,
|
||||||
t,
|
t,
|
||||||
@ -79,9 +123,39 @@ function ListOperationsForm({ node }: INextOperatorForm) {
|
|||||||
`flow.SortMethodOptions`,
|
`flow.SortMethodOptions`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
const operatorOptions = useBuildSwitchOperatorOptions(
|
const operatorOptions = useBuildSwitchOperatorOptions(
|
||||||
DataOperationsOperatorOptions,
|
DataOperationsOperatorOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { showFilter, showNum, showSortMethod } = showField(operations);
|
||||||
|
|
||||||
|
const handleOperationsChange = useCallback(
|
||||||
|
(operations: string) => {
|
||||||
|
const { showFilter, showNum, showSortMethod } = showField(operations);
|
||||||
|
|
||||||
|
if (showNum) {
|
||||||
|
form.setValue('n', 1, { shouldDirty: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSortMethod) {
|
||||||
|
form.setValue('sort_method', SortMethodOptions.at(0)?.value, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (showFilter) {
|
||||||
|
form.setValue('filter.operator', operatorOptions.at(0)?.value, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[SortMethodOptions, form, operatorOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValue('outputs', currentOutputs, { shouldDirty: true });
|
||||||
|
}, [currentOutputs, form]);
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form, true);
|
useWatchFormChange(node?.id, form, true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -90,37 +164,46 @@ function ListOperationsForm({ node }: INextOperatorForm) {
|
|||||||
<QueryVariable
|
<QueryVariable
|
||||||
name="query"
|
name="query"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
types={[JsonSchemaDataType.Array]}
|
types={ArrayFields as any[]}
|
||||||
></QueryVariable>
|
></QueryVariable>
|
||||||
<Separator />
|
<Separator />
|
||||||
<RAGFlowFormItem name="operations" label={t('flow.operations')}>
|
<RAGFlowFormItem name="operations" label={t('flow.operations')}>
|
||||||
<SelectWithSearch options={ListOperationsOptions} />
|
{(field) => (
|
||||||
|
<SelectWithSearch
|
||||||
|
options={ListOperationsOptions}
|
||||||
|
value={field.value}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleOperationsChange(val);
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
{[
|
{showNum && (
|
||||||
ListOperations.TopN,
|
|
||||||
ListOperations.Head,
|
|
||||||
ListOperations.Tail,
|
|
||||||
].includes(operations as ListOperations) && (
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="n"
|
name="n"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('flowNum')}</FormLabel>
|
<FormLabel>{t('flow.flowNum')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<NumberInput {...field} className="w-full"></NumberInput>
|
<NumberInput
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
min={1}
|
||||||
|
></NumberInput>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{[ListOperations.Sort].includes(operations as ListOperations) && (
|
{showSortMethod && (
|
||||||
<RAGFlowFormItem name="sort_method" label={t('flow.sortMethod')}>
|
<RAGFlowFormItem name="sort_method" label={t('flow.sortMethod')}>
|
||||||
<SelectWithSearch options={SortMethodOptions} />
|
<SelectWithSearch options={SortMethodOptions} />
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
)}
|
)}
|
||||||
{[ListOperations.Filter].includes(operations as ListOperations) && (
|
{showFilter && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<RAGFlowFormItem name="filter.operator" className="flex-1">
|
<RAGFlowFormItem name="filter.operator" className="flex-1">
|
||||||
<SelectWithSearch options={operatorOptions}></SelectWithSearch>
|
<SelectWithSearch options={operatorOptions}></SelectWithSearch>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
VariableAssignerLogicalOperator,
|
VariableAssignerLogicalOperator,
|
||||||
} from '../../constant';
|
} from '../../constant';
|
||||||
import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query';
|
import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query';
|
||||||
|
import { getArrayElementType } from '../../utils';
|
||||||
import { DynamicFormHeader } from '../components/dynamic-fom-header';
|
import { DynamicFormHeader } from '../components/dynamic-fom-header';
|
||||||
import { QueryVariable } from '../components/query-variable';
|
import { QueryVariable } from '../components/query-variable';
|
||||||
import { useBuildLogicalOptions } from './use-build-logical-options';
|
import { useBuildLogicalOptions } from './use-build-logical-options';
|
||||||
@ -152,9 +153,13 @@ export function DynamicVariables({
|
|||||||
} else if (
|
} else if (
|
||||||
logicalOperator === VariableAssignerLogicalArrayOperator.Append
|
logicalOperator === VariableAssignerLogicalArrayOperator.Append
|
||||||
) {
|
) {
|
||||||
const subType = type.match(/<([^>]+)>/).at(1);
|
const subType = getArrayElementType(type);
|
||||||
return (
|
return (
|
||||||
<QueryVariable types={[subType]} hideLabel pureQuery></QueryVariable>
|
<QueryVariable
|
||||||
|
types={[subType as JsonSchemaDataType]}
|
||||||
|
hideLabel
|
||||||
|
pureQuery
|
||||||
|
></QueryVariable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { FormFieldConfig, FormFieldType } from '@/components/dynamic-form';
|
import { FormFieldConfig, FormFieldType } from '@/components/dynamic-form';
|
||||||
import { buildSelectOptions } from '@/utils/component-util';
|
import { buildSelectOptions } from '@/utils/component-util';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { TypesWithArray } from '../constant';
|
||||||
|
export { TypesWithArray } from '../constant';
|
||||||
// const TypesWithoutArray = Object.values(JsonSchemaDataType).filter(
|
// const TypesWithoutArray = Object.values(JsonSchemaDataType).filter(
|
||||||
// (item) => item !== JsonSchemaDataType.Array,
|
// (item) => item !== JsonSchemaDataType.Array,
|
||||||
// );
|
// );
|
||||||
@ -9,17 +11,6 @@ import { t } from 'i18next';
|
|||||||
// ...TypesWithoutArray.map((item) => `array<${item}>`),
|
// ...TypesWithoutArray.map((item) => `array<${item}>`),
|
||||||
// ];
|
// ];
|
||||||
|
|
||||||
export enum TypesWithArray {
|
|
||||||
String = 'string',
|
|
||||||
Number = 'number',
|
|
||||||
Boolean = 'boolean',
|
|
||||||
Object = 'object',
|
|
||||||
ArrayString = 'array<string>',
|
|
||||||
ArrayNumber = 'array<number>',
|
|
||||||
ArrayBoolean = 'array<boolean>',
|
|
||||||
ArrayObject = 'array<object>',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GlobalFormFields = [
|
export const GlobalFormFields = [
|
||||||
{
|
{
|
||||||
label: t('flow.name'),
|
label: t('flow.name'),
|
||||||
|
|||||||
@ -732,3 +732,7 @@ export function buildBeginQueryWithObject(
|
|||||||
|
|
||||||
return nextInputs;
|
return nextInputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getArrayElementType(type: string) {
|
||||||
|
return typeof type === 'string' ? type.match(/<([^>]+)>/)?.at(1) ?? '' : '';
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user