Merge pull request #934 from SuanmoSuanyangTechnology/feature/if_else_zy
Feature/if else zy
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.',
|
||||
unset: 'Condition Not Set',
|
||||
set: 'Set',
|
||||
addSubVariable: 'Add Sub Variable',
|
||||
},
|
||||
'http-request': {
|
||||
auth: 'Authentication',
|
||||
|
||||
@@ -2350,6 +2350,7 @@ export const zh = {
|
||||
else_desc: '用于定义当 if 条件不满足时应执行的逻辑。',
|
||||
unset: '条件未设置',
|
||||
set: '已设置',
|
||||
addSubVariable: '添加子变量',
|
||||
},
|
||||
'http-request': {
|
||||
auth: '鉴权',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -162,4 +162,7 @@
|
||||
padding-inline-start: 0px;
|
||||
border-radius: 4px;
|
||||
margin-block: 0px;
|
||||
}
|
||||
.properties :global(.ant-input-number-affix-wrapper) {
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user