Merge pull request #940 from SuanmoSuanyangTechnology/fix/v0.3.1_zy

Fix/v0.3.1 zy
This commit is contained in:
yingzhao
2026-04-20 15:34:48 +08:00
committed by GitHub
8 changed files with 111 additions and 138 deletions

View File

@@ -2564,6 +2564,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
'jinja-render.template': 'Template', 'jinja-render.template': 'Template',
'document-extractor.file_selector': 'File variable', 'document-extractor.file_selector': 'File variable',
'list-operator.input_list': 'Input list', 'list-operator.input_list': 'Input list',
'tool.tool_id': 'Tool',
}, },
checkListHasErrors: 'Please resolve all issues in the checklist before publishing', checkListHasErrors: 'Please resolve all issues in the checklist before publishing',
variableSelect: { variableSelect: {

View File

@@ -2528,6 +2528,7 @@ export const zh = {
'jinja-render.template': '模板', 'jinja-render.template': '模板',
'document-extractor.file_selector': '文件变量', 'document-extractor.file_selector': '文件变量',
'list-operator.input_list': '输入变量', 'list-operator.input_list': '输入变量',
'tool.tool_id': '工具',
}, },
checkListHasErrors: '发布前确认检查清单中所有问题均已解决', checkListHasErrors: '发布前确认检查清单中所有问题均已解决',
variableSelect: { variableSelect: {

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-04-09 18:58:21
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-20 10:39:17
*/
import { useState, useCallback, useEffect, useRef, type FC } from 'react' import { useState, useCallback, useEffect, useRef, type FC } from 'react'
import { Popover, Flex } from 'antd' import { Popover, Flex } from 'antd'
import { WarningFilled } from '@ant-design/icons' import { WarningFilled } from '@ant-design/icons'
@@ -49,7 +55,7 @@ const specialValidators: Record<string, (val: any) => boolean> = {
if (expr?.sub_variable_condition?.conditions?.length > 0) return expr.sub_variable_condition?.conditions.every(isSubExprSet) if (expr?.sub_variable_condition?.conditions?.length > 0) return expr.sub_variable_condition?.conditions.every(isSubExprSet)
if (!expr.left) return false if (!expr.left) return false
if (['not_empty', 'empty'].includes(expr.operator)) return true if (['not_empty', 'empty'].includes(expr.operator)) return true
return !!expr.left && (!!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number') return !!expr.left && (expr?.sub_variable_condition || !!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number')
} }
return val.some(c => !c?.expressions?.length || c.expressions.some((expr: any) => !isExprSet(expr))) return val.some(c => !c?.expressions?.length || c.expressions.some((expr: any) => !isExprSet(expr)))
}, },

View File

