feat: support array reference multi-select (#3041)
* feat: support array reference multi-select * fix build * fix * fix loop multi-select * adjust condition * fix get value * array and non-array conversion * fix plugin input * merge func
This commit is contained in:
parent
f4dbe7c021
commit
0db0cbf376
@ -334,3 +334,14 @@ export enum ContentTypes {
|
|||||||
xml = 'xml',
|
xml = 'xml',
|
||||||
raw = 'raw-text'
|
raw = 'raw-text'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ArrayTypeMap = {
|
||||||
|
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
|
||||||
|
[WorkflowIOValueTypeEnum.number]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||||
|
[WorkflowIOValueTypeEnum.boolean]: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||||
|
[WorkflowIOValueTypeEnum.object]: WorkflowIOValueTypeEnum.arrayObject,
|
||||||
|
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.arrayString,
|
||||||
|
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||||
|
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||||
|
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.arrayObject
|
||||||
|
};
|
||||||
|
|||||||
@ -235,9 +235,9 @@ export const getReferenceVariableValue = ({
|
|||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
}) => {
|
}) => {
|
||||||
const nodeIds = nodes.map((node) => node.nodeId);
|
const nodeIds = nodes.map((node) => node.nodeId);
|
||||||
if (!isReferenceValue(value, nodeIds)) {
|
|
||||||
return value;
|
// handle single reference value
|
||||||
}
|
if (isReferenceValue(value, nodeIds)) {
|
||||||
const sourceNodeId = value[0];
|
const sourceNodeId = value[0];
|
||||||
const outputId = value[1];
|
const outputId = value[1];
|
||||||
|
|
||||||
@ -246,14 +246,46 @@ export const getReferenceVariableValue = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||||
|
if (!node) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.outputs.find((output) => output.id === outputId)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle reference array
|
||||||
|
if (
|
||||||
|
Array.isArray(value) &&
|
||||||
|
value.every(
|
||||||
|
(val) => val?.length === 2 && typeof val[0] === 'string' && typeof val[1] === 'string'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const result = value.map((val) => {
|
||||||
|
if (!isReferenceValue(val, nodeIds)) {
|
||||||
|
return [val];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceNodeId = val?.[0];
|
||||||
|
const outputId = val?.[1];
|
||||||
|
|
||||||
|
if (sourceNodeId === VARIABLE_NODE_ID && outputId) {
|
||||||
|
const variableValue = variables[outputId];
|
||||||
|
return Array.isArray(variableValue) ? variableValue : [variableValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputValue = node.outputs.find((output) => output.id === outputId)?.value;
|
const outputValue = node.outputs.find((output) => output.id === outputId)?.value;
|
||||||
|
return Array.isArray(outputValue) ? outputValue : [outputValue];
|
||||||
|
});
|
||||||
|
|
||||||
return outputValue;
|
return result.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const textAdaptGptResponse = ({
|
export const textAdaptGptResponse = ({
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export const getOneQuoteInputTemplate = ({
|
|||||||
}): FlowNodeInputItemType => ({
|
}): FlowNodeInputItemType => ({
|
||||||
key,
|
key,
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||||
label: `${i18nT('workflow:quote_num')},{ num: ${index} }`,
|
label: `${i18nT('workflow:quote_num')}`,
|
||||||
debugLabel: i18nT('workflow:knowledge_base_reference'),
|
debugLabel: i18nT('workflow:knowledge_base_reference'),
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
valueType: WorkflowIOValueTypeEnum.datasetQuote
|
valueType: WorkflowIOValueTypeEnum.datasetQuote
|
||||||
|
|||||||
@ -40,11 +40,13 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
|
|||||||
})
|
})
|
||||||
: formatValue;
|
: formatValue;
|
||||||
} else {
|
} else {
|
||||||
return getReferenceVariableValue({
|
const value = getReferenceVariableValue({
|
||||||
value: item.value,
|
value: item.value,
|
||||||
variables,
|
variables,
|
||||||
nodes: runtimeNodes
|
nodes: runtimeNodes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import React, { useRef, useCallback, useState } from 'react';
|
import React, { useRef, useCallback, useState } from 'react';
|
||||||
import { Button, useDisclosure, Box, Flex, useOutsideClick } from '@chakra-ui/react';
|
import { Button, useDisclosure, Box, Flex, useOutsideClick, Checkbox } from '@chakra-ui/react';
|
||||||
import { MultipleSelectProps } from './type';
|
import { MultipleSelectProps } from './type';
|
||||||
import EmptyTip from '../EmptyTip';
|
import EmptyTip from '../EmptyTip';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import MyIcon from '../../common/Icon';
|
import MyIcon from '../../common/Icon';
|
||||||
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
const MultipleRowSelect = ({
|
const MultipleRowSelect = ({
|
||||||
placeholder,
|
placeholder,
|
||||||
@ -14,12 +15,14 @@ const MultipleRowSelect = ({
|
|||||||
maxH = 300,
|
maxH = 300,
|
||||||
onSelect,
|
onSelect,
|
||||||
popDirection = 'bottom',
|
popDirection = 'bottom',
|
||||||
styles
|
styles,
|
||||||
|
isArray = false
|
||||||
}: MultipleSelectProps) => {
|
}: MultipleSelectProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const [cloneValue, setCloneValue] = useState(value);
|
|
||||||
|
const [navigationPath, setNavigationPath] = useState<string[]>([]);
|
||||||
|
|
||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
ref: ref,
|
ref: ref,
|
||||||
@ -28,24 +31,56 @@ const MultipleRowSelect = ({
|
|||||||
|
|
||||||
const RenderList = useCallback(
|
const RenderList = useCallback(
|
||||||
({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => {
|
({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => {
|
||||||
const selectedValue = cloneValue[index];
|
const currentNav = navigationPath[index];
|
||||||
const selectedIndex = list.findIndex((item) => item.value === selectedValue);
|
const selectedIndex = list.findIndex((item) => item.value === currentNav);
|
||||||
const children = list[selectedIndex]?.children || [];
|
const children = list[selectedIndex]?.children || [];
|
||||||
const hasChildren = list.some((item) => item.children && item.children?.length > 0);
|
const hasChildren = list.some((item) => item.children && item.children?.length > 0);
|
||||||
|
|
||||||
|
const handleSelect = (item: any) => {
|
||||||
|
if (hasChildren) {
|
||||||
|
// Update parent menu path
|
||||||
|
const newPath = [...navigationPath];
|
||||||
|
newPath[index] = item.value;
|
||||||
|
// Clear sub paths
|
||||||
|
newPath.splice(index + 1);
|
||||||
|
setNavigationPath(newPath);
|
||||||
|
} else {
|
||||||
|
if (!isArray) {
|
||||||
|
onSelect([navigationPath[0], item.value]);
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
const parentValue = navigationPath[0];
|
||||||
|
const newValues = [...value];
|
||||||
|
const newValue = [parentValue, item.value];
|
||||||
|
|
||||||
|
if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) {
|
||||||
|
onSelect(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value)));
|
||||||
|
} else {
|
||||||
|
onSelect([...newValues, newValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
className="nowheel"
|
className="nowheel"
|
||||||
flex={'1 0 auto'}
|
flex={'1 0 auto'}
|
||||||
// width={0}
|
|
||||||
px={2}
|
px={2}
|
||||||
borderLeft={index !== 0 ? 'base' : 'none'}
|
borderLeft={index !== 0 ? 'base' : 'none'}
|
||||||
maxH={`${maxH}px`}
|
maxH={`${maxH}px`}
|
||||||
overflowY={'auto'}
|
overflowY={'auto'}
|
||||||
whiteSpace={'nowrap'}
|
whiteSpace={'nowrap'}
|
||||||
>
|
>
|
||||||
{list.map((item) => (
|
{list.map((item) => {
|
||||||
|
const isSelected = item.value === currentNav;
|
||||||
|
const showCheckbox = isArray && index !== 0;
|
||||||
|
const isChecked =
|
||||||
|
showCheckbox &&
|
||||||
|
value.some((v) => v[1] === item.value && v[0] === navigationPath[0]);
|
||||||
|
|
||||||
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
key={item.value}
|
key={item.value}
|
||||||
py={2}
|
py={2}
|
||||||
@ -56,31 +91,20 @@ const MultipleRowSelect = ({
|
|||||||
bg: 'primary.50',
|
bg: 'primary.50',
|
||||||
color: 'primary.600'
|
color: 'primary.600'
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => handleSelect(item)}
|
||||||
const newValue = [...cloneValue];
|
{...(isSelected ? { color: 'primary.600' } : {})}
|
||||||
|
|
||||||
if (item.value === selectedValue) {
|
|
||||||
newValue[index] = undefined;
|
|
||||||
setCloneValue(newValue);
|
|
||||||
onSelect(newValue);
|
|
||||||
} else {
|
|
||||||
newValue[index] = item.value;
|
|
||||||
setCloneValue(newValue);
|
|
||||||
if (!hasChildren) {
|
|
||||||
onSelect(newValue);
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{...(item.value === selectedValue
|
|
||||||
? {
|
|
||||||
color: 'primary.600'
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
>
|
>
|
||||||
|
{showCheckbox && (
|
||||||
|
<Checkbox
|
||||||
|
isChecked={isChecked}
|
||||||
|
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||||
|
mr={1}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{item.label}
|
{item.label}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
{list.length === 0 && (
|
{list.length === 0 && (
|
||||||
<EmptyTip
|
<EmptyTip
|
||||||
text={emptyTip ?? t('common:common.MultipleRowSelect.No data')}
|
text={emptyTip ?? t('common:common.MultipleRowSelect.No data')}
|
||||||
@ -93,28 +117,39 @@ const MultipleRowSelect = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[cloneValue]
|
[navigationPath, value, isArray, onSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onOpenSelect = useCallback(() => {
|
const onOpenSelect = useCallback(() => {
|
||||||
setCloneValue(value);
|
setNavigationPath(isArray ? [] : [value[0]?.[0], value[0]?.[1]]);
|
||||||
onOpen();
|
onOpen();
|
||||||
}, [value, onOpen]);
|
}, [value, isArray, onOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={ref} position={'relative'}>
|
<Box ref={ref} position={'relative'}>
|
||||||
<Button
|
<Flex
|
||||||
justifyContent={'space-between'}
|
justifyContent={'space-between'}
|
||||||
|
alignItems={'center'}
|
||||||
|
overflow={'auto'}
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
variant={'whitePrimaryOutline'}
|
variant={'whitePrimaryOutline'}
|
||||||
size={'lg'}
|
size={'lg'}
|
||||||
fontSize={'sm'}
|
fontSize={'sm'}
|
||||||
px={3}
|
px={3}
|
||||||
|
py={1}
|
||||||
|
minH={10}
|
||||||
|
maxH={24}
|
||||||
outline={'none'}
|
outline={'none'}
|
||||||
rightIcon={<MyIcon name={'core/chat/chevronDown'} w={4} color={'myGray.500'} />}
|
rightIcon={<MyIcon name={'core/chat/chevronDown'} w={4} color={'myGray.500'} />}
|
||||||
|
border={'1px solid'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
bg={'white'}
|
||||||
_active={{
|
_active={{
|
||||||
transform: 'none'
|
transform: 'none'
|
||||||
}}
|
}}
|
||||||
|
_hover={{
|
||||||
|
borderColor: 'primary.500'
|
||||||
|
}}
|
||||||
{...(isOpen
|
{...(isOpen
|
||||||
? {
|
? {
|
||||||
borderColor: 'primary.600',
|
borderColor: 'primary.600',
|
||||||
@ -127,9 +162,13 @@ const MultipleRowSelect = ({
|
|||||||
})}
|
})}
|
||||||
{...styles}
|
{...styles}
|
||||||
onClick={() => (isOpen ? onClose() : onOpenSelect())}
|
onClick={() => (isOpen ? onClose() : onOpenSelect())}
|
||||||
|
className="nowheel"
|
||||||
>
|
>
|
||||||
<Box>{label ?? placeholder}</Box>
|
<Box>{label ?? placeholder}</Box>
|
||||||
</Button>
|
<Flex alignItems={'center'} ml={1}>
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Box
|
<Box
|
||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
|
|||||||
@ -14,4 +14,5 @@ export type MultipleSelectProps<T = any> = {
|
|||||||
onSelect: (val: any[]) => void;
|
onSelect: (val: any[]) => void;
|
||||||
styles?: ButtonProps;
|
styles?: ButtonProps;
|
||||||
popDirection?: 'top' | 'bottom';
|
popDirection?: 'top' | 'bottom';
|
||||||
|
isArray?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
|||||||
import { useDeepCompareEffect } from 'ahooks';
|
import { useDeepCompareEffect } from 'ahooks';
|
||||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||||
|
|
||||||
export const VariableInputItem = ({
|
export const VariableInputItem = ({
|
||||||
item,
|
item,
|
||||||
@ -108,23 +109,15 @@ export const VariableInputItem = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name={`variables.${item.key}`}
|
name={`variables.${item.key}`}
|
||||||
rules={{ required: item.required, min: item.min, max: item.max }}
|
rules={{ required: item.required, min: item.min, max: item.max }}
|
||||||
render={({ field: { ref, value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<NumberInput
|
<MyNumberInput
|
||||||
step={1}
|
step={1}
|
||||||
min={item.min}
|
min={item.min}
|
||||||
max={item.max}
|
max={item.max}
|
||||||
bg={'white'}
|
bg={'white'}
|
||||||
rounded={'md'}
|
|
||||||
clampValueOnBlur={false}
|
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(valueString) => onChange(Number(valueString))}
|
onChange={onChange}
|
||||||
>
|
/>
|
||||||
<NumberInputField ref={ref} bg={'white'} />
|
|
||||||
<NumberInputStepper>
|
|
||||||
<NumberIncrementStepper />
|
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberInputStepper>
|
|
||||||
</NumberInput>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -80,7 +80,7 @@ const RenderInput = () => {
|
|||||||
setRestartData(e);
|
setRestartData(e);
|
||||||
onNewChat?.();
|
onNewChat?.();
|
||||||
},
|
},
|
||||||
[onNewChat]
|
[onNewChat, setRestartData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatPluginInputs = useMemo(() => {
|
const formatPluginInputs = useMemo(() => {
|
||||||
@ -101,12 +101,12 @@ const RenderInput = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Set config default value
|
// Set config default value
|
||||||
if (histories.length === 0) {
|
if (histories.length === 0) {
|
||||||
// Restart
|
|
||||||
if (restartData) {
|
if (restartData) {
|
||||||
reset(restartData);
|
reset(restartData);
|
||||||
setRestartData(undefined);
|
setRestartData(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFormValues = formatPluginInputs.reduce(
|
const defaultFormValues = formatPluginInputs.reduce(
|
||||||
(acc, input) => {
|
(acc, input) => {
|
||||||
acc[input.key] = input.defaultValue;
|
acc[input.key] = input.defaultValue;
|
||||||
@ -160,7 +160,8 @@ const RenderInput = () => {
|
|||||||
variables: historyVariables,
|
variables: historyVariables,
|
||||||
files: historyFileList
|
files: historyFileList
|
||||||
});
|
});
|
||||||
}, [histories.length]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [histories]);
|
||||||
|
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,4 @@
|
|||||||
import {
|
import { Box, Button, Flex, Switch, Textarea } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
NumberDecrementStepper,
|
|
||||||
NumberIncrementStepper,
|
|
||||||
NumberInput,
|
|
||||||
NumberInputField,
|
|
||||||
NumberInputStepper,
|
|
||||||
Switch,
|
|
||||||
Textarea
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||||
@ -27,17 +16,21 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
import { useFieldArray } from 'react-hook-form';
|
import { useFieldArray } from 'react-hook-form';
|
||||||
|
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||||
|
|
||||||
const FileSelector = ({
|
const FileSelector = ({
|
||||||
input,
|
input,
|
||||||
setUploading,
|
setUploading,
|
||||||
onChange
|
onChange,
|
||||||
|
value
|
||||||
}: {
|
}: {
|
||||||
input: FlowNodeInputItemType;
|
input: FlowNodeInputItemType;
|
||||||
setUploading: React.Dispatch<React.SetStateAction<boolean>>;
|
setUploading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
onChange: (...event: any[]) => void;
|
onChange: (...event: any[]) => void;
|
||||||
|
value: any;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { variablesForm, histories, chatId, outLinkAuthData } = useContextSelector(
|
const { variablesForm, histories, chatId, outLinkAuthData } = useContextSelector(
|
||||||
@ -56,7 +49,8 @@ const FileSelector = ({
|
|||||||
uploadFiles,
|
uploadFiles,
|
||||||
onOpenSelectFile,
|
onOpenSelectFile,
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
removeFiles
|
removeFiles,
|
||||||
|
replaceFiles
|
||||||
} = useFileUpload({
|
} = useFileUpload({
|
||||||
outLinkAuthData,
|
outLinkAuthData,
|
||||||
chatId: chatId || '',
|
chatId: chatId || '',
|
||||||
@ -68,6 +62,22 @@ const FileSelector = ({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
fileCtrl
|
fileCtrl
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
replaceFiles([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare file names and update if different
|
||||||
|
const valueFileNames = value.map((item) => item.name);
|
||||||
|
const currentFileNames = fileList.map((item) => item.name);
|
||||||
|
if (!isEqual(valueFileNames, currentFileNames)) {
|
||||||
|
replaceFiles(value);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
const isDisabledInput = histories.length > 0;
|
const isDisabledInput = histories.length > 0;
|
||||||
useRequest2(uploadFiles, {
|
useRequest2(uploadFiles, {
|
||||||
manual: false,
|
manual: false,
|
||||||
@ -151,7 +161,9 @@ const RenderPluginInput = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (inputType === FlowNodeInputTypeEnum.fileSelect) {
|
if (inputType === FlowNodeInputTypeEnum.fileSelect) {
|
||||||
return <FileSelector onChange={onChange} input={input} setUploading={setUploading} />;
|
return (
|
||||||
|
<FileSelector onChange={onChange} input={input} setUploading={setUploading} value={value} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
||||||
@ -169,20 +181,17 @@ const RenderPluginInput = ({
|
|||||||
}
|
}
|
||||||
if (input.valueType === WorkflowIOValueTypeEnum.number) {
|
if (input.valueType === WorkflowIOValueTypeEnum.number) {
|
||||||
return (
|
return (
|
||||||
<NumberInput
|
<MyNumberInput
|
||||||
step={1}
|
step={1}
|
||||||
min={input.min}
|
min={input.min}
|
||||||
max={input.max}
|
max={input.max}
|
||||||
bg={'myGray.50'}
|
bg={'myGray.50'}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isInvalid={isInvalid}
|
isInvalid={isInvalid}
|
||||||
>
|
value={value}
|
||||||
<NumberInputField value={value} onChange={onChange} defaultValue={input.defaultValue} />
|
onChange={onChange}
|
||||||
<NumberInputStepper>
|
defaultValue={input.defaultValue}
|
||||||
<NumberIncrementStepper />
|
/>
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberInputStepper>
|
|
||||||
</NumberInput>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||||
|
|||||||
@ -15,15 +15,26 @@ import RenderInput from '../render/RenderInput';
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
import RenderOutput from '../render/RenderOutput';
|
import RenderOutput from '../render/RenderOutput';
|
||||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import {
|
||||||
|
ArrayTypeMap,
|
||||||
|
NodeInputKeyEnum,
|
||||||
|
VARIABLE_NODE_ID,
|
||||||
|
WorkflowIOValueTypeEnum
|
||||||
|
} from '@fastgpt/global/core/workflow/constants';
|
||||||
import { Input_Template_Children_Node_List } from '@fastgpt/global/core/workflow/template/input';
|
import { Input_Template_Children_Node_List } from '@fastgpt/global/core/workflow/template/input';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '../../../context';
|
import { WorkflowContext } from '../../../context';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||||
|
import { AppContext } from '../../../../context';
|
||||||
|
|
||||||
const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { nodeId, inputs, outputs, isFolded } = data;
|
const { nodeId, inputs, outputs, isFolded } = data;
|
||||||
const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v);
|
const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v);
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
|
const arrayValue = inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray)?.value;
|
||||||
|
|
||||||
const { nodeWidth, nodeHeight } = useMemo(() => {
|
const { nodeWidth, nodeHeight } = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -37,6 +48,42 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
);
|
);
|
||||||
}, [nodeId, nodeList]);
|
}, [nodeId, nodeList]);
|
||||||
|
|
||||||
|
// Detect and update array input type
|
||||||
|
useEffect(() => {
|
||||||
|
const inputsClone = cloneDeep(inputs);
|
||||||
|
const globalVariables = getWorkflowGlobalVariables({
|
||||||
|
nodes: nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
let arrayType: WorkflowIOValueTypeEnum | undefined;
|
||||||
|
if (arrayValue[0]?.[0] === VARIABLE_NODE_ID) {
|
||||||
|
arrayType = globalVariables.find((item) => item.key === arrayValue[0]?.[1])?.valueType;
|
||||||
|
} else {
|
||||||
|
const node = nodeList.find((node) => node.nodeId === arrayValue[0]?.[0]);
|
||||||
|
const output = node?.outputs.find((output) => output.id === arrayValue[0]?.[1]);
|
||||||
|
arrayType = output?.valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayInput = inputsClone.find((input) => input.key === NodeInputKeyEnum.loopInputArray);
|
||||||
|
|
||||||
|
if (!arrayInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'updateInput',
|
||||||
|
key: NodeInputKeyEnum.loopInputArray,
|
||||||
|
value: {
|
||||||
|
...arrayInput,
|
||||||
|
valueType: arrayType
|
||||||
|
? ArrayTypeMap[arrayType as keyof typeof ArrayTypeMap]
|
||||||
|
: WorkflowIOValueTypeEnum.arrayAny
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [appDetail.chatConfig, arrayValue, inputs, nodeId, nodeList, onChangeNode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
nodeId,
|
nodeId,
|
||||||
@ -47,7 +94,7 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
value: JSON.parse(childrenNodeIdList)
|
value: JSON.parse(childrenNodeIdList)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [childrenNodeIdList]);
|
}, [childrenNodeIdList, nodeId, onChangeNode]);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -80,7 +127,7 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
</Container>
|
</Container>
|
||||||
</NodeCard>
|
</NodeCard>
|
||||||
);
|
);
|
||||||
}, [selected, nodeWidth, nodeHeight, data, t, nodeId, inputs, outputs]);
|
}, [selected, isFolded, nodeWidth, nodeHeight, data, t, nodeId, inputs, outputs]);
|
||||||
|
|
||||||
return Render;
|
return Render;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,8 +19,7 @@ const typeMap = {
|
|||||||
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.string,
|
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.string,
|
||||||
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.number,
|
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.number,
|
||||||
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.boolean,
|
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.boolean,
|
||||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.object,
|
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.object
|
||||||
[WorkflowIOValueTypeEnum.arrayAny]: WorkflowIOValueTypeEnum.any
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
@ -39,12 +38,7 @@ const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
const parentArrayInput = parentNode?.inputs.find(
|
const parentArrayInput = parentNode?.inputs.find(
|
||||||
(input) => input.key === NodeInputKeyEnum.loopInputArray
|
(input) => input.key === NodeInputKeyEnum.loopInputArray
|
||||||
);
|
);
|
||||||
return parentArrayInput?.value
|
return typeMap[parentArrayInput?.valueType as keyof typeof typeMap];
|
||||||
? (nodeList
|
|
||||||
.find((node) => node.nodeId === parentArrayInput?.value[0])
|
|
||||||
?.outputs.find((output) => output.id === parentArrayInput?.value[1])
|
|
||||||
?.valueType as keyof typeof typeMap)
|
|
||||||
: undefined;
|
|
||||||
}, [loopStartNode?.parentNodeId, nodeList]);
|
}, [loopStartNode?.parentNodeId, nodeList]);
|
||||||
|
|
||||||
// Auth update loopStartInput output
|
// Auth update loopStartInput output
|
||||||
@ -71,7 +65,7 @@ const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
key: NodeOutputKeyEnum.loopStartInput,
|
key: NodeOutputKeyEnum.loopStartInput,
|
||||||
label: t('workflow:Array_element'),
|
label: t('workflow:Array_element'),
|
||||||
type: FlowNodeOutputTypeEnum.static,
|
type: FlowNodeOutputTypeEnum.static,
|
||||||
valueType: typeMap[loopItemInputType as keyof typeof typeMap]
|
valueType: loopItemInputType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -83,7 +77,7 @@ const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
key: NodeOutputKeyEnum.loopStartInput,
|
key: NodeOutputKeyEnum.loopStartInput,
|
||||||
value: {
|
value: {
|
||||||
...loopArrayOutput,
|
...loopArrayOutput,
|
||||||
valueType: typeMap[loopItemInputType as keyof typeof typeMap]
|
valueType: loopItemInputType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -128,7 +122,7 @@ const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
{t('workflow:Array_element')}
|
{t('workflow:Array_element')}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{typeMap[loopItemInputType]}</Td>
|
<Td>{loopItemInputType}</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(e: ReferenceValueProps) => {
|
(e: ReferenceValueProps | ReferenceValueProps[]) => {
|
||||||
const workflowStartNode = nodeList.find(
|
const workflowStartNode = nodeList.find(
|
||||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||||
);
|
);
|
||||||
@ -60,7 +60,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
value: {
|
value: {
|
||||||
...inputChildren,
|
...inputChildren,
|
||||||
value:
|
value:
|
||||||
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1])
|
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1] as string)
|
||||||
? [VARIABLE_NODE_ID, e[1]]
|
? [VARIABLE_NODE_ID, e[1]]
|
||||||
: e
|
: e
|
||||||
}
|
}
|
||||||
|
|||||||
@ -324,7 +324,7 @@ const Reference = ({
|
|||||||
placeholder={t('common:select_reference_variable')}
|
placeholder={t('common:select_reference_variable')}
|
||||||
list={referenceList}
|
list={referenceList}
|
||||||
value={formatValue}
|
value={formatValue}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect as any}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -118,13 +118,13 @@ function Reference({
|
|||||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(e: ReferenceValueProps) => {
|
(e: ReferenceValueProps | ReferenceValueProps[]) => {
|
||||||
const workflowStartNode = nodeList.find(
|
const workflowStartNode = nodeList.find(
|
||||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||||
);
|
);
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1])
|
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1] as string)
|
||||||
? [VARIABLE_NODE_ID, e[1]]
|
? [VARIABLE_NODE_ID, e[1]]
|
||||||
: e;
|
: e;
|
||||||
|
|
||||||
@ -219,6 +219,7 @@ function Reference({
|
|||||||
list={referenceList}
|
list={referenceList}
|
||||||
value={formatValue}
|
value={formatValue}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
isArray={input.valueType?.includes('array')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!!editField && (
|
{!!editField && (
|
||||||
|
|||||||
@ -326,7 +326,7 @@ const Reference = ({
|
|||||||
placeholder={t('common:select_reference_variable')}
|
placeholder={t('common:select_reference_variable')}
|
||||||
list={referenceList}
|
list={referenceList}
|
||||||
value={formatValue}
|
value={formatValue}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect as any}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -365,7 +365,7 @@ const NodeCard = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
|
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
|
||||||
{Header}
|
{Header}
|
||||||
<Flex flexDirection={'column'} flex={1} my={4} gap={2}>
|
<Flex flexDirection={'column'} flex={1} my={!isFolded ? 4 : 0} gap={2}>
|
||||||
{!isFolded ? children : <Box h={4} />}
|
{!isFolded ? children : <Box h={4} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
{RenderHandle}
|
{RenderHandle}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponent
|
|||||||
import { defaultInput } from '../../FieldEditModal';
|
import { defaultInput } from '../../FieldEditModal';
|
||||||
import { getInputComponentProps } from '@fastgpt/global/core/workflow/node/io/utils';
|
import { getInputComponentProps } from '@fastgpt/global/core/workflow/node/io/utils';
|
||||||
import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants';
|
import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { ReferSelector, useReference } from '../Reference';
|
import { isReference, ReferSelector, useReference } from '../Reference';
|
||||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
import ValueTypeLabel from '../../../ValueTypeLabel';
|
import ValueTypeLabel from '../../../ValueTypeLabel';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
@ -126,13 +126,13 @@ function Reference({
|
|||||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(e: ReferenceValueProps) => {
|
(e: ReferenceValueProps | ReferenceValueProps[]) => {
|
||||||
const workflowStartNode = nodeList.find(
|
const workflowStartNode = nodeList.find(
|
||||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||||
);
|
);
|
||||||
|
|
||||||
const newValue =
|
const newValue =
|
||||||
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1])
|
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1] as string)
|
||||||
? [VARIABLE_NODE_ID, e[1]]
|
? [VARIABLE_NODE_ID, e[1]]
|
||||||
: e;
|
: e;
|
||||||
|
|
||||||
@ -155,18 +155,42 @@ function Reference({
|
|||||||
value: inputChildren.value
|
value: inputChildren.value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle array and non-array type conversion
|
||||||
|
const getValueTypeChange = useCallback(
|
||||||
|
(data: FlowNodeInputItemType, oldType: string | undefined) => {
|
||||||
|
const newType = data.valueType;
|
||||||
|
if (oldType === newType) return data.value;
|
||||||
|
|
||||||
|
if (!oldType?.includes('array') && newType?.includes('array')) {
|
||||||
|
return Array.isArray(data.value) && data.value.every((item) => isReference(item))
|
||||||
|
? data.value
|
||||||
|
: [data.value];
|
||||||
|
}
|
||||||
|
if (oldType?.includes('array') && !newType?.includes('array')) {
|
||||||
|
return Array.isArray(data.value) ? data.value[0] : data.value;
|
||||||
|
}
|
||||||
|
return data.value;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const onUpdateField = useCallback(
|
const onUpdateField = useCallback(
|
||||||
({ data }: { data: FlowNodeInputItemType }) => {
|
({ data }: { data: FlowNodeInputItemType }) => {
|
||||||
if (!data.key) return;
|
if (!data.key) return;
|
||||||
|
|
||||||
|
const updatedValue = getValueTypeChange(data, inputChildren.valueType);
|
||||||
|
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
nodeId,
|
nodeId,
|
||||||
type: 'replaceInput',
|
type: 'replaceInput',
|
||||||
key: inputChildren.key,
|
key: inputChildren.key,
|
||||||
value: data
|
value: {
|
||||||
|
...data,
|
||||||
|
value: updatedValue
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[inputChildren.key, nodeId, onChangeNode]
|
[inputChildren, nodeId, onChangeNode, getValueTypeChange]
|
||||||
);
|
);
|
||||||
const onDel = useCallback(() => {
|
const onDel = useCallback(() => {
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
@ -212,6 +236,7 @@ function Reference({
|
|||||||
list={referenceList}
|
list={referenceList}
|
||||||
value={formatValue}
|
value={formatValue}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
isArray={inputChildren.valueType?.includes('array')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!!editField && !!item.customInputConfig && (
|
{!!editField && !!item.customInputConfig && (
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const MultipleRowSelect = dynamic(
|
|||||||
const Avatar = dynamic(() => import('@fastgpt/web/components/common/Avatar'));
|
const Avatar = dynamic(() => import('@fastgpt/web/components/common/Avatar'));
|
||||||
|
|
||||||
type SelectProps = {
|
type SelectProps = {
|
||||||
value?: ReferenceValueProps;
|
value?: ReferenceValueProps[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
list: {
|
list: {
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
@ -33,18 +33,25 @@ type SelectProps = {
|
|||||||
valueType?: WorkflowIOValueTypeEnum;
|
valueType?: WorkflowIOValueTypeEnum;
|
||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
onSelect: (val: ReferenceValueProps) => void;
|
onSelect: (val: ReferenceValueProps | ReferenceValueProps[]) => void;
|
||||||
popDirection?: 'top' | 'bottom';
|
popDirection?: 'top' | 'bottom';
|
||||||
styles?: ButtonProps;
|
styles?: ButtonProps;
|
||||||
|
isArray?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isReference = (val: any) =>
|
||||||
|
Array.isArray(val) &&
|
||||||
|
val.length === 2 &&
|
||||||
|
typeof val[0] === 'string' &&
|
||||||
|
typeof val[1] === 'string';
|
||||||
|
|
||||||
const Reference = ({ item, nodeId }: RenderInputProps) => {
|
const Reference = ({ item, nodeId }: RenderInputProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(e: ReferenceValueProps) => {
|
(e: ReferenceValueProps | ReferenceValueProps[]) => {
|
||||||
const workflowStartNode = nodeList.find(
|
const workflowStartNode = nodeList.find(
|
||||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||||
);
|
);
|
||||||
@ -92,6 +99,7 @@ const Reference = ({ item, nodeId }: RenderInputProps) => {
|
|||||||
value={formatValue}
|
value={formatValue}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
popDirection={popDirection}
|
popDirection={popDirection}
|
||||||
|
isArray={item.valueType?.includes('array')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -111,6 +119,8 @@ export const useReference = ({
|
|||||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||||
|
const isArray = valueType?.includes('array');
|
||||||
|
const currentType = isArray ? valueType.replace('array', '').toLowerCase() : valueType;
|
||||||
|
|
||||||
const referenceList = useMemo(() => {
|
const referenceList = useMemo(() => {
|
||||||
const sourceNodes = computedNodeInputReference({
|
const sourceNodes = computedNodeInputReference({
|
||||||
@ -129,8 +139,8 @@ export const useReference = ({
|
|||||||
return {
|
return {
|
||||||
label: (
|
label: (
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
<Avatar src={node.avatar} w={'1.25rem'} borderRadius={'xs'} />
|
<Avatar src={node.avatar} w={isArray ? '1rem' : '1.25rem'} borderRadius={'xs'} />
|
||||||
<Box ml={2}>{t(node.name as any)}</Box>
|
<Box ml={1}>{t(node.name as any)}</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
),
|
),
|
||||||
value: node.nodeId,
|
value: node.nodeId,
|
||||||
@ -139,10 +149,20 @@ export const useReference = ({
|
|||||||
(output) =>
|
(output) =>
|
||||||
valueType === WorkflowIOValueTypeEnum.any ||
|
valueType === WorkflowIOValueTypeEnum.any ||
|
||||||
output.valueType === WorkflowIOValueTypeEnum.any ||
|
output.valueType === WorkflowIOValueTypeEnum.any ||
|
||||||
|
currentType === output.valueType ||
|
||||||
|
// array
|
||||||
output.valueType === valueType ||
|
output.valueType === valueType ||
|
||||||
// When valueType is arrayAny, return all array type outputs
|
|
||||||
(valueType === WorkflowIOValueTypeEnum.arrayAny &&
|
(valueType === WorkflowIOValueTypeEnum.arrayAny &&
|
||||||
output.valueType?.includes('array'))
|
[
|
||||||
|
WorkflowIOValueTypeEnum.arrayString,
|
||||||
|
WorkflowIOValueTypeEnum.arrayNumber,
|
||||||
|
WorkflowIOValueTypeEnum.arrayBoolean,
|
||||||
|
WorkflowIOValueTypeEnum.arrayObject,
|
||||||
|
WorkflowIOValueTypeEnum.string,
|
||||||
|
WorkflowIOValueTypeEnum.number,
|
||||||
|
WorkflowIOValueTypeEnum.boolean,
|
||||||
|
WorkflowIOValueTypeEnum.object
|
||||||
|
].includes(output.valueType as WorkflowIOValueTypeEnum))
|
||||||
)
|
)
|
||||||
.filter((output) => output.id !== NodeOutputKeyEnum.addOutputParam)
|
.filter((output) => output.id !== NodeOutputKeyEnum.addOutputParam)
|
||||||
.map((output) => {
|
.map((output) => {
|
||||||
@ -157,16 +177,14 @@ export const useReference = ({
|
|||||||
.filter((item) => item.children.length > 0);
|
.filter((item) => item.children.length > 0);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}, [appDetail.chatConfig, edges, nodeId, nodeList, t, valueType]);
|
}, [appDetail.chatConfig, currentType, edges, isArray, nodeId, nodeList, t, valueType]);
|
||||||
|
|
||||||
const formatValue = useMemo(() => {
|
const formatValue = useMemo(() => {
|
||||||
if (
|
// convert origin reference [variableId, outputId] to new reference [[variableId, outputId], ...]
|
||||||
Array.isArray(value) &&
|
if (isReference(value)) {
|
||||||
value.length === 2 &&
|
return [value] as ReferenceValueProps[];
|
||||||
typeof value[0] === 'string' &&
|
} else if (Array.isArray(value) && value.every((item) => isReference(item))) {
|
||||||
typeof value[1] === 'string'
|
return value as ReferenceValueProps[];
|
||||||
) {
|
|
||||||
return value as ReferenceValueProps;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [value]);
|
}, [value]);
|
||||||
@ -176,40 +194,115 @@ export const useReference = ({
|
|||||||
formatValue
|
formatValue
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const ReferSelector = ({
|
|
||||||
|
const ReferSelectorComponent = ({
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
list = [],
|
list = [],
|
||||||
onSelect,
|
onSelect,
|
||||||
popDirection
|
popDirection,
|
||||||
|
isArray
|
||||||
}: SelectProps) => {
|
}: SelectProps) => {
|
||||||
const selectItemLabel = useMemo(() => {
|
const { t } = useTranslation();
|
||||||
if (!value) {
|
|
||||||
|
const selectValue = useMemo(() => {
|
||||||
|
if (!value || value.every((item) => !item || item.every((subItem) => !subItem))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const firstColumn = list.find((item) => item.value === value[0]);
|
return value.map((valueItem) => {
|
||||||
|
const firstColumn = list.find((item) => item.value === valueItem[0]);
|
||||||
if (!firstColumn) {
|
if (!firstColumn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const secondColumn = firstColumn.children.find((item) => item.value === value[1]);
|
const secondColumn = firstColumn.children.find((item) => item.value === valueItem[1]);
|
||||||
if (!secondColumn) {
|
if (!secondColumn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return [firstColumn, secondColumn];
|
return [firstColumn, secondColumn];
|
||||||
|
});
|
||||||
}, [list, value]);
|
}, [list, value]);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<MultipleRowSelect
|
<MultipleRowSelect
|
||||||
label={
|
label={
|
||||||
selectItemLabel ? (
|
selectValue && selectValue.length > 0 ? (
|
||||||
<Flex alignItems={'center'}>
|
<Flex
|
||||||
{selectItemLabel[0].label}
|
gap={2}
|
||||||
<MyIcon name={'core/chat/chevronRight'} mx={1} w={4} />
|
flexWrap={isArray ? 'wrap' : undefined}
|
||||||
{selectItemLabel[1].label}
|
alignItems={'center'}
|
||||||
|
fontSize={'14px'}
|
||||||
|
>
|
||||||
|
{isArray ? (
|
||||||
|
// [[variableId, outputId], ...]
|
||||||
|
selectValue.map((item, index) => {
|
||||||
|
const isInvalidItem = item === undefined;
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
key={index}
|
||||||
|
bg={isInvalidItem ? 'red.50' : 'primary.50'}
|
||||||
|
color={isInvalidItem ? 'red.600' : 'myGray.900'}
|
||||||
|
py={1}
|
||||||
|
px={1.5}
|
||||||
|
rounded={'sm'}
|
||||||
|
>
|
||||||
|
{isInvalidItem ? (
|
||||||
|
t('common:invalid_variable')
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{item?.[0].label}
|
||||||
|
<MyIcon
|
||||||
|
name={'common/rightArrowLight'}
|
||||||
|
mx={1}
|
||||||
|
w={'12px'}
|
||||||
|
color={'myGray.500'}
|
||||||
|
/>
|
||||||
|
{item?.[1].label}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<MyIcon
|
||||||
|
name={'common/closeLight'}
|
||||||
|
w={'16px'}
|
||||||
|
ml={1}
|
||||||
|
cursor={'pointer'}
|
||||||
|
color={'myGray.500'}
|
||||||
|
_hover={{
|
||||||
|
color: 'primary.600'
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (isInvalidItem) {
|
||||||
|
const filteredValue = value?.filter((_, i) => i !== index);
|
||||||
|
onSelect(filteredValue as any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filteredValue = value?.filter(
|
||||||
|
(val) => val[0] !== item?.[0].value || val[1] !== item?.[1].value
|
||||||
|
);
|
||||||
|
filteredValue && onSelect(filteredValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : // [variableId, outputId]
|
||||||
|
selectValue[0] ? (
|
||||||
|
<Flex py={1} pl={1}>
|
||||||
|
{selectValue[0][0].label}
|
||||||
|
<MyIcon name={'common/rightArrowLight'} mx={1} w={'12px'} color={'myGray.500'} />
|
||||||
|
{selectValue[0][1].label}
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<Box>{placeholder}</Box>
|
<Box pl={2} py={1} fontSize={'14px'}>
|
||||||
|
{placeholder}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Box pl={2} py={1} fontSize={'14px'}>
|
||||||
|
{placeholder}
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={value as any[]}
|
value={value as any[]}
|
||||||
@ -218,9 +311,14 @@ export const ReferSelector = ({
|
|||||||
onSelect(e as ReferenceValueProps);
|
onSelect(e as ReferenceValueProps);
|
||||||
}}
|
}}
|
||||||
popDirection={popDirection}
|
popDirection={popDirection}
|
||||||
|
isArray={isArray}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [list, onSelect, placeholder, popDirection, selectItemLabel, value]);
|
}, [isArray, list, onSelect, placeholder, popDirection, selectValue, t, value]);
|
||||||
|
|
||||||
return Render;
|
return Render;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ReferSelectorComponent.displayName = 'ReferSelector';
|
||||||
|
|
||||||
|
export const ReferSelector = React.memo(ReferSelectorComponent);
|
||||||
|
|||||||
@ -327,24 +327,37 @@ export const checkWorkflowNodeAndConnection = ({
|
|||||||
// check reference invalid
|
// check reference invalid
|
||||||
const renderType = input.renderTypeList[input.selectedTypeIndex || 0];
|
const renderType = input.renderTypeList[input.selectedTypeIndex || 0];
|
||||||
if (renderType === FlowNodeInputTypeEnum.reference && input.required) {
|
if (renderType === FlowNodeInputTypeEnum.reference && input.required) {
|
||||||
if (!input.value || !Array.isArray(input.value) || input.value.length !== 2) {
|
const checkReference = (value: [string, string]) => {
|
||||||
return true;
|
if (value[0] === VARIABLE_NODE_ID) {
|
||||||
}
|
|
||||||
|
|
||||||
// variable key not need to check
|
|
||||||
if (input.value[0] === VARIABLE_NODE_ID) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can not find key
|
const sourceNode = nodes.find((item) => item.data.nodeId === value[0]);
|
||||||
const sourceNode = nodes.find((item) => item.data.nodeId === input.value[0]);
|
|
||||||
if (!sourceNode) {
|
if (!sourceNode) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const sourceOutput = sourceNode.data.outputs.find((item) => item.id === input.value[1]);
|
|
||||||
if (!sourceOutput) {
|
const sourceOutput = sourceNode.data.outputs.find((item) => item.id === value[1]);
|
||||||
|
return !sourceOutput;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Old format
|
||||||
|
if (
|
||||||
|
Array.isArray(input.value) &&
|
||||||
|
input.value.length === 2 &&
|
||||||
|
typeof input.value[0] === 'string' &&
|
||||||
|
typeof input.value[1] === 'string'
|
||||||
|
) {
|
||||||
|
return checkReference(input.value as [string, string]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New format
|
||||||
|
return input.value.some((inputItem: ReferenceValueProps) => {
|
||||||
|
if (!Array.isArray(inputItem) || inputItem.length !== 2) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return checkReference(inputItem as [string, string]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user