Explorar o código

echo标签完美

yichael hai 4 meses
pai
achega
4671cf51a2
Modificáronse 34 ficheiros con 1991 adicións e 209 borrados
  1. 82 2
      document/工作流语法.md
  2. 16 0
      src/pages/Chat/Input/generate-processing.js
  3. 2 2
      src/pages/Chat/Input/resource-require.js
  4. 244 45
      src/pages/blueprint/blueprint-core.js
  5. 8 0
      src/pages/blueprint/canvas/blueprint-canvas.jsx
  6. 21 5
      src/pages/blueprint/canvas/canvas.css
  7. 5 3
      src/pages/blueprint/canvas/canvas.js
  8. 164 0
      src/pages/blueprint/node-renderer/append/append-node.css
  9. 111 0
      src/pages/blueprint/node-renderer/append/append-node.js
  10. 170 0
      src/pages/blueprint/node-renderer/append/append-node.jsx
  11. 95 0
      src/pages/blueprint/node-renderer/arithmetic/arithmetic-node.css
  12. 62 0
      src/pages/blueprint/node-renderer/arithmetic/arithmetic-node.js
  13. 32 0
      src/pages/blueprint/node-renderer/arithmetic/arithmetic-node.jsx
  14. 107 0
      src/pages/blueprint/node-renderer/comparison/comparison-node.css
  15. 63 0
      src/pages/blueprint/node-renderer/comparison/comparison-node.js
  16. 33 0
      src/pages/blueprint/node-renderer/comparison/comparison-node.jsx
  17. 107 27
      src/pages/blueprint/node-renderer/echo/echo-node.css
  18. 110 13
      src/pages/blueprint/node-renderer/echo/echo-node.js
  19. 128 2
      src/pages/blueprint/node-renderer/echo/echo-node.jsx
  20. 77 42
      src/pages/blueprint/node-renderer/if/if-node.css
  21. 32 10
      src/pages/blueprint/node-renderer/if/if-node.js
  22. 8 7
      src/pages/blueprint/node-renderer/if/if-node.jsx
  23. 46 1
      src/pages/blueprint/node-renderer/index.js
  24. 26 6
      src/pages/blueprint/node-renderer/node-renderer.css
  25. 4 2
      src/pages/blueprint/node-renderer/node-renderer.js
  26. 25 5
      src/pages/blueprint/node-renderer/node-renderer.jsx
  27. 8 5
      src/pages/blueprint/utils/auto-layout.js
  28. 5 1
      src/pages/blueprint/utils/canvas-controller.js
  29. 45 4
      src/pages/blueprint/utils/node-operations.js
  30. 96 8
      src/pages/blueprint/utils/workflow-converter.js
  31. 10 3
      src/pages/blueprint/variable-panel/variable-panel.js
  32. 20 8
      src/pages/blueprint/variable-panel/variable-panel.jsx
  33. 19 7
      static/processing/测试/bp.json
  34. 10 1
      static/processing/测试/processing.json

+ 82 - 2
document/工作流语法.md

@@ -66,6 +66,48 @@
 
 支持操作符:`==` `!=` `>` `<` `>=` `<=`
 
+### 条件判断示例
+
+**数值比较:**
+```json
+{
+  "type": "if",
+  "condition": "{count} > 5",
+  "ture": [{"type": "echo", "value": "count大于5"}],
+  "false": []
+}
+```
+
+**字符串比较:**
+```json
+{
+  "type": "if",
+  "condition": "{message} == \"hello\"",
+  "ture": [{"type": "echo", "value": "消息是hello"}],
+  "false": []
+}
+```
+
+**布尔值判断:**
+```json
+{
+  "type": "if",
+  "condition": "{isReady} == true",
+  "ture": [{"type": "echo", "value": "已准备好"}],
+  "false": [{"type": "echo", "value": "未准备好"}]
+}
+```
+
+或直接使用布尔变量:
+```json
+{
+  "type": "if",
+  "condition": "{isReady}",
+  "ture": [{"type": "echo", "value": "已准备好"}],
+  "false": []
+}
+```
+
 ## 循环(while)
 
 ```json
@@ -107,7 +149,26 @@
 
 ### 随机数(random)
 ```json
-{ "type": "random", "variable": "num", "min": 1, "max": 100, "integer": true }
+{
+  "type": "random",
+  "inVars": ["1", "100"],
+  "outVars": ["{randomNum}"]
+}
+```
+
+- `inVars[0]`: 最小值(字符串格式的数字或变量引用)
+- `inVars[1]`: 最大值(字符串格式的数字或变量引用)
+- `outVars[0]`: 输出变量(**`number` 类型**,可以是整数或小数)
+
+**传统格式(已过时,建议使用上述格式):**
+```json
+{ 
+  "type": "random", 
+  "variable": "num", 
+  "min": 1, 
+  "max": 100, 
+  "integer": true 
+}
 ```
 
 ## 变量
@@ -115,7 +176,26 @@
 - 定义:`"variables": {"name": "value"}`
 - 使用:`"{name}"` 在字段中引用
 - 保存:`"outVars": ["{name}"]` 或 `"variable": "{name}"`
-- 类型:变量值只能是 `number` 或 `string` 类型(`null`、`true`、`false` 等会被自动转换)
+- 类型:支持三种数据类型:
+  - **`number`**:数值类型(整数或小数),例如:`1`、`3.14`、`-5.2`
+  - **`string`**:字符串类型,例如:`"hello"`、`""`
+  - **`bool`**:布尔类型,只能是 `true` 或 `false`(不是字符串)
+
+### 变量类型说明
+- 变量的类型由其初始值自动推断:
+  - 如果初始值是数字(如 `1`、`3.14`),类型为 `number`
+  - 如果初始值是字符串(如 `"text"`、`""`),类型为 `string`
+  - 如果初始值是布尔值(如 `true`、`false`),类型为 `bool`
+- 示例:
+  ```json
+  {
+    "variables": {
+      "count": 0,           // number 类型
+      "message": "",        // string 类型
+      "isReady": false      // bool 类型
+    }
+  }
+  ```
 
 ## 时间格式
 

+ 16 - 0
src/pages/Chat/Input/generate-processing.js

@@ -15,6 +15,20 @@ const SYSTEM_PROMPT = `你是一个通用的手机应用自动化工作流生成
   ]
 }
 
+变量类型说明:
+- number: 数值类型(整数或小数),如 0, 1, 3.14, -5.2
+- string: 字符串类型,如 "", "hello"
+- bool: 布尔类型,如 true, false(注意:不是字符串 "true"/"false")
+
+变量的类型由初始值自动推断。例如:
+{
+  "variables": {
+    "count": 0,        // number 类型
+    "message": "",     // string 类型
+    "isReady": false   // bool 类型
+  }
+}
+
 所有可用标签:
 
 【基础语法】
@@ -23,6 +37,8 @@ const SYSTEM_PROMPT = `你是一个通用的手机应用自动化工作流生成
 
 - if: 条件判断,condition为条件表达式,ture/false为分支操作数组
   例:{"type": "if", "condition": "{count} > 0", "ture": [], "false": []}
+  支持布尔变量:{"type": "if", "condition": "{isReady}", "ture": [], "false": []}
+  或显式比较:{"type": "if", "condition": "{isReady} == true", "ture": [], "false": []}
 
 - while: 循环执行,condition为循环条件,ture为循环体
   例:{"type": "while", "condition": "1 == 1", "ture": []}

+ 2 - 2
src/pages/Chat/Input/resource-require.js

@@ -7,7 +7,7 @@ const REQUIREMENTS_PROMPT = `分析用户需求,只返回需要准备的素材
 
 【基础语法】
 - schedule: 定时执行,condition.interval为间隔,condition.repeat为重复次数(-1无限循环)
-- if: 条件判断,condition为条件表达式,ture/false为分支操作数组
+- if: 条件判断,condition为条件表达式,ture/false为分支操作数组(支持布尔变量直接判断)
 - while: 循环执行,condition为循环条件,ture为循环体
 
 【ADB操作】
@@ -24,7 +24,7 @@ const REQUIREMENTS_PROMPT = `分析用户需求,只返回需要准备的素材
 - delay: 延迟执行,value为延迟时间("2s"/"1m")
 - set: 设置变量,variable为变量名,value为变量值
 - echo: 打印信息,value为文本(支持{{变量}}),或inVars为变量数组
-- random: 生成随机数,variable为变量名,min/max为范围,integer为是否整数
+- random: 生成随机数,inVars[0]为最小值,inVars[1]为最大值,outVars[0]保存结果(number类型)
 
 【扩展标签】
 - image-center-location: 图像中心点定位,inVars[0]为模板图片路径,outVars[0]保存坐标

+ 244 - 45
src/pages/blueprint/blueprint-core.js

@@ -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);
       

+ 8 - 0
src/pages/blueprint/canvas/blueprint-canvas.jsx

@@ -186,8 +186,16 @@ export function BlueprintCanvas({
         </svg>
         
         {/* 渲染节点 */}
+        {(() => {
+          console.log(`🎨 [Blueprint-canvas] 开始渲染节点, nodes 数量: ${nodes.length}`);
+          if (nodes.length > 0) {
+            console.log(`  📋 第一个节点:`, nodes[0]);
+          }
+          return null;
+        })()}
         {nodes.map(node => {
           const NodeComponent = getNodeComponent(node);
+          console.log(`  🔍 节点 ${node.id} (${node.type}) - 获取到组件:`, !!NodeComponent);
           return NodeComponent ? (
             <NodeComponent
               key={node.id}

+ 21 - 5
src/pages/blueprint/canvas/canvas.css

@@ -74,13 +74,13 @@
   stroke-width: 3;
 }
 
-/* int 类型参数连线(绿色) */
-.Blueprint-connection.connection-int {
+/* number 类型参数连线(绿色) */
+.Blueprint-connection.connection-number {
   stroke: #4caf50;
   stroke-width: 2;
 }
 
-.Blueprint-connection.connection-int:hover {
+.Blueprint-connection.connection-number:hover {
   stroke: #66bb6a;
   stroke-width: 2.5;
 }
@@ -96,6 +96,17 @@
   stroke-width: 2.5;
 }
 
+/* bool 类型参数连线(红色) */
+.Blueprint-connection.connection-bool {
+  stroke: #ff4444;
+  stroke-width: 2;
+}
+
+.Blueprint-connection.connection-bool:hover {
+  stroke: #ff6666;
+  stroke-width: 2.5;
+}
+
 .Blueprint-connection.selected {
   stroke: #ffa500;
   stroke-width: 3;
@@ -114,8 +125,8 @@
   stroke: #fff;
 }
 
-/* int 类型参数连线预览(绿色) */
-.Blueprint-connection-preview.connection-int {
+/* number 类型参数连线预览(绿色) */
+.Blueprint-connection-preview.connection-number {
   stroke: #4caf50;
 }
 
@@ -123,3 +134,8 @@
 .Blueprint-connection-preview.connection-string {
   stroke: #2196f3;
 }
+
+/* bool 类型参数连线预览(红色) */
+.Blueprint-connection-preview.connection-bool {
+  stroke: #ff4444;
+}

+ 5 - 3
src/pages/blueprint/canvas/canvas.js

@@ -803,15 +803,17 @@ export function createEventHandlers(logicHandlers, props, nodesForPortType) {
 /**
  * 根据端口类型获取连线样式类
  * @param {string} portType - 端口类型 ('execution' 或 'data')
- * @param {string} paramType - 参数类型 ('int' 或 'string')
+ * @param {string} paramType - 参数类型 ('int', 'string', 或 'bool')
  * @returns {string} 连线样式类名
  */
 export function getConnectionClass(portType, paramType) {
   if (portType === 'execution') {
     return 'connection-execution';
   } else if (portType === 'data') {
-    if (paramType === 'int' || paramType === 'integer') {
-      return 'connection-int';
+    if (paramType === 'number' || paramType === 'int' || paramType === 'integer') {
+      return 'connection-number';
+    } else if (paramType === 'bool' || paramType === 'boolean') {
+      return 'connection-bool';
     } else {
       return 'connection-string';
     }

+ 164 - 0
src/pages/blueprint/node-renderer/append/append-node.css

@@ -0,0 +1,164 @@
+/* Append 拼接节点样式 */
+.Blueprint-append-node {
+  /* 节点特有样式 */
+  min-width: 220px;
+  max-width: 600px;
+  background: linear-gradient(135deg, #2d3436 0%, #1e272e 100%);
+  border: 2px solid #2196f3;
+  border-radius: 8px;
+  box-shadow: 
+    0 0 12px rgba(33, 150, 243, 0.5),
+    inset 0 1px 0 rgba(255, 255, 255, 0.1),
+    0 4px 12px rgba(0, 0, 0, 0.4);
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  position: absolute;
+  overflow: visible;
+  pointer-events: auto;
+  user-select: none;
+  cursor: move;
+  z-index: 10;
+  padding: 0;
+}
+
+.Blueprint-append-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(33, 150, 243, 0.4);
+}
+
+.Blueprint-append-node-header {
+  background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
+  color: #fff;
+  padding: 8px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 6px 6px 0 0;
+  letter-spacing: 0.5px;
+  pointer-events: none;
+  position: relative;
+}
+
+.Blueprint-append-node-header::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 50%;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, transparent 100%);
+  border-radius: 6px 6px 0 0;
+  pointer-events: none;
+}
+
+/* 内容区域 - 左右布局 */
+.Blueprint-append-node-content {
+  padding: 12px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+  min-height: 50px;
+  overflow: visible;
+  pointer-events: none;
+}
+
+/* 左侧输入区域 */
+.Blueprint-append-inputs {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  flex: 1;
+  overflow: visible;
+  pointer-events: none;
+}
+
+/* 片段容器 */
+.Blueprint-append-segment {
+  display: flex;
+  align-items: center;
+  pointer-events: auto;
+}
+
+/* 文本片段 */
+.Blueprint-append-text-segment {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.Blueprint-append-text-input {
+  background: #1e1e1e;
+  border: 1px solid #3e3e3e;
+  border-radius: 3px;
+  color: #fff;
+  font-size: 12px;
+  padding: 4px 8px;
+  min-width: 60px;
+  max-width: 200px;
+  outline: none;
+  pointer-events: auto;
+}
+
+.Blueprint-append-text-input:focus {
+  border-color: #2196f3;
+  background: #252525;
+}
+
+/* 变量片段 */
+.Blueprint-append-variable-segment {
+  display: flex;
+  align-items: center;
+  background: rgba(33, 150, 243, 0.1);
+  border: 1px solid rgba(33, 150, 243, 0.3);
+  border-radius: 12px;
+  padding: 2px 8px 2px 2px;
+}
+
+.Blueprint-append-variable-segment .Blueprint-node-port {
+  position: relative;
+  left: auto;
+  right: auto;
+  margin: 0;
+}
+
+.Blueprint-append-variable-segment .Blueprint-node-port-label {
+  color: #2196f3;
+  font-size: 11px;
+  font-weight: 600;
+  margin-left: 4px;
+}
+
+/* 右侧输出区域 */
+.Blueprint-append-output {
+  display: flex;
+  align-items: center;
+  pointer-events: auto;
+}
+
+.Blueprint-append-output .Blueprint-node-port {
+  position: relative;
+  left: auto;
+  right: auto;
+  flex-direction: row-reverse;
+  gap: 6px;
+}
+
+.Blueprint-append-output .Blueprint-node-port-label {
+  color: #2196f3;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+/* 确保 Append 节点的端口能够被正确定位和点击 */
+.Blueprint-append-node .Blueprint-node-port {
+  pointer-events: auto;
+  cursor: crosshair;
+  z-index: 20;
+}
+
+.Blueprint-append-node .Blueprint-node-port-dot {
+  pointer-events: none;
+}

+ 111 - 0
src/pages/blueprint/node-renderer/append/append-node.js

@@ -0,0 +1,111 @@
+/**
+ * Append 节点逻辑
+ * 用于拼接字符串
+ */
+
+import { parseEchoValue } from '../echo/echo-node.js';
+
+/**
+ * 根据 value 字段创建输入端口
+ * @param {string} value - append 的 value 字段
+ * @returns {Array} 输入端口数组
+ */
+export function createAppendInputPorts(value) {
+  const segments = parseEchoValue(value);
+  const inputs = [];
+  
+  segments.forEach((segment, index) => {
+    if (segment.type === 'text') {
+      // 文本片段:空心圆 + 输入框(既可输入也可连接)
+      inputs.push({
+        id: `input_text_${index}`,
+        label: `文本${index + 1}`,
+        type: 'data',
+        paramType: 'string',
+        paramName: `text_${index}`,
+        segmentType: 'text',
+        segmentIndex: index,
+        defaultValue: segment.content
+      });
+      console.log(`  📝 [createAppendInputPorts] 添加文本端口 [${index}]: "${segment.content}" (空心圆)`);
+    } else if (segment.type === 'variable') {
+      // 变量引用:实心圆 + 连线到变量
+      inputs.push({
+        id: `input_var_${index}`,
+        label: segment.content,
+        type: 'data',
+        paramType: 'string',
+        paramName: `var_${index}`,
+        segmentType: 'variable',
+        segmentIndex: index,
+        variableName: segment.content
+      });
+      console.log(`  🔌 [createAppendInputPorts] 添加变量端口 [${index}]: "${segment.content}" (实心圆)`);
+    }
+  });
+  
+  console.log(`  ✅ 创建了 ${inputs.length} 个输入端口`, inputs);
+  return inputs;
+}
+
+/**
+ * 创建 Append 节点数据
+ * @param {Object} config - 节点配置
+ */
+export function createAppendNodeData(config = {}) {
+  const value = config.value || config.data?.value || '';
+  const inputs = createAppendInputPorts(value);
+  
+  return {
+    id: config.id || `node_append_${Date.now()}`,
+    type: 'append',
+    label: config.label || 'Append',
+    inputs,
+    outputs: [
+      { 
+        id: 'output_result', 
+        label: 'Result', 
+        type: 'data',
+        paramType: 'string'
+      }
+    ],
+    data: {
+      value: value,
+      ...config.data
+    }
+  };
+}
+
+/**
+ * 验证 Append 节点
+ * @param {Object} node - 节点数据
+ * @returns {Object} 验证结果 { valid: boolean, errors: string[] }
+ */
+export function validateAppendNode(node) {
+  const errors = [];
+  
+  if (!node.inputs || node.inputs.length === 0) {
+    errors.push('Append节点必须有输入端口');
+  }
+  
+  if (!node.outputs || node.outputs.length === 0) {
+    errors.push('Append节点必须有输出端口');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}
+
+/**
+ * Append 节点转换为工作流数据
+ * @param {Object} node - 节点数据
+ * @returns {Object} 工作流数据
+ */
+export function appendNodeToWorkflow(node) {
+  return {
+    type: 'append',
+    value: node.data?.value || ''
+  };
+}

+ 170 - 0
src/pages/blueprint/node-renderer/append/append-node.jsx

@@ -0,0 +1,170 @@
+/**
+ * Append 拼接节点组件
+ * 用于拼接多个字符串片段
+ * 
+ * 特点:
+ * - 没有执行端口(纯数据节点)
+ * - 左边:动态数量的输入端口(文本片段 + 变量引用)
+ * - 右边:一个输出端口(拼接结果)
+ * - 蓝色主题
+ */
+
+import { useState, useEffect } from 'react';
+import { parseEchoValue } from '../echo/echo-node.js';
+import { NodeRenderer } from '../node-renderer.jsx';
+import './append-node.css';
+
+export function AppendNode(props) {
+  const { node, connections, onPortMouseDown, onPortMouseUp } = props;
+  const [segments, setSegments] = useState([]);
+  
+  console.log(`🎨 [AppendNode] 渲染 Append 节点:`, { id: node.id, x: node.x, y: node.y, value: node.data?.value });
+  
+  // 解析 value 获取片段
+  useEffect(() => {
+    const value = node.data?.value || '';
+    const parsed = parseEchoValue(value);
+    console.log(`  📊 [AppendNode useEffect] 更新 segments:`, parsed);
+    setSegments(parsed);
+  }, [node.data?.value]);
+  
+  // 检查端口是否已连接
+  const isPortConnected = (portId) => {
+    if (!connections || !Array.isArray(connections)) return false;
+    return connections.some(conn => 
+      (conn.target === node.id && conn.targetPort === portId) ||
+      (conn.source === node.id && conn.sourcePort === portId)
+    );
+  };
+  
+  // 数据端口
+  const inputPorts = node.inputs || [];
+  const outputPort = node.outputs?.[0];
+  
+  console.log(`  🔌 [AppendNode] 端口信息 - inputPorts:`, inputPorts.length, `outputPort:`, !!outputPort);
+  
+  // 自定义内容渲染
+  const renderCustomBody = () => {
+    console.log(`  🎨 [AppendNode renderCustomBody] 开始渲染,segments 数量: ${segments.length}, inputPorts 数量: ${inputPorts.length}`);
+    
+    return (
+      <div className="Blueprint-append-node-content">
+        {/* 左侧:输入片段 */}
+        <div className="Blueprint-append-inputs">
+          {segments.map((segment, index) => {
+            const port = inputPorts.find(p => p.segmentIndex === index);
+            console.log(`    🔍 渲染 segment[${index}]:`, segment, `找到的 port:`, port);
+            
+            if (!port) {
+              console.log(`    ⚠️ segment[${index}] 没有对应的 port,跳过渲染`);
+              return null;
+            }
+            
+            const connected = isPortConnected(port.id);
+            const portClass = 'port-string';
+            
+            // 文本片段:空心圆 + 输入框(既可输入也可连接)
+            if (segment.type === 'text') {
+              console.log(`      ✅ 渲染 segment[${index}] (text) - 空心圆 + 输入框`);
+              return (
+                <div key={index} className="Blueprint-append-segment">
+                  <div className="Blueprint-append-text-segment">
+                    <div
+                      className={`Blueprint-node-port input ${portClass} ${connected ? 'connected' : 'disconnected'}`}
+                      data-port-type="data"
+                      onMouseDown={(e) => {
+                        e.stopPropagation();
+                        onPortMouseDown?.(node.id, port.id);
+                      }}
+                      onMouseUp={(e) => {
+                        e.stopPropagation();
+                        onPortMouseUp?.(node.id, port.id);
+                      }}
+                      title="文本输入(可连接变量)"
+                    >
+                      <div className="Blueprint-node-port-dot hollow"></div>
+                    </div>
+                    <input
+                      type="text"
+                      className="Blueprint-append-text-input"
+                      defaultValue={segment.content}
+                      placeholder="文本"
+                      onClick={(e) => e.stopPropagation()}
+                      onMouseDown={(e) => e.stopPropagation()}
+                      onChange={(e) => {
+                        // TODO: 更新 value 字段
+                        const newValue = e.target.value;
+                        // 需要重新组合整个 value
+                      }}
+                    />
+                  </div>
+                </div>
+              );
+            }
+            
+            // 变量引用:实心圆 + 标签(专门连接变量)
+            if (segment.type === 'variable') {
+              console.log(`      ✅ 渲染 segment[${index}] (variable) - 实心圆 + 标签`);
+              return (
+                <div key={index} className="Blueprint-append-segment">
+                  <div className="Blueprint-append-variable-segment">
+                    <div
+                      className={`Blueprint-node-port input ${portClass} ${connected ? 'connected' : 'disconnected'}`}
+                      data-port-type="data"
+                      onMouseDown={(e) => {
+                        e.stopPropagation();
+                        onPortMouseDown?.(node.id, port.id);
+                      }}
+                      onMouseUp={(e) => {
+                        e.stopPropagation();
+                        onPortMouseUp?.(node.id, port.id);
+                      }}
+                      title={`变量: ${segment.content}`}
+                    >
+                      <div className="Blueprint-node-port-dot solid"></div>
+                      <span className="Blueprint-node-port-label">{segment.content}</span>
+                    </div>
+                  </div>
+                </div>
+              );
+            }
+            
+            return null;
+          })}
+        </div>
+        
+        {/* 右侧:输出端口 */}
+        {outputPort && (
+          <div className="Blueprint-append-output">
+            <div
+              className={`Blueprint-node-port output port-string ${isPortConnected(outputPort.id) ? 'connected' : 'disconnected'}`}
+              data-port-type="data"
+              onMouseDown={(e) => {
+                e.stopPropagation();
+                onPortMouseDown?.(node.id, outputPort.id);
+              }}
+              onMouseUp={(e) => {
+                e.stopPropagation();
+                onPortMouseUp?.(node.id, outputPort.id);
+              }}
+              title="拼接结果"
+            >
+              <span className="Blueprint-node-port-label">Result</span>
+              <div className="Blueprint-node-port-dot solid"></div>
+            </div>
+          </div>
+        )}
+      </div>
+    );
+  };
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-append-node"
+      headerText="Append"
+      layoutType="append-custom"
+      renderCustomBody={renderCustomBody}
+    />
+  );
+}

+ 95 - 0
src/pages/blueprint/node-renderer/arithmetic/arithmetic-node.css

@@ -0,0 +1,95 @@
+/* 算术运算节点样式(图3风格 - 虚幻蓝图) */
+.Blueprint-arithmetic-node {
+  /* 深色圆角矩形,顶部光泽高光 */
+  min-width: 160px;
+  background: linear-gradient(180deg, #3d3d40 0%, #2a2a2d 100%);
+  border: 1px solid rgba(255, 255, 255, 0.15);
+  border-radius: 12px;
+  box-shadow: 
+    0 1px 0 0 rgba(255, 255, 255, 0.2) inset,  /* 顶部光泽 */
+    0 4px 12px rgba(0, 0, 0, 0.5),
+    0 0 20px rgba(76, 175, 80, 0.3);  /* 绿色外发光 */
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  position: relative;
+  overflow: visible;
+}
+
+.Blueprint-arithmetic-node.selected {
+  border-color: rgba(255, 165, 0, 0.6);
+  box-shadow: 
+    0 1px 0 0 rgba(255, 255, 255, 0.2) inset,
+    0 0 0 2px rgba(255, 165, 0, 0.4),
+    0 4px 12px rgba(0, 0, 0, 0.5),
+    0 0 20px rgba(255, 165, 0, 0.4);
+}
+
+/* 内部发光效果(绿色) */
+.Blueprint-arithmetic-node::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: radial-gradient(circle at 20% 50%, rgba(76, 175, 80, 0.2) 0%, transparent 60%);
+  border-radius: 12px;
+  pointer-events: none;
+}
+
+.Blueprint-arithmetic-node-header {
+  background: transparent;
+  color: #fff;
+  padding: 10px 16px;
+  font-size: 22px;
+  font-weight: 700;
+  text-align: center;
+  letter-spacing: 2px;
+  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+  position: relative;
+  z-index: 1;
+}
+
+.Blueprint-arithmetic-node-execution {
+  height: 36px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 12px;
+  z-index: 1;
+}
+
+.Blueprint-arithmetic-node-data {
+  padding: 8px 16px 12px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  position: relative;
+  min-height: 60px;
+  z-index: 1;
+}
+
+/* 数据端口圆点样式(青绿色,参考图3) */
+.Blueprint-arithmetic-node .Blueprint-node-port[data-port-type="data"] .Blueprint-node-port-dot {
+  background: #26c6da;  /* 青绿色 */
+  border: 2px solid #4dd0e1;
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+  box-shadow: 0 0 8px rgba(38, 198, 218, 0.6);
+}
+
+.Blueprint-arithmetic-node .Blueprint-node-port[data-port-type="data"].connected .Blueprint-node-port-dot {
+  background: #26c6da;
+  box-shadow: 0 0 12px rgba(38, 198, 218, 0.8);
+}
+
+/* 执行端口箭头发光 */
+.Blueprint-arithmetic-node .Blueprint-node-port-arrow {
+  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.3));
+}

+ 62 - 0
src/pages/blueprint/node-renderer/arithmetic/arithmetic-node.js

@@ -0,0 +1,62 @@
+/**
+ * 算术运算节点逻辑
+ * 支持:+、-、×、÷、%
+ */
+
+/**
+ * 创建算术运算节点数据
+ * @param {string} operator - 运算符:'add', 'subtract', 'multiply', 'divide', 'modulo'
+ * @param {number} x - X坐标
+ * @param {number} y - Y坐标
+ */
+export function createArithmeticNodeData(operator = 'add', x = 0, y = 0) {
+  const operatorMap = {
+    add: '+',
+    subtract: '-',
+    multiply: '×',
+    divide: '÷',
+    modulo: '%'
+  };
+
+  return {
+    id: `arithmetic_${Date.now()}`,
+    type: 'arithmetic',
+    operator: operator,
+    label: operatorMap[operator] || '+',
+    x,
+    y,
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      { id: 'input_a', label: 'A', type: 'data', paramType: 'number', value: 0 },
+      { id: 'input_b', label: 'B', type: 'data', paramType: 'number', value: 0 }
+    ],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' },
+      { id: 'output_result', label: '结果', type: 'data', paramType: 'number' }
+    ],
+    data: {
+      operator: operator
+    }
+  };
+}
+
+/**
+ * 验证算术运算节点
+ */
+export function validateArithmeticNode(node) {
+  const errors = [];
+  
+  if (!node.operator) {
+    errors.push('算术运算节点必须指定运算符');
+  }
+  
+  const validOperators = ['add', 'subtract', 'multiply', 'divide', 'modulo'];
+  if (!validOperators.includes(node.operator)) {
+    errors.push('无效的运算符');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}

+ 32 - 0
src/pages/blueprint/node-renderer/arithmetic/arithmetic-node.jsx

@@ -0,0 +1,32 @@
+/**
+ * 算术运算节点组件
+ * 支持:+、-、×、÷、%
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './arithmetic-node.css';
+
+export function ArithmeticNode(props) {
+  const { node } = props;
+  
+  // 运算符显示映射
+  const operatorMap = {
+    add: '+',
+    subtract: '-',
+    multiply: '×',
+    divide: '÷',
+    modulo: '%'
+  };
+  
+  const operatorSymbol = operatorMap[node.operator] || node.operator || '+';
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-arithmetic-node"
+      headerText={operatorSymbol}
+      showHeader={true}
+      layoutType="standard"
+    />
+  );
+}

+ 107 - 0
src/pages/blueprint/node-renderer/comparison/comparison-node.css

@@ -0,0 +1,107 @@
+/* 比较运算节点样式(图3风格 - 虚幻蓝图) */
+.Blueprint-comparison-node {
+  /* 深色圆角矩形,顶部光泽高光 */
+  min-width: 160px;
+  background: linear-gradient(180deg, #3d3d40 0%, #2a2a2d 100%);
+  border: 1px solid rgba(255, 255, 255, 0.15);
+  border-radius: 12px;
+  box-shadow: 
+    0 1px 0 0 rgba(255, 255, 255, 0.2) inset,  /* 顶部光泽 */
+    0 4px 12px rgba(0, 0, 0, 0.5),
+    0 0 20px rgba(255, 152, 0, 0.3);  /* 橙色外发光 */
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  position: relative;
+  overflow: visible;
+}
+
+.Blueprint-comparison-node.selected {
+  border-color: rgba(255, 165, 0, 0.6);
+  box-shadow: 
+    0 1px 0 0 rgba(255, 255, 255, 0.2) inset,
+    0 0 0 2px rgba(255, 165, 0, 0.4),
+    0 4px 12px rgba(0, 0, 0, 0.5),
+    0 0 20px rgba(255, 165, 0, 0.4);
+}
+
+/* 内部发光效果(橙色) */
+.Blueprint-comparison-node::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: radial-gradient(circle at 20% 50%, rgba(255, 152, 0, 0.2) 0%, transparent 60%);
+  border-radius: 12px;
+  pointer-events: none;
+}
+
+.Blueprint-comparison-node-header {
+  background: transparent;
+  color: #fff;
+  padding: 10px 16px;
+  font-size: 22px;
+  font-weight: 700;
+  text-align: center;
+  letter-spacing: 2px;
+  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+  position: relative;
+  z-index: 1;
+}
+
+.Blueprint-comparison-node-execution {
+  height: 36px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 12px;
+  z-index: 1;
+}
+
+.Blueprint-comparison-node-data {
+  padding: 8px 16px 12px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  position: relative;
+  min-height: 60px;
+  z-index: 1;
+}
+
+/* 数据端口圆点样式(青绿色,参考图3) */
+.Blueprint-comparison-node .Blueprint-node-port[data-port-type="data"] .Blueprint-node-port-dot {
+  background: #26c6da;  /* 青绿色 */
+  border: 2px solid #4dd0e1;
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+  box-shadow: 0 0 8px rgba(38, 198, 218, 0.6);
+}
+
+.Blueprint-comparison-node .Blueprint-node-port[data-port-type="data"].connected .Blueprint-node-port-dot {
+  background: #26c6da;
+  box-shadow: 0 0 12px rgba(38, 198, 218, 0.8);
+}
+
+/* 输出端口(布尔结果)使用红色 */
+.Blueprint-comparison-node .Blueprint-node-port.output[data-port-type="data"] .Blueprint-node-port-dot {
+  background: #ff4444;
+  border: 2px solid #ff6666;
+  box-shadow: 0 0 8px rgba(255, 68, 68, 0.6);
+}
+
+.Blueprint-comparison-node .Blueprint-node-port.output[data-port-type="data"].connected .Blueprint-node-port-dot {
+  background: #ff4444;
+  box-shadow: 0 0 12px rgba(255, 68, 68, 0.8);
+}
+
+/* 执行端口箭头发光 */
+.Blueprint-comparison-node .Blueprint-node-port-arrow {
+  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.3));
+}

+ 63 - 0
src/pages/blueprint/node-renderer/comparison/comparison-node.js

@@ -0,0 +1,63 @@
+/**
+ * 比较运算节点逻辑
+ * 支持:==、>、<、>=、<=、!=
+ */
+
+/**
+ * 创建比较运算节点数据
+ * @param {string} operator - 运算符:'equal', 'greater', 'less', 'greaterEqual', 'lessEqual', 'notEqual'
+ * @param {number} x - X坐标
+ * @param {number} y - Y坐标
+ */
+export function createComparisonNodeData(operator = 'equal', x = 0, y = 0) {
+  const operatorMap = {
+    equal: '==',
+    greater: '>',
+    less: '<',
+    greaterEqual: '>=',
+    lessEqual: '<=',
+    notEqual: '!='
+  };
+
+  return {
+    id: `comparison_${Date.now()}`,
+    type: 'comparison',
+    operator: operator,
+    label: operatorMap[operator] || '==',
+    x,
+    y,
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      { id: 'input_a', label: 'A', type: 'data', paramType: 'any', value: '' },
+      { id: 'input_b', label: 'B', type: 'data', paramType: 'any', value: '' }
+    ],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' },
+      { id: 'output_result', label: '结果', type: 'data', paramType: 'boolean' }
+    ],
+    data: {
+      operator: operator
+    }
+  };
+}
+
+/**
+ * 验证比较运算节点
+ */
+export function validateComparisonNode(node) {
+  const errors = [];
+  
+  if (!node.operator) {
+    errors.push('比较运算节点必须指定运算符');
+  }
+  
+  const validOperators = ['equal', 'greater', 'less', 'greaterEqual', 'lessEqual', 'notEqual'];
+  if (!validOperators.includes(node.operator)) {
+    errors.push('无效的运算符');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}

+ 33 - 0
src/pages/blueprint/node-renderer/comparison/comparison-node.jsx

@@ -0,0 +1,33 @@
+/**
+ * 比较运算节点组件
+ * 支持:==、>、<、>=、<=、!=
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './comparison-node.css';
+
+export function ComparisonNode(props) {
+  const { node } = props;
+  
+  // 运算符显示映射
+  const operatorMap = {
+    equal: '==',
+    greater: '>',
+    less: '<',
+    greaterEqual: '>=',
+    lessEqual: '<=',
+    notEqual: '!='
+  };
+  
+  const operatorSymbol = operatorMap[node.operator] || node.operator || '==';
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-comparison-node"
+      headerText={operatorSymbol}
+      showHeader={true}
+      layoutType="standard"
+    />
+  );
+}

+ 107 - 27
src/pages/blueprint/node-renderer/echo/echo-node.css

@@ -1,17 +1,25 @@
 /* Echo 输出节点样式 */
 .Blueprint-echo-node {
-  /* 公共逻辑已在 node-renderer.css 中定义 */
-  /* position, width, overflow, pointer-events, user-select, cursor, z-index */
-  
   /* 节点特有样式 */
-  min-width: 180px;
-  background: #2d2d30;
+  min-width: 220px;
+  max-width: 600px;
+  background: linear-gradient(135deg, #2d3436 0%, #1e272e 100%);
   border: 2px solid #4caf50;
-  border-radius: 4px;
-  box-shadow: 0 0 8px rgba(76, 175, 80, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  border-radius: 8px;
+  box-shadow: 
+    0 0 12px rgba(76, 175, 80, 0.5),
+    inset 0 1px 0 rgba(255, 255, 255, 0.1),
+    0 4px 12px rgba(0, 0, 0, 0.4);
   display: flex;
   flex-direction: column;
   align-items: stretch;
+  position: absolute;
+  overflow: visible;
+  pointer-events: auto;
+  user-select: none;
+  cursor: move;
+  z-index: 10;
+  padding: 0;
 }
 
 .Blueprint-echo-node.selected {
@@ -20,20 +28,32 @@
 }
 
 .Blueprint-echo-node-header {
-  /* pointer-events 和 user-select 已在公共样式中定义 */
   background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
   color: #fff;
-  padding: 12px 16px;
+  padding: 8px 16px;
   font-size: 13px;
   font-weight: 600;
   text-align: center;
-  border-radius: 2px 2px 0 0;
+  border-radius: 6px 6px 0 0;
   letter-spacing: 0.5px;
+  pointer-events: none;
+  position: relative;
+}
+
+.Blueprint-echo-node-header::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 50%;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, transparent 100%);
+  border-radius: 6px 6px 0 0;
+  pointer-events: none;
 }
 
 .Blueprint-echo-node-execution {
-  /* pointer-events 已在公共样式中定义 */
-  height: 40px;
+  height: 32px;
   display: flex;
   flex-direction: row;
   justify-content: space-between;
@@ -41,11 +61,12 @@
   position: relative;
   overflow: visible;
   padding: 0 8px;
+  pointer-events: none;
 }
 
 .Blueprint-echo-node-execution .Blueprint-node-port {
-  /* pointer-events 已在公共样式中定义 */
   position: relative;
+  pointer-events: auto;
 }
 
 .Blueprint-echo-node-execution .Blueprint-node-port.input.port-execution {
@@ -59,27 +80,86 @@
   margin-left: auto;
 }
 
-.Blueprint-echo-node-params {
-  /* pointer-events 已在公共样式中定义 */
-  padding: 12px 16px;
+/* Echo 内容区域 - 包含文本和变量片段 */
+.Blueprint-echo-node-content {
+  padding: 8px 12px 12px;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 4px;
+  min-height: 40px;
   overflow: visible;
+  pointer-events: none;
+}
+
+/* 片段容器 */
+.Blueprint-echo-segment {
   display: flex;
-  flex-direction: column;
-  gap: 8px;
-  position: relative;
-  min-height: 20px;
+  align-items: center;
+  pointer-events: auto;
 }
 
-.Blueprint-echo-node-params .Blueprint-node-port {
-  /* pointer-events 已在公共样式中定义 */
-  position: relative;
+/* 文本片段 */
+.Blueprint-echo-text-segment {
+  display: flex;
+  align-items: center;
+  gap: 6px;
 }
 
-.Blueprint-echo-node-params .Blueprint-node-port.input:not(.port-execution) {
-  left: auto;
+.Blueprint-echo-text-input {
+  background: #1e1e1e;
+  border: 1px solid #3e3e3e;
+  border-radius: 3px;
+  color: #fff;
+  font-size: 12px;
+  padding: 4px 8px;
+  min-width: 60px;
+  max-width: 200px;
+  outline: none;
+  pointer-events: auto;
 }
 
-.Blueprint-echo-node-params .Blueprint-node-port.output:not(.port-execution) {
+.Blueprint-echo-text-input:focus {
+  border-color: #4caf50;
+  background: #252525;
+}
+
+/* 变量片段 */
+.Blueprint-echo-variable-segment {
+  display: flex;
+  align-items: center;
+  background: rgba(76, 175, 80, 0.1);
+  border: 1px solid rgba(76, 175, 80, 0.3);
+  border-radius: 12px;
+  padding: 2px 8px 2px 2px;
+}
+
+.Blueprint-echo-variable-segment .Blueprint-node-port {
+  position: relative;
+  left: auto;
   right: auto;
-  align-self: flex-end;
+  margin: 0;
+}
+
+.Blueprint-echo-variable-segment .Blueprint-node-port-label {
+  color: #4caf50;
+  font-size: 11px;
+  font-weight: 600;
+  margin-left: 4px;
+}
+
+/* 确保 Echo 节点的端口能够被正确定位和点击 */
+.Blueprint-echo-node .Blueprint-node-port {
+  pointer-events: auto;
+  cursor: crosshair;
+  z-index: 20;
+}
+
+.Blueprint-echo-node .Blueprint-node-port-dot {
+  pointer-events: none;
+}
+
+.Blueprint-echo-node .Blueprint-node-port-arrow {
+  pointer-events: none;
 }

+ 110 - 13
src/pages/blueprint/node-renderer/echo/echo-node.js

@@ -3,24 +3,128 @@
  * 用于打印/输出信息
  */
 
+/**
+ * 解析 echo 的 value 字段,提取文本片段和变量引用
+ * @param {string} value - 包含 {{varName}} 格式的字符串
+ * @returns {Array} 片段数组 [{type: 'text'|'variable', content: string}]
+ * 
+ * 例如:
+ * "==第 {{turn}} 轮==" 解析为:
+ * [
+ *   {type: 'text', content: '==第 '},
+ *   {type: 'variable', content: 'turn'},
+ *   {type: 'text', content: ' 轮=='}
+ * ]
+ */
+export function parseEchoValue(value) {
+  console.log(`🔍 [parseEchoValue] 解析 value: "${value}"`);
+  if (!value || typeof value !== 'string') {
+    console.log(`  ⚠️ value 为空或非字符串,返回空数组`);
+    return [];
+  }
+  
+  const segments = [];
+  const regex = /\{\{([^}]+)\}\}/g;
+  let lastIndex = 0;
+  let match;
+  
+  while ((match = regex.exec(value)) !== null) {
+    // 添加变量前的文本片段
+    if (match.index > lastIndex) {
+      const textContent = value.substring(lastIndex, match.index);
+      if (textContent) {
+        segments.push({ type: 'text', content: textContent });
+      }
+    }
+    
+    // 添加变量引用片段
+    const varName = match[1].trim();
+    segments.push({ type: 'variable', content: varName });
+    
+    lastIndex = regex.lastIndex;
+  }
+  
+  // 添加最后一个变量后的文本片段
+  if (lastIndex < value.length) {
+    const textContent = value.substring(lastIndex);
+    if (textContent) {
+      segments.push({ type: 'text', content: textContent });
+    }
+  }
+  
+  // 如果没有任何片段(空字符串或只有空格),返回一个空文本片段
+  if (segments.length === 0) {
+    segments.push({ type: 'text', content: '' });
+  }
+  
+  console.log(`  ✅ 解析结果: ${segments.length} 个片段`, segments);
+  return segments;
+}
+
+/**
+ * 根据 value 字段创建输入端口
+ * @param {string} value - echo 的 value 字段
+ * @returns {Array} 输入端口数组
+ */
+export function createEchoInputPorts(value) {
+  console.log(`🔌 [createEchoInputPorts] 创建 Echo 输入端口, value: "${value}"`);
+  const segments = parseEchoValue(value);
+  const inputs = [
+    { id: 'input_0', label: '', type: 'execution' }
+  ];
+  
+  segments.forEach((segment, index) => {
+    if (segment.type === 'text') {
+      // 文本片段:空心圆 + 输入框(既可输入也可连接)
+      inputs.push({
+        id: `input_text_${index}`,
+        label: `文本${index + 1}`,
+        type: 'data',
+        paramType: 'string',
+        paramName: `text_${index}`,
+        segmentType: 'text',
+        segmentIndex: index,
+        defaultValue: segment.content
+      });
+      console.log(`  📝 [createEchoInputPorts] 添加文本端口 [${index}]: "${segment.content}" (空心圆)`);
+    } else if (segment.type === 'variable') {
+      // 变量引用:实心圆 + 连线到变量
+      inputs.push({
+        id: `input_var_${index}`,
+        label: segment.content,
+        type: 'data',
+        paramType: 'string', // 变量类型稍后根据实际变量推断
+        paramName: `var_${index}`,
+        segmentType: 'variable',
+        segmentIndex: index,
+        variableName: segment.content
+      });
+      console.log(`  🔌 [createEchoInputPorts] 添加变量端口 [${index}]: "${segment.content}" (实心圆)`);
+    }
+  });
+  
+  console.log(`  ✅ 创建了 ${inputs.length} 个输入端口 (1个执行端口 + ${inputs.length - 1}个数据端口)`, inputs);
+  return inputs;
+}
+
 /**
  * 创建 Echo 节点数据
  * @param {Object} config - 节点配置
  */
 export function createEchoNodeData(config = {}) {
+  const value = config.value || config.data?.value || '';
+  const inputs = createEchoInputPorts(value);
+  
   return {
     id: config.id || `node_echo_${Date.now()}`,
     type: 'echo',
     label: config.label || 'Echo',
-    inputs: [
-      { id: 'input_0', label: '', type: 'execution' },
-      { id: 'input_message', label: '消息', type: 'data', paramType: 'string', paramName: 'message' }
-    ],
+    inputs,
     outputs: [
       { id: 'output_0', label: '', type: 'execution' }
     ],
     data: {
-      message: config.message || '',
+      value: value,
       ...config.data
     }
   };
@@ -42,11 +146,6 @@ export function validateEchoNode(node) {
     errors.push('Echo节点必须有输出端口');
   }
   
-  const messageInput = node.inputs?.find(p => p.paramName === 'message');
-  if (!messageInput) {
-    errors.push('Echo节点必须有消息输入参数');
-  }
-  
   return {
     valid: errors.length === 0,
     errors
@@ -61,8 +160,6 @@ export function validateEchoNode(node) {
 export function echoNodeToWorkflow(node) {
   return {
     type: 'echo',
-    params: {
-      message: node.data?.message || ''
-    }
+    value: node.data?.value || ''
   };
 }

+ 128 - 2
src/pages/blueprint/node-renderer/echo/echo-node.jsx

@@ -4,19 +4,145 @@
  * 
  * 特点:
  * - 1个输入执行端口 + 1个输出执行端口
- * - 1个输入数据端口(message)
+ * - 动态数量的数据端口(根据 value 中的变量数量)
+ * - 文本片段显示为输入框(空心圆)
+ * - 变量引用显示为连接点(实心圆)
  * - 绿色主题
  */
 
+import { useState, useEffect } from 'react';
+import { parseEchoValue } from './echo-node.js';
 import { NodeRenderer } from '../node-renderer.jsx';
 import './echo-node.css';
 
 export function EchoNode(props) {
+  const { node, connections, onPortMouseDown, onPortMouseUp } = props;
+  const [segments, setSegments] = useState([]);
+  
+  console.log(`🎨 [EchoNode] 渲染 Echo 节点:`, { id: node.id, x: node.x, y: node.y, value: node.data?.value });
+  
+  // 解析 value 获取片段
+  useEffect(() => {
+    const value = node.data?.value || '';
+    const parsed = parseEchoValue(value);
+    console.log(`  📊 [EchoNode useEffect] 更新 segments:`, parsed);
+    setSegments(parsed);
+  }, [node.data?.value]);
+  
+  // 检查端口是否已连接
+  const isPortConnected = (portId) => {
+    if (!connections || !Array.isArray(connections)) return false;
+    return connections.some(conn => 
+      (conn.target === node.id && conn.targetPort === portId) ||
+      (conn.source === node.id && conn.sourcePort === portId)
+    );
+  };
+  
+  // 数据端口
+  const dataPorts = node.inputs?.filter(p => p.type === 'data') || [];
+  
+  console.log(`  🔌 [EchoNode] 端口信息 - dataPorts:`, dataPorts.length);
+  console.log(`  📋 [EchoNode] segments:`, segments);
+  console.log(`  📋 [EchoNode] dataPorts 完整内容:`, JSON.stringify(dataPorts, null, 2));
+  console.log(`  📋 [EchoNode] segments 完整内容:`, JSON.stringify(segments, null, 2));
+  
+  // 自定义内容渲染 - 动态显示片段
+  const renderCustomBody = () => {
+    console.log(`  🎨 [EchoNode renderCustomBody] 开始渲染,segments 数量: ${segments.length}, dataPorts 数量: ${dataPorts.length}`);
+    
+    return (
+      <div className="Blueprint-echo-node-content">
+        {segments.map((segment, index) => {
+          const port = dataPorts.find(p => p.segmentIndex === index);
+          console.log(`    🔍 渲染 segment[${index}]:`, segment, `找到的 port:`, port);
+          
+          if (!port) {
+            console.log(`    ⚠️ segment[${index}] 没有对应的 port,跳过渲染`);
+            return null;
+          }
+          
+          const connected = isPortConnected(port.id);
+          const portClass = 'port-string';
+          
+          // 文本片段:空心圆 + 输入框(既可输入也可连接)
+          if (segment.type === 'text') {
+            console.log(`      ✅ 渲染 segment[${index}] (text) - 空心圆 + 输入框`);
+            return (
+              <div key={index} className="Blueprint-echo-segment">
+                <div className="Blueprint-echo-text-segment">
+                  <div
+                    className={`Blueprint-node-port input ${portClass} ${connected ? 'connected' : 'disconnected'}`}
+                    data-port-type="data"
+                    onMouseDown={(e) => {
+                      e.stopPropagation();
+                      onPortMouseDown?.(node.id, port.id);
+                    }}
+                    onMouseUp={(e) => {
+                      e.stopPropagation();
+                      onPortMouseUp?.(node.id, port.id);
+                    }}
+                    title="文本输入(可连接变量)"
+                  >
+                    <div className="Blueprint-node-port-dot hollow"></div>
+                  </div>
+                  <input
+                    type="text"
+                    className="Blueprint-echo-text-input"
+                    defaultValue={segment.content}
+                    placeholder="文本"
+                    onClick={(e) => e.stopPropagation()}
+                    onMouseDown={(e) => e.stopPropagation()}
+                    onChange={(e) => {
+                      // TODO: 更新 value 字段
+                      const newValue = e.target.value;
+                      // 需要重新组合整个 value
+                    }}
+                  />
+                </div>
+              </div>
+            );
+          }
+          
+          // 变量引用:实心圆 + 标签(专门连接变量)
+          if (segment.type === 'variable') {
+            console.log(`      ✅ 渲染 segment[${index}] (variable) - 实心圆 + 标签`);
+            return (
+              <div key={index} className="Blueprint-echo-segment">
+                <div className="Blueprint-echo-variable-segment">
+                  <div
+                    className={`Blueprint-node-port input ${portClass} ${connected ? 'connected' : 'disconnected'}`}
+                    data-port-type="data"
+                    onMouseDown={(e) => {
+                      e.stopPropagation();
+                      onPortMouseDown?.(node.id, port.id);
+                    }}
+                    onMouseUp={(e) => {
+                      e.stopPropagation();
+                      onPortMouseUp?.(node.id, port.id);
+                    }}
+                    title={`变量: ${segment.content}`}
+                  >
+                    <div className="Blueprint-node-port-dot solid"></div>
+                    <span className="Blueprint-node-port-label">{segment.content}</span>
+                  </div>
+                </div>
+              </div>
+            );
+          }
+          
+          return null;
+        })}
+      </div>
+    );
+  };
+  
   return (
     <NodeRenderer
       {...props}
       className="Blueprint-echo-node"
-      layoutType="standard"
+      headerText="Echo"
+      layoutType="echo-custom"
+      renderCustomBody={renderCustomBody}
     />
   );
 }

+ 77 - 42
src/pages/blueprint/node-renderer/if/if-node.css

@@ -1,78 +1,113 @@
-/* If 条件节点样式 */
+/* IF 分支节点样式(模仿 Unreal Engine Branch 节点 - 图3风格) */
 .Blueprint-if-node {
-  position: absolute;
-  min-width: 200px;
-  width: fit-content;
-  background: #2d2d30;
-  border: 2px solid #ff9800;
-  border-radius: 4px;
-  box-shadow: 0 0 8px rgba(255, 152, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
-  user-select: none;
-  cursor: move;
-  overflow: visible;
+  /* 深色圆角矩形,类似图3 */
+  min-width: 180px;
+  background: linear-gradient(180deg, #3d3d40 0%, #2a2a2d 100%);  /* 深灰渐变 */
+  border: 1px solid rgba(255, 255, 255, 0.15);  /* 微弱边框 */
+  border-radius: 12px;
+  box-shadow: 
+    0 1px 0 0 rgba(255, 255, 255, 0.2) inset,  /* 顶部光泽高光 */
+    0 4px 12px rgba(0, 0, 0, 0.5),
+    0 0 20px rgba(156, 39, 176, 0.3);  /* 紫色外发光 */
   display: flex;
   flex-direction: column;
   align-items: stretch;
+  position: relative;
+  overflow: visible;
 }
 
 .Blueprint-if-node.selected {
-  border-color: #ffa500;
-  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(255, 152, 0, 0.4);
+  border-color: rgba(255, 165, 0, 0.6);
+  box-shadow: 
+    0 1px 0 0 rgba(255, 255, 255, 0.2) inset,
+    0 0 0 2px rgba(255, 165, 0, 0.4),
+    0 4px 12px rgba(0, 0, 0, 0.5),
+    0 0 20px rgba(255, 165, 0, 0.4);
+}
+
+/* 内部发光效果(类似图3的 Aws 节点) */
+.Blueprint-if-node::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: radial-gradient(circle at 20% 50%, rgba(156, 39, 176, 0.2) 0%, transparent 60%);
+  border-radius: 12px;
+  pointer-events: none;
 }
 
 .Blueprint-if-node-header {
-  background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
+  background: transparent;  /* 无独立背景,融入节点 */
   color: #fff;
-  padding: 12px 16px;
+  padding: 8px 16px;
   font-size: 13px;
   font-weight: 600;
   text-align: center;
-  border-radius: 2px 2px 0 0;
   letter-spacing: 1px;
+  position: relative;
+  z-index: 1;
 }
 
+/* 执行端口区域 */
 .Blueprint-if-node-execution {
-  height: auto;
+  height: 36px;
   display: flex;
   flex-direction: row;
   justify-content: space-between;
-  align-items: flex-start;
+  align-items: center;
   position: relative;
   overflow: visible;
-  padding: 8px;
+  padding: 0 12px;
+  z-index: 1;
 }
 
-.Blueprint-if-node-branches {
+/* 数据端口区域(Condition) */
+.Blueprint-if-node-params {
+  padding: 8px 16px 12px;
+  overflow: visible;
   display: flex;
   flex-direction: column;
-  gap: 8px;
-  margin-left: auto;
+  gap: 10px;
+  position: relative;
+  min-height: 40px;
+  z-index: 1;
 }
 
-.Blueprint-if-node-branch {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  gap: 8px;
+/* 特殊样式:Condition 端口使用红色圆点(布尔类型,参考图3) */
+.Blueprint-if-node .Blueprint-node-port[data-port-type="data"] .Blueprint-node-port-dot {
+  background: #ff4444;  /* 鲜红色 */
+  border: 2px solid #ff6666;
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;  /* 圆形 */
+  box-shadow: 0 0 8px rgba(255, 68, 68, 0.6);  /* 红色发光 */
 }
 
-.Blueprint-if-node-branch-label {
-  font-size: 11px;
-  color: #999;
-  min-width: 40px;
-  text-align: right;
+.Blueprint-if-node .Blueprint-node-port[data-port-type="data"].connected .Blueprint-node-port-dot {
+  background: #ff4444;
+  box-shadow: 0 0 12px rgba(255, 68, 68, 0.8);
 }
 
-.Blueprint-if-node-params {
-  padding: 8px 16px;
-  overflow: visible;
-  display: flex;
-  flex-direction: column;
-  gap: 6px;
-  position: relative;
-  min-height: 20px;
+/* Condition 标签样式 */
+.Blueprint-if-node .Blueprint-node-port[data-port-type="data"] .Blueprint-node-port-label {
+  color: #e0e0e0;
+  font-size: 12px;
+  font-weight: 500;
+  margin-left: 8px;
 }
 
-.Blueprint-if-node-params .Blueprint-node-port {
-  position: relative;
+/* True/False 输出端口标签样式 */
+.Blueprint-if-node .Blueprint-node-port.output.port-execution .Blueprint-node-port-label {
+  color: #ffffff;
+  font-size: 12px;
+  font-weight: 600;
+  margin-right: 8px;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+}
+
+/* 执行端口箭头样式优化 */
+.Blueprint-if-node .Blueprint-node-port-arrow {
+  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.3));
 }

+ 32 - 10
src/pages/blueprint/node-renderer/if/if-node.js

@@ -1,35 +1,57 @@
 /**
- * If 条件节点逻辑
+ * IF 分支节点逻辑(模仿 Unreal Engine 的 Branch 节点)
+ * 
+ * 特点:
+ * - 1个执行输入端口
+ * - 2个执行输出端口(True 和 False)
+ * - 1个布尔数据输入端口(Condition)
  */
 
 /**
- * 创建 If 节点数据
+ * 创建 IF 节点数据
+ * @param {number} x - X坐标
+ * @param {number} y - Y坐标
  */
-export function createIfNodeData(config = {}) {
+export function createIfNodeData(x = 0, y = 0) {
   return {
-    id: config.id || `node_if_${Date.now()}`,
+    id: `if_${Date.now()}`,
     type: 'if',
-    label: 'IF',
+    label: 'Branch',
+    x,
+    y,
     inputs: [
       { id: 'input_0', label: '', type: 'execution' },
-      { id: 'input_condition', label: '条件', type: 'data', paramType: 'string' }
+      { id: 'input_condition', label: 'Condition', type: 'data', paramType: 'boolean', value: false }
     ],
     outputs: [
       { id: 'output_true', label: 'True', type: 'execution' },
       { id: 'output_false', label: 'False', type: 'execution' }
     ],
-    data: config.data || {}
+    data: {}
   };
 }
 
 /**
- * 验证 If 节点
+ * 验证 IF 节点
  */
 export function validateIfNode(node) {
   const errors = [];
   
-  if (node.outputs?.length !== 2) {
-    errors.push('If节点必须有两个输出端口(True/False)');
+  const execInput = node.inputs?.find(p => p.type === 'execution');
+  if (!execInput) {
+    errors.push('IF节点必须有执行输入端口');
+  }
+  
+  const conditionInput = node.inputs?.find(p => p.id === 'input_condition');
+  if (!conditionInput) {
+    errors.push('IF节点必须有条件输入端口');
+  }
+  
+  const trueOutput = node.outputs?.find(p => p.id === 'output_true');
+  const falseOutput = node.outputs?.find(p => p.id === 'output_false');
+  
+  if (!trueOutput || !falseOutput) {
+    errors.push('IF节点必须有True和False两个输出端口');
   }
   
   return {

+ 8 - 7
src/pages/blueprint/node-renderer/if/if-node.jsx

@@ -1,23 +1,24 @@
 /**
- * If 条件节点组件
- * 用于条件判断分支
+ * IF 分支节点组件(模仿 Unreal Engine 的 Branch 节点)
  * 
  * 特点:
- * - 1个输入执行端口
- * - 2个输出执行端口(True、False)
- * - 1个条件数据输入端口
- * - 黄色主题
+ * - 紧凑的布局
+ * - 红色 Condition 端口
+ * - True/False 标签清晰显示
  */
 
 import { NodeRenderer } from '../node-renderer.jsx';
 import './if-node.css';
 
 export function IfNode(props) {
+  const { node } = props;
+  
   return (
     <NodeRenderer
       {...props}
       className="Blueprint-if-node"
-      headerText="IF"
+      headerText="Branch"
+      showHeader={true}
       layoutType="standard"
     />
   );

+ 46 - 1
src/pages/blueprint/node-renderer/index.js

@@ -9,6 +9,10 @@ import { SetNode } from './set/set-node.jsx';
 import { GetNode } from './get/get-node.jsx';
 import { FuncNode } from './func/func-node.jsx';
 import { EchoNode } from './echo/echo-node.jsx';
+import { AppendNode } from './append/append-node.jsx';
+import { ArithmeticNode } from './arithmetic/arithmetic-node.jsx';
+import { ComparisonNode } from './comparison/comparison-node.jsx';
+import { IfNode } from './if/if-node.jsx';
 
 // 导出基础节点渲染器
 export { NodeRenderer } from './node-renderer.jsx';
@@ -19,6 +23,10 @@ export { SetNode } from './set/set-node.jsx';
 export { GetNode } from './get/get-node.jsx';
 export { FuncNode } from './func/func-node.jsx';
 export { EchoNode } from './echo/echo-node.jsx';
+export { AppendNode } from './append/append-node.jsx';
+export { ArithmeticNode } from './arithmetic/arithmetic-node.jsx';
+export { ComparisonNode } from './comparison/comparison-node.jsx';
+export { IfNode } from './if/if-node.jsx';
 
 // 导出节点逻辑函数
 export * from './begin/begin-node.js';
@@ -26,6 +34,10 @@ export * from './set/set-node.js';
 export * from './get/get-node.js';
 export * from './func/func-node.js';
 export * from './echo/echo-node.js';
+export * from './append/append-node.js';
+export * from './arithmetic/arithmetic-node.js';
+export * from './comparison/comparison-node.js';
+export * from './if/if-node.js';
 
 // 导出公共工具函数
 export * from './node-renderer.js';
@@ -36,27 +48,60 @@ export * from './node-renderer.js';
  * @returns {Function} 节点组件
  */
 export function getNodeComponent(node) {
-  if (!node) return null;
+  console.log(`🎨 [getNodeComponent] 获取节点组件, type: ${node?.type}, varMode: ${node?.varMode}`);
+  if (!node) {
+    console.log(`  ❌ 节点为空`);
+    return null;
+  }
   
   // Begin 节点
   if (node.type === 'begin') {
+    console.log(`  ✅ 返回 BeginNode`);
     return BeginNode;
   }
   
   // 变量节点
   if (node.type === 'variable') {
     if (node.varMode === 'set') {
+      console.log(`  ✅ 返回 SetNode`);
       return SetNode;
     } else {
+      console.log(`  ✅ 返回 GetNode`);
       return GetNode;
     }
   }
   
   // Echo 节点
   if (node.type === 'echo') {
+    console.log(`  ✅ 返回 EchoNode`);
     return EchoNode;
   }
   
+  // Append 拼接节点
+  if (node.type === 'append') {
+    console.log(`  ✅ 返回 AppendNode`);
+    return AppendNode;
+  }
+  
+  // IF 分支节点
+  if (node.type === 'if') {
+    console.log(`  ✅ 返回 IfNode`);
+    return IfNode;
+  }
+  
+  // 算术运算节点
+  if (node.type === 'arithmetic') {
+    console.log(`  ✅ 返回 ArithmeticNode`);
+    return ArithmeticNode;
+  }
+  
+  // 比较运算节点
+  if (node.type === 'comparison') {
+    console.log(`  ✅ 返回 ComparisonNode`);
+    return ComparisonNode;
+  }
+  
   // 其他功能节点
+  console.log(`  ✅ 返回 FuncNode (默认)`);
   return FuncNode;
 }

+ 26 - 6
src/pages/blueprint/node-renderer/node-renderer.css

@@ -365,23 +365,23 @@
   border: 2px solid #007acc;
 }
 
-/* int 类型端口 - 实心(绿色) */
-.Blueprint-node-port.port-int .Blueprint-node-port-dot.solid {
+/* number 类型端口 - 实心(绿色) */
+.Blueprint-node-port.port-number .Blueprint-node-port-dot.solid {
   background: #4caf50;
   border-color: #fff;
 }
 
-/* int 类型端口 - 空心(绿色边框) */
-.Blueprint-node-port.port-int .Blueprint-node-port-dot.hollow {
+/* number 类型端口 - 空心(绿色边框) */
+.Blueprint-node-port.port-number .Blueprint-node-port-dot.hollow {
   background: transparent;
   border-color: #4caf50;
 }
 
-.Blueprint-node-port.port-int:hover .Blueprint-node-port-dot {
+.Blueprint-node-port.port-number:hover .Blueprint-node-port-dot {
   transform: scale(1.2);
 }
 
-.Blueprint-node-port.port-int:hover .Blueprint-node-port-dot.solid {
+.Blueprint-node-port.port-number:hover .Blueprint-node-port-dot.solid {
   background: #66bb6a;
 }
 
@@ -405,6 +405,26 @@
   background: #42a5f5;
 }
 
+/* bool 类型端口 - 实心(红色) */
+.Blueprint-node-port.port-bool .Blueprint-node-port-dot.solid {
+  background: #ff4444;
+  border-color: #fff;
+}
+
+/* bool 类型端口 - 空心(红色边框) */
+.Blueprint-node-port.port-bool .Blueprint-node-port-dot.hollow {
+  background: transparent;
+  border-color: #ff4444;
+}
+
+.Blueprint-node-port.port-bool:hover .Blueprint-node-port-dot {
+  transform: scale(1.2);
+}
+
+.Blueprint-node-port.port-bool:hover .Blueprint-node-port-dot.solid {
+  background: #ff6666;
+}
+
 /* 端口输入框 - 在节点内部 */
 .Blueprint-node-port-input {
   flex: 0 1 auto; /* 不自动扩展,但可以收缩 */

+ 4 - 2
src/pages/blueprint/node-renderer/node-renderer.js

@@ -432,8 +432,10 @@ export function getPortTypeClass(port) {
   // 数据端口根据参数类型设置颜色
   if (port.type === 'data') {
     const paramType = port.paramType || 'string';
-    if (paramType === 'int' || paramType === 'integer') {
-      return 'port-int';
+    if (paramType === 'number' || paramType === 'int' || paramType === 'integer') {
+      return 'port-number';
+    } else if (paramType === 'bool' || paramType === 'boolean') {
+      return 'port-bool';
     } else {
       return 'port-string';
     }

+ 25 - 5
src/pages/blueprint/node-renderer/node-renderer.jsx

@@ -120,9 +120,6 @@ export function NodeRenderer({
       {/* 标题栏(GET节点不显示) */}
       {showHeader && renderHeader(headerText || node.label || node.type, className)}
       
-      {/* 自定义主体内容(可选,用于特殊节点) */}
-      {renderCustomBody && renderCustomBody(node)}
-      
       {/* 根据布局类型渲染端口 */}
       {layoutType === 'variable' ? (
         // GET变量节点布局:只有输出数据端口,端口在右侧中间
@@ -148,6 +145,19 @@ export function NodeRenderer({
             {dataInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
           </div>
         </>
+      ) : layoutType === 'echo-custom' || layoutType === 'append-custom' ? (
+        // Echo/Append 自定义布局:执行端口 + 自定义内容
+        <>
+          {/* 执行端口区域 */}
+          {(executionInputs.length > 0 || executionOutputs.length > 0) && (
+            <div className="Blueprint-echo-node-execution">
+              {executionInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
+              {executionOutputs.map(port => renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp }))}
+            </div>
+          )}
+          {/* 自定义内容区域(由子组件提供)*/}
+          {renderCustomBody && renderCustomBody(node)}
+        </>
       ) : (
         // 标准流程节点布局:执行端口区域 + 数据端口区域
         <>
@@ -184,6 +194,9 @@ function renderHeader(text, nodeClassName) {
                       nodeClassName.includes('set-node') ? 'Blueprint-set-node-header' :
                       nodeClassName.includes('get-node') ? 'Blueprint-get-node-label' :
                       nodeClassName.includes('echo') ? 'Blueprint-echo-node-header' :
+                      nodeClassName.includes('arithmetic') ? 'Blueprint-arithmetic-node-header' :
+                      nodeClassName.includes('comparison') ? 'Blueprint-comparison-node-header' :
+                      nodeClassName.includes('if') ? 'Blueprint-if-node-header' :
                       nodeClassName.includes('schedule') ? 'Blueprint-schedule-node-header' :
                       nodeClassName.includes('while') ? 'Blueprint-while-node-header' :
                       nodeClassName.includes('if') ? 'Blueprint-if-node-header' :
@@ -216,7 +229,10 @@ function renderPort(port, nodeId, isInput, connections, handlers) {
       title={port.label || port.id}
     >
       {port.type === 'execution' ? (
-        <div className={`Blueprint-node-port-arrow ${isInput ? 'execution-input' : 'execution-output'}`}></div>
+        <>
+          <div className={`Blueprint-node-port-arrow ${isInput ? 'execution-input' : 'execution-output'}`}></div>
+          {port.label && <span className="Blueprint-node-port-label">{port.label}</span>}
+        </>
       ) : (
         <>
           <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
@@ -286,7 +302,11 @@ function renderDataPort(port, node, isInput, connections, handlers) {
           type="text"
           className="Blueprint-node-port-input"
           value={defaultValue}
-          placeholder={port.paramType === 'int' ? '0' : '""'}
+          placeholder={
+            (port.paramType === 'number' || port.paramType === 'int' || port.paramType === 'integer') ? '0' : 
+            (port.paramType === 'bool' || port.paramType === 'boolean') ? 'false' : 
+            '""'
+          }
           onChange={(e) => handleInputChange(e, onPortValueChange, node.id, port.id, port.paramName)}
           onClick={(e) => e.stopPropagation()}
           onMouseDown={(e) => e.stopPropagation()}

+ 8 - 5
src/pages/blueprint/utils/auto-layout.js

@@ -245,16 +245,19 @@ function calculateNodePositionsFast(nodes, levels, graph) {
     const levelNodes = optimizedOrder.get(level) || nodesByLevel.get(level) || [];
     if (levelNodes.length === 0) continue;
     
-    const levelY = START_Y + level * VERTICAL_SPACING;
+    // 关键:所有主执行流节点使用相同的 Y 坐标,确保白色连线完全水平(平行于X轴)
+    const levelY = START_Y;
     
-    // 水平排列节点,确保有足够的间距
+    // 水平排列节点,根据层级计算 X 坐标
     const nodeSpacing = NODE_WIDTH + MIN_HORIZONTAL_GAP;
-    const startX = START_X;
+    const startX = START_X + level * nodeSpacing;
     
+    // 同一层的所有节点使用相同的 Y 坐标(不垂直排列)
+    // 如果同一层有多个节点,横向排列而不是纵向排列
     levelNodes.forEach((nodeId, index) => {
       positions.set(nodeId, {
-        x: startX + index * nodeSpacing,
-        y: levelY
+        x: startX + index * (nodeSpacing / 2),  // 如果同层有多个节点,横向微调
+        y: levelY  // Y坐标完全相同,确保连线水平
       });
     });
   }

+ 5 - 1
src/pages/blueprint/utils/canvas-controller.js

@@ -43,7 +43,11 @@ export function initCanvasController(canvasElement, onTransformChange) {
                            e.target.closest('.Blueprint-set-node') ||
                            e.target.closest('.Blueprint-get-node') ||
                            e.target.closest('.Blueprint-echo-node') ||
-                           e.target.closest('.Blueprint-func-node');
+                           e.target.closest('.Blueprint-append-node') ||
+                           e.target.closest('.Blueprint-func-node') ||
+                           e.target.closest('.Blueprint-arithmetic-node') ||
+                           e.target.closest('.Blueprint-comparison-node') ||
+                           e.target.closest('.Blueprint-if-node');
     const isClickingPort = e.target.closest('.Blueprint-node-port');
     
     // 检查是否点击在 canvas-area 内(包括红色边框区域)

+ 45 - 4
src/pages/blueprint/utils/node-operations.js

@@ -2,6 +2,9 @@
  * 节点操作(复制、删除、创建、粘贴)
  */
 
+import { createEchoInputPorts } from '../node-renderer/echo/echo-node.js';
+import { createAppendInputPorts } from '../node-renderer/append/append-node.js';
+
 /**
  * 创建新节点
  * @param {string} nodeType - 节点类型
@@ -201,12 +204,43 @@ export function getNodeParameterDefinitions(nodeType, data = {}) {
  * @returns {Object} {inputs: [], outputs: []}
  */
 export function getNodePortsFromType(nodeType, data) {
+  console.log(`🔌 [getNodePortsFromType] nodeType: ${nodeType}, data:`, data);
   const inputs = [];
   const outputs = [];
   
   // Begin 节点只有输出端口,没有输入端口
   if (nodeType === 'begin') {
     outputs.push({ id: 'output_0', label: '', type: 'execution' });
+    console.log(`  ✅ Begin 节点端口生成完成`);
+    return { inputs, outputs };
+  }
+  
+  // Echo 节点特殊处理:根据 value 字段动态创建端口
+  if (nodeType === 'echo') {
+    const value = data.value || '';
+    console.log(`  🎤 Echo 节点 value: "${value}"`);
+    const echoInputs = createEchoInputPorts(value);
+    console.log(`  ✅ Echo 输入端口数量: ${echoInputs.length}`, echoInputs);
+    inputs.push(...echoInputs);
+    outputs.push({ id: 'output_0', label: '', type: 'execution' });
+    console.log(`  ✅ Echo 节点端口生成完成 - inputs: ${inputs.length}, outputs: ${outputs.length}`);
+    return { inputs, outputs };
+  }
+  
+  // Append 节点特殊处理:根据 value 字段动态创建端口(纯数据节点)
+  if (nodeType === 'append') {
+    const value = data.value || '';
+    console.log(`  📎 Append 节点 value: "${value}"`);
+    const appendInputs = createAppendInputPorts(value);
+    console.log(`  ✅ Append 输入端口数量: ${appendInputs.length}`, appendInputs);
+    inputs.push(...appendInputs);
+    outputs.push({ 
+      id: 'output_result', 
+      label: 'Result', 
+      type: 'data',
+      paramType: 'string'
+    });
+    console.log(`  ✅ Append 节点端口生成完成 - inputs: ${inputs.length}, outputs: ${outputs.length}`);
     return { inputs, outputs };
   }
   
@@ -233,14 +267,14 @@ export function getNodePortsFromType(nodeType, data) {
   
   // 根据类型添加数据输出端口
   if (nodeType === 'random') {
-    // random 节点输出 int 类型
+    // random 节点输出 number 类型
     // 支持新格式:outVars[{variable}]
     if (data.outVars && Array.isArray(data.outVars) && data.outVars.length > 0) {
       const varName = data.outVars[0];
-      outputs.push({ id: 'output_1', label: varName, type: 'data', paramType: 'int' });
+      outputs.push({ id: 'output_1', label: varName, type: 'data', paramType: 'number' });
     } else if (data.variable) {
       // 向后兼容旧格式
-      outputs.push({ id: 'output_1', label: data.variable, type: 'data', paramType: 'int' });
+      outputs.push({ id: 'output_1', label: data.variable, type: 'data', paramType: 'number' });
     }
   } else if (nodeType === 'set' && data.variable) {
     // set 节点输出类型需要从变量定义中获取,默认 string
@@ -288,7 +322,14 @@ function getNodeLabelFromType(nodeType, data = {}) {
 export function createVariableNode(varName, varValue, x, y, mode = 'get', customId = null) {
   // 如果没有提供自定义 ID,则生成一个(用于用户手动创建的节点)
   const nodeId = customId || `var_${mode}_${varName}_${Date.now()}`;
-  const varType = typeof varValue === 'number' ? 'int' : 'string';
+  
+  // 根据变量值自动判断类型:bool > number > string
+  let varType = 'string';
+  if (typeof varValue === 'boolean') {
+    varType = 'bool';
+  } else if (typeof varValue === 'number') {
+    varType = 'number';
+  }
   
   let inputs = [];
   let outputs = [];

+ 96 - 8
src/pages/blueprint/utils/workflow-converter.js

@@ -10,9 +10,9 @@ import { getNodePortsFromType } from './node-operations.js';
  * @returns {Object} 蓝图节点图数据 {nodes: [], connections: []}
  */
 export function workflowToBlueprint(workflow) {
-  // console.log('workflowToBlueprint 被调用,workflow:', workflow);
+  console.log('🔍 [workflowToBlueprint] 开始转换,workflow:', workflow);
   if (!workflow) {
-    // console.log('workflow 为空,返回空数组');
+    console.log('❌ [workflowToBlueprint] workflow 为空,返回空数组');
     return { nodes: [], connections: [] };
   }
   
@@ -20,17 +20,17 @@ export function workflowToBlueprint(workflow) {
   let executeArray = null;
   if (workflow.execute && Array.isArray(workflow.execute)) {
     executeArray = workflow.execute;
-    // console.log('使用 workflow.execute,长度:', executeArray.length);
+    console.log('✅ [workflowToBlueprint] 使用 workflow.execute,长度:', executeArray.length);
   } else if (workflow.actions && Array.isArray(workflow.actions)) {
     executeArray = workflow.actions;
-    // console.log('使用 workflow.actions,长度:', executeArray.length);
+    console.log('✅ [workflowToBlueprint] 使用 workflow.actions,长度:', executeArray.length);
   } else if (Array.isArray(workflow)) {
     executeArray = workflow;
-    // console.log('workflow 是数组,长度:', executeArray.length);
+    console.log('✅ [workflowToBlueprint] workflow 是数组,长度:', executeArray.length);
   }
   
   if (!executeArray || executeArray.length === 0) {
-    // console.log('executeArray 为空或长度为 0,返回空数组');
+    console.log('❌ [workflowToBlueprint] executeArray 为空或长度为 0,返回空数组');
     return { nodes: [], connections: [] };
   }
 
@@ -149,7 +149,8 @@ export function workflowToBlueprint(workflow) {
   }
   
   parseActions(executeArray, x, y);
-  // console.log('parseActions 完成,节点数量:', nodes.length, '连线数量:', connections.length);
+  console.log('✅ [workflowToBlueprint] parseActions 完成,节点数量:', nodes.length, '连线数量:', connections.length);
+  console.log('  📋 nodes:', nodes);
   
   // 检查是否有 Begin 节点,如果没有则创建一个并连接到第一个节点
   const hasBeginNode = nodes.some(node => node.type === 'begin');
@@ -203,12 +204,50 @@ export function analyzeVariableReferences(processNodes) {
     return null;
   };
   
+  // 辅助函数:从 echo value 中提取变量引用({{varName}})
+  const extractVarsFromEchoValue = (value) => {
+    const varNames = [];
+    if (typeof value === 'string') {
+      const regex = /\{\{([^}]+)\}\}/g;
+      let match;
+      while ((match = regex.exec(value)) !== null) {
+        const varName = match[1].trim();
+        if (varName) {
+          varNames.push(varName);
+        }
+      }
+    }
+    return varNames;
+  };
+  
   // 遍历所有流程节点
   processNodes.forEach(processNode => {
     if (processNode.type === 'begin' || !processNode.data) {
       return;
     }
     
+    // Echo 节点特殊处理:从 value 字段提取变量引用
+    if (processNode.type === 'echo' && processNode.data.value) {
+      const varNames = extractVarsFromEchoValue(processNode.data.value);
+      varNames.forEach(varName => {
+        if (!varReferences.has(varName)) {
+          varReferences.set(varName, { asInput: false, asOutput: false });
+        }
+        varReferences.get(varName).asInput = true;
+      });
+    }
+    
+    // Append 节点特殊处理:从 value 字段提取变量引用
+    if (processNode.type === 'append' && processNode.data.value) {
+      const varNames = extractVarsFromEchoValue(processNode.data.value);
+      varNames.forEach(varName => {
+        if (!varReferences.has(varName)) {
+          varReferences.set(varName, { asInput: false, asOutput: false });
+        }
+        varReferences.get(varName).asInput = true;
+      });
+    }
+    
     // 分析输入变量引用(inVars)
     if (processNode.data.inVars && Array.isArray(processNode.data.inVars)) {
       processNode.data.inVars.forEach(value => {
@@ -301,6 +340,52 @@ export function createVariableConnections(processNodes, variableNodes, workflow,
       return;
     }
     
+    // Echo 节点特殊处理:为每个变量片段创建连接
+    if (processNode.type === 'echo' && processNode.data.value) {
+      const dataInputPorts = processNode.inputs?.filter(input => input.type === 'data' && input.segmentType === 'variable') || [];
+      
+      dataInputPorts.forEach(inputPort => {
+        const varName = inputPort.variableName;
+        if (!varName) return;
+        
+        // 从 Get 节点连接到 echo 节点的变量片段端口
+        const getNode = getNodes.get(varName);
+        if (getNode && getNode.outputs && getNode.outputs.length > 0) {
+          const varOutputPort = getNode.outputs[0];
+          dataConnections.push({
+            id: `conn_echo_var_${processNode.id}_${inputPort.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+            source: getNode.id,
+            target: processNode.id,
+            sourcePort: varOutputPort.id,
+            targetPort: inputPort.id
+          });
+        }
+      });
+    }
+    
+    // Append 节点特殊处理:为每个变量片段创建连接
+    if (processNode.type === 'append' && processNode.data.value) {
+      const dataInputPorts = processNode.inputs?.filter(input => input.type === 'data' && input.segmentType === 'variable') || [];
+      
+      dataInputPorts.forEach(inputPort => {
+        const varName = inputPort.variableName;
+        if (!varName) return;
+        
+        // 从 Get 节点连接到 append 节点的变量片段端口
+        const getNode = getNodes.get(varName);
+        if (getNode && getNode.outputs && getNode.outputs.length > 0) {
+          const varOutputPort = getNode.outputs[0];
+          dataConnections.push({
+            id: `conn_append_var_${processNode.id}_${inputPort.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+            source: getNode.id,
+            target: processNode.id,
+            sourcePort: varOutputPort.id,
+            targetPort: inputPort.id
+          });
+        }
+      });
+    }
+    
     // 处理输入变量连接(从 Get 变量节点到流程节点)
     if (processNode.data.inVars && Array.isArray(processNode.data.inVars)) {
       const inVars = processNode.data.inVars;
@@ -437,6 +522,7 @@ export function createVariableConnections(processNodes, variableNodes, workflow,
  */
 function createNodeFromAction(action, nodeId, x, y) {
   const nodeType = action.type;
+  console.log(`📦 [createNodeFromAction] 创建节点: ${nodeType}, ID: ${nodeId}, 坐标: (${x}, ${y}), action:`, action);
   
   // 对于 random 节点,如果使用旧格式,转换为新格式
   let processedAction = { ...action };
@@ -457,6 +543,7 @@ function createNodeFromAction(action, nodeId, x, y) {
   }
   
   const { inputs, outputs } = getNodePorts(nodeType, processedAction);
+  console.log(`  ✅ 端口生成完成 - inputs: ${inputs.length}, outputs: ${outputs.length}`);
   
   const node = {
     id: nodeId,
@@ -471,9 +558,10 @@ function createNodeFromAction(action, nodeId, x, y) {
   
   // 调试:检查节点坐标
   if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) {
-    console.warn('节点坐标无效:', nodeId, 'x:', x, 'y:', y);
+    console.warn('⚠️ 节点坐标无效:', nodeId, 'x:', x, 'y:', y);
   }
   
+  console.log(`  ✅ 节点创建完成:`, node);
   return node;
 }
 

+ 10 - 3
src/pages/blueprint/variable-panel/variable-panel.js

@@ -25,14 +25,18 @@ export function handleVariableChange(variables, varName, field, value, onVariabl
     const currentValue = newVariables[varName];
     if (value === 'string') {
       newVariables[varName] = typeof currentValue === 'string' ? currentValue : String(currentValue || '');
-    } else if (value === 'int') {
+    } else if (value === 'number') {
       newVariables[varName] = typeof currentValue === 'number' ? currentValue : Number(currentValue || 0);
+    } else if (value === 'bool') {
+      newVariables[varName] = typeof currentValue === 'boolean' ? currentValue : Boolean(currentValue);
     }
   } else if (field === 'value') {
     // 设置变量值
     const varType = getVariableType(newVariables[varName]);
-    if (varType === 'int') {
+    if (varType === 'number') {
       newVariables[varName] = Number(value) || 0;
+    } else if (varType === 'bool') {
+      newVariables[varName] = value === 'true' || value === true;
     } else {
       newVariables[varName] = String(value);
     }
@@ -45,8 +49,11 @@ export function handleVariableChange(variables, varName, field, value, onVariabl
  * 获取变量类型
  */
 export function getVariableType(value) {
+  if (typeof value === 'boolean') {
+    return 'bool';
+  }
   if (typeof value === 'number') {
-    return 'int';
+    return 'number';
   }
   return 'string';
 }

+ 20 - 8
src/pages/blueprint/variable-panel/variable-panel.jsx

@@ -83,7 +83,8 @@ export function BlueprintVariablePanel({ variables = {}, onVariablesChange }) {
                     onChange={(e) => handleTypeChange(varName, e.target.value)}
                   >
                     <option value="string">str</option>
-                    <option value="int">int</option>
+                    <option value="number">num</option>
+                    <option value="bool">bool</option>
                   </select>
                   <button
                     className="Variable-panel-item-delete"
@@ -93,13 +94,24 @@ export function BlueprintVariablePanel({ variables = {}, onVariablesChange }) {
                     删除
                   </button>
                 </div>
-                <input
-                  className="Variable-panel-item-value"
-                  type={varType === 'int' ? 'number' : 'text'}
-                  value={varValue === null || varValue === undefined ? '' : String(varValue)}
-                  onChange={(e) => handleValueChange(varName, e.target.value)}
-                  placeholder={varType === 'int' ? '数值' : '文本值'}
-                />
+                {varType === 'bool' ? (
+                  <label className="Variable-panel-item-checkbox">
+                    <input
+                      type="checkbox"
+                      checked={Boolean(varValue)}
+                      onChange={(e) => handleValueChange(varName, e.target.checked)}
+                    />
+                    <span>{varValue ? 'true' : 'false'}</span>
+                  </label>
+                ) : (
+                  <input
+                    className="Variable-panel-item-value"
+                    type={varType === 'number' ? 'number' : 'text'}
+                    value={varValue === null || varValue === undefined ? '' : String(varValue)}
+                    onChange={(e) => handleValueChange(varName, e.target.value)}
+                    placeholder={varType === 'number' ? '数值' : '文本值'}
+                  />
+                )}
               </div>
             );
           })

+ 19 - 7
static/processing/测试/bp.json

@@ -1,20 +1,32 @@
 {
 	"nodePositions": {
 		"node_begin": {
-			"x": -40,
+			"x": 120,
 			"y": 100
 		},
 		"node_0": {
-			"x": 220,
-			"y": 200
+			"x": 460,
+			"y": 140
+		},
+		"node_1": {
+			"x": 1060,
+			"y": 140
+		},
+		"node_2": {
+			"x": 1380,
+			"y": 160
 		},
 		"var_get_mini": {
-			"x": 40,
-			"y": 360
+			"x": 160,
+			"y": 420
 		},
 		"var_set_swipeDirection": {
-			"x": 660,
-			"y": 200
+			"x": 800,
+			"y": 140
+		},
+		"var_get_turn": {
+			"x": 1060,
+			"y": 560
 		}
 	}
 }

+ 10 - 1
static/processing/测试/processing.json

@@ -1,7 +1,8 @@
 {
 	"variables": {
 		"mini": 1,
-		"swipeDirection": ""
+		"swipeDirection": "",
+		"turn":1
 	},
 	"execute": [
 		{
@@ -13,6 +14,14 @@
 			"outVars": [
 				"{swipeDirection}"
 			]
+		},
+		{
+			"type": "echo",
+			"value": "hello"
+		},
+		{
+			"type": "echo",
+			"value": "第{{turn}}轮"
 		}
 	]
 }