mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 05:12:39 +00:00
feat: add elseif to ifelse node (#1378)
This commit is contained in:
@@ -935,6 +935,7 @@
|
||||
},
|
||||
"input": {
|
||||
"Add Input": "Add Input",
|
||||
"Add Branch": "Add Branch",
|
||||
"Input Number": "Input: {{length}}",
|
||||
"add": "",
|
||||
"description": {
|
||||
|
@@ -937,6 +937,7 @@
|
||||
"Add Input": "添加入参",
|
||||
"Input Number": "入参: {{length}}",
|
||||
"add": "添加条件",
|
||||
"Add Branch": "添加分支",
|
||||
"description": {
|
||||
"Background": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。",
|
||||
"HTTP Dynamic Input": "接收前方节点的输出值作为变量,这些变量可以被HTTP请求参数使用。",
|
||||
|
@@ -47,6 +47,7 @@
|
||||
"nextjs-node-loader": "^1.1.5",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.43.1",
|
||||
@@ -72,6 +73,7 @@
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^20.8.5",
|
||||
"@types/react": "18.2.0",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-dom": "18.2.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@types/request-ip": "^0.0.37",
|
||||
|
@@ -1,381 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Flex, background } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import RenderOutput from './render/RenderOutput';
|
||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import {
|
||||
IfElseConditionType,
|
||||
IfElseListItemType
|
||||
} from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
||||
import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { ReferSelector, useReference } from './render/RenderInput/templates/Reference';
|
||||
import {
|
||||
VariableConditionEnum,
|
||||
allConditionList,
|
||||
arrayConditionList,
|
||||
booleanConditionList,
|
||||
numberConditionList
|
||||
} from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
|
||||
import { stringConditionList } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyInput from '@/components/MyInput';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
|
||||
const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs = [], outputs } = data;
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const condition = useMemo(
|
||||
() =>
|
||||
(inputs.find((input) => input.key === NodeInputKeyEnum.condition)
|
||||
?.value as IfElseConditionType) || 'OR',
|
||||
[inputs]
|
||||
);
|
||||
const ifElseList = useMemo(
|
||||
() =>
|
||||
(inputs.find((input) => input.key === NodeInputKeyEnum.ifElseList)
|
||||
?.value as IfElseListItemType[]) || [],
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const onUpdateIfElseList = useCallback(
|
||||
(value: IfElseListItemType[]) => {
|
||||
const ifElseListInput = inputs.find((input) => input.key === NodeInputKeyEnum.ifElseList);
|
||||
if (!ifElseListInput) return;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.ifElseList,
|
||||
value: {
|
||||
...ifElseListInput,
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, nodeId, onChangeNode]
|
||||
);
|
||||
|
||||
const RenderAddCondition = useMemo(() => {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onUpdateIfElseList([
|
||||
...ifElseList,
|
||||
{
|
||||
variable: undefined,
|
||||
condition: undefined,
|
||||
value: undefined
|
||||
}
|
||||
]);
|
||||
}}
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
my={3}
|
||||
w={'full'}
|
||||
>
|
||||
{t('core.module.input.add')}
|
||||
</Button>
|
||||
);
|
||||
}, [ifElseList, onUpdateIfElseList, t]);
|
||||
|
||||
return (
|
||||
<NodeCard selected={selected} maxW={'1000px'} {...data}>
|
||||
<Box px={6}>
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={[outputs[0]]} />
|
||||
</Box>
|
||||
<Box py={3} px={4}>
|
||||
<Box className="nowheel">
|
||||
{ifElseList.map((item, i) => {
|
||||
return (
|
||||
<Box key={i}>
|
||||
{/* border */}
|
||||
{i !== 0 && (
|
||||
<Flex alignItems={'center'} w={'full'} py={'5px'}>
|
||||
<Box
|
||||
w={'auto'}
|
||||
flex={1}
|
||||
height={'1px'}
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(90deg, rgba(232, 235, 240, 0.00) 0%, #E8EBF0 100%)'
|
||||
}}
|
||||
></Box>
|
||||
<Flex
|
||||
px={'2.5'}
|
||||
color={'primary.600'}
|
||||
fontWeight={'medium'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
rounded={'md'}
|
||||
onClick={() => {
|
||||
const conditionInput = inputs.find(
|
||||
(input) => input.key === NodeInputKeyEnum.condition
|
||||
);
|
||||
if (!conditionInput) return;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.condition,
|
||||
value: {
|
||||
...conditionInput,
|
||||
value: conditionInput.value === 'OR' ? 'AND' : 'OR'
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{condition}
|
||||
<MyIcon ml={1} boxSize={5} name="change" />
|
||||
</Flex>
|
||||
<Box
|
||||
w={'auto'}
|
||||
flex={1}
|
||||
height={'1px'}
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(90deg, #E8EBF0 0%, rgba(232, 235, 240, 0.00) 100%)'
|
||||
}}
|
||||
></Box>
|
||||
</Flex>
|
||||
)}
|
||||
{/* condition list */}
|
||||
<Flex gap={2} alignItems={'center'}>
|
||||
{/* variable reference */}
|
||||
<Box minW={'250px'}>
|
||||
<Reference
|
||||
nodeId={nodeId}
|
||||
variable={item.variable}
|
||||
onSelect={(e) => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...ifElse,
|
||||
variable: e
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* condition select */}
|
||||
<Box w={'130px'} flex={1}>
|
||||
<ConditionSelect
|
||||
condition={item.condition}
|
||||
variable={item.variable}
|
||||
onSelect={(e) => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...ifElse,
|
||||
condition: e
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* value */}
|
||||
<Box w={'200px'}>
|
||||
<ConditionValueInput
|
||||
value={item.value}
|
||||
condition={item.condition}
|
||||
variable={item.variable}
|
||||
onChange={(e) => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...ifElse,
|
||||
value: e
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* delete */}
|
||||
{ifElseList.length > 1 && (
|
||||
<MyIcon
|
||||
ml={2}
|
||||
boxSize={5}
|
||||
name="delete"
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
color={'myGray.400'}
|
||||
onClick={() => {
|
||||
onUpdateIfElseList(ifElseList.filter((_, index) => index !== i));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
{RenderAddCondition}
|
||||
</Box>
|
||||
<Box px={6} mb={4}>
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={[outputs[1]]} />
|
||||
</Box>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeIfElse);
|
||||
|
||||
const Reference = ({
|
||||
nodeId,
|
||||
variable,
|
||||
onSelect
|
||||
}: {
|
||||
nodeId: string;
|
||||
variable?: ReferenceValueProps;
|
||||
onSelect: (e: ReferenceValueProps) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { referenceList, formatValue } = useReference({
|
||||
nodeId,
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: variable
|
||||
});
|
||||
|
||||
return (
|
||||
<ReferSelector
|
||||
placeholder={t('选择引用变量')}
|
||||
list={referenceList}
|
||||
value={formatValue}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/* Different data types have different options */
|
||||
const ConditionSelect = ({
|
||||
condition,
|
||||
variable,
|
||||
onSelect
|
||||
}: {
|
||||
condition?: VariableConditionEnum;
|
||||
variable?: ReferenceValueProps;
|
||||
onSelect: (e: VariableConditionEnum) => void;
|
||||
}) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
// get condition type
|
||||
const valueType = useMemo(() => {
|
||||
if (!variable) return;
|
||||
const node = nodeList.find((node) => node.nodeId === variable[0]);
|
||||
|
||||
if (!node) return WorkflowIOValueTypeEnum.any;
|
||||
const output = node.outputs.find((item) => item.id === variable[1]);
|
||||
|
||||
if (!output) return WorkflowIOValueTypeEnum.any;
|
||||
return output.valueType;
|
||||
}, [nodeList, variable]);
|
||||
|
||||
const conditionList = useMemo(() => {
|
||||
if (valueType === WorkflowIOValueTypeEnum.string) return stringConditionList;
|
||||
if (valueType === WorkflowIOValueTypeEnum.number) return numberConditionList;
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) return booleanConditionList;
|
||||
if (
|
||||
valueType === WorkflowIOValueTypeEnum.chatHistory ||
|
||||
valueType === WorkflowIOValueTypeEnum.datasetQuote ||
|
||||
valueType === WorkflowIOValueTypeEnum.dynamic ||
|
||||
valueType === WorkflowIOValueTypeEnum.selectApp ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayBoolean ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayNumber ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayObject ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayString ||
|
||||
valueType === WorkflowIOValueTypeEnum.object
|
||||
)
|
||||
return arrayConditionList;
|
||||
|
||||
if (valueType === WorkflowIOValueTypeEnum.any) return allConditionList;
|
||||
|
||||
return [];
|
||||
}, [valueType]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
w={'100%'}
|
||||
list={conditionList}
|
||||
value={condition}
|
||||
onchange={onSelect}
|
||||
placeholder="选择条件"
|
||||
></MySelect>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
Different condition can be entered differently
|
||||
empty, notEmpty: forbid input
|
||||
boolean type: select true/false
|
||||
*/
|
||||
const ConditionValueInput = ({
|
||||
value = '',
|
||||
variable,
|
||||
condition,
|
||||
onChange
|
||||
}: {
|
||||
value?: string;
|
||||
variable?: ReferenceValueProps;
|
||||
condition?: VariableConditionEnum;
|
||||
onChange: (e: string) => void;
|
||||
}) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
// get value type
|
||||
const valueType = useMemo(() => {
|
||||
if (!variable) return;
|
||||
const node = nodeList.find((node) => node.nodeId === variable[0]);
|
||||
|
||||
if (!node) return WorkflowIOValueTypeEnum.any;
|
||||
const output = node.outputs.find((item) => item.id === variable[1]);
|
||||
|
||||
if (!output) return WorkflowIOValueTypeEnum.any;
|
||||
return output.valueType;
|
||||
}, [nodeList, variable]);
|
||||
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return (
|
||||
<MySelect
|
||||
list={[
|
||||
{ label: 'True', value: 'true' },
|
||||
{ label: 'False', value: 'false' }
|
||||
]}
|
||||
onchange={onChange}
|
||||
value={value}
|
||||
placeholder={'选择值'}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<MyInput
|
||||
value={value}
|
||||
placeholder={'输入值'}
|
||||
w={'100%'}
|
||||
isDisabled={
|
||||
condition === VariableConditionEnum.isEmpty ||
|
||||
condition === VariableConditionEnum.isNotEmpty
|
||||
}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
@@ -0,0 +1,412 @@
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd';
|
||||
import Container from '../../components/Container';
|
||||
import { DragHandleIcon, MinusIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReferSelector, useReference } from '../render/RenderInput/templates/Reference';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
VariableConditionEnum,
|
||||
allConditionList,
|
||||
arrayConditionList,
|
||||
booleanConditionList,
|
||||
numberConditionList,
|
||||
stringConditionList
|
||||
} from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import React, { useMemo } from 'react';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyInput from '@/components/MyInput';
|
||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
import { SourceHandle } from '../render/Handle';
|
||||
import { Position, useReactFlow } from 'reactflow';
|
||||
|
||||
const ListItem = ({
|
||||
provided,
|
||||
snapshot,
|
||||
conditionIndex,
|
||||
conditionItem,
|
||||
ifElseList,
|
||||
onUpdateIfElseList,
|
||||
nodeId
|
||||
}: {
|
||||
provided: DraggableProvided;
|
||||
snapshot: DraggableStateSnapshot;
|
||||
conditionIndex: number;
|
||||
conditionItem: IfElseListItemType;
|
||||
ifElseList: IfElseListItemType[];
|
||||
onUpdateIfElseList: (value: IfElseListItemType[]) => void;
|
||||
nodeId: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { getZoom } = useReactFlow();
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
opacity: snapshot.isDragging ? 0.8 : 1
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
transform={snapshot.isDragging ? `scale(${getZoom()})` : ''}
|
||||
transformOrigin={'top left'}
|
||||
>
|
||||
<Container w={snapshot.isDragging ? '' : 'full'} className="nodrag">
|
||||
<Flex mb={4} alignItems={'center'}>
|
||||
<Box {...provided.dragHandleProps}>
|
||||
<DragHandleIcon color={'blackAlpha.600'} />
|
||||
</Box>
|
||||
<Box color={'black'} fontSize={'lg'} ml={2}>
|
||||
{conditionIndex === 0 ? 'IF' : 'ELSE IF'}
|
||||
</Box>
|
||||
<Flex
|
||||
px={'2.5'}
|
||||
color={'primary.600'}
|
||||
fontWeight={'medium'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
rounded={'md'}
|
||||
onClick={() => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === conditionIndex) {
|
||||
return {
|
||||
...ifElse,
|
||||
condition: ifElse.condition === 'AND' ? 'OR' : 'AND'
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
{conditionItem.condition}
|
||||
<MyIcon ml={1} boxSize={5} name="change" />
|
||||
</Flex>
|
||||
<Box flex={1}></Box>
|
||||
{ifElseList.length > 1 && (
|
||||
<MyIcon
|
||||
ml={2}
|
||||
boxSize={5}
|
||||
name="delete"
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
color={'myGray.400'}
|
||||
onClick={() => {
|
||||
onUpdateIfElseList(ifElseList.filter((_, index) => index !== conditionIndex));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box>
|
||||
{conditionItem.list?.map((item, i) => {
|
||||
return (
|
||||
<Box key={i}>
|
||||
{/* condition list */}
|
||||
<Flex gap={2} mb={2} alignItems={'center'}>
|
||||
{/* variable reference */}
|
||||
<Box minW={'250px'}>
|
||||
<Reference
|
||||
nodeId={nodeId}
|
||||
variable={item.variable}
|
||||
onSelect={(e) => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === conditionIndex) {
|
||||
return {
|
||||
...ifElse,
|
||||
list: ifElse.list.map((item, index) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...item,
|
||||
variable: e
|
||||
};
|
||||
}
|
||||
return item;
|
||||
})
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* condition select */}
|
||||
<Box w={'130px'} flex={1}>
|
||||
<ConditionSelect
|
||||
condition={item.condition}
|
||||
variable={item.variable}
|
||||
onSelect={(e) => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === conditionIndex) {
|
||||
return {
|
||||
...ifElse,
|
||||
list: ifElse.list.map((item, index) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...item,
|
||||
condition: e
|
||||
};
|
||||
}
|
||||
return item;
|
||||
})
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* value */}
|
||||
<Box w={'200px'}>
|
||||
<ConditionValueInput
|
||||
value={item.value}
|
||||
condition={item.condition}
|
||||
variable={item.variable}
|
||||
onChange={(e) => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === conditionIndex) {
|
||||
return {
|
||||
...ifElse,
|
||||
list: ifElse.list.map((item, index) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...item,
|
||||
value: e
|
||||
};
|
||||
}
|
||||
return item;
|
||||
})
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* delete */}
|
||||
{conditionItem.list.length > 1 && (
|
||||
<MinusIcon
|
||||
ml={2}
|
||||
boxSize={3}
|
||||
name="delete"
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
color={'myGray.400'}
|
||||
onClick={() => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === conditionIndex) {
|
||||
return {
|
||||
...ifElse,
|
||||
list: ifElse.list.filter((_, index) => index !== i)
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onUpdateIfElseList(
|
||||
ifElseList.map((ifElse, index) => {
|
||||
if (index === conditionIndex) {
|
||||
return {
|
||||
...ifElse,
|
||||
list: ifElse.list.concat({
|
||||
variable: undefined,
|
||||
condition: undefined,
|
||||
value: undefined
|
||||
})
|
||||
};
|
||||
}
|
||||
return ifElse;
|
||||
})
|
||||
);
|
||||
}}
|
||||
variant={'link'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
my={3}
|
||||
color={'primary.600'}
|
||||
>
|
||||
{t('core.module.input.add')}
|
||||
</Button>
|
||||
</Container>
|
||||
{!snapshot.isDragging && (
|
||||
<SourceHandle
|
||||
nodeId={nodeId}
|
||||
handleId={getHandleId(nodeId, 'source', 'IF' + conditionIndex.toString())}
|
||||
position={Position.Right}
|
||||
translate={[18, 0]}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ListItem);
|
||||
|
||||
const Reference = ({
|
||||
nodeId,
|
||||
variable,
|
||||
onSelect
|
||||
}: {
|
||||
nodeId: string;
|
||||
variable?: ReferenceValueProps;
|
||||
onSelect: (e: ReferenceValueProps) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { referenceList, formatValue } = useReference({
|
||||
nodeId,
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: variable
|
||||
});
|
||||
|
||||
return (
|
||||
<ReferSelector
|
||||
placeholder={t('选择引用变量')}
|
||||
list={referenceList}
|
||||
value={formatValue}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/* Different data types have different options */
|
||||
const ConditionSelect = ({
|
||||
condition,
|
||||
variable,
|
||||
onSelect
|
||||
}: {
|
||||
condition?: VariableConditionEnum;
|
||||
variable?: ReferenceValueProps;
|
||||
onSelect: (e: VariableConditionEnum) => void;
|
||||
}) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
// get condition type
|
||||
const valueType = useMemo(() => {
|
||||
if (!variable) return;
|
||||
const node = nodeList.find((node) => node.nodeId === variable[0]);
|
||||
|
||||
if (!node) return WorkflowIOValueTypeEnum.any;
|
||||
const output = node.outputs.find((item) => item.id === variable[1]);
|
||||
|
||||
if (!output) return WorkflowIOValueTypeEnum.any;
|
||||
return output.valueType;
|
||||
}, [nodeList, variable]);
|
||||
|
||||
const conditionList = useMemo(() => {
|
||||
if (valueType === WorkflowIOValueTypeEnum.string) return stringConditionList;
|
||||
if (valueType === WorkflowIOValueTypeEnum.number) return numberConditionList;
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) return booleanConditionList;
|
||||
if (
|
||||
valueType === WorkflowIOValueTypeEnum.chatHistory ||
|
||||
valueType === WorkflowIOValueTypeEnum.datasetQuote ||
|
||||
valueType === WorkflowIOValueTypeEnum.dynamic ||
|
||||
valueType === WorkflowIOValueTypeEnum.selectApp ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayBoolean ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayNumber ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayObject ||
|
||||
valueType === WorkflowIOValueTypeEnum.arrayString ||
|
||||
valueType === WorkflowIOValueTypeEnum.object
|
||||
)
|
||||
return arrayConditionList;
|
||||
|
||||
if (valueType === WorkflowIOValueTypeEnum.any) return allConditionList;
|
||||
|
||||
return [];
|
||||
}, [valueType]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
w={'100%'}
|
||||
list={conditionList}
|
||||
value={condition}
|
||||
onchange={onSelect}
|
||||
placeholder="选择条件"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
Different condition can be entered differently
|
||||
empty, notEmpty: forbid input
|
||||
boolean type: select true/false
|
||||
*/
|
||||
const ConditionValueInput = ({
|
||||
value = '',
|
||||
variable,
|
||||
condition,
|
||||
onChange
|
||||
}: {
|
||||
value?: string;
|
||||
variable?: ReferenceValueProps;
|
||||
condition?: VariableConditionEnum;
|
||||
onChange: (e: string) => void;
|
||||
}) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
// get value type
|
||||
const valueType = useMemo(() => {
|
||||
if (!variable) return;
|
||||
const node = nodeList.find((node) => node.nodeId === variable[0]);
|
||||
|
||||
if (!node) return WorkflowIOValueTypeEnum.any;
|
||||
const output = node.outputs.find((item) => item.id === variable[1]);
|
||||
|
||||
if (!output) return WorkflowIOValueTypeEnum.any;
|
||||
return output.valueType;
|
||||
}, [nodeList, variable]);
|
||||
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return (
|
||||
<MySelect
|
||||
list={[
|
||||
{ label: 'True', value: 'true' },
|
||||
{ label: 'False', value: 'false' }
|
||||
]}
|
||||
onchange={onChange}
|
||||
value={value}
|
||||
placeholder={'选择值'}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<MyInput
|
||||
value={value}
|
||||
placeholder={'输入值'}
|
||||
w={'100%'}
|
||||
bg={'white'}
|
||||
isDisabled={
|
||||
condition === VariableConditionEnum.isEmpty ||
|
||||
condition === VariableConditionEnum.isNotEmpty
|
||||
}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
@@ -0,0 +1,161 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeProps, Position } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import Container from '../../components/Container';
|
||||
import { DragDropContext, DragStart, Draggable, DropResult, Droppable } from 'react-beautiful-dnd';
|
||||
import { SourceHandle } from '../render/Handle';
|
||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
import ListItem from './ListItem';
|
||||
|
||||
const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs = [] } = data;
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const [draggingItemHeight, setDraggingItemHeight] = useState(0);
|
||||
|
||||
const ifElseList = useMemo(
|
||||
() =>
|
||||
(inputs.find((input) => input.key === NodeInputKeyEnum.ifElseList)
|
||||
?.value as IfElseListItemType[]) || [],
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const onUpdateIfElseList = useCallback(
|
||||
(value: IfElseListItemType[]) => {
|
||||
const ifElseListInput = inputs.find((input) => input.key === NodeInputKeyEnum.ifElseList);
|
||||
if (!ifElseListInput) return;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.ifElseList,
|
||||
value: {
|
||||
...ifElseListInput,
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, nodeId, onChangeNode]
|
||||
);
|
||||
|
||||
const reorder = (list: IfElseListItemType[], startIndex: number, endIndex: number) => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const onDragStart = (start: DragStart) => {
|
||||
const draggingNode = document.querySelector(`[data-rbd-draggable-id="${start.draggableId}"]`);
|
||||
setDraggingItemHeight(draggingNode?.getBoundingClientRect().height || 0);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
const newList = reorder(ifElseList, result.source.index, result.destination.index);
|
||||
|
||||
onUpdateIfElseList(newList);
|
||||
setDraggingItemHeight(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<NodeCard selected={selected} maxW={'1000px'} {...data}>
|
||||
<Box px={4}>
|
||||
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
||||
<Droppable
|
||||
droppableId="droppable"
|
||||
renderClone={(provided, snapshot, rubric) => (
|
||||
<ListItem
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
conditionItem={ifElseList[rubric.source.index]}
|
||||
conditionIndex={rubric.source.index}
|
||||
ifElseList={ifElseList}
|
||||
onUpdateIfElseList={onUpdateIfElseList}
|
||||
nodeId={nodeId}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<Box {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{ifElseList.map((conditionItem, conditionIndex) => (
|
||||
<Draggable
|
||||
key={conditionIndex}
|
||||
draggableId={conditionIndex.toString()}
|
||||
index={conditionIndex}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ListItem
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
conditionItem={conditionItem}
|
||||
conditionIndex={conditionIndex}
|
||||
ifElseList={ifElseList}
|
||||
onUpdateIfElseList={onUpdateIfElseList}
|
||||
nodeId={nodeId}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
<Box height={draggingItemHeight} />
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<Container position={'relative'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box color={'black'} fontSize={'lg'} ml={2}>
|
||||
ELSE
|
||||
</Box>
|
||||
<SourceHandle
|
||||
nodeId={nodeId}
|
||||
handleId={getHandleId(nodeId, 'source', 'ELSE')}
|
||||
position={Position.Right}
|
||||
translate={[26, 0]}
|
||||
/>
|
||||
</Flex>
|
||||
</Container>
|
||||
</Box>
|
||||
<Box py={3} px={6}>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
w={'full'}
|
||||
onClick={() => {
|
||||
const ifElseListInput = inputs.find(
|
||||
(input) => input.key === NodeInputKeyEnum.ifElseList
|
||||
);
|
||||
if (!ifElseListInput) return;
|
||||
|
||||
onUpdateIfElseList([
|
||||
...ifElseList,
|
||||
{
|
||||
condition: 'AND',
|
||||
list: [
|
||||
{
|
||||
variable: undefined,
|
||||
condition: undefined,
|
||||
value: undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
}}
|
||||
>
|
||||
{t('core.module.input.Add Branch')}
|
||||
</Button>
|
||||
</Box>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeIfElse);
|
Reference in New Issue
Block a user