|
|
@@ -48,21 +48,21 @@ export function useBlueprint(workflowName = null) {
|
|
|
*/
|
|
|
const loadWorkflow = useCallback(async (folderName) => {
|
|
|
try {
|
|
|
- // console.log('loadWorkflow 被调用,folderName:', folderName);
|
|
|
+ console.log('🚀 [loadWorkflow] 开始加载工作流,folderName:', folderName);
|
|
|
if (!folderName) {
|
|
|
- // console.log('folderName 为空,返回');
|
|
|
+ console.log('❌ [loadWorkflow] folderName 为空,返回');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!window.electronAPI || !window.electronAPI.readProcessingJson) {
|
|
|
- // console.log('electronAPI 不可用,返回');
|
|
|
+ console.log('❌ [loadWorkflow] electronAPI 不可用,返回');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const processingData = await window.electronAPI.readProcessingJson(folderName);
|
|
|
- // console.log('读取到的 processingData:', processingData);
|
|
|
+ console.log('✅ [loadWorkflow] 读取到的 processingData:', processingData);
|
|
|
if (!processingData) {
|
|
|
- // console.log('processingData 为空,返回');
|
|
|
+ console.log('❌ [loadWorkflow] processingData 为空,返回');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -70,14 +70,17 @@ export function useBlueprint(workflowName = null) {
|
|
|
|
|
|
// 加载变量
|
|
|
if (processingData.variables && typeof processingData.variables === 'object') {
|
|
|
+ console.log('✅ [loadWorkflow] 加载变量:', processingData.variables);
|
|
|
setVariables(processingData.variables);
|
|
|
} else {
|
|
|
setVariables({});
|
|
|
}
|
|
|
|
|
|
// 转换为蓝图节点图
|
|
|
+ console.log('🔄 [loadWorkflow] 开始转换为蓝图节点图...');
|
|
|
const blueprint = workflowToBlueprint(processingData);
|
|
|
- // console.log('转换后的 blueprint:', blueprint);
|
|
|
+ console.log('✅ [loadWorkflow] 转换后的 blueprint:', blueprint);
|
|
|
+ console.log(' 📊 节点数量:', blueprint.nodes?.length || 0, '连线数量:', blueprint.connections?.length || 0);
|
|
|
|
|
|
// 分析需要创建哪些变量节点(Get/Set)
|
|
|
const variables = processingData.variables || {};
|
|
|
@@ -150,8 +153,8 @@ export function useBlueprint(workflowName = null) {
|
|
|
// 合并所有新的变量连接(数据连接 + 执行连接)
|
|
|
blueprint.connections = [...finalConnections, ...variableDataConnections, ...variableExecConnections];
|
|
|
|
|
|
- // console.log('blueprint.nodes 是否存在:', !!blueprint.nodes);
|
|
|
- // console.log('blueprint.nodes 长度:', blueprint.nodes?.length);
|
|
|
+ console.log('📋 [loadWorkflow] blueprint.nodes 存在:', !!blueprint.nodes, '长度:', blueprint.nodes?.length);
|
|
|
+ console.log('📋 [loadWorkflow] variableNodes 长度:', variableNodes.length);
|
|
|
|
|
|
// 加载位置信息和连线信息
|
|
|
let nodePositions = null;
|
|
|
@@ -159,26 +162,26 @@ export function useBlueprint(workflowName = null) {
|
|
|
if (window.electronAPI && window.electronAPI.readBlueprintJson) {
|
|
|
try {
|
|
|
const bpData = await window.electronAPI.readBlueprintJson(folderName);
|
|
|
- // console.log('读取到的 bpData:', bpData);
|
|
|
+ console.log('✅ [loadWorkflow] 读取到的 bpData:', bpData);
|
|
|
if (bpData && bpData.nodePositions) {
|
|
|
nodePositions = bpData.nodePositions;
|
|
|
- // console.log('nodePositions:', nodePositions);
|
|
|
}
|
|
|
if (bpData && bpData.connections) {
|
|
|
savedConnections = bpData.connections;
|
|
|
- // console.log('savedConnections:', savedConnections);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- // console.log('读取位置信息失败:', error);
|
|
|
+ console.log('⚠️ [loadWorkflow] 读取位置信息失败:', error);
|
|
|
// 读取位置信息失败,忽略错误
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // console.log('检查 blueprint.nodes:', blueprint.nodes && blueprint.nodes.length > 0);
|
|
|
+ console.log('🔍 [loadWorkflow] 检查节点数组:', blueprint.nodes && blueprint.nodes.length > 0);
|
|
|
if (blueprint.nodes && blueprint.nodes.length > 0) {
|
|
|
- // console.log('进入节点设置分支');
|
|
|
+ console.log('✅ [loadWorkflow] 进入节点设置分支');
|
|
|
// 合并流程节点和变量节点
|
|
|
let finalNodes = [...blueprint.nodes, ...variableNodes];
|
|
|
+ console.log('📊 [loadWorkflow] finalNodes 长度:', finalNodes.length);
|
|
|
+ console.log('📋 [loadWorkflow] finalNodes:', finalNodes);
|
|
|
let needsLayout = false;
|
|
|
|
|
|
if (nodePositions) {
|
|
|
@@ -244,18 +247,20 @@ export function useBlueprint(workflowName = null) {
|
|
|
}
|
|
|
|
|
|
// 调试:检查节点数据
|
|
|
- // console.log('加载的节点数量:', finalNodes.length);
|
|
|
+ console.log('📊 [loadWorkflow] 最终加载的节点数量:', finalNodes.length);
|
|
|
if (finalNodes.length > 0) {
|
|
|
- // console.log('第一个节点:', finalNodes[0]);
|
|
|
- // console.log('节点坐标范围:', {
|
|
|
- // minX: Math.min(...finalNodes.map(n => n.x || 0)),
|
|
|
- // maxX: Math.max(...finalNodes.map(n => n.x || 0)),
|
|
|
- // minY: Math.min(...finalNodes.map(n => n.y || 0)),
|
|
|
- // maxY: Math.max(...finalNodes.map(n => n.y || 0))
|
|
|
- // });
|
|
|
+ console.log(' 🎯 第一个节点:', finalNodes[0]);
|
|
|
+ console.log(' 📍 节点坐标范围:', {
|
|
|
+ minX: Math.min(...finalNodes.map(n => n.x || 0)),
|
|
|
+ maxX: Math.max(...finalNodes.map(n => n.x || 0)),
|
|
|
+ minY: Math.min(...finalNodes.map(n => n.y || 0)),
|
|
|
+ maxY: Math.max(...finalNodes.map(n => n.y || 0))
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
+ console.log('🎨 [loadWorkflow] 准备调用 setNodes,节点数量:', finalNodes.length);
|
|
|
setNodes(finalNodes);
|
|
|
+ console.log('✅ [loadWorkflow] setNodes 调用完成');
|
|
|
|
|
|
// 优先使用保存的连线信息,否则使用从工作流解析的连线
|
|
|
let connectionsToUse = [];
|
|
|
@@ -608,10 +613,20 @@ export function useBlueprint(workflowName = null) {
|
|
|
const newInVars = [...updatedData.inVars];
|
|
|
// 根据参数类型处理值
|
|
|
const paramType = node.inputs[paramIndex].paramType;
|
|
|
- if (paramType === 'int' || paramType === 'integer') {
|
|
|
- // int 类型:如果是数字,直接存储;否则存储为字符串
|
|
|
- const numValue = parseInt(value);
|
|
|
+ if (paramType === 'number' || paramType === 'int' || paramType === 'integer') {
|
|
|
+ // number 类型:如果是数字,直接存储;否则存储为字符串
|
|
|
+ const numValue = parseFloat(value);
|
|
|
newInVars[dataIndex] = isNaN(numValue) ? value : numValue;
|
|
|
+ } else if (paramType === 'bool' || paramType === 'boolean') {
|
|
|
+ // bool 类型:转换为布尔值
|
|
|
+ if (typeof value === 'boolean') {
|
|
|
+ newInVars[dataIndex] = value;
|
|
|
+ } else if (typeof value === 'string') {
|
|
|
+ const lowerValue = value.toLowerCase();
|
|
|
+ newInVars[dataIndex] = lowerValue === 'true' || lowerValue === '1';
|
|
|
+ } else {
|
|
|
+ newInVars[dataIndex] = Boolean(value);
|
|
|
+ }
|
|
|
} else {
|
|
|
// string 类型:如果值不为空,用引号包裹;否则为空字符串
|
|
|
newInVars[dataIndex] = value ? `"${value}"` : '""';
|
|
|
@@ -777,6 +792,118 @@ export function useBlueprint(workflowName = null) {
|
|
|
const arrangeLayout = useCallback(async () => {
|
|
|
if (nodes.length === 0) return;
|
|
|
|
|
|
+ // 定义节点尺寸和最小间距常量
|
|
|
+ const NODE_WIDTH = 200;
|
|
|
+ const NODE_HEIGHT = 100;
|
|
|
+ const MIN_HORIZONTAL_GAP = 120;
|
|
|
+ const MIN_VERTICAL_GAP = 100;
|
|
|
+
|
|
|
+ // 解决节点重叠的辅助函数
|
|
|
+ const resolveNodeOverlaps = (nodesList) => {
|
|
|
+ const resolved = [...nodesList];
|
|
|
+ let hasOverlap = true;
|
|
|
+ let iterations = 0;
|
|
|
+ const maxIterations = 30;
|
|
|
+
|
|
|
+ while (hasOverlap && iterations < maxIterations) {
|
|
|
+ hasOverlap = false;
|
|
|
+ iterations++;
|
|
|
+
|
|
|
+ for (let i = 0; i < resolved.length; i++) {
|
|
|
+ for (let j = i + 1; j < resolved.length; j++) {
|
|
|
+ const node1 = resolved[i];
|
|
|
+ const node2 = resolved[j];
|
|
|
+
|
|
|
+ const node1Right = node1.x + NODE_WIDTH;
|
|
|
+ const node1Bottom = node1.y + NODE_HEIGHT;
|
|
|
+ const node2Right = node2.x + NODE_WIDTH;
|
|
|
+ const node2Bottom = node2.y + NODE_HEIGHT;
|
|
|
+
|
|
|
+ // 检查是否重叠
|
|
|
+ const isOverlapping =
|
|
|
+ node1.x < node2Right && node1Right > node2.x &&
|
|
|
+ node1.y < node2Bottom && node1Bottom > node2.y;
|
|
|
+
|
|
|
+ // 计算实际间距
|
|
|
+ const horizontalGap = node1.x < node2.x ?
|
|
|
+ (node2.x - node1Right) : (node1.x - node2Right);
|
|
|
+ const verticalGap = node1.y < node2.y ?
|
|
|
+ (node2.y - node1Bottom) : (node1.y - node2Bottom);
|
|
|
+
|
|
|
+ // 检查是否需要更多空间
|
|
|
+ const needsMoreHorizontalSpace =
|
|
|
+ horizontalGap >= 0 && horizontalGap < MIN_HORIZONTAL_GAP &&
|
|
|
+ Math.abs(node1.y - node2.y) < NODE_HEIGHT + MIN_VERTICAL_GAP;
|
|
|
+
|
|
|
+ const needsMoreVerticalSpace =
|
|
|
+ verticalGap >= 0 && verticalGap < MIN_VERTICAL_GAP &&
|
|
|
+ Math.abs(node1.x - node2.x) < NODE_WIDTH + MIN_HORIZONTAL_GAP;
|
|
|
+
|
|
|
+ if (isOverlapping || needsMoreHorizontalSpace || needsMoreVerticalSpace) {
|
|
|
+ hasOverlap = true;
|
|
|
+
|
|
|
+ if (isOverlapping) {
|
|
|
+ // 完全重叠,需要分离
|
|
|
+ const overlapX = Math.min(node1Right - node2.x, node2Right - node1.x);
|
|
|
+ const overlapY = Math.min(node1Bottom - node2.y, node2Bottom - node1.y);
|
|
|
+
|
|
|
+ // 关键:检查两个节点是否在同一水平线上(Y坐标相近)
|
|
|
+ const sameHorizontalLevel = Math.abs(node1.y - node2.y) < 10;
|
|
|
+
|
|
|
+ if (sameHorizontalLevel) {
|
|
|
+ // 如果在同一水平线,只做水平分离,保持Y坐标不变
|
|
|
+ if (node1.x < node2.x) {
|
|
|
+ resolved[j].x += overlapX + MIN_HORIZONTAL_GAP;
|
|
|
+ } else {
|
|
|
+ resolved[j].x -= overlapX + MIN_HORIZONTAL_GAP;
|
|
|
+ }
|
|
|
+ // 不改变Y坐标,保持水平对齐
|
|
|
+ } else if (overlapX <= overlapY) {
|
|
|
+ // 水平分离
|
|
|
+ if (node1.x < node2.x) {
|
|
|
+ resolved[j].x += overlapX + MIN_HORIZONTAL_GAP;
|
|
|
+ } else {
|
|
|
+ resolved[j].x -= overlapX + MIN_HORIZONTAL_GAP;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 垂直分离
|
|
|
+ if (node1.y < node2.y) {
|
|
|
+ resolved[j].y += overlapY + MIN_VERTICAL_GAP;
|
|
|
+ } else {
|
|
|
+ resolved[j].y -= overlapY + MIN_VERTICAL_GAP;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (needsMoreHorizontalSpace) {
|
|
|
+ // 需要增加水平间距(优先使用,避免破坏Y对齐)
|
|
|
+ const neededGap = MIN_HORIZONTAL_GAP - horizontalGap;
|
|
|
+ if (node1.x < node2.x) {
|
|
|
+ resolved[j].x += neededGap;
|
|
|
+ } else {
|
|
|
+ resolved[j].x -= neededGap;
|
|
|
+ }
|
|
|
+ } else if (needsMoreVerticalSpace) {
|
|
|
+ // 需要增加垂直间距(仅在必要时使用)
|
|
|
+ const neededGap = MIN_VERTICAL_GAP - verticalGap;
|
|
|
+ if (node1.y < node2.y) {
|
|
|
+ resolved[j].y += neededGap;
|
|
|
+ } else {
|
|
|
+ resolved[j].y -= neededGap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保所有节点都在可见区域内
|
|
|
+ resolved.forEach(node => {
|
|
|
+ if (node.x < 100) node.x = 100;
|
|
|
+ if (node.y < 100) node.y = 100;
|
|
|
+ });
|
|
|
+
|
|
|
+ return resolved;
|
|
|
+ };
|
|
|
+
|
|
|
setIsLoadingLayout(true);
|
|
|
setLayoutProgress(0);
|
|
|
setLayoutMessage('准备开始...');
|
|
|
@@ -802,10 +929,16 @@ export function useBlueprint(workflowName = null) {
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- // 变量节点放在流程节点下方,按类型分组
|
|
|
+ // 智能布局变量节点:让连线尽量垂直或水平
|
|
|
const getVarNodes = varNodes.filter(n => n.varMode === 'get' || !n.varMode);
|
|
|
const setVarNodes = varNodes.filter(n => n.varMode === 'set');
|
|
|
|
|
|
+ // 建立节点ID到节点对象的映射
|
|
|
+ const processNodeMap = new Map();
|
|
|
+ layoutedProcessNodes.forEach(node => {
|
|
|
+ processNodeMap.set(node.id, node);
|
|
|
+ });
|
|
|
+
|
|
|
// 计算流程节点的边界
|
|
|
let maxY = 100;
|
|
|
let minX = 150;
|
|
|
@@ -814,33 +947,99 @@ export function useBlueprint(workflowName = null) {
|
|
|
if (node.x < minX) minX = node.x;
|
|
|
});
|
|
|
|
|
|
- // Get 变量节点放在左下方(确保 X 坐标至少为 100)
|
|
|
- let varX = Math.max(minX, 100);
|
|
|
- let varY = maxY + 200;
|
|
|
+ // 布局 GET 变量节点:优先水平布局(左侧),Y坐标与目标节点对齐
|
|
|
const layoutedGetVarNodes = getVarNodes.map((node, index) => {
|
|
|
- const newNode = { ...node, x: varX, y: varY };
|
|
|
- varX += 200;
|
|
|
- if (varX > 800) {
|
|
|
- varX = minX;
|
|
|
- varY += 80;
|
|
|
+ // 查找 GET 节点的输出连接(数据连接)
|
|
|
+ const outputConnection = connections.find(conn =>
|
|
|
+ conn.source === node.id && conn.sourcePort === 'output_0'
|
|
|
+ );
|
|
|
+
|
|
|
+ if (outputConnection) {
|
|
|
+ // 找到目标流程节点
|
|
|
+ const targetNode = processNodeMap.get(outputConnection.target);
|
|
|
+ if (targetNode) {
|
|
|
+ // 策略1:放在目标节点左侧,Y坐标完全对齐(水平连线)
|
|
|
+ const leftPosition = {
|
|
|
+ x: targetNode.x - 250, // 在目标节点左侧 250px(增加间距)
|
|
|
+ y: targetNode.y // Y坐标完全相同,确保连线水平
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查左侧位置是否会与其他流程节点冲突
|
|
|
+ const hasConflict = layoutedProcessNodes.some(pNode =>
|
|
|
+ Math.abs(pNode.x - leftPosition.x) < 180 && Math.abs(pNode.y - leftPosition.y) < 120
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!hasConflict && leftPosition.x >= 100) {
|
|
|
+ // 左侧位置可用,使用水平连线
|
|
|
+ return { ...node, ...leftPosition };
|
|
|
+ } else {
|
|
|
+ // 策略2:左侧不可用,放在正下方,X坐标对齐(垂直连线)
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ x: targetNode.x, // X坐标相同,连线垂直
|
|
|
+ y: targetNode.y + 180 // 在目标节点下方 180px
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- return newNode;
|
|
|
+
|
|
|
+ // 如果没有连接,放在左下方(纵向对齐到第一层)
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ x: Math.max(minX - 250, 100),
|
|
|
+ y: 100 + index * 180 // 纵向对齐
|
|
|
+ };
|
|
|
});
|
|
|
|
|
|
- // Set 变量节点放在 Get 节点右侧
|
|
|
- varX = Math.max(varX + 100, 600);
|
|
|
- varY = maxY + 200;
|
|
|
+ // 布局 SET 变量节点:优先水平布局(右侧),Y坐标与源节点对齐
|
|
|
const layoutedSetVarNodes = setVarNodes.map((node, index) => {
|
|
|
- const newNode = { ...node, x: varX, y: varY };
|
|
|
- varX += 200;
|
|
|
- if (varX > 1200) {
|
|
|
- varX = 600;
|
|
|
- varY += 80;
|
|
|
+ // 查找连接到 SET 节点的数据输入连接
|
|
|
+ const inputConnection = connections.find(conn =>
|
|
|
+ conn.target === node.id && conn.targetPort === 'input_value'
|
|
|
+ );
|
|
|
+
|
|
|
+ if (inputConnection) {
|
|
|
+ // 找到源流程节点
|
|
|
+ const sourceNode = processNodeMap.get(inputConnection.source);
|
|
|
+ if (sourceNode) {
|
|
|
+ // 策略1:放在源节点右侧,Y坐标完全对齐(水平连线)
|
|
|
+ const rightPosition = {
|
|
|
+ x: sourceNode.x + 420, // 在源节点右侧(节点宽度 + 间距)
|
|
|
+ y: sourceNode.y // Y坐标完全相同,确保连线水平
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查右侧位置是否会与其他流程节点冲突
|
|
|
+ const hasConflict = layoutedProcessNodes.some(pNode =>
|
|
|
+ Math.abs(pNode.x - rightPosition.x) < 180 && Math.abs(pNode.y - rightPosition.y) < 120
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!hasConflict) {
|
|
|
+ // 右侧位置可用,使用水平连线
|
|
|
+ return { ...node, ...rightPosition };
|
|
|
+ } else {
|
|
|
+ // 策略2:右侧不可用,放在正下方,X坐标对齐(垂直连线)
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ x: sourceNode.x, // X坐标相同,连线垂直
|
|
|
+ y: sourceNode.y + 180 // 在源节点下方 180px
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- return newNode;
|
|
|
+
|
|
|
+ // 如果没有连接,放在右下方(纵向对齐)
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ x: Math.max(minX + 600, 700),
|
|
|
+ y: 100 + index * 180 // 纵向对齐到第一层
|
|
|
+ };
|
|
|
});
|
|
|
|
|
|
- const finalNodes = [...layoutedProcessNodes, ...layoutedGetVarNodes, ...layoutedSetVarNodes];
|
|
|
+ // 合并所有节点
|
|
|
+ let finalNodes = [...layoutedProcessNodes, ...layoutedGetVarNodes, ...layoutedSetVarNodes];
|
|
|
+
|
|
|
+ // 解决节点重叠问题,确保所有节点之间有足够的间隔
|
|
|
+ finalNodes = resolveNodeOverlaps(finalNodes);
|
|
|
|
|
|
// console.log('整理布局完成,节点数量:', finalNodes.length);
|
|
|
|