feat(web): if-else support sub variable
This commit is contained in:
@@ -2389,6 +2389,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
else_desc: 'Used to define the logic that should be executed when the if condition is not met.',
|
else_desc: 'Used to define the logic that should be executed when the if condition is not met.',
|
||||||
unset: 'Condition Not Set',
|
unset: 'Condition Not Set',
|
||||||
set: 'Set',
|
set: 'Set',
|
||||||
|
addSubVariable: 'Add Sub Variable',
|
||||||
},
|
},
|
||||||
'http-request': {
|
'http-request': {
|
||||||
auth: 'Authentication',
|
auth: 'Authentication',
|
||||||
|
|||||||
@@ -2350,6 +2350,7 @@ export const zh = {
|
|||||||
else_desc: '用于定义当 if 条件不满足时应执行的逻辑。',
|
else_desc: '用于定义当 if 条件不满足时应执行的逻辑。',
|
||||||
unset: '条件未设置',
|
unset: '条件未设置',
|
||||||
set: '已设置',
|
set: '已设置',
|
||||||
|
addSubVariable: '添加子变量',
|
||||||
},
|
},
|
||||||
'http-request': {
|
'http-request': {
|
||||||
auth: '鉴权',
|
auth: '鉴权',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Node } from '@antv/x6';
|
|||||||
|
|
||||||
import type { WorkflowRef } from '@/views/ApplicationConfig/types'
|
import type { WorkflowRef } from '@/views/ApplicationConfig/types'
|
||||||
import { nodeLibrary } from '../../constant'
|
import { nodeLibrary } from '../../constant'
|
||||||
|
import { isSubExprSet } from '../../utils'
|
||||||
import { getToolMethods } from '@/api/tools'
|
import { getToolMethods } from '@/api/tools'
|
||||||
import RbDrawer from '@/components/RbDrawer'
|
import RbDrawer from '@/components/RbDrawer'
|
||||||
import { useWorkflowStore } from '@/store/workflow'
|
import { useWorkflowStore } from '@/store/workflow'
|
||||||
@@ -44,14 +45,13 @@ const specialValidators: Record<string, (val: any) => boolean> = {
|
|||||||
// if-else.cases: every case must have at least one expression, and every expression must be fully set
|
// if-else.cases: every case must have at least one expression, and every expression must be fully set
|
||||||
'if-else.cases': (val: any[]) => {
|
'if-else.cases': (val: any[]) => {
|
||||||
if (!Array.isArray(val) || !val.length) return true
|
if (!Array.isArray(val) || !val.length) return true
|
||||||
return val.some(c => {
|
const isExprSet = (expr: any) => {
|
||||||
if (!c?.expressions?.length) return true
|
if (expr?.sub_variable_condition?.conditions?.length > 0) return expr.sub_variable_condition?.conditions.every(isSubExprSet)
|
||||||
return c.expressions.some((expr: any) => {
|
if (!expr.left) return false
|
||||||
if (!expr?.left) return true
|
if (['not_empty', 'empty'].includes(expr.operator)) return true
|
||||||
if (['not_empty', 'empty'].includes(expr.operator)) return false
|
return !!expr.left && (!!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number')
|
||||||
return !(!!expr.left && (!!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number'))
|
}
|
||||||
})
|
return val.some(c => !c?.expressions?.length || c.expressions.some((expr: any) => !isExprSet(expr)))
|
||||||
})
|
|
||||||
},
|
},
|
||||||
// question-classifier.categories: every category must have a value
|
// question-classifier.categories: every category must have a value
|
||||||
'question-classifier.categories': (val: any[]) => !Array.isArray(val) || !val.some(c => c?.class_name && String(c.class_name).trim()),
|
'question-classifier.categories': (val: any[]) => !Array.isArray(val) || !val.some(c => c?.class_name && String(c.class_name).trim()),
|
||||||
|
|||||||
@@ -6,12 +6,17 @@ import { Flex } from 'antd';
|
|||||||
|
|
||||||
import NodeTools from './NodeTools'
|
import NodeTools from './NodeTools'
|
||||||
import { useVariableList } from '../Properties/hooks/useVariableList'
|
import { useVariableList } from '../Properties/hooks/useVariableList'
|
||||||
|
import { isSubExprSet } from '../../utils'
|
||||||
|
import { fileSubFieldOperators } from '../Properties/CaseList'
|
||||||
|
|
||||||
const caculateIsSet = (item: any, type: string) => {
|
const caculateIsSet = (item: any, type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'categories':
|
case 'categories':
|
||||||
return typeof item?.class_name === 'string' && item?.class_name !== ''
|
return typeof item?.class_name === 'string' && item?.class_name !== ''
|
||||||
case 'cases': {
|
case 'cases': {
|
||||||
|
if (item?.sub_variable_condition !== undefined) {
|
||||||
|
return !!item.left && !!item.operator
|
||||||
|
}
|
||||||
if (!item.left) return false
|
if (!item.left) return false
|
||||||
if (['not_empty', 'empty'].includes(item.operator)) return true
|
if (['not_empty', 'empty'].includes(item.operator)) return true
|
||||||
return !!item.left && (!!item.right || typeof item.right === 'boolean' || typeof item.right === 'number')
|
return !!item.left && (!!item.right || typeof item.right === 'boolean' || typeof item.right === 'number')
|
||||||
@@ -25,10 +30,20 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
|||||||
const variableList = useVariableList(node ?? null, graphRef, data.chatVariables ?? [])
|
const variableList = useVariableList(node ?? null, graphRef, data.chatVariables ?? [])
|
||||||
|
|
||||||
const getLocaleField = (field: string, filedType: string) => {
|
const getLocaleField = (field: string, filedType: string) => {
|
||||||
const key = filedType === 'boolean' ? `workflow.config.if-else..boolean.${field}` : filedType === 'number' ? `workflow.config.if-else.num.${field}` : `workflow.config.if-else.${field}`
|
const key = filedType === 'boolean'
|
||||||
|
? `workflow.config.if-else..boolean.${field}`
|
||||||
|
: filedType === 'number'
|
||||||
|
? `workflow.config.if-else.num.${field}`
|
||||||
|
: `workflow.config.if-else.${field}`
|
||||||
const value = t(key)
|
const value = t(key)
|
||||||
return value !== key ? value : t(`workflow.config.if-else.num.${field}`)
|
return value !== key ? value : t(`workflow.config.if-else.num.${field}`)
|
||||||
};
|
};
|
||||||
|
const getSubLocaleField = (field: string, fieldKey: string) => {
|
||||||
|
const operators = fileSubFieldOperators[fieldKey] ?? fileSubFieldOperators.default
|
||||||
|
const match = operators?.find(op => op.value === field)
|
||||||
|
return match?.label ? t(match.label as string) : field
|
||||||
|
}
|
||||||
|
|
||||||
const labelRender = (value: string) => {
|
const labelRender = (value: string) => {
|
||||||
const filterOption = variableList.find(vo => `{{${vo.value}}}` === value)
|
const filterOption = variableList.find(vo => `{{${vo.value}}}` === value)
|
||||||
?? variableList.flatMap(vo => vo.children ?? []).find(child => `{{${child.value}}}` === value)
|
?? variableList.flatMap(vo => vo.children ?? []).find(child => `{{${child.value}}}` === value)
|
||||||
@@ -82,7 +97,8 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
|||||||
{item.expressions.map((expression: any, eIndex: number) => (
|
{item.expressions.map((expression: any, eIndex: number) => (
|
||||||
<div key={eIndex} className="rb:relative">
|
<div key={eIndex} className="rb:relative">
|
||||||
{item.expressions.length > 1 && eIndex > 0 && <div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{item.logical_operator?.toLocaleUpperCase()}</div>}
|
{item.expressions.length > 1 && eIndex > 0 && <div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{item.logical_operator?.toLocaleUpperCase()}</div>}
|
||||||
<Flex align="center" className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
<Flex vertical gap={2} className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||||
|
<Flex align="center">
|
||||||
{caculateIsSet(expression, 'cases')
|
{caculateIsSet(expression, 'cases')
|
||||||
? <>
|
? <>
|
||||||
{labelRender(expression.left)}
|
{labelRender(expression.left)}
|
||||||
@@ -91,6 +107,33 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
|||||||
</>
|
</>
|
||||||
: t(`workflow.config.${data.type}.unset`)
|
: t(`workflow.config.${data.type}.unset`)
|
||||||
}
|
}
|
||||||
|
</Flex>
|
||||||
|
{expression.sub_variable_condition?.conditions?.length > 0 && expression.sub_variable_condition?.conditions.every(isSubExprSet)
|
||||||
|
? <div className="rb-border-l rb:ml-2">
|
||||||
|
{expression.sub_variable_condition?.conditions.map((sub: any, sIndex: number) => (
|
||||||
|
<div key={sIndex} className="rb:relative">
|
||||||
|
{expression.sub_variable_condition?.conditions.length > 1 && sIndex > 0 && <div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{expression.sub_variable_condition?.logical_operator?.toLocaleUpperCase()}</div>}
|
||||||
|
<Flex align="center" className=" rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||||
|
<span className="rb:text-[#155EEF]">{sub.key}</span>
|
||||||
|
<span className="rb:mx-1">{getSubLocaleField(sub.operator, sub.key)}</span>
|
||||||
|
<span className="rb:break-all rb:line-clamp-1">
|
||||||
|
{sub.key === 'type'
|
||||||
|
? t(`application.${sub.value}`)
|
||||||
|
:!['not_empty', 'empty'].includes(sub.operator)
|
||||||
|
? <span>{typeof sub.value === 'boolean' ? String(sub.value).charAt(0).toUpperCase() + String(sub.value).slice(1) : sub.value}</span>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
: expression.sub_variable_condition?.conditions?.length > 0
|
||||||
|
? <Flex align="center" className="rb:mt-1! rb:pl-2! rb:rounded-md rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||||
|
{t(`workflow.config.${data.type}.unset`)}
|
||||||
|
</Flex>
|
||||||
|
: null
|
||||||
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-09 18:24:53
|
* @Date: 2026-02-09 18:24:53
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-04-16 12:06:16
|
* @Last Modified time: 2026-04-17 19:48:59
|
||||||
*/
|
*/
|
||||||
import { useMemo, type FC } from 'react'
|
import { useEffect, useMemo, type FC } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Form, Button, Select, Space, Divider, InputNumber, type SelectProps, Flex, Row, Col } from 'antd'
|
import { Form, Button, Select, Space, Divider, InputNumber, type SelectProps, Flex, Row, Col } from 'antd'
|
||||||
@@ -15,10 +15,36 @@ import Editor from '../../Editor'
|
|||||||
import { edgeAttrs, nodeWidth } from '../../../constant'
|
import { edgeAttrs, nodeWidth } from '../../../constant'
|
||||||
import RbButton from '@/components/RbButton';
|
import RbButton from '@/components/RbButton';
|
||||||
import RadioGroupBtn from '../RadioGroupBtn'
|
import RadioGroupBtn from '../RadioGroupBtn'
|
||||||
import { calcConditionNodeTotalHeight, getConditionNodeCasePortY } from '../../../utils';
|
import { calcConditionNodeTotalHeight, getConditionNodeCasePortY, isSubExprSet } from '../../../utils';
|
||||||
|
import { typeOptions } from '../ListOperator/FilterConditions'
|
||||||
|
|
||||||
|
|
||||||
|
interface SubCondition {
|
||||||
|
key: string;
|
||||||
|
operator: string;
|
||||||
|
value: string | number;
|
||||||
|
var_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubVariableCondition {
|
||||||
|
logical_operator: 'and' | 'or';
|
||||||
|
conditions: SubCondition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Expression {
|
||||||
|
left: string;
|
||||||
|
operator: string;
|
||||||
|
right?: string | number;
|
||||||
|
input_type?: string;
|
||||||
|
sub_variable_condition?: SubVariableCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CaseItem {
|
||||||
|
logical_operator: 'and' | 'or';
|
||||||
|
expressions: Expression[];
|
||||||
|
}
|
||||||
interface CaseListProps {
|
interface CaseListProps {
|
||||||
value?: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; input_type?: string; }[] }>;
|
value?: CaseItem[];
|
||||||
onChange?: (value: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; }[] }>) => void;
|
onChange?: (value: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; }[] }>) => void;
|
||||||
options: Suggestion[];
|
options: Suggestion[];
|
||||||
name: string;
|
name: string;
|
||||||
@@ -60,13 +86,12 @@ const operatorsObj: { [key: string]: SelectProps['options'] } = {
|
|||||||
{ value: 'empty', label: 'workflow.config.if-else.file.empty' },
|
{ value: 'empty', label: 'workflow.config.if-else.file.empty' },
|
||||||
{ value: 'not_empty', label: 'workflow.config.if-else.file.not_empty' },
|
{ value: 'not_empty', label: 'workflow.config.if-else.file.not_empty' },
|
||||||
],
|
],
|
||||||
// TODO:包含、不包含、全都是
|
|
||||||
'array[file]': [
|
'array[file]': [
|
||||||
|
{ value: 'contains', label: 'workflow.config.if-else.contains' },
|
||||||
|
{ value: 'not_contains', label: 'workflow.config.if-else.not_contains' },
|
||||||
|
{ value: 'eq', label: 'workflow.config.if-else.file.eq' },
|
||||||
{ value: 'empty', label: 'workflow.config.if-else.empty' },
|
{ value: 'empty', label: 'workflow.config.if-else.empty' },
|
||||||
{ value: 'not_empty', label: 'workflow.config.if-else.not_empty' },
|
{ value: 'not_empty', label: 'workflow.config.if-else.not_empty' },
|
||||||
// { value: 'eq', label: 'workflow.config.if-else.eq' },
|
|
||||||
// { value: 'contains', label: 'workflow.config.if-else.contains' },
|
|
||||||
// { value: 'not_contains', label: 'workflow.config.if-else.not_contains' },
|
|
||||||
],
|
],
|
||||||
'array': [
|
'array': [
|
||||||
{ value: 'contains', label: 'workflow.config.if-else.contains' },
|
{ value: 'contains', label: 'workflow.config.if-else.contains' },
|
||||||
@@ -80,6 +105,218 @@ const operatorsObj: { [key: string]: SelectProps['options'] } = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileSubFieldOptions = [
|
||||||
|
{ value: 'type', label: 'type' },
|
||||||
|
{ value: 'size', label: 'size' },
|
||||||
|
{ value: 'name', label: 'name' },
|
||||||
|
{ value: 'url', label: 'url' },
|
||||||
|
{ value: 'extension', label: 'extension' },
|
||||||
|
{ value: 'mime_type', label: 'mime_type' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const fileSubFieldOperators: { [key: string]: SelectProps['options'] } = {
|
||||||
|
type: [
|
||||||
|
{ value: 'eq', label: 'workflow.config.list-operator.type.eq' },
|
||||||
|
{ value: 'ne', label: 'workflow.config.list-operator.type.ne' },
|
||||||
|
],
|
||||||
|
size: [
|
||||||
|
{ value: 'lt', label: 'workflow.config.if-else.num.lt' },
|
||||||
|
{ value: 'le', label: 'workflow.config.if-else.num.le' },
|
||||||
|
{ value: 'gt', label: 'workflow.config.if-else.num.gt' },
|
||||||
|
{ value: 'ge', label: 'workflow.config.if-else.num.ge' },
|
||||||
|
],
|
||||||
|
default: [
|
||||||
|
{ value: 'contains', label: 'workflow.config.if-else.contains' },
|
||||||
|
{ value: 'not_contains', label: 'workflow.config.if-else.not_contains' },
|
||||||
|
{ value: 'startwith', label: 'workflow.config.if-else.startwith' },
|
||||||
|
{ value: 'endwith', label: 'workflow.config.if-else.endwith' },
|
||||||
|
{ value: 'eq', label: 'workflow.config.if-else.eq' },
|
||||||
|
{ value: 'ne', label: 'workflow.config.if-else.ne' },
|
||||||
|
{ value: 'empty', label: 'workflow.config.if-else.empty' },
|
||||||
|
{ value: 'not_empty', label: 'workflow.config.if-else.not_empty' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArrayFileSubConditionsProps {
|
||||||
|
conditionFieldName: number;
|
||||||
|
caseIndex: number;
|
||||||
|
conditionIndex: number;
|
||||||
|
name: string;
|
||||||
|
filterNumberOptions: Suggestion[];
|
||||||
|
options: Suggestion[];
|
||||||
|
updateNodeLayout: (cases: any[]) => void;
|
||||||
|
updateNodePorts: (caseCount: number, removedCaseIndex?: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArrayFileSubConditions: FC<ArrayFileSubConditionsProps> = ({ conditionFieldName, caseIndex, conditionIndex, name, filterNumberOptions, options, updateNodeLayout, updateNodePorts }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const subValues = Form.useWatch([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'conditions'], form)
|
||||||
|
|
||||||
|
const handleChangeSubLogicalOperator = () => {
|
||||||
|
const current = form.getFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'logical_operator']) || 'and';
|
||||||
|
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'logical_operator'], current === 'and' ? 'or' : 'and');
|
||||||
|
};
|
||||||
|
const handleInputTypeChange = (caseIndex: number, conditionIndex: number, subIndex: number) => {
|
||||||
|
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'conditions', subIndex, 'value'], undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('subValues', subValues)
|
||||||
|
if (!subValues) return
|
||||||
|
const cases = form.getFieldValue(name) || [];
|
||||||
|
setTimeout(() => {
|
||||||
|
updateNodeLayout(cases);
|
||||||
|
const allSet = (subValues ?? []).every(isSubExprSet);
|
||||||
|
console.log('allSet', allSet)
|
||||||
|
updateNodePorts(cases.length);
|
||||||
|
}, 100);
|
||||||
|
}, [subValues])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rb:bg-white rb:rounded-lg rb:p-1 rb:w-62">
|
||||||
|
<Form.List name={[conditionFieldName, 'sub_variable_condition', 'conditions']}>
|
||||||
|
{(subFields, { add: addSub, remove: removeSub }) => {
|
||||||
|
const subLogicalOperator = form.getFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'logical_operator']) || 'and';
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={clsx("rb:relative", {
|
||||||
|
'rb:ml-11': subFields.length > 1,
|
||||||
|
})}>
|
||||||
|
{subFields.length > 1 && (
|
||||||
|
<div className="rb:absolute rb:-left-8 rb:top-4 rb:bottom-4 rb:w-6 rb:h-[calc(100%-32px)]">
|
||||||
|
<div className="rb:absolute rb:w-2 rb:h-[calc(50%-20px)] rb:left-5 rb:top-0 rb:z-10 rb:border-l rb:border-t rb:border-[#EBEBEB] rb:rounded-tl-[10px] rb:border-r-0"></div>
|
||||||
|
<div className="rb:absolute rb:z-10 rb:-right-1.25 rb:top-[calc(50%-10px)]">
|
||||||
|
<Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:text-[10px] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={handleChangeSubLogicalOperator}>
|
||||||
|
{subLogicalOperator}
|
||||||
|
<div className="rb:size-2.5 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<div className="rb:absolute rb:w-2 rb:h-[calc(50%-20px)] rb:left-5 rb:bottom-0 rb:z-10 rb:border-l rb:border-b rb:border-[#EBEBEB] rb:rounded-bl-[10px] rb:border-r-0"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{subFields.map((subField, subIndex) => {
|
||||||
|
const subExpr = form.getFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'conditions', subIndex]) || {};
|
||||||
|
const subOperator = subExpr.operator;
|
||||||
|
const subLeft = subExpr.key;
|
||||||
|
const subOperatorList = subLeft === 'type' ? fileSubFieldOperators.type : subLeft === 'size' ? fileSubFieldOperators.size : fileSubFieldOperators.default;
|
||||||
|
const hideSubRight = subOperator === 'empty' || subOperator === 'not_empty';
|
||||||
|
const subInputType = subExpr.input_type
|
||||||
|
return (
|
||||||
|
<Flex key={subField.key} gap={4} align="start" className="rb:mb-1.5!">
|
||||||
|
<div className={clsx("rb:flex-1 rb:bg-[#F6F6F6] rb:rounded-lg rb:border rb:border-[#EBEBEB]", {
|
||||||
|
'rb:w-43.5': subFields.length > 1,
|
||||||
|
'rb:w-54.5': subFields.length === 1
|
||||||
|
})}>
|
||||||
|
<Row className={clsx('rb:p-1!', { 'rb-border-b': !hideSubRight })}>
|
||||||
|
<Col flex="100px">
|
||||||
|
<Form.Item name={[subField.name, 'key']} noStyle>
|
||||||
|
<Select
|
||||||
|
options={fileSubFieldOptions}
|
||||||
|
size="small"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
variant="borderless"
|
||||||
|
className="rb:w-full!"
|
||||||
|
onChange={(value: string) => {
|
||||||
|
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'conditions', subIndex], {
|
||||||
|
key: value,
|
||||||
|
input_type: value === 'size' ? 'Constant' : undefined,
|
||||||
|
value: undefined,
|
||||||
|
operator: value === 'size' ? 'ge' : 'eq',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col flex="1">
|
||||||
|
<Form.Item name={[subField.name, 'operator']} noStyle>
|
||||||
|
<Select
|
||||||
|
options={(subOperatorList ?? []).map(vo => ({ ...vo, label: t(String(vo?.label || '')) }))}
|
||||||
|
size="small"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
variant="borderless"
|
||||||
|
className="rb:w-full!"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{!hideSubRight && (
|
||||||
|
<div>
|
||||||
|
{subLeft === 'size'
|
||||||
|
? <Flex align="center">
|
||||||
|
<Form.Item name={[subField.name, 'input_type']} noStyle>
|
||||||
|
<Select
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={[{ value: 'variable', label: 'Variable' }, { value: 'constant', label: 'Constant' }]}
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
variant="borderless"
|
||||||
|
size="small"
|
||||||
|
onChange={() => { handleInputTypeChange(caseIndex, conditionIndex, subIndex); }}
|
||||||
|
className="rb:w-20!"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Divider type="vertical" className="rb:mx-0!" />
|
||||||
|
<Form.Item name={[subField.name, 'value']} noStyle>
|
||||||
|
{subInputType === 'variable'
|
||||||
|
? <VariableSelect
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={filterNumberOptions}
|
||||||
|
allowClear={true}
|
||||||
|
variant="borderless"
|
||||||
|
size="small"
|
||||||
|
className="rb:flex-1!"
|
||||||
|
/>
|
||||||
|
: <InputNumber
|
||||||
|
placeholder={t('common.pleaseEnter')}
|
||||||
|
variant="borderless"
|
||||||
|
className="rb:w-full!"
|
||||||
|
suffix="Byte"
|
||||||
|
size="small"
|
||||||
|
onChange={(value) => { form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value); }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
</Flex>
|
||||||
|
: <Form.Item name={[subField.name, 'value']} noStyle>
|
||||||
|
{subLeft === 'type'
|
||||||
|
? <Select
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={typeOptions.map(vo => ({ value: vo, label: t(`application.${vo}`) }))}
|
||||||
|
variant="borderless"
|
||||||
|
className="rb:w-full!"
|
||||||
|
/>
|
||||||
|
: <Editor options={options} size="small" type="input" variant='borderless' height={28} className="rb:w-full!" />
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
|
||||||
|
onClick={() => { removeSub(subField.name); }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => { addSub({ key: undefined, operator: undefined, value: undefined, var_type: undefined }); }}
|
||||||
|
className="rb:py-0! rb:px-1! rb:h-4.5! rb:rounded-sm! rb:text-[12px]!"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
+ {t('workflow.config.if-else.addSubVariable')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const CaseList: FC<CaseListProps> = ({
|
const CaseList: FC<CaseListProps> = ({
|
||||||
options,
|
options,
|
||||||
name,
|
name,
|
||||||
@@ -251,7 +488,7 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
left: newValue,
|
left: newValue,
|
||||||
operator: undefined,
|
operator: undefined,
|
||||||
right: undefined,
|
right: undefined,
|
||||||
input_type: undefined
|
input_type: 'Constant'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -303,7 +540,7 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
const logicalOperator = form.getFieldValue(name)?.[caseIndex]?.logical_operator || 'and'
|
const logicalOperator = form.getFieldValue(name)?.[caseIndex]?.logical_operator || 'and'
|
||||||
return (
|
return (
|
||||||
<Row className="rb:text-[12px] rb:mb-4!">
|
<Row className="rb:text-[12px] rb:mb-4!">
|
||||||
<Col flex="48px">
|
<Col flex="44px">
|
||||||
<div className="rb:font-medium rb:leading-4.5">{caseIndex === 0 ? 'IF' : 'ELIF'}</div>
|
<div className="rb:font-medium rb:leading-4.5">{caseIndex === 0 ? 'IF' : 'ELIF'}</div>
|
||||||
{caseFields.length > 1 && <div className="rb:text-[10px] rb:text-[#5B6167] rb:leading-2.5"> {`CASE ${caseIndex + 1}`}</div>}
|
{caseFields.length > 1 && <div className="rb:text-[10px] rb:text-[#5B6167] rb:leading-2.5"> {`CASE ${caseIndex + 1}`}</div>}
|
||||||
</Col>
|
</Col>
|
||||||
@@ -314,9 +551,9 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
<div className="rb:absolute rb:w-3 rb:h-[calc(50%-20px)] rb:left-5 rb:top-0 rb:z-10 rb:border-l rb:border-t rb:border-[#EBEBEB] rb:rounded-tl-[10px] rb:border-r-0"></div>
|
<div className="rb:absolute rb:w-3 rb:h-[calc(50%-20px)] rb:left-5 rb:top-0 rb:z-10 rb:border-l rb:border-t rb:border-[#EBEBEB] rb:rounded-tl-[10px] rb:border-r-0"></div>
|
||||||
<div className="rb:absolute rb:z-10 rb:-right-1.25 rb:top-[calc(50%-10px)]">
|
<div className="rb:absolute rb:z-10 rb:-right-1.25 rb:top-[calc(50%-10px)]">
|
||||||
<Form.Item name={[caseField.name, 'logical_operator']} noStyle >
|
<Form.Item name={[caseField.name, 'logical_operator']} noStyle >
|
||||||
<Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={() => handleChangeLogicalOperator(caseIndex)}>
|
<Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:text-[10px] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={() => handleChangeLogicalOperator(caseIndex)}>
|
||||||
{logicalOperator}
|
{logicalOperator}
|
||||||
<div className="rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div>
|
<div className="rb:size-2.5 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div>
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
@@ -333,7 +570,7 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
?? options.flatMap(o => o.children ?? []).find(child => `{{${child.value}}}` === leftFieldValue)
|
?? options.flatMap(o => o.children ?? []).find(child => `{{${child.value}}}` === leftFieldValue)
|
||||||
?? options.flatMap(o => o.children ?? []).flatMap((c: any) => c.children ?? []).find((gc: any) => `{{${gc.value}}}` === leftFieldValue);
|
?? options.flatMap(o => o.children ?? []).flatMap((c: any) => c.children ?? []).find((gc: any) => `{{${gc.value}}}` === leftFieldValue);
|
||||||
const leftFieldType = leftFieldOption?.dataType;
|
const leftFieldType = leftFieldOption?.dataType;
|
||||||
const hideRightField = currentOperator === 'empty' || currentOperator === 'not_empty' || leftFieldType === 'file' || leftFieldType === 'array[object]' || leftFieldType === 'array[file]';
|
const hideRightField = currentOperator === 'empty' || currentOperator === 'not_empty' || leftFieldType === 'file' || leftFieldType === 'array[object]';
|
||||||
const operatorList = leftFieldType && operatorsObj[leftFieldType]
|
const operatorList = leftFieldType && operatorsObj[leftFieldType]
|
||||||
? operatorsObj[leftFieldType]
|
? operatorsObj[leftFieldType]
|
||||||
: leftFieldType && leftFieldType?.includes('array')
|
: leftFieldType && leftFieldType?.includes('array')
|
||||||
@@ -377,22 +614,40 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{!hideRightField && (
|
{!hideRightField && (
|
||||||
<div className={['boolean', 'array[boolean]'].includes(leftFieldType as string) ? "rb:py-1 rb:px-1.5" : ''}>
|
<div
|
||||||
|
className={clsx({
|
||||||
|
"rb:py-1 rb:px-1.5": ['boolean', 'array[boolean]', 'array[file]'].includes(leftFieldType as string)
|
||||||
|
})}
|
||||||
|
>
|
||||||
{leftFieldType === 'array[file]'
|
{leftFieldType === 'array[file]'
|
||||||
? <>TODO</>
|
? <>
|
||||||
|
<Form.Item name={[conditionField.name, 'sub_variable_condition', 'logical_operator']} initialValue="and" noStyle>
|
||||||
|
<span />
|
||||||
|
</Form.Item>
|
||||||
|
<ArrayFileSubConditions
|
||||||
|
conditionFieldName={conditionField.name}
|
||||||
|
caseIndex={caseIndex}
|
||||||
|
conditionIndex={conditionIndex}
|
||||||
|
name={name}
|
||||||
|
options={options}
|
||||||
|
filterNumberOptions={filterNumberOptions}
|
||||||
|
updateNodeLayout={updateNodeLayout}
|
||||||
|
updateNodePorts={updateNodePorts}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
: leftFieldType === 'number'
|
: leftFieldType === 'number'
|
||||||
? <Flex align="center">
|
? <Flex align="center">
|
||||||
<Form.Item name={[conditionField.name, 'input_type']} noStyle>
|
<Form.Item name={[conditionField.name, 'input_type']} noStyle>
|
||||||
<Select
|
<Select
|
||||||
placeholder={t('common.pleaseSelect')}
|
placeholder={t('common.pleaseSelect')}
|
||||||
options={[{ value: 'variable', label: 'Variable' }, { value: 'Constant', label: 'constant' }]}
|
options={[{ value: 'variable', label: 'Variable' }, { value: 'constant', label: 'Constant' }]}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
onChange={() => handleInputTypeChange(caseIndex, conditionIndex)}
|
onChange={() => handleInputTypeChange(caseIndex, conditionIndex)}
|
||||||
className="rb:w-20!"
|
className="rb:w-20!"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" className="rb:mx-0!" />
|
||||||
<Form.Item name={[conditionField.name, 'right']} noStyle>
|
<Form.Item name={[conditionField.name, 'right']} noStyle>
|
||||||
{inputType === 'variable'
|
{inputType === 'variable'
|
||||||
? <VariableSelect
|
? <VariableSelect
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const operatorsObj: { [key: string]: SelectProps['options'] } = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeOptions = ['image', 'document', 'video', 'audio']
|
export const typeOptions = ['image', 'document', 'video', 'audio']
|
||||||
|
|
||||||
const FilterConditions: FC<FilterConditionsProps> = ({
|
const FilterConditions: FC<FilterConditionsProps> = ({
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -162,4 +162,7 @@
|
|||||||
padding-inline-start: 0px;
|
padding-inline-start: 0px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-block: 0px;
|
margin-block: 0px;
|
||||||
|
}
|
||||||
|
.properties :global(.ant-input-number-affix-wrapper) {
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-03-24 15:07:49
|
* @Date: 2026-03-24 15:07:49
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-24 15:07:49
|
* @Last Modified time: 2026-04-17 19:13:22
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { portItemArgsY, conditionNodePortItemArgsY, conditionNodeHeight } from './constant'
|
import { portItemArgsY, conditionNodePortItemArgsY, conditionNodeHeight } from './constant'
|
||||||
@@ -22,11 +22,31 @@ import { portItemArgsY, conditionNodePortItemArgsY, conditionNodeHeight } from '
|
|||||||
* @param cases - Array of case objects, each containing an `expressions` array.
|
* @param cases - Array of case objects, each containing an `expressions` array.
|
||||||
* @returns The total pixel height for the condition node.
|
* @returns The total pixel height for the condition node.
|
||||||
*/
|
*/
|
||||||
|
export const isSubExprSet = (sub: any) => {
|
||||||
|
if (!sub?.key) return false;
|
||||||
|
if (['not_empty', 'empty'].includes(sub?.operator)) return true;
|
||||||
|
return !!sub.value || typeof sub.value === 'boolean' || typeof sub.value === 'number';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEffectiveExprCount = (expr: any): number => {
|
||||||
|
const subs = expr?.sub_variable_condition?.conditions;
|
||||||
|
if (subs?.length && subs.every(isSubExprSet)) return 1 + subs.length;
|
||||||
|
if (subs?.length > 0) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
export const calcConditionNodeTotalHeight = (cases: any[]) => {
|
export const calcConditionNodeTotalHeight = (cases: any[]) => {
|
||||||
// Total number of expressions across all cases
|
// Total number of effective expression rows (sub_variable_condition expand height when all set)
|
||||||
const exprCount = cases.reduce((acc: number, c: any) => acc + (c?.expressions?.length || 0), 0);
|
const exprCount = cases.reduce((acc: number, c: any) =>
|
||||||
// Sum of expression counts only for cases that have more than one expression
|
acc + (c?.expressions?.reduce((s: number, e: any) => s + getEffectiveExprCount(e), 0) || 0), 0);
|
||||||
const hasMultiExprCount = cases.reduce((acc: number, c: any) => acc + (c?.expressions?.length > 1 ? c?.expressions?.length : 0), 0);
|
// Sum of effective expression counts only for cases that have more than one expression
|
||||||
|
const hasMultiExprCount = cases.reduce((acc: number, c: any) => {
|
||||||
|
if (!c?.expressions?.length || c.expressions.length <= 1) return acc;
|
||||||
|
const effectiveCount = c.expressions.reduce((s: number, e: any) => s + getEffectiveExprCount(e), 0);
|
||||||
|
return acc + effectiveCount;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return conditionNodeHeight + (cases.length - 1) * 26 + exprCount * 20 + hasMultiExprCount * 3;
|
return conditionNodeHeight + (cases.length - 1) * 26 + exprCount * 20 + hasMultiExprCount * 3;
|
||||||
};
|
};
|
||||||
@@ -68,17 +88,38 @@ export const getConditionNodeCasePortY = (cases: any[], caseIndex: number) => {
|
|||||||
let singleExprCount = 0;
|
let singleExprCount = 0;
|
||||||
let multiExprCount = 0;
|
let multiExprCount = 0;
|
||||||
let extraExprs = 0;
|
let extraExprs = 0;
|
||||||
|
let portItemArgsYNum = 0;
|
||||||
|
|
||||||
for (let i = 0; i < caseIndex; i++) {
|
for (let i = 0; i < caseIndex; i++) {
|
||||||
const n = cases[i]?.expressions?.length || 0;
|
const n = cases[i]?.expressions?.length || 0;
|
||||||
y += portItemArgsY * (n + 1);
|
let casePortItemArgsYNum = n + 1;
|
||||||
if (n === 1) singleExprCount++;
|
// Add extra y for expressions with all sub_variable_condition set
|
||||||
else if (n >= 2) {
|
cases[i]?.expressions?.forEach((expr: any) => {
|
||||||
|
const subs = expr?.sub_variable_condition?.conditions;
|
||||||
|
if (subs?.length && subs.every(isSubExprSet)) {
|
||||||
|
casePortItemArgsYNum += subs.length;
|
||||||
|
} else if (subs?.length) {
|
||||||
|
casePortItemArgsYNum += 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
portItemArgsYNum += casePortItemArgsYNum;
|
||||||
|
if (n === 1 && !cases[i]?.expressions?.some((e: any) => e?.sub_variable_condition?.conditions?.length > 0)) {
|
||||||
|
singleExprCount++
|
||||||
|
} else if (n >= 2 || cases[i]?.expressions?.some((e: any) => e?.sub_variable_condition?.conditions?.length > 0)) {
|
||||||
multiExprCount++;
|
multiExprCount++;
|
||||||
if (n > 2) extraExprs += n - 2;
|
cases[i]?.expressions?.forEach((e: any) => {
|
||||||
|
const subs = e?.sub_variable_condition?.conditions;
|
||||||
|
if (subs?.length && subs.every(isSubExprSet) && subs.length > 1) {
|
||||||
|
extraExprs += subs.length;
|
||||||
|
} else if (!subs && n > 2) {
|
||||||
|
extraExprs += n - 2;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('singleExprCount', singleExprCount, 'multiExprCount', multiExprCount, 'extraExprs', extraExprs)
|
||||||
|
y += portItemArgsY * portItemArgsYNum
|
||||||
// Correction for single-expression cases (slightly shorter rendered height)
|
// Correction for single-expression cases (slightly shorter rendered height)
|
||||||
if (singleExprCount > 0) y -= singleExprCount * 7 + 2;
|
if (singleExprCount > 0) y -= singleExprCount * 7 + 2;
|
||||||
// Correction for multi-expression cases (compact logical operator row)
|
// Correction for multi-expression cases (compact logical operator row)
|
||||||
|
|||||||
Reference in New Issue
Block a user