Commit 2053bbdb by heheer Committed by GitHub

feat: add elseif to ifelse node (#1378)

parent 9e192c6d
...@@ -141,8 +141,7 @@ export enum NodeOutputKeyEnum { ...@@ -141,8 +141,7 @@ export enum NodeOutputKeyEnum {
// plugin // plugin
pluginStart = 'pluginStart', pluginStart = 'pluginStart',
if = 'IF', ifElseResult = 'ifElseResult'
else = 'ELSE'
} }
export enum VariableInputEnum { export enum VariableInputEnum {
......
...@@ -75,7 +75,7 @@ export type DispatchNodeResponseType = { ...@@ -75,7 +75,7 @@ export type DispatchNodeResponseType = {
pluginDetail?: ChatHistoryItemResType[]; pluginDetail?: ChatHistoryItemResType[];
// if-else // if-else
ifElseResult?: 'IF' | 'ELSE'; ifElseResult?: string;
// tool // tool
toolCallTokens?: number; toolCallTokens?: number;
......
...@@ -24,41 +24,31 @@ export const IfElseNode: FlowNodeTemplateType = { ...@@ -24,41 +24,31 @@ export const IfElseNode: FlowNodeTemplateType = {
showStatus: true, showStatus: true,
inputs: [ inputs: [
{ {
key: NodeInputKeyEnum.condition,
valueType: WorkflowIOValueTypeEnum.string,
label: '',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
required: false,
value: 'AND' // AND, OR
},
{
key: NodeInputKeyEnum.ifElseList, key: NodeInputKeyEnum.ifElseList,
renderTypeList: [FlowNodeInputTypeEnum.hidden], renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.any, valueType: WorkflowIOValueTypeEnum.any,
label: '', label: '',
value: [ value: [
{ {
condition: 'AND', // AND, OR
list: [
{
variable: undefined, variable: undefined,
condition: undefined, condition: undefined,
value: undefined value: undefined
} }
] ]
} }
]
}
], ],
outputs: [ outputs: [
{ {
id: NodeOutputKeyEnum.if, id: NodeOutputKeyEnum.ifElseResult,
key: NodeOutputKeyEnum.if, key: NodeOutputKeyEnum.ifElseResult,
label: 'IF', label: 'IF ELSE',
valueType: WorkflowIOValueTypeEnum.any, valueType: WorkflowIOValueTypeEnum.string,
type: FlowNodeOutputTypeEnum.source type: FlowNodeOutputTypeEnum.static
},
{
id: NodeOutputKeyEnum.else,
key: NodeOutputKeyEnum.else,
label: 'ELSE',
valueType: WorkflowIOValueTypeEnum.any,
type: FlowNodeOutputTypeEnum.source
} }
] ]
}; };
...@@ -2,8 +2,12 @@ import { ReferenceValueProps } from 'core/workflow/type/io'; ...@@ -2,8 +2,12 @@ import { ReferenceValueProps } from 'core/workflow/type/io';
import { VariableConditionEnum } from './constant'; import { VariableConditionEnum } from './constant';
export type IfElseConditionType = 'AND' | 'OR'; export type IfElseConditionType = 'AND' | 'OR';
export type IfElseListItemType = { export type ConditionListItemType = {
variable?: ReferenceValueProps; variable?: ReferenceValueProps;
condition?: VariableConditionEnum; condition?: VariableConditionEnum;
value?: string; value?: string;
}; };
export type IfElseListItemType = {
condition: IfElseConditionType;
list: ConditionListItemType[];
};
...@@ -3,6 +3,7 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti ...@@ -3,6 +3,7 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { VariableConditionEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant'; import { VariableConditionEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
import { import {
ConditionListItemType,
IfElseConditionType, IfElseConditionType,
IfElseListItemType IfElseListItemType
} from '@fastgpt/global/core/workflow/template/system/ifElse/type'; } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
...@@ -41,15 +42,13 @@ function checkCondition(condition: VariableConditionEnum, variableValue: any, va ...@@ -41,15 +42,13 @@ function checkCondition(condition: VariableConditionEnum, variableValue: any, va
return (operations[condition] || (() => false))(); return (operations[condition] || (() => false))();
} }
export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultType<{}>> => { function getResult(
const { condition: IfElseConditionType,
params, list: ConditionListItemType[],
runtimeNodes, variables: any,
variables, runtimeNodes: any[]
node: { nodeId } ) {
} = props; const listResult = list.map((item) => {
const { condition, ifElseList } = params;
const listResult = ifElseList.map((item) => {
const { variable, condition: variableCondition, value } = item; const { variable, condition: variableCondition, value } = item;
const variableValue = getReferenceVariableValue({ const variableValue = getReferenceVariableValue({
...@@ -61,15 +60,40 @@ export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultTy ...@@ -61,15 +60,40 @@ export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultTy
return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || ''); return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || '');
}); });
const result = condition === 'AND' ? listResult.every(Boolean) : listResult.some(Boolean); return condition === 'AND' ? listResult.every(Boolean) : listResult.some(Boolean);
}
export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultType<{}>> => {
const {
params,
runtimeNodes,
variables,
node: { nodeId }
} = props;
const { ifElseList } = params;
let res = 'ELSE';
for (let i = 0; i < ifElseList.length; i++) {
const item = ifElseList[i];
const result = getResult(item.condition, item.list, variables, runtimeNodes);
if (result) {
res = `IF${i}`;
break;
}
}
const resArray = Array.from({ length: ifElseList.length + 1 }, (_, index) => {
const label = index < ifElseList.length ? `IF${index}` : 'ELSE';
return getHandleId(nodeId, 'source', label);
});
return { return {
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0, totalPoints: 0,
ifElseResult: result ? 'IF' : 'ELSE' ifElseResult: res
}, },
[DispatchNodeResponseKeyEnum.skipHandleId]: result [DispatchNodeResponseKeyEnum.skipHandleId]: resArray.filter(
? [getHandleId(nodeId, 'source', 'ELSE')] (item) => item !== getHandleId(nodeId, 'source', res)
: [getHandleId(nodeId, 'source', 'IF')] )
}; };
}; };
...@@ -935,6 +935,7 @@ ...@@ -935,6 +935,7 @@
}, },
"input": { "input": {
"Add Input": "Add Input", "Add Input": "Add Input",
"Add Branch": "Add Branch",
"Input Number": "Input: {{length}}", "Input Number": "Input: {{length}}",
"add": "", "add": "",
"description": { "description": {
......
...@@ -937,6 +937,7 @@ ...@@ -937,6 +937,7 @@
"Add Input": "添加入参", "Add Input": "添加入参",
"Input Number": "入参: {{length}}", "Input Number": "入参: {{length}}",
"add": "添加条件", "add": "添加条件",
"Add Branch": "添加分支",
"description": { "description": {
"Background": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。", "Background": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。",
"HTTP Dynamic Input": "接收前方节点的输出值作为变量,这些变量可以被HTTP请求参数使用。", "HTTP Dynamic Input": "接收前方节点的输出值作为变量,这些变量可以被HTTP请求参数使用。",
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
"nextjs-node-loader": "^1.1.5", "nextjs-node-loader": "^1.1.5",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"react": "18.2.0", "react": "18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-day-picker": "^8.7.1", "react-day-picker": "^8.7.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "7.43.1", "react-hook-form": "7.43.1",
...@@ -72,6 +73,7 @@ ...@@ -72,6 +73,7 @@
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/node": "^20.8.5", "@types/node": "^20.8.5",
"@types/react": "18.2.0", "@types/react": "18.2.0",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "18.2.0", "@types/react-dom": "18.2.0",
"@types/react-syntax-highlighter": "^15.5.6", "@types/react-syntax-highlighter": "^15.5.6",
"@types/request-ip": "^0.0.37", "@types/request-ip": "^0.0.37",
......
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);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment