Merge pull request #934 from SuanmoSuanyangTechnology/feature/if_else_zy

Feature/if else zy
This commit is contained in:
yingzhao
2026-04-17 20:00:30 +08:00
committed by GitHub
10 changed files with 389 additions and 45 deletions

View File

@@ -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.',
unset: 'Condition Not Set',
set: 'Set',
addSubVariable: 'Add Sub Variable',
},
'http-request': {
auth: 'Authentication',

View File

@@ -2350,6 +2350,7 @@ export const zh = {
else_desc: '用于定义当 if 条件不满足时应执行的逻辑。',
unset: '条件未设置',
set: '已设置',
addSubVariable: '添加子变量',
},
'http-request': {
auth: '鉴权',

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 18:33:30
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-14 16:03:41
* @Last Modified time: 2026-04-17 17:57:15
*/
/**
* End User Profile Component
@@ -89,11 +89,11 @@ const EndUserProfile = forwardRef<EndUserProfileRef, EndUserProfileProps>(({ cla
</div>
<div>
<div className="rb:text-[#7B8085]">{t('userMemory.role')}</div>
<div className="rb:mt-0.5">{data?.profile?.role || '-'}</div>
<div className="rb:mt-0.5">{data?.profile?.role?.join(' | ') || '-'}</div>
</div>
<div>
<div className="rb:text-[#7B8085]">{t('userMemory.domain')}</div>
<div className="rb:mt-0.5">{data?.profile?.domain || '-'}</div>
<div className="rb:mt-0.5">{data?.profile?.domain?.join(' | ') || '-'}</div>
</div>
<div>
<div className="rb:text-[#7B8085]">{t('userMemory.expertise')}</div>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:57:15
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-14 16:03:16
* @Last Modified time: 2026-04-17 17:57:00
*/
/**
* User Memory Detail Types
@@ -178,8 +178,8 @@ export interface EndUser {
created_at: string;
updated_at: string;
profile: {
role: string;
domain: string;
role: string[];
domain: string[];
expertise: string[];
interests: string[];
};

View File

@@ -6,6 +6,7 @@ import { Node } from '@antv/x6';
import type { WorkflowRef } from '@/views/ApplicationConfig/types'
import { nodeLibrary } from '../../constant'
import { isSubExprSet } from '../../utils'
import { getToolMethods } from '@/api/tools'
import RbDrawer from '@/components/RbDrawer'
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': (val: any[]) => {
if (!Array.isArray(val) || !val.length) return true
return val.some(c => {
if (!c?.expressions?.length) return true
return c.expressions.some((expr: any) => {
if (!expr?.left) return true
if (['not_empty', 'empty'].includes(expr.operator)) return false
return !(!!expr.left && (!!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number'))
})
})
const isExprSet = (expr: any) => {
if (expr?.sub_variable_condition?.conditions?.length > 0) return expr.sub_variable_condition?.conditions.every(isSubExprSet)
if (!expr.left) return false
if (['not_empty', 'empty'].includes(expr.operator)) return true
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': (val: any[]) => !Array.isArray(val) || !val.some(c => c?.class_name && String(c.class_name).trim()),

View File

@@ -6,12 +6,17 @@ import { Flex } from 'antd';
import NodeTools from './NodeTools'
import { useVariableList } from '../Properties/hooks/useVariableList'
import { isSubExprSet } from '../../utils'
import { fileSubFieldOperators } from '../Properties/CaseList'
const caculateIsSet = (item: any, type: string) => {
switch (type) {
case 'categories':
return typeof item?.class_name === 'string' && item?.class_name !== ''
case 'cases': {
if (item?.sub_variable_condition !== undefined) {
return !!item.left && !!item.operator
}
if (!item.left) return false
if (['not_empty', 'empty'].includes(item.operator)) return true
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 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)
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 filterOption = variableList.find(vo => `{{${vo.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) => (
<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>}
<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')
? <>
{labelRender(expression.left)}
@@ -91,6 +107,33 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
</>
: 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>
</div>
))}

View File

@@ -2,9 +2,9 @@
* @Author: ZhaoYing
* @Date: 2026-02-09 18:24:53
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-16 12:06:16
* @Last Modified time: 2026-04-17 19:58:13
*/
import { useMemo, type FC } from 'react'
import { useEffect, useMemo, type FC } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Space, Divider, InputNumber, type SelectProps, Flex, Row, Col } from 'antd'
@@ -15,11 +15,37 @@ import Editor from '../../Editor'
import { edgeAttrs, nodeWidth } from '../../../constant'
import RbButton from '@/components/RbButton';
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 {
value?: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; input_type?: string; }[] }>;
onChange?: (value: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; }[] }>) => void;
value?: CaseItem[];
onChange?: (value: CaseItem[]) => void;
options: Suggestion[];
name: string;
selectedNode?: any;
@@ -60,13 +86,12 @@ const operatorsObj: { [key: string]: SelectProps['options'] } = {
{ value: 'empty', label: 'workflow.config.if-else.file.empty' },
{ value: 'not_empty', label: 'workflow.config.if-else.file.not_empty' },
],
// TODO包含、不包含、全都是
'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: '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': [
{ 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> = ({
options,
name,
@@ -251,7 +488,7 @@ const CaseList: FC<CaseListProps> = ({
left: newValue,
operator: 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'
return (
<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>
{caseFields.length > 1 && <div className="rb:text-[10px] rb:text-[#5B6167] rb:leading-2.5"> {`CASE ${caseIndex + 1}`}</div>}
</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:z-10 rb:-right-1.25 rb:top-[calc(50%-10px)]">
<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}
<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>
</Form.Item>
</div>
@@ -333,7 +570,7 @@ const CaseList: FC<CaseListProps> = ({
?? 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);
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]
? operatorsObj[leftFieldType]
: leftFieldType && leftFieldType?.includes('array')
@@ -377,22 +614,40 @@ const CaseList: FC<CaseListProps> = ({
</Row>
{!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]'
? <>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'
? <Flex align="center">
<Form.Item name={[conditionField.name, 'input_type']} noStyle>
<Select
placeholder={t('common.pleaseSelect')}
options={[{ value: 'variable', label: 'Variable' }, { value: 'Constant', label: 'constant' }]}
options={[{ value: 'variable', label: 'Variable' }, { value: 'constant', label: 'Constant' }]}
popupMatchSelectWidth={false}
variant="borderless"
onChange={() => handleInputTypeChange(caseIndex, conditionIndex)}
className="rb:w-20!"
/>
</Form.Item>
<Divider type="vertical" />
<Divider type="vertical" className="rb:mx-0!" />
<Form.Item name={[conditionField.name, 'right']} noStyle>
{inputType === 'variable'
? <VariableSelect

View File

@@ -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> = ({
options,

View File

@@ -162,4 +162,7 @@
padding-inline-start: 0px;
border-radius: 4px;
margin-block: 0px;
}
.properties :global(.ant-input-number-affix-wrapper) {
font-size: 12px;
}

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-03-24 15:07:49
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-24 15:07:49
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-17 19:13:22
*/
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.
* @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[]) => {
// Total number of expressions across all cases
const exprCount = cases.reduce((acc: number, c: any) => acc + (c?.expressions?.length || 0), 0);
// Sum of expression counts only for cases that have more than one expression
const hasMultiExprCount = cases.reduce((acc: number, c: any) => acc + (c?.expressions?.length > 1 ? c?.expressions?.length : 0), 0);
// 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?.reduce((s: number, e: any) => s + getEffectiveExprCount(e), 0) || 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;
};
@@ -68,17 +88,38 @@ export const getConditionNodeCasePortY = (cases: any[], caseIndex: number) => {
let singleExprCount = 0;
let multiExprCount = 0;
let extraExprs = 0;
let portItemArgsYNum = 0;
for (let i = 0; i < caseIndex; i++) {
const n = cases[i]?.expressions?.length || 0;
y += portItemArgsY * (n + 1);
if (n === 1) singleExprCount++;
else if (n >= 2) {
let casePortItemArgsYNum = n + 1;
// Add extra y for expressions with all sub_variable_condition set
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++;
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)
if (singleExprCount > 0) y -= singleExprCount * 7 + 2;
// Correction for multi-expression cases (compact logical operator row)