@@ -9,7 +9,7 @@ import { useVariableList } from '../Properties/hooks/useVariableList'
import { isSubExprSet } from '../../utils' import { isSubExprSet } from '../../utils'
import { fileSubFieldOperators } from '../Properties/CaseList' import { fileSubFieldOperators } from '../Properties/CaseList'
const caculateIsSet = (item: any, type: string) => { const calculateIsSet = (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 !== ''
@@ -79,7 +79,7 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
<div key={index} 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"> <div key={index} 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 justify="space-between"> <Flex justify="space-between">
<span>{t('workflow.config.question-classifier.class_name')} {index + 1}</span> <span>{t('workflow.config.question-classifier.class_name')} {index + 1}</span>
{caculateIsSet(item, 'categories') ? t(`workflow.config.${data.type}.set`) : t(`workflow.config.${data.type}.unset`)} {calculateIsSet(item, 'categories') ? t(`workflow.config.${data.type}.set`) : t(`workflow.config.${data.type}.unset`)}
</Flex> </Flex>
</div> </div>
))} ))}
@@ -89,17 +89,24 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
<Flex vertical gap={4} className="rb:mt-3!"> <Flex vertical gap={4} className="rb:mt-3!">
{data.config?.cases?.defaultValue.map((item: any, index: number) => ( {data.config?.cases?.defaultValue.map((item: any, index: number) => (
<div key={index} className={item.expressions.length > 0 ? '' : 'rb:mb-1'}> <div key={index} className={item.expressions.length > 0 ? '' : 'rb:mb-1'}>
<Flex justify={item.expressions.length > 0 ? "space-between" : 'end'} className="rb:mb-1"> <Flex justify={item.expressions.length > 0 ? "space-between" : 'end'} className="rb:mb-1! rb:leading-4">
{item.expressions.length > 0 && <span className="rb:text-[#5B6167] rb:text-[10px]">CASE{index + 1}</span>} {item.expressions.length > 0 && <span className="rb:text-[#5B6167] rb:text-[10px] rb:pl-1">CASE{index + 1}</span>}
<span className="rb:text-[#212332] rb:font-medium rb:text-[12px]">{index === 0 ? 'IF' : `ELIF`}</span> <span className="rb:text-[#212332] rb:font-medium rb:text-[12px]">{index === 0 ? 'IF' : `ELIF`}</span>
</Flex> </Flex>
{item.expressions.length > 0 && <Flex vertical gap={2}> {item.expressions.length > 0 && <Flex vertical gap={2}>
{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 &&
<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"> <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 vertical gap={2}
className={clsx("rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-4", {
'rb:pt-1!': expression.sub_variable_condition?.conditions?.length > 0,
'rb:py-1!': !expression.sub_variable_condition?.conditions || !expression.sub_variable_condition?.conditions?.length
})}
>
<Flex align="center"> <Flex align="center">
{caculateIsSet(expression, 'cases') {calculateIsSet(expression, 'cases')
? <> ? <>
{labelRender(expression.left)} {labelRender(expression.left)}
<span className="rb:mx-1">{getLocaleField(expression.operator, typeof expression.right)}</span> <span className="rb:mx-1">{getLocaleField(expression.operator, typeof expression.right)}</span>
@@ -109,11 +116,16 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
} }
</Flex> </Flex>
{expression.sub_variable_condition?.conditions?.length > 0 && expression.sub_variable_condition?.conditions.every(isSubExprSet) {expression.sub_variable_condition?.conditions?.length > 0 && expression.sub_variable_condition?.conditions.every(isSubExprSet)
? <div className="rb-border-l rb:ml-2 rb:mt-1.5"> ? <div className="rb-border-l rb:ml-2 rb:mt-1">
{expression.sub_variable_condition?.conditions.map((sub: any, sIndex: number) => ( {expression.sub_variable_condition?.conditions.map((sub: any, sIndex: number) => (
<div key={sIndex} className="rb:relative"> <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>} {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"> <Flex align="center"
className={clsx("rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5", {
'rb:py-1!': sIndex !== 0,
'rb:pb-1': sIndex === 0
})}
>
<span className="rb:text-[#155EEF]">{sub.key}</span> <span className="rb:text-[#155EEF]">{sub.key}</span>
<span className="rb:mx-1">{getSubLocaleField(sub.operator, sub.key)}</span> <span className="rb:mx-1">{getSubLocaleField(sub.operator, sub.key)}</span>
<span className="rb:break-all rb:line-clamp-1"> <span className="rb:break-all rb:line-clamp-1">
@@ -129,7 +141,7 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
))} ))}
</div> </div>
: expression.sub_variable_condition?.conditions?.length > 0 : 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"> ? <Flex align="center" className="rb:pl-2! rb:rounded-md rb:pb-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-4">
{t(`workflow.config.${data.type}.unset`)} {t(`workflow.config.${data.type}.unset`)}
</Flex> </Flex>
: null : null

View File

@@ -2,7 +2,7 @@
* @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-17 20:47:49 * @Last Modified time: 2026-04-20 10:46:05
*/ */
import { useEffect, useMemo, type FC } from 'react' import { useEffect, useMemo, type FC } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
@@ -39,7 +39,7 @@ interface Expression {
sub_variable_condition?: SubVariableCondition; sub_variable_condition?: SubVariableCondition;
} }
interface CaseItem { export interface CaseItem {
logical_operator: 'and' | 'or'; logical_operator: 'and' | 'or';
expressions: Expression[]; expressions: Expression[];
} }
@@ -274,7 +274,9 @@ const ArrayFileSubConditions: FC<ArrayFileSubConditionsProps> = ({ conditionFiel
className="rb:w-full!" className="rb:w-full!"
suffix="Byte" suffix="Byte"
size="small" size="small"
onChange={(value) => { form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value); }} onChange={(value) => {
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'conditions', subIndex, 'value'], value);
}}
/> />
} }
</Form.Item> </Form.Item>
@@ -483,13 +485,24 @@ const CaseList: FC<CaseListProps> = ({
form.setFieldValue([name, index, 'logical_operator'], currentValue === 'and' ? 'or' : 'and'); form.setFieldValue([name, index, 'logical_operator'], currentValue === 'and' ? 'or' : 'and');
}; };
const handleLeftFieldChange = (caseIndex: number, conditionIndex: number, newValue: string) => { const handleLeftFieldChange = (caseIndex: number, conditionIndex: number, newValue: string, option?: Suggestion | undefined) => {
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex], { if (option?.dataType === 'array[file]') {
left: newValue, form.setFieldValue([name, caseIndex, 'expressions', conditionIndex], {
operator: undefined, left: newValue,
right: undefined, operator: undefined,
input_type: 'constant' sub_variable_condition: {
}); conditions: [],
logical_operator: 'and'
}
});
} else {
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex], {
left: newValue,
operator: undefined,
right: undefined,
input_type: 'constant'
});
}
}; };
const handleAddCase = (addCaseFunc: Function) => { const handleAddCase = (addCaseFunc: Function) => {
@@ -590,7 +603,7 @@ const CaseList: FC<CaseListProps> = ({
options={options} options={options}
size="small" size="small"
allowClear={false} allowClear={false}
onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val as string)} onChange={(val, option) => handleLeftFieldChange(caseIndex, conditionIndex, val as string, option as unknown as Suggestion)}
variant="borderless" variant="borderless"
className="rb:w-36!" className="rb:w-36!"
/> />

View File

@@ -147,6 +147,11 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
}; };
const handleChange: CascaderProps<Option>['onChange'] = (value, selectedOptions) => { const handleChange: CascaderProps<Option>['onChange'] = (value, selectedOptions) => {
if (!value) {
setParameters([])
form.resetFields()
return
}
const targetOption = selectedOptions[selectedOptions.length - 1]; const targetOption = selectedOptions[selectedOptions.length - 1];
const curParameters = [...(targetOption.parameters ?? [])] const curParameters = [...(targetOption.parameters ?? [])]
setParameters([...curParameters]) setParameters([...curParameters])

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:06:18 * @Date: 2026-02-03 15:06:18
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-16 17:52:30 * @Last Modified time: 2026-04-20 14:36:41
*/ */
import LoopNode from './components/Nodes/LoopNode'; import LoopNode from './components/Nodes/LoopNode';
import NormalNode from './components/Nodes/NormalNode'; import NormalNode from './components/Nodes/NormalNode';
@@ -428,7 +428,8 @@ export const nodeLibrary: NodeLibrary[] = [
{ type: "tool", icon: 'rb:bg-[url("@/assets/images/workflow/tools.svg")]', { type: "tool", icon: 'rb:bg-[url("@/assets/images/workflow/tools.svg")]',
config: { config: {
tool_id: { tool_id: {
type: 'cascader' type: 'cascader',
required: true
}, },
tool_parameters: { tool_parameters: {
type: 'define' type: 'define'
@@ -734,7 +735,7 @@ export const portTextAttrs = { fontSize: 12, fill: '#5B6167' }
/** /**
* Port position arguments * Port position arguments
*/ */
export const portItemArgsY = 26.5; export const portItemArgsY = 27.5;
export const portArgs = { x: nodeWidth, y: portItemArgsY } export const portArgs = { x: nodeWidth, y: portItemArgsY }
const defaultPortGroup = { const defaultPortGroup = {

View File

@@ -2,136 +2,70 @@
* @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-04-17 20:40:47 * @Last Modified time: 2026-04-20 14:20:34
*/ */
import { portItemArgsY, conditionNodePortItemArgsY, conditionNodeHeight } from './constant' import { conditionNodePortItemArgsY, conditionNodeHeight } from './constant'
/**
* Calculate the total height of a condition (if-else) node based on its cases.
*
* The height is composed of:
* - `conditionNodeHeight`: the base height of the node (header + padding).
* - `(cases.length - 1) * 26`: vertical spacing added for each additional case
* beyond the first (each case separator row is 26px).
* - `exprCount * 20`: each individual expression row occupies 20px.
* - `hasMultiExprCount * 3`: a small extra padding (3px per expression) is added
* for cases that contain more than one expression, to account for the logical
* operator indicator (AND/OR) between expressions.
*
* @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) => { export const isSubExprSet = (sub: any) => {
if (!sub?.key) return false; if (!sub?.key) return false;
if (['not_empty', 'empty'].includes(sub?.operator)) return true; if (['not_empty', 'empty'].includes(sub?.operator)) return true;
return !!sub.value || typeof sub.value === 'boolean' || typeof sub.value === 'number'; return !!sub.value || typeof sub.value === 'boolean' || typeof sub.value === 'number';
}; };
/**
const getEffectiveExprCount = (expr: any): number => { * Calculate the total height of a condition (if-else) node based on its cases.
const subs = expr?.sub_variable_condition?.conditions; * Uses the same per-expression height logic as getConditionNodeCasePortY.
if (subs?.length && subs.every(isSubExprSet)) return 1 + subs.length; */
if (subs?.length > 0) { export const calcConditionNodeTotalHeight = (cases: any[]) => {
return 2 const casesHeight = cases.reduce((acc: number, c: any) => {
} const exprs = c?.expressions ?? [];
return 1; const n = exprs.length;
const exprsHeight = n === 0 ? 0 : exprs.reduce((s: number, e: any) => s + calcExpressionHeight(e), 0) + 2 * (n - 1);
return acc + 20 + exprsHeight;
}, 0);
return conditionNodeHeight + casesHeight + (cases.length - 1) * 4 - 27.5;
}; };
export const calcConditionNodeTotalHeight = (cases: any[]) => { /**
// Total number of effective expression rows (sub_variable_condition expand height when all set) * Height of a single expression block in ConditionNode (px).
const exprCount = cases.reduce((acc: number, c: any) => *
acc + (c?.expressions?.reduce((s: number, e: any) => s + getEffectiveExprCount(e), 0) || 0), 0); * expression outer Flex padding:
// Sum of effective expression counts only for cases that have more than one expression * - has sub conditions (length > 0): pt-1 (4px top only)
const hasMultiExprCount = cases.reduce((acc: number, c: any) => { * - no sub conditions: py-1 (4px top + 4px bottom)
if (!c?.expressions?.length || c.expressions.length <= 1) return acc; * expression main row: leading-4 = 16px
const effectiveCount = c.expressions.reduce((s: number, e: any) => s + getEffectiveExprCount(e), 0); * sub_variable_condition block (mt-1 = 4px gap):
return acc + effectiveCount; * - all isSet, m subs: sub[0] = leading-3.5(14) + pb-1(4) = 18px;
}, 0); * sub[k>0] = py-1(8) + leading-3.5(14) = 22px
* total = 18 + 22*(m-1)
return conditionNodeHeight + (cases.length - 1) * 26 + exprCount * 20 + hasMultiExprCount * 3; * - exists but not all isSet: pb-1(4) + leading-4(16) = 20px
*/
const calcExpressionHeight = (expression: any): number => {
const subs = expression?.sub_variable_condition?.conditions;
if (!subs?.length) return 24; // py-1(8) + leading-4(16)
const subBlockHeight = subs.every(isSubExprSet)
? 18 + 22 * (subs.length - 1)
: 20;
return 4 + 16 + 4 + subBlockHeight - 2; // pt-1 + main row + mt-1 + sub block (-2 rendering correction)
}; };
/** /**
* Calculate the Y-coordinate of the right-side output port for a specific case * Calculate the Y-coordinate of the right-side output port for a specific case
* in a condition (if-else) node. * in a condition (if-else) node, aligned with the IF/ELIF label in ConditionNode.
* *
* The port position is determined by iterating through all preceding cases * Layout (from node top):
* (index 0 to caseIndex - 1) and accumulating their visual heights. Several * - 12px padding-top + 24px header + 12px mt-3 = 48px to cases area
* pixel-level corrections are applied to align ports with the rendered UI: * - Each IF/ELIF label row: leading-4 (16px), center at +8px → first port Y = 56.5
* * - Each case: IF/ELIF row (leading-4=16) + mb-1(4) + expressions (gap={2}=2px between)
* 1. **Base offset**: starts at `conditionNodePortItemArgsY`, which is the Y * - Gap between cases (Flex gap={4}): 4px
* position of the first case port relative to the node top.
*
* 2. **Per-case accumulation**: for each preceding case with `n` expressions,
* add `portItemArgsY * (n + 1)` — this accounts for `n` expression rows
* plus one case header/separator row.
*
* 3. **Single-expression correction**: cases with exactly 1 expression render
* slightly shorter than the generic formula predicts. Subtract
* `singleExprCount * 7 + 2` to compensate for the reduced row height when
* no logical operator row is shown.
*
* 4. **Multi-expression correction**: cases with 2+ expressions have a compact
* logical operator row. Subtract `multiExprCount * 9` to offset the
* over-estimated spacing.
*
* 5. **Extra expression correction**: for cases with more than 2 expressions,
* each additional expression beyond the second introduces a minor spacing
* discrepancy. Subtract `(extraExprs + 1) * 2` to fine-tune alignment.
*
* @param cases - Array of case objects, each containing an `expressions` array.
* @param caseIndex - The zero-based index of the target case whose port Y is needed.
* @returns The Y-coordinate (in pixels) for the output port of the given case.
*/ */
export const getConditionNodeCasePortY = (cases: any[], caseIndex: number) => { export const getConditionNodeCasePortY = (cases: any[], caseIndex: number) => {
let y = conditionNodePortItemArgsY; let y = conditionNodePortItemArgsY; // 56.5, center of first IF label
let singleExprCount = 0;
let multiExprCount = 0;
let extraExprs = 0;
let portItemArgsYNum = 0;
for (let i = 0; i < caseIndex; i++) { for (let i = 0; i < caseIndex; i++) {
const notHasSub = cases[i]?.expressions?.filter((e: any) => !e?.sub_variable_condition?.conditions || e?.sub_variable_condition?.conditions.length <1).length const exprs = cases[i]?.expressions ?? [];
const n = cases[i]?.expressions?.length || 0; const n = exprs.length;
let casePortItemArgsYNum = n + 1; // IF/ELIF row (16) + mb-1 (4) = 20px base; expressions: sum of heights + 2px gap between
// Add extra y for expressions with all sub_variable_condition set const exprsHeight = n === 0 ? 0 : exprs.reduce((acc: number, e: any) => acc + calcExpressionHeight(e), 0) + 2 * (n - 1);
cases[i]?.expressions?.forEach((expr: any) => { y += 20 + exprsHeight + 4; // case height + Flex gap between cases
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++;
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 + 2;
}
});
console.log('extraExprs notHasSub', notHasSub)
if (notHasSub > 3) {
extraExprs += n - 2 + notHasSub/4;
} else {
extraExprs += n - 2 + notHasSub/4
}
}
} }
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)
y -= multiExprCount * 9;
// Correction for cases with more than 2 expressions (minor spacing drift)
if (extraExprs > 0) y -= (extraExprs + 1) * 2;
return y; return y;
}; };