Merge pull request #374 from SuanmoSuanyangTechnology/feature/workflow_zy
Feature/workflow zy
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, type FC } from 'react';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import { $getSelection, $isRangeSelection, $isTextNode } from 'lexical';
|
||||
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical';
|
||||
|
||||
import { INSERT_VARIABLE_COMMAND } from '../commands';
|
||||
import type { NodeProperties } from '../../../types'
|
||||
@@ -45,6 +45,9 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
|
||||
(textBeforeCursor === '/' && anchorOffset === 1);
|
||||
|
||||
setShowSuggestions(shouldShow);
|
||||
if (!shouldShow) {
|
||||
setSelectedIndex(0);
|
||||
}
|
||||
|
||||
if (shouldShow) {
|
||||
const domSelection = window.getSelection();
|
||||
@@ -113,9 +116,6 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
|
||||
setShowSuggestions(false);
|
||||
};
|
||||
|
||||
if (!showSuggestions) return null;
|
||||
|
||||
// Group options by node id
|
||||
const groupedSuggestions = options.reduce((groups: Record<string, any[]>, suggestion) => {
|
||||
const { nodeData } = suggestion
|
||||
const nodeId = nodeData.id as string;
|
||||
@@ -126,6 +126,93 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
useEffect(() => {
|
||||
if (!showSuggestions) return;
|
||||
|
||||
const allOptions = Object.values(groupedSuggestions).flat();
|
||||
|
||||
return editor.registerCommand(
|
||||
KEY_ENTER_COMMAND,
|
||||
(event) => {
|
||||
if (showSuggestions && allOptions.length > 0) {
|
||||
const selectedOption = allOptions[selectedIndex];
|
||||
if (selectedOption && !selectedOption.disabled) {
|
||||
event?.preventDefault();
|
||||
insertMention(selectedOption);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
COMMAND_PRIORITY_HIGH
|
||||
);
|
||||
}, [showSuggestions, selectedIndex, groupedSuggestions, insertMention, editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showSuggestions) return;
|
||||
|
||||
const allOptions = Object.values(groupedSuggestions).flat();
|
||||
|
||||
const unregisterArrowDown = editor.registerCommand(
|
||||
KEY_ARROW_DOWN_COMMAND,
|
||||
(event) => {
|
||||
if (showSuggestions && allOptions.length > 0) {
|
||||
event?.preventDefault();
|
||||
setSelectedIndex(prev => {
|
||||
let nextIndex = prev + 1;
|
||||
while (nextIndex < allOptions.length && allOptions[nextIndex].disabled) {
|
||||
nextIndex++;
|
||||
}
|
||||
return nextIndex >= allOptions.length ? prev : nextIndex;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
COMMAND_PRIORITY_HIGH
|
||||
);
|
||||
|
||||
const unregisterArrowUp = editor.registerCommand(
|
||||
KEY_ARROW_UP_COMMAND,
|
||||
(event) => {
|
||||
if (showSuggestions && allOptions.length > 0) {
|
||||
event?.preventDefault();
|
||||
setSelectedIndex(prev => {
|
||||
let prevIndex = prev - 1;
|
||||
while (prevIndex >= 0 && allOptions[prevIndex].disabled) {
|
||||
prevIndex--;
|
||||
}
|
||||
return prevIndex < 0 ? prev : prevIndex;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
COMMAND_PRIORITY_HIGH
|
||||
);
|
||||
|
||||
const unregisterEscape = editor.registerCommand(
|
||||
KEY_ESCAPE_COMMAND,
|
||||
(event) => {
|
||||
if (showSuggestions) {
|
||||
event?.preventDefault();
|
||||
setShowSuggestions(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
COMMAND_PRIORITY_HIGH
|
||||
);
|
||||
|
||||
return () => {
|
||||
unregisterArrowDown();
|
||||
unregisterArrowUp();
|
||||
unregisterEscape();
|
||||
};
|
||||
}, [showSuggestions, selectedIndex, groupedSuggestions, editor]);
|
||||
|
||||
if (!showSuggestions) return null;
|
||||
|
||||
if (Object.entries(groupedSuggestions).length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-09 18:31:30
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 18:31:30
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { Popover } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import type { ReactShapeConfig } from '@antv/x6-react-shape';
|
||||
import { nodeLibrary, graphNodeLibrary, edgeAttrs } from '../../constant';
|
||||
import { nodeLibrary, graphNodeLibrary, edgeAttrs, nodeWidth } from '../../constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
@@ -10,6 +16,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Handle node selection from popover and create new node replacing the add-node placeholder
|
||||
const handleNodeSelect = (selectedNodeType: any) => {
|
||||
const parentBBox = node.getBBox();
|
||||
const cycleId = data.cycle;
|
||||
@@ -32,7 +39,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
},
|
||||
});
|
||||
|
||||
// 将新节点添加为父节点的子节点
|
||||
// Add new node as child of parent node
|
||||
if (cycleId) {
|
||||
const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId);
|
||||
if (parentNode) {
|
||||
@@ -61,14 +68,14 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 删除所有add-node类型的节点
|
||||
// Remove all add-node type nodes
|
||||
graph.getNodes().forEach((n: any) => {
|
||||
if (n.getData()?.type === 'add-node' && n.getData()?.cycle === cycleId) {
|
||||
n.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// 自动调整循环节点大小
|
||||
// Automatically adjust loop node size
|
||||
const loopNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId);
|
||||
if (loopNode) {
|
||||
const adjustLoopSize = () => {
|
||||
@@ -85,7 +92,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
}, { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });
|
||||
|
||||
const padding = 20;
|
||||
const newWidth = Math.max(240, bounds.maxX - bounds.minX + padding * 2);
|
||||
const newWidth = Math.max(nodeWidth, bounds.maxX - bounds.minX + padding * 2);
|
||||
const newHeight = Math.max(120, bounds.maxY - bounds.minY + padding * 2);
|
||||
|
||||
loopNode.prop('size', { width: newWidth, height: newHeight });
|
||||
@@ -94,7 +101,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
|
||||
adjustLoopSize();
|
||||
|
||||
// 监听子节点移动事件
|
||||
// Listen to child node movement events
|
||||
const childNodes = graph.getNodes().filter((n: any) => n.getData()?.cycle === cycleId);
|
||||
childNodes.forEach((childNode: any) => {
|
||||
childNode.on('change:position', adjustLoopSize);
|
||||
@@ -104,7 +111,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div style={{ maxHeight: '300px', overflowY: 'auto', minWidth: '240px' }}>
|
||||
<div style={{ maxHeight: '300px', overflowY: 'auto', minWidth: `${nodeWidth}px'` }}>
|
||||
{nodeLibrary.map((category, categoryIndex) => {
|
||||
const filteredNodes = category.nodes.filter(nodeType =>
|
||||
nodeType.type !== 'start' && nodeType.type !== 'end' && nodeType.type !== 'iteration' && nodeType.type !== 'loop' && nodeType.type !== 'cycle-start'
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-09 18:30:28
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 18:30:28
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Popover } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { nodeLibrary, graphNodeLibrary, edgeAttrs } from '../constant';
|
||||
import { nodeLibrary, graphNodeLibrary, edgeAttrs, nodeWidth } from '../constant';
|
||||
|
||||
interface PortClickHandlerProps {
|
||||
graph: any;
|
||||
@@ -32,13 +38,14 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle node selection from popover menu and create new node with edge connection
|
||||
const handleNodeSelect = (selectedNodeType: any) => {
|
||||
if (!sourceNode || !graph) return;
|
||||
|
||||
const sourceNodeData = sourceNode.getData();
|
||||
const sourceNodeType = sourceNodeData?.type;
|
||||
|
||||
// 如果是cycle-start节点,需要处理add-node节点
|
||||
// If it's a cycle-start node, handle the add-node placeholder
|
||||
let addNodePosition = null;
|
||||
if (sourceNodeType === 'cycle-start' && sourceNodeData.cycle) {
|
||||
const cycleId = sourceNodeData.cycle;
|
||||
@@ -53,38 +60,38 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算新节点位置,避免重叠
|
||||
// Calculate new node position to avoid overlapping
|
||||
const sourceBBox = sourceNode.getBBox();
|
||||
const nodeWidth = graphNodeLibrary[selectedNodeType.type]?.width || 120;
|
||||
const nodeHeight = graphNodeLibrary[selectedNodeType.type]?.height || 88;
|
||||
const horizontalSpacing = sourceNodeType === 'cycle-start' ? 40 : 80;
|
||||
const verticalSpacing = 10;
|
||||
|
||||
// 获取源连接桩的group信息
|
||||
// Get source port group information
|
||||
const sourcePortInfo = sourceNode.getPorts().find((p: any) => p.id === sourcePort);
|
||||
const sourcePortGroup = sourcePortInfo?.group || sourcePort;
|
||||
console.log('sourcePortGroup', sourcePortGroup, sourcePortInfo)
|
||||
|
||||
// 如果有add-node位置,使用该位置,否则计算新位置
|
||||
// If add-node position exists, use it; otherwise calculate new position
|
||||
let newX, newY;
|
||||
if (addNodePosition) {
|
||||
newX = addNodePosition.x;
|
||||
newY = addNodePosition.y;
|
||||
} else {
|
||||
// 根据连接桩位置决定节点放置方向
|
||||
// Determine node placement direction based on port position
|
||||
if (sourcePortGroup === 'left') {
|
||||
// 左侧连接桩,在左侧添加节点
|
||||
// Left port: add node to the left
|
||||
newX = sourceBBox.x - nodeWidth*2 - horizontalSpacing;
|
||||
newY = sourceBBox.y;
|
||||
} else {
|
||||
// 右侧连接桩,在右侧添加节点
|
||||
// Right port: add node to the right
|
||||
newX = sourceBBox.x + sourceBBox.width + horizontalSpacing;
|
||||
newY = sourceBBox.y;
|
||||
}
|
||||
|
||||
// 检查位置是否与现有节点重叠(只考虑与当前节点相连的节点)
|
||||
// Check if position overlaps with existing nodes (only consider connected nodes)
|
||||
const checkOverlap = (x: number, y: number) => {
|
||||
// 获取与源节点相连的节点
|
||||
// Get nodes connected to the source node
|
||||
const connectedNodes = new Set();
|
||||
graph.getConnectedEdges(sourceNode).forEach((edge: any) => {
|
||||
const sourceId = edge.getSourceCellId();
|
||||
@@ -95,20 +102,20 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
|
||||
return graph.getNodes().some((node: any) => {
|
||||
if (node.id === sourceNode.id) return false;
|
||||
if (!connectedNodes.has(node.id)) return false; // 只考虑相连的节点
|
||||
if (!connectedNodes.has(node.id)) return false; // Only consider connected nodes
|
||||
const bbox = node.getBBox();
|
||||
return !(x + nodeWidth < bbox.x || x > bbox.x + bbox.width ||
|
||||
y + nodeHeight < bbox.y || y > bbox.y + bbox.height);
|
||||
});
|
||||
};
|
||||
|
||||
// 如果位置被占用,向下寻找空位
|
||||
// If position is occupied, search downward for empty space
|
||||
while (checkOverlap(newX, newY)) {
|
||||
newY += nodeHeight + verticalSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新节点
|
||||
// Create new node
|
||||
const id = `${selectedNodeType.type.replace(/-/g, '_')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
const newNode = graph.addNode({
|
||||
...(graphNodeLibrary[selectedNodeType.type] || graphNodeLibrary.default),
|
||||
@@ -120,12 +127,12 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
type: selectedNodeType.type,
|
||||
icon: selectedNodeType.icon,
|
||||
name: t(`workflow.${selectedNodeType.type}`),
|
||||
cycle: sourceNodeData.cycle, // 继承源节点的cycle
|
||||
cycle: sourceNodeData.cycle, // Inherit cycle from source node
|
||||
config: selectedNodeType.config || {}
|
||||
},
|
||||
});
|
||||
|
||||
// 将新节点添加为父节点的子节点
|
||||
// Add new node as child of parent node
|
||||
if (sourceNodeData.cycle) {
|
||||
const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === sourceNodeData.cycle);
|
||||
if (parentNode) {
|
||||
@@ -133,16 +140,16 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建连线
|
||||
// Create edge connection
|
||||
setTimeout(() => {
|
||||
const targetPorts = newNode.getPorts();
|
||||
let targetPort;
|
||||
|
||||
if (sourcePortGroup === 'left') {
|
||||
// 从左侧连接桩连出,连接到新节点的右侧
|
||||
// Connect from left port to new node's right side
|
||||
targetPort = targetPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||
} else {
|
||||
// 从右侧连接桩连出,连接到新节点的左侧
|
||||
// Connect from right port to new node's left side
|
||||
targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||
}
|
||||
|
||||
@@ -153,7 +160,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
// zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0
|
||||
});
|
||||
|
||||
// 循环节点内子节点通过连接桩添加时,调整循环节点大小
|
||||
// Adjust loop node size when child node is added via port within loop node
|
||||
const cycleId = sourceNodeData.cycle;
|
||||
if (cycleId) {
|
||||
const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId);
|
||||
@@ -174,7 +181,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
|
||||
const padding = 20;
|
||||
const bottomPadding = 50;
|
||||
const newWidth = Math.max(240, bounds.maxX - bounds.minX + padding * 2);
|
||||
const newWidth = Math.max(nodeWidth, bounds.maxX - bounds.minX + padding * 2);
|
||||
const newHeight = Math.max(120, bounds.maxY - bounds.minY + padding + bottomPadding);
|
||||
|
||||
parentNode.prop('size', { width: newWidth, height: newHeight });
|
||||
@@ -183,7 +190,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
|
||||
adjustLoopSize();
|
||||
|
||||
// 监听子节点移动事件
|
||||
// Listen to child node movement events
|
||||
const childNodes = graph.getNodes().filter((n: any) => n.getData()?.cycle === cycleId);
|
||||
childNodes.forEach((childNode: any) => {
|
||||
childNode.on('change:position', adjustLoopSize);
|
||||
@@ -192,7 +199,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// 清理临时元素
|
||||
// Clean up temporary element
|
||||
if (tempElement) {
|
||||
document.body.removeChild(tempElement);
|
||||
setTempElement(null);
|
||||
@@ -210,7 +217,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div style={{ maxHeight: '300px', overflowY: 'auto', minWidth: '240px' }}>
|
||||
<div style={{ maxHeight: '300px', overflowY: 'auto', minWidth: `${nodeWidth}px` }}>
|
||||
{nodeLibrary.map((category, categoryIndex) => {
|
||||
const sourceNodeData = sourceNode?.getData();
|
||||
const isChildOfLoop = sourceNodeData?.cycle && graph?.getNodes().find((n: any) => n.getData()?.id === sourceNodeData.cycle && n.getData()?.type === 'loop');
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-09 18:24:53
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 18:24:53
|
||||
*/
|
||||
import { type FC } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -6,7 +12,7 @@ import { Form, Button, Select, Space, Divider, InputNumber, Radio, type SelectPr
|
||||
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
|
||||
import VariableSelect from '../VariableSelect'
|
||||
import Editor from '../../Editor'
|
||||
import { edgeAttrs, portArgs } from '../../../constant'
|
||||
import { edgeAttrs, portTextAttrs, nodeWidth } from '../../../constant'
|
||||
|
||||
interface CaseListProps {
|
||||
value?: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; input_type?: string; }[] }>;
|
||||
@@ -52,15 +58,16 @@ const CaseList: FC<CaseListProps> = ({
|
||||
const { t } = useTranslation();
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
// Update node ports based on case count changes (add/remove cases)
|
||||
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
|
||||
if (!selectedNode || !graphRef?.current) return;
|
||||
|
||||
// 获取当前端口数量来判断是添加还是删除操作
|
||||
// Get current port count to determine if it's an add or remove operation
|
||||
const currentPorts = selectedNode.getPorts().filter((port: any) => port.group === 'right');
|
||||
const currentCaseCount = currentPorts.length - 1; // 减去ELSE端口
|
||||
const currentCaseCount = currentPorts.length - 1; // Exclude ELSE port
|
||||
const isAddingCase = removedCaseIndex === undefined && caseCount > currentCaseCount;
|
||||
|
||||
// 保存现有连线信息(包括左侧端口连线)
|
||||
// Save existing edge connections (including left-side port connections)
|
||||
const existingEdges = graphRef.current.getEdges().filter((edge: any) =>
|
||||
edge.getSourceCellId() === selectedNode.id || edge.getTargetCellId() === selectedNode.id
|
||||
);
|
||||
@@ -73,7 +80,7 @@ const CaseList: FC<CaseListProps> = ({
|
||||
isIncoming: edge.getTargetCellId() === selectedNode.id
|
||||
}));
|
||||
|
||||
// 移除所有现有的右侧端口
|
||||
// Remove all existing right-side ports
|
||||
const existingPorts = selectedNode.getPorts();
|
||||
existingPorts.forEach((port: any) => {
|
||||
if (port.group === 'right') {
|
||||
@@ -81,43 +88,52 @@ const CaseList: FC<CaseListProps> = ({
|
||||
}
|
||||
});
|
||||
|
||||
// 计算新的节点高度:基础高度88px + 每个额外port增加30px
|
||||
// Calculate new node height: base height 88px + 30px for each additional port
|
||||
const baseHeight = 88;
|
||||
const totalPorts = caseCount + 1; // IF/ELIF + ELSE
|
||||
const newHeight = baseHeight + (totalPorts - 2) * 30;
|
||||
|
||||
selectedNode.prop('size', { width: 240, height: newHeight })
|
||||
|
||||
// 添加 IF 端口
|
||||
selectedNode.prop('size', { width: nodeWidth, height: newHeight })
|
||||
|
||||
// Add IF port
|
||||
selectedNode.addPort({
|
||||
id: 'CASE1',
|
||||
group: 'right',
|
||||
args: portArgs,
|
||||
attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }}
|
||||
});
|
||||
args: {
|
||||
x: nodeWidth,
|
||||
y: 42,
|
||||
},
|
||||
attrs: { text: { text: 'IF', ...portTextAttrs } }
|
||||
})
|
||||
|
||||
// 添加 ELIF 端口
|
||||
// Add ELIF ports
|
||||
for (let i = 1; i < caseCount; i++) {
|
||||
selectedNode.addPort({
|
||||
id: `CASE${i + 1}`,
|
||||
group: 'right',
|
||||
args: portArgs,
|
||||
attrs: { text: { text: 'ELIF', fontSize: 12, fill: '#5B6167' }}
|
||||
args: {
|
||||
x: nodeWidth,
|
||||
y: 30 * i + 42,
|
||||
},
|
||||
attrs: { text: { text: 'ELIF', ...portTextAttrs }}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加 ELSE 端口
|
||||
// Add ELSE port
|
||||
selectedNode.addPort({
|
||||
id: `CASE${caseCount + 1}`,
|
||||
group: 'right',
|
||||
args: portArgs,
|
||||
attrs: { text: { text: 'ELSE', fontSize: 12, fill: '#5B6167' }}
|
||||
args: {
|
||||
x: nodeWidth,
|
||||
y: 30 * caseCount + 42,
|
||||
},
|
||||
attrs: { text: { text: 'ELSE', ...portTextAttrs }}
|
||||
});
|
||||
|
||||
// 恢复连线
|
||||
// Restore edge connections
|
||||
setTimeout(() => {
|
||||
edgeConnections.forEach(({ edge, sourcePortId, targetCellId, targetPortId, sourceCellId, isIncoming }: any) => {
|
||||
// 如果是进入连线(左侧端口),直接恢复
|
||||
// If it's an incoming connection (left-side port), restore directly
|
||||
if (isIncoming) {
|
||||
const sourceCell = graphRef.current?.getCellById(sourceCellId);
|
||||
if (sourceCell) {
|
||||
@@ -131,10 +147,10 @@ const CaseList: FC<CaseListProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理右侧端口连线
|
||||
// Handle right-side port connections
|
||||
const originalCaseNumber = parseInt(sourcePortId.match(/CASE(\d+)/)?.[1] || '0');
|
||||
|
||||
// 如果是删除操作且是被删除的端口,删除连线
|
||||
// If it's a remove operation and the port is being removed, delete the connection
|
||||
if (removedCaseIndex !== undefined && originalCaseNumber === removedCaseIndex + 1) {
|
||||
graphRef.current?.removeCell(edge);
|
||||
return;
|
||||
@@ -142,22 +158,22 @@ const CaseList: FC<CaseListProps> = ({
|
||||
|
||||
let newPortId = sourcePortId;
|
||||
|
||||
// 如果是删除操作,需要重新映射端口ID
|
||||
// If it's a remove operation, remap port IDs
|
||||
if (removedCaseIndex !== undefined) {
|
||||
if (originalCaseNumber > removedCaseIndex + 1) {
|
||||
// 被删除端口之后的端口,编号向前移动
|
||||
// Ports after the removed port, shift numbering forward
|
||||
newPortId = `CASE${originalCaseNumber - 1}`;
|
||||
}
|
||||
// ELSE端口始终映射到新的ELSE端口位置
|
||||
// ELSE port always maps to the new ELSE port position
|
||||
else if (originalCaseNumber === currentCaseCount + 1) {
|
||||
newPortId = `CASE${caseCount + 1}`;
|
||||
}
|
||||
} else if (isAddingCase) {
|
||||
// 如果是添加操作,ELSE端口需要重新映射
|
||||
// If it's an add operation, ELSE port needs to be remapped
|
||||
if (originalCaseNumber === currentCaseCount + 1) {
|
||||
newPortId = `CASE${caseCount + 1}`; // 新的ELSE端口
|
||||
newPortId = `CASE${caseCount + 1}`; // New ELSE port
|
||||
}
|
||||
// 新添加的端口不恢复任何连线
|
||||
// Newly added ports don't restore any connections
|
||||
}
|
||||
|
||||
const newPorts = selectedNode.getPorts();
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-09 18:34:33
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 18:34:33
|
||||
*/
|
||||
import { type FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Space } from 'antd';
|
||||
@@ -5,7 +11,7 @@ import { Graph, Node } from '@antv/x6';
|
||||
|
||||
import Editor from '../../Editor';
|
||||
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
|
||||
import { edgeAttrs, portArgs } from '../../../constant'
|
||||
import { edgeAttrs, portTextAttrs, nodeWidth } from '../../../constant'
|
||||
|
||||
interface CategoryListProps {
|
||||
parentName: string;
|
||||
@@ -19,10 +25,11 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
const form = Form.useFormInstance();
|
||||
const formValues = Form.useWatch([parentName], form);
|
||||
|
||||
// Update node ports based on category count changes (add/remove categories)
|
||||
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
|
||||
if (!selectedNode || !graphRef?.current) return;
|
||||
|
||||
// 保存现有连线信息(包括左侧端口连线)
|
||||
// Save existing edge connections (including left-side port connections)
|
||||
const existingEdges = graphRef.current.getEdges().filter((edge: any) =>
|
||||
edge.getSourceCellId() === selectedNode.id || edge.getTargetCellId() === selectedNode.id
|
||||
);
|
||||
@@ -35,7 +42,7 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
isIncoming: edge.getTargetCellId() === selectedNode.id
|
||||
}));
|
||||
|
||||
// 移除所有现有的右侧端口
|
||||
// Remove all existing right-side ports
|
||||
const existingPorts = selectedNode.getPorts();
|
||||
existingPorts.forEach((port: any) => {
|
||||
if (port.group === 'right') {
|
||||
@@ -43,28 +50,30 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
}
|
||||
});
|
||||
|
||||
// 计算新的节点高度:基础高度88px + 每个额外port增加30px
|
||||
// Calculate new node height: base height 88px + 30px for each additional port
|
||||
const baseHeight = 88;
|
||||
const totalPorts = caseCount + 1; // IF/ELIF + ELSE
|
||||
const newHeight = baseHeight + (totalPorts - 2) * 30;
|
||||
const newHeight = baseHeight + (caseCount - 2) * 30;
|
||||
|
||||
selectedNode.prop('size', { width: 240, height: newHeight < baseHeight ? baseHeight : newHeight })
|
||||
selectedNode.prop('size', { width: nodeWidth, height: newHeight < baseHeight ? baseHeight : newHeight })
|
||||
|
||||
// 添加 分类 端口
|
||||
// Add category ports
|
||||
for (let i = 0; i < caseCount; i++) {
|
||||
selectedNode.addPort({
|
||||
id: `CASE${i + 1}`,
|
||||
group: 'right',
|
||||
args: portArgs,
|
||||
attrs: { text: { text: `分类${i + 1}`, fontSize: 12, fill: '#5B6167' } }
|
||||
args: {
|
||||
x: nodeWidth,
|
||||
y: 30 * i + 42,
|
||||
},
|
||||
attrs: { text: { text: `分类${i + 1}`, ...portTextAttrs } }
|
||||
});
|
||||
}
|
||||
// 恢复连线
|
||||
// Restore edge connections
|
||||
setTimeout(() => {
|
||||
edgeConnections.forEach(({ edge, sourcePortId, targetCellId, targetPortId, sourceCellId, isIncoming }: any) => {
|
||||
graphRef.current?.removeCell(edge);
|
||||
|
||||
// 如果是进入连线(左侧端口),直接恢复
|
||||
// If it's an incoming connection (left-side port), restore directly
|
||||
if (isIncoming) {
|
||||
const sourceCell = graphRef.current?.getCellById(sourceCellId);
|
||||
if (sourceCell) {
|
||||
@@ -77,22 +86,22 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理右侧端口连线
|
||||
// Handle right-side port connections
|
||||
const originalCaseNumber = parseInt(sourcePortId.match(/CASE(\d+)/)?.[1] || '0');
|
||||
|
||||
// 如果是被删除的端口,不重新创建连线
|
||||
// If it's a removed port, don't recreate the connection
|
||||
if (removedCaseIndex !== undefined && originalCaseNumber === removedCaseIndex + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newPortId = sourcePortId;
|
||||
|
||||
// 如果删除了某个端口,需要重新映射后续端口的ID
|
||||
// If a port was removed, remap subsequent port IDs
|
||||
if (removedCaseIndex !== undefined && originalCaseNumber > removedCaseIndex + 1) {
|
||||
newPortId = `CASE${originalCaseNumber - 1}`;
|
||||
}
|
||||
|
||||
// 检查新端口是否存在
|
||||
// Check if the new port exists
|
||||
const newPorts = selectedNode.getPorts();
|
||||
const matchingPort = newPorts.find((port: any) => port.id === newPortId);
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-09 18:35:43
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 18:35:43
|
||||
*/
|
||||
import { type FC, useRef, useState } from "react";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Form, Row, Col, Select, Button, Divider, InputNumber, Switch, Input } from 'antd'
|
||||
import { CaretDownOutlined, CaretRightOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
|
||||
import Editor from '../../Editor'
|
||||
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
|
||||
import AuthConfigModal from './AuthConfigModal'
|
||||
@@ -9,6 +16,7 @@ import type { AuthConfigModalRef, HttpRequestConfigForm } from './types'
|
||||
import VariableSelect from "../VariableSelect";
|
||||
import MessageEditor from '../MessageEditor'
|
||||
import EditableTable from './EditableTable'
|
||||
import { portTextAttrs } from '../../../constant'
|
||||
|
||||
const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: any; }> = ({
|
||||
options,
|
||||
@@ -32,6 +40,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
|
||||
form.setFieldValue(['body', 'data'], undefined)
|
||||
}
|
||||
|
||||
// Handle error handling method change and update node ports accordingly
|
||||
const handleChangeErrorHandleMethod = (method: string) => {
|
||||
form.setFieldsValue({
|
||||
error_handle: {
|
||||
@@ -42,21 +51,21 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
|
||||
}
|
||||
})
|
||||
|
||||
// 更新节点连接桩
|
||||
// Update node ports
|
||||
console.log('handleChangeErrorHandleMethod', selectedNode, graphRef?.current)
|
||||
if (selectedNode && graphRef?.current) {
|
||||
const existingPorts = selectedNode.getPorts();
|
||||
const errorPort = existingPorts.find((port: any) => port.id === 'ERROR');
|
||||
|
||||
if (method === 'branch' && !errorPort) {
|
||||
// 添加异常节点连接桩
|
||||
// Add error branch port
|
||||
selectedNode.addPort({
|
||||
id: 'ERROR',
|
||||
group: 'right',
|
||||
attrs: { text: { text: t('workflow.config.http-request.errorBranch'), fontSize: 12, fill: '#5B6167' }}
|
||||
attrs: { text: { text: t('workflow.config.http-request.errorBranch'), ...portTextAttrs }}
|
||||
});
|
||||
} else if (method !== 'branch' && errorPort) {
|
||||
// 移除异常节点连接桩和相关连线
|
||||
// Remove error branch port and related edges
|
||||
const edges = graphRef.current.getEdges().filter((edge: any) =>
|
||||
edge.getSourceCellId() === selectedNode.id && edge.getSourcePortId() === 'ERROR'
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:06:18
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 14:15:13
|
||||
* @Last Modified time: 2026-02-09 17:48:46
|
||||
*/
|
||||
import LoopNode from './components/Nodes/LoopNode';
|
||||
import NormalNode from './components/Nodes/NormalNode';
|
||||
@@ -518,6 +518,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
// },
|
||||
];
|
||||
|
||||
export const nodeWidth = 240;
|
||||
/**
|
||||
* Node registration library for X6 graph
|
||||
* Maps node shapes to their React components
|
||||
@@ -525,13 +526,13 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
export const nodeRegisterLibrary: ReactShapeConfig[] = [
|
||||
{
|
||||
shape: 'loop-node',
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 120,
|
||||
component: LoopNode,
|
||||
},
|
||||
{
|
||||
shape: 'iteration-node',
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 120,
|
||||
component: LoopNode,
|
||||
},
|
||||
@@ -543,7 +544,7 @@ export const nodeRegisterLibrary: ReactShapeConfig[] = [
|
||||
},
|
||||
{
|
||||
shape: 'condition-node',
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 88,
|
||||
component: ConditionNode,
|
||||
},
|
||||
@@ -625,8 +626,9 @@ export const portAttrs = {
|
||||
textAnchor: 'middle',
|
||||
textVerticalAnchor: 'middle',
|
||||
pointerEvents: 'none',
|
||||
}
|
||||
},
|
||||
}
|
||||
export const portTextAttrs = { fontSize: 12, fill: '#5B6167' }
|
||||
|
||||
/**
|
||||
* Unified port group configuration
|
||||
@@ -638,6 +640,12 @@ const defaultPortGroups = {
|
||||
// bottom: { position: 'bottom', markup: portMarkup, attrs: portAttrs },
|
||||
left: { position: 'left', markup: portMarkup, attrs: portAttrs },
|
||||
}
|
||||
export const defaultAbsolutePortGroups = {
|
||||
// top: { position: 'top', markup: portMarkup, attrs: portAttrs },
|
||||
right: { position: { name: 'absolute' }, markup: portMarkup, attrs: portAttrs },
|
||||
// bottom: { position: 'bottom', markup: portMarkup, attrs: portAttrs },
|
||||
left: { position: 'left', markup: portMarkup, attrs: portAttrs },
|
||||
}
|
||||
/**
|
||||
* Default port items for standard nodes
|
||||
*/
|
||||
@@ -650,7 +658,7 @@ const defaultPortItems = [
|
||||
/**
|
||||
* Port position arguments
|
||||
*/
|
||||
export const portArgs = { dy: 18 }
|
||||
export const portArgs = { x: nodeWidth, y: 42 }
|
||||
|
||||
/**
|
||||
* Graph node library configuration
|
||||
@@ -658,7 +666,7 @@ export const portArgs = { dy: 18 }
|
||||
*/
|
||||
export const graphNodeLibrary: Record<string, NodeConfig> = {
|
||||
iteration: {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 120,
|
||||
shape: 'iteration-node',
|
||||
ports: {
|
||||
@@ -667,7 +675,7 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
||||
},
|
||||
},
|
||||
loop: {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 120,
|
||||
shape: 'loop-node',
|
||||
ports: {
|
||||
@@ -676,33 +684,47 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
||||
},
|
||||
},
|
||||
'if-else': {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 88,
|
||||
shape: 'condition-node',
|
||||
ports: {
|
||||
groups: defaultPortGroups,
|
||||
groups: defaultAbsolutePortGroups,
|
||||
items: [
|
||||
{ group: 'left' },
|
||||
{ group: 'right', id: 'CASE1', args: portArgs, attrs: { text: { text: 'IF', fontSize: 12, color: '#5B6167' }} },
|
||||
{ group: 'right', id: 'CASE2', args: portArgs, attrs: { text: { text: 'ELSE', fontSize: 12, color: '#5B6167' }} }
|
||||
...(['IF', 'ELSE'].map((text, index) => ({
|
||||
group: 'right',
|
||||
id: `CASE${index}`,
|
||||
args: {
|
||||
...portArgs,
|
||||
y: 30 * index + 42,
|
||||
},
|
||||
attrs: { text: { text: text, ...portTextAttrs } }
|
||||
}))),
|
||||
],
|
||||
},
|
||||
},
|
||||
'question-classifier': {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 88,
|
||||
shape: 'condition-node',
|
||||
ports: {
|
||||
groups: defaultPortGroups,
|
||||
groups: defaultAbsolutePortGroups,
|
||||
items: [
|
||||
{ group: 'left' },
|
||||
{ group: 'right', id: 'CASE1', args: portArgs, attrs: { text: { text: '分类1', fontSize: 12, color: '#5B6167' } } },
|
||||
{ group: 'right', id: 'CASE2', args: portArgs, attrs: { text: { text: '分类2', fontSize: 12, color: '#5B6167' } } }
|
||||
...(['分类1', '分类2'].map((text, index) => ({
|
||||
group: 'right',
|
||||
id: `CASE${index}`,
|
||||
args: {
|
||||
...portArgs,
|
||||
y: 30 * index + 42,
|
||||
},
|
||||
attrs: { text: { text: text, ...portTextAttrs } }
|
||||
}))),
|
||||
],
|
||||
},
|
||||
},
|
||||
start: {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 64,
|
||||
shape: 'normal-node',
|
||||
ports: {
|
||||
@@ -711,7 +733,7 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
||||
},
|
||||
},
|
||||
end: {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 64,
|
||||
shape: 'normal-node',
|
||||
ports: {
|
||||
@@ -738,7 +760,7 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
||||
},
|
||||
},
|
||||
default: {
|
||||
width: 240,
|
||||
width: nodeWidth,
|
||||
height: 64,
|
||||
shape: 'normal-node',
|
||||
ports: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:17:48
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 15:17:48
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 18:37:01
|
||||
*/
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -12,7 +12,7 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
|
||||
import { register } from '@antv/x6-react-shape';
|
||||
import type { PortMetadata } from '@antv/x6/lib/model/port';
|
||||
|
||||
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portArgs } from '../constant';
|
||||
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth } from '../constant';
|
||||
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
|
||||
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
|
||||
|
||||
@@ -168,6 +168,7 @@ export const useWorkflowGraph = ({
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const nodeConfig = {
|
||||
...(graphNodeLibrary[type] ?? graphNodeLibrary.default),
|
||||
id,
|
||||
@@ -179,39 +180,28 @@ export const useWorkflowGraph = ({
|
||||
|
||||
// Generate ports dynamically for if-else node based on cases
|
||||
if (type === 'if-else' && config.cases && Array.isArray(config.cases)) {
|
||||
const caseCount = config.cases.length;
|
||||
const totalPorts = caseCount + 1; // IF/ELIF + ELSE
|
||||
const totalPorts = config.cases.length + 1; // IF/ELIF + ELSE
|
||||
const baseHeight = 88;
|
||||
const newHeight = baseHeight + (totalPorts - 2) * 30;
|
||||
|
||||
const portItems: PortMetadata[] = [
|
||||
{ group: 'left' },
|
||||
{ group: 'right', id: 'CASE1', args: portArgs, attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }} }
|
||||
];
|
||||
|
||||
// Add ELIF ports
|
||||
for (let i = 1; i < caseCount; i++) {
|
||||
// Add IF/ELIF/ELSE ports
|
||||
for (let i = 0; i < totalPorts; i++) {
|
||||
portItems.push({
|
||||
group: 'right',
|
||||
id: `CASE${i + 1}`,
|
||||
args: portArgs,
|
||||
attrs: { text: { text: 'ELIF', fontSize: 12, fill: '#5B6167' }}
|
||||
args: {
|
||||
x: nodeWidth,
|
||||
y: 30 * i + 42,
|
||||
},
|
||||
attrs: { text: { text: i === 0 ? 'IF' : i === totalPorts - 1 ? 'ELSE' : 'ELIF', ...portTextAttrs } }
|
||||
});
|
||||
}
|
||||
|
||||
// Add ELSE port
|
||||
portItems.push({
|
||||
group: 'right',
|
||||
id: `CASE${caseCount + 1}`,
|
||||
args: portArgs,
|
||||
attrs: { text: { text: 'ELSE', fontSize: 12, fill: '#5B6167' }}
|
||||
});
|
||||
|
||||
nodeConfig.ports = {
|
||||
groups: {
|
||||
right: { position: 'right', markup: portMarkup, attrs: portAttrs },
|
||||
left: { position: 'left', markup: portMarkup, attrs: portAttrs },
|
||||
},
|
||||
groups: defaultAbsolutePortGroups,
|
||||
items: portItems
|
||||
};
|
||||
|
||||
@@ -222,7 +212,7 @@ export const useWorkflowGraph = ({
|
||||
if (type === 'question-classifier' && config.categories && Array.isArray(config.categories)) {
|
||||
const categoryCount = config.categories.length;
|
||||
const baseHeight = 88;
|
||||
const newHeight = baseHeight + (categoryCount - 1) * 30;
|
||||
const newHeight = baseHeight + (categoryCount - 2) * 30;
|
||||
|
||||
const portItems: PortMetadata[] = [
|
||||
{ group: 'left' }
|
||||
@@ -233,16 +223,16 @@ export const useWorkflowGraph = ({
|
||||
portItems.push({
|
||||
group: 'right',
|
||||
id: `CASE${index + 1}`,
|
||||
args: portArgs,
|
||||
attrs: { text: { text: `分类${index + 1}`, fontSize: 12, fill: '#5B6167' }}
|
||||
args: {
|
||||
x: nodeWidth,
|
||||
y: 30 * index + 42,
|
||||
},
|
||||
attrs: { text: { text: `分类${index + 1}`, ...portTextAttrs }}
|
||||
});
|
||||
});
|
||||
|
||||
nodeConfig.ports = {
|
||||
groups: {
|
||||
right: { position: 'right', markup: portMarkup, attrs: portAttrs },
|
||||
left: { position: 'left', markup: portMarkup, attrs: portAttrs },
|
||||
},
|
||||
groups: defaultAbsolutePortGroups,
|
||||
items: portItems
|
||||
};
|
||||
|
||||
@@ -259,7 +249,7 @@ export const useWorkflowGraph = ({
|
||||
items: [
|
||||
{ group: 'left' },
|
||||
{ group: 'right', id: 'right' },
|
||||
{ group: 'right', id: 'ERROR', attrs: { text: { text: t('workflow.config.http-request.errorBranch'), fontSize: 12, fill: '#5B6167' }}}
|
||||
{ group: 'right', id: 'ERROR', attrs: { text: { text: t('workflow.config.http-request.errorBranch'), ...portTextAttrs }}}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user