Przeglądaj źródła

小红书浏览 工作流

yichael 4 miesięcy temu
rodzic
commit
387051efc9

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

@@ -9,8 +9,6 @@
 }
 ```
 
-> 注意:为了向后兼容,`excuse` 字段名仍然支持,但建议使用 `execute`。
-
 ## 语法分层
 
 - **基础语法**:`schedule`、`if`、`while`

+ 2 - 0
main.js

@@ -9,6 +9,7 @@ import { registerIpcHandlers as registerScreenshotHandlers } from './main-js/adb
 import { registerIpcHandlers as registerTouchEventHandlers } from './main-js/adb/touch-event.js';
 import { registerIpcHandlers as registerInputHandlers } from './main-js/adb/input.js';
 import { registerIpcHandlers as registerScrollHandlers } from './main-js/adb/scroll.js';
+import { registerIpcHandlers as registerSystemHandlers } from './main-js/adb/system.js';
 import { registerIpcHandlers as registerReadWriteHandlers } from './main-js/read-and-write.js';
 import { registerIpcHandlers as registerExecutePyHandlers } from './main-js/execute-py.js';
 import { registerIpcHandlers as registerWorkflowHandlers } from './main-js/workflow.js';
@@ -31,6 +32,7 @@ app.whenReady().then(() => {
   registerTouchEventHandlers();
   registerInputHandlers();
   registerScrollHandlers();
+  registerSystemHandlers();
   registerReadWriteHandlers();
   registerExecutePyHandlers();
   registerWorkflowHandlers();

+ 4 - 0
preload.cjs

@@ -13,6 +13,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
   sendText: (ipPort, text) => ipcRenderer.invoke('send-text', ipPort, text),
   sendScroll: (ipPort, direction, width, height, scrollDistance, duration) => ipcRenderer.invoke('send-scroll', ipPort, direction, width, height, scrollDistance, duration),
   sendKeyEvent: (ipPort, keyCode) => ipcRenderer.invoke('send-key-event', ipPort, keyCode),
+  sendSystemKey: (ipPort, keyCode) => ipcRenderer.invoke('send-system-key', ipPort, keyCode),
+  sendBackKey: (ipPort) => ipcRenderer.invoke('send-back-key', ipPort),
+  sendHomeKey: (ipPort) => ipcRenderer.invoke('send-home-key', ipPort),
+  sendRecentAppsKey: (ipPort) => ipcRenderer.invoke('send-recent-apps-key', ipPort),
   // 监听设备发现事件
   onDeviceFound: (callback) => {
     ipcRenderer.on('device-found', (event, device) => callback(device));

+ 6 - 6
src/pages/Chat/Input/Input.js

@@ -312,10 +312,10 @@ export function InputLogic(onSendMessage, onLoadingChange) {
         
         // 然后显示需求信息
         if (needsImages > 0 || needsTexts > 0) {
-          requirementMessage += '📋 根据您的需求,我需要以下信息:\n\n';
+          requirementMessage += '需要准备以下素材:\n\n';
           
           if (needsImages > 0) {
-            requirementMessage += `🖼️ 需要 ${needsImages} 张图片:\n`;
+            requirementMessage += `需要 ${needsImages} 张图片:\n`;
             (requirements.imageDescriptions || []).forEach((desc, index) => {
               requirementMessage += `${index + 1}. ${desc}\n`;
             });
@@ -323,7 +323,7 @@ export function InputLogic(onSendMessage, onLoadingChange) {
           }
           
           if (needsTexts > 0) {
-            requirementMessage += `📝 需要 ${needsTexts} 个文字参考:\n`;
+            requirementMessage += `需要 ${needsTexts} 个文字参考:\n`;
             (requirements.textDescriptions || []).forEach((desc, index) => {
               requirementMessage += `${index + 1}. ${desc}\n`;
             });
@@ -351,12 +351,12 @@ export function InputLogic(onSendMessage, onLoadingChange) {
 
       if (providedImages < needsImages || providedTexts < needsTexts) {
         // 信息不完整,提示用户
-        let missingMessage = '信息不完整,请提供:\n\n';
+        let missingMessage = '信息不完整,请提供:\n\n';
         if (providedImages < needsImages) {
-          missingMessage += `🖼️ 还需要 ${needsImages - providedImages} 张图片\n`;
+          missingMessage += `还需要 ${needsImages - providedImages} 张图片\n`;
         }
         if (providedTexts < needsTexts) {
-          missingMessage += `📝 还需要 ${needsTexts - providedTexts} 个文字参考\n`;
+          missingMessage += `还需要 ${needsTexts - providedTexts} 个文字参考\n`;
         }
         missingMessage += '\n请按顺序上传图片和输入文字参考,然后再次发送消息。';
         

+ 78 - 157
src/pages/Chat/Input/generate-processing.js

@@ -1,5 +1,7 @@
 // 第二阶段提示词:生成工作流
-const SYSTEM_PROMPT = `你是一个友好的自动化工作流生成助手。用户已经提供了所需的图片和文字参考,现在你需要生成JSON格式的工作流配置文件。
+const SYSTEM_PROMPT = `你是一个通用的手机应用自动化工作流生成助手。用户已经提供了所需的图片和文字参考,现在你需要生成JSON格式的工作流配置文件。
+
+这是一个通用的自动化工具,可以自动化操作任意手机应用(如微信、小红书、抖音、游戏、购物应用等),不仅仅是聊天应用。
 
 请用自然、友好的语言与用户交流,保持对话的连贯性。在生成工作流时,请先简短地确认一下,然后生成JSON格式的工作流配置。
 
@@ -13,136 +15,87 @@ const SYSTEM_PROMPT = `你是一个友好的自动化工作流生成助手。用
   ]
 }
 
-基础语法
+所有可用标签
 
-1. 定时执行(schedule):
-{
-  "type": "schedule",
-  "condition": {
-    "interval": "1s",
-    "repeat": -1
-  },
-  "interval": [
-    // 要执行的操作数组
-  ]
-}
-- interval: 执行间隔("1s", "2m", "3h")
-- repeat: 重复次数(-1 表示无限循环)
+【基础语法】
+- schedule: 定时执行,condition.interval为间隔,condition.repeat为重复次数(-1无限循环)
+  例:{"type": "schedule", "condition": {"interval": "1s", "repeat": -1}, "interval": []}
 
-2. ADB操作(adb):
-统一格式,通过 method 区分:
-{
-  "type": "adb",
-  "method": "input|click|locate|swipe|scroll|press|string-press",
-  "inVars": [],
-  "outVars": []
-}
+- if: 条件判断,condition为条件表达式,ture/false为分支操作数组
+  例:{"type": "if", "condition": "{count} > 0", "ture": [], "false": []}
 
-| method | 说明 | inVars |
-|--------|------|--------|
-| input | 输入文本 | [0]: 文本内容 |
-| click | 点击 | [0]: 位置坐标 {x, y} |
-| locate | 定位 | [0]: 图片/文字,outVars[0]: 保存位置 |
-| swipe | 滑动 | [0]: 方向(up-down/down-up/left-right/right-left) |
-| scroll | 滚动 | [0]: 方向(up/down) |
-| press | 图像匹配并点击 | [0]: 图片路径 |
-| string-press | 文字识别并点击 | [0]: 文字内容 |
-
-3. 条件判断(if):
-{
-  "type": "if",
-  "condition": "{变量} == '值'",
-  "ture": [],
-  "false": []
-}
-- 支持操作符:== != > < >= <=
-- 注意:使用 ture 而不是 then
+- while: 循环执行,condition为循环条件,ture为循环体
+  例:{"type": "while", "condition": "1 == 1", "ture": []}
 
-4. 循环(while):
-{
-  "type": "while",
-  "condition": "{变量} > 0",
-  "ture": []
-}
-- 注意:使用 ture 作为 body 的别名
+【ADB操作】
+- adb input: 输入文本,inVars[0]为文本内容
+  例:{"type": "adb", "method": "input", "inVars": ["Hello"], "outVars": []}
 
-5. 内置操作:
+- adb click: 点击坐标,inVars[0]为位置("x,y"或"{\"x\":123,\"y\":456}")
+  例:{"type": "adb", "method": "click", "inVars": ["540,1200"], "outVars": []}
 
-延迟(delay):
-{ "type": "delay", "value": "2s" }
+- adb locate: 定位图片或文字,inVars[0]为图片路径/文字,outVars[0]保存位置
+  例:{"type": "adb", "method": "locate", "inVars": ["按钮.png"], "outVars": ["{pos}"]}
 
-设置变量(set):
-{ "type": "set", "variable": "name", "value": "value" }
+- adb swipe: 滑动屏幕,inVars[0]为方向(up-down/down-up/left-right/right-left)
+  例:{"type": "adb", "method": "swipe", "inVars": ["up-down"], "outVars": []}
 
-随机数(random):
-{ "type": "random", "variable": "num", "min": 1, "max": 100, "integer": true }
+- adb scroll: 滚动屏幕,inVars[0]为方向(up/down)
+  例:{"type": "adb", "method": "scroll", "inVars": ["up"], "outVars": []}
 
-6. 扩展标签(Func):
-由 src/pages/processing/func/ 目录下的脚本文件决定,常用标签:
+- adb press: 图像匹配并点击,inVars[0]为图片路径
+  例:{"type": "adb", "method": "press", "inVars": ["按钮.png"], "outVars": []}
 
-- ocr-chat: OCR识别对话内容
-  - inVars: [好友RGB颜色, 我的RGB颜色, 区域坐标]
-    - RGB格式:"(r,g,b)",如 "(242,242,242)" 表示好友对话框颜色
-    - 区域坐标:JSON字符串格式,如 "{topLeft: {x, y}, bottomRight: {x, y}}"
-  - outVars: [聊天记录变量(JSON字符串格式)]
-  
-- read-last-message: 读取最后一条消息
-  - inVars: [聊天记录变量(可选)]
-  - outVars: [消息文本变量, 发送者变量]
-  
-- smart-chat-append: 智能合并历史聊天记录和当前聊天记录
-  - inVars: [历史记录, 当前记录]
-  - outVars: [合并后的记录]
-  
-- read-txt: 读取文本文件内容
-  - inVars: [文件路径](相对于工作流目录,如 "history/chat-history.txt")
-  - outVars: [文件内容变量]
-  
-- save-txt: 保存字符串为文本文件
-  - inVars: [内容, 文件路径](路径相对于工作流目录)
-  - outVars: []
-  
-- image-center-location: 图像中心点定位
-  - inVars: [模板图片路径](相对于工作流目录的 resources 文件夹)
-  - outVars: [位置坐标变量 {x, y}]
-  
-- image-region-location: 图像区域定位(在完整截图中查找区域图片的位置)
-  - inVars: [截图路径, 区域图片路径](都相对于工作流目录的 resources 文件夹,如 "ScreenShot.jpg" 和 "ChatArea.png")
-  - outVars: [区域坐标变量](返回四个顶点坐标)
-  - 注意:此标签不主动截图,使用 resources/ 文件夹下已有的截图文件进行匹配
-  
-- image-area-cropping: 图像区域裁剪(从当前屏幕截图裁剪指定区域)
-  - inVars: [区域坐标, 保存路径]
-    - 区域坐标:JSON字符串格式,如 "{topLeft: {x, y}, bottomRight: {x, y}}"
-    - 保存路径:相对于工作流目录的 history 文件夹,如 "history/chat-area-cropped.png"
-  - outVars: []
-  - 功能:从主进程缓存获取最新截图,保存到 history/ScreenShot.jpg,然后根据区域坐标裁剪并保存
-  
-- ai-generate: AI生成内容
-  - inVars: [提示词中的变量]
-  - outVars: [结果变量]
-
-重要规则:
-- 使用 execute 作为顶级数组字段(不再使用 actions 或 triggers)
-- 变量使用 {variableName} 格式引用
-- 图片路径直接写文件名(不需要 resources/ 前缀,代码会自动处理)
-- 所有操作都支持 inVars 和 outVars(可以为空数组)
-- if 和 while 使用 ture 而不是 then 或 body
-- 图片文件名按顺序命名为"1.png"、"2.png"等
-- 使用 echo 而不是 log 来打印信息(log 已废弃)
-- image-region-location 使用 resources/ 文件夹下的截图文件进行匹配,不主动截图
-- image-area-cropping 从缓存获取截图并保存到 history/ 文件夹,然后裁剪
-
-返回格式:
-1. 如果用户已经提供了所有需要的图片和文字参考,且明确要求生成工作流:
-   - 先用简短的自然语言确认(如"好的,我现在为您生成工作流配置...")
-   - 然后单独一行返回JSON格式的工作流配置(可以用代码块包裹)
-   
-2. 如果用户只是在询问问题或者还没准备好生成工作流:
-   - 用自然语言友好地回答用户的问题
-   - 不需要返回JSON
-
-请保持对话的自然和友好,不要生硬地只返回JSON。`;
+- adb string-press: 文字识别并点击,inVars[0]为文字内容
+  例:{"type": "adb", "method": "string-press", "inVars": ["登录"], "outVars": []}
+- adb keyevent: 发送系统按键,inVars[0]为按键代码("4"返回键,"3"Home键,"187"最近任务键)
+  例:{"type": "adb", "method": "keyevent", "inVars": ["4"], "outVars": []}
+
+【内置操作】
+- delay: 延迟执行,value为延迟时间("2s"/"1m")
+  例:{"type": "delay", "value": "2s"}
+
+- set: 设置变量,variable为变量名,value为变量值
+  例:{"type": "set", "variable": "{count}", "value": "10"}
+
+- echo: 打印信息,value为文本(支持{{变量}}),或inVars为变量数组
+  例:{"type": "echo", "value": "当前计数: {{count}}"}
+
+- random: 生成随机数,variable为变量名,min/max为范围,integer为是否整数
+  例:{"type": "random", "variable": "{num}", "min": 1, "max": 100, "integer": true}
+
+【扩展标签】
+- image-center-location: 图像中心点定位,inVars[0]为模板图片路径,outVars[0]保存坐标
+  例:{"type": "image-center-location", "inVars": ["按钮.png"], "outVars": ["{pos}"]}
+
+- image-region-location: 图像区域定位,inVars[0]为截图路径,inVars[1]为区域图片路径,outVars[0]保存区域坐标
+  例:{"type": "image-region-location", "inVars": ["ScreenShot.jpg", "ChatArea.png"], "outVars": ["{area}"]}
+
+- image-area-cropping: 图像区域裁剪,inVars[0]为区域坐标JSON,inVars[1]为保存路径
+  例:{"type": "image-area-cropping", "inVars": ["{\"topLeft\":{\"x\":100,\"y\":200},\"bottomRight\":{\"x\":500,\"y\":800}}", "history/cropped.png"], "outVars": []}
+
+- read-txt: 读取文本文件,inVars[0]为文件路径,outVars[0]保存内容
+  例:{"type": "read-txt", "inVars": ["history/data.txt"], "outVars": ["{content}"]}
+
+- save-txt: 保存文本文件,inVars[0]为内容,inVars[1]为文件路径
+  例:{"type": "save-txt", "inVars": ["Hello", "history/log.txt"], "outVars": []}
+
+- string-reg-location: 文字区域定位,inVars[0]为文字内容,outVars[0]保存坐标
+  例:{"type": "string-reg-location", "inVars": ["登录"], "outVars": ["{pos}"]}
+
+- ai-generate: AI生成内容,prompt为提示词(支持{变量}),inVars为变量数组,outVars[0]保存结果
+  例:{"type": "ai-generate", "prompt": "回复: {message}", "inVars": ["{message}"], "outVars": ["{reply}"]}
+
+- ocr-chat: OCR识别对话内容,inVars[0]为好友RGB颜色,inVars[1]为我的RGB颜色,inVars[2]为区域坐标,outVars[0]保存聊天记录
+  例:{"type": "ocr-chat", "inVars": ["(242,242,242)", "(255,255,255)", "{\"topLeft\":{\"x\":0,\"y\":0},\"bottomRight\":{\"x\":1080,\"y\":2400}}"], "outVars": ["{chat}"]}
+
+- read-last-message: 读取最后一条消息,inVars[0]为聊天记录(可选),outVars[0]保存消息文本,outVars[1]保存发送者
+  例:{"type": "read-last-message", "inVars": ["{chat}"], "outVars": ["{text}", "{role}"]}
+
+- smart-chat-append: 智能合并聊天记录,inVars[0]为历史记录,inVars[1]为当前记录,outVars[0]保存合并结果
+  例:{"type": "smart-chat-append", "inVars": ["{history}", "{current}"], "outVars": ["{merged}"]}
+
+请保持对话的自然和友好,生成工作流时先简短确认,然后返回JSON格式的工作流配置。`;
 
 /**
  * 调用 AI 生成工作流配置
@@ -222,14 +175,7 @@ export function extractWorkflowJsonFromText(text) {
     if (parsed && typeof parsed === 'object' && Array.isArray(parsed.execute)) {
       return parsed;
     }
-    // 向后兼容:包含 actions 字段
-    if (parsed && typeof parsed === 'object' && Array.isArray(parsed.actions)) {
-      return parsed;
-    }
-    // 向后兼容:直接是数组
-    if (Array.isArray(parsed) && parsed.length > 0) {
-      return { execute: parsed };
-    }
+    // 如果不是包含 execute 字段的对象,返回 null
   } catch (e) {
     // 不是纯JSON,继续尝试提取
   }
@@ -240,25 +186,17 @@ export function extractWorkflowJsonFromText(text) {
     try {
       const parsed = JSON.parse(codeBlockMatch[1]);
       if (parsed && typeof parsed === 'object') {
-        // 新格式:包含 execute
+        // 包含 execute 字段
         if (Array.isArray(parsed.execute)) {
           return parsed;
         }
-        // 向后兼容:包含 actions
-        if (Array.isArray(parsed.actions)) {
-          return parsed;
-        }
-        // 向后兼容:直接是数组
-        if (Array.isArray(parsed)) {
-          return { execute: parsed };
-        }
       }
     } catch (e) {
       // 解析失败
     }
   }
   
-  // 尝试查找JSON对象(优先查找包含"execute"的对象,其次查找"actions")
+  // 尝试查找包含"execute"的JSON对象
   const executeMatch = text.match(/\{[\s\S]*?"execute"[\s\S]*?\}/);
   if (executeMatch) {
     try {
@@ -266,19 +204,6 @@ export function extractWorkflowJsonFromText(text) {
       if (parsed && typeof parsed === 'object' && Array.isArray(parsed.execute)) {
         return parsed;
       }
-    } catch (e) {
-      // 解析失败,尝试更宽松的匹配
-    }
-  }
-  
-  // 向后兼容:查找包含"actions"的对象
-  const actionsMatch = text.match(/\{[\s\S]*?"actions"[\s\S]*?\}/);
-  if (actionsMatch) {
-    try {
-      const parsed = JSON.parse(actionsMatch[0]);
-      if (parsed && typeof parsed === 'object' && Array.isArray(parsed.actions)) {
-        return parsed;
-      }
     } catch (e) {
       // 解析失败
     }
@@ -411,14 +336,10 @@ export function updateImagePathsInWorkflow(workflowJson, imageNameMap) {
     }
   }
   
-  // 更新 execute 数组(新格式)
+  // 更新 execute 数组
   if (updated.execute && Array.isArray(updated.execute)) {
     updateActions(updated.execute);
   }
-  // 向后兼容:actions 数组
-  if (updated.actions && Array.isArray(updated.actions)) {
-    updateActions(updated.actions);
-  }
   
   return updated;
 }

+ 44 - 81
src/pages/Chat/Input/resource-require.js

@@ -1,85 +1,48 @@
 // 第一阶段提示词:分析需求,返回需要的信息
-const REQUIREMENTS_PROMPT = `你是一个友好的自动化工作流生成助手。你需要像聊天一样与用户对话。
-
-如果用户想要创建自动化工作流,你需要分析需求并返回需要的信息。请用自然、友好的语言与用户交流,保持对话的连贯性和友好性。
-
-工作流语法分层(重要):
-- 基础语法(控制流/定时):schedule、if、while
-- 基础 action:adb(通过 method 区分:input、click、locate、swipe、scroll、press、string-press)
-- 扩展标签(Func):由 src/pages/processing/func/ 目录下的脚本文件决定,每个脚本文件名即为标签名
-  - 常用标签:ocr-chat、read-last-message、smart-chat-append、read-txt、save-txt、image-center-location、image-region-location、image-area-cropping、ai-generate
-
-工作流基本结构:
-{
-  "variables": {},
-  "execute": []
-}
-
-在生成工作流时:
-- 逻辑结构使用 schedule、if、while 表达
-- 手机操作统一使用 adb 类型,通过 method 区分
-- 当需求超出基础 action,使用对应的 Func 标签(如:ocr-chat、image-center-location)
-
-重要规则:
-- 变量使用 {variableName} 格式引用
-- 图片路径直接写文件名(不需要 resources/ 前缀,代码会自动处理)
-- 如果工作流中需要输入文字(使用 adb 的 input 方法或 ai-generate 操作),且这些文字需要根据场景背景和用户角色来生成,你必须询问用户以下信息:
-  - 场景的背景:这个工作流在什么场景下使用?(例如:微信聊天、客服回复、评论互动等)
-  - 用户的角色:用户在这个场景中扮演什么角色?(例如:刚认识女方的男生、客服人员、产品推广者等)
-  - 这些信息对于生成合适的文字内容非常重要,请务必在询问需求时主动询问
-
-请分析用户的需求,根据上述操作类型,判断需要哪些图片和文字参考:
-- 图片(needsImages):用于 adb 的 locate/click 方法的 image 方式,或 ocr-chat 的头像
-- 文字参考(needsTexts):用于 adb 的 locate/click 方法的 text 方式
-
-如果用户想要创建自动化工作流:
-1. 先友好地确认理解用户的需求
-2. 然后返回JSON格式的需求信息(不包含在自然语言回复中,仅作为JSON返回)
-
-需求分析的JSON格式:
-{
-  "needsImages": 2,  // 需要多少张图片(用于图像匹配定位)
-  "needsTexts": 1,   // 需要多少个文字参考(用于文字识别定位)
-  "imageDescriptions": ["登录按钮", "用户名输入框"],  // 每张图片的描述
-  "textDescriptions": ["登录按钮文字"]  // 每个文字参考的描述
-}
-
-返回格式:先自然语言回复,然后单独一行JSON。
-
-**特别提醒**:如果工作流涉及输入文字(如自动回复、自动评论、自动发送消息等),请务必询问用户场景背景和角色信息。
-
-例如(不涉及文字输入的场景):
-我理解了,您想创建一个登录流程的自动化工作流。为了生成准确的工作流配置,我需要您提供以下信息:
-
-需要的信息:
-需要 2 张图片:登录按钮截图、用户名输入框截图
-需要 1 个文字参考:登录按钮上的文字
-
-{
-  "needsImages": 2,
-  "needsTexts": 1,
-  "imageDescriptions": ["登录按钮", "用户名输入框"],
-  "textDescriptions": ["登录按钮文字"]
-}
-
-例如(涉及文字输入的场景):
-我理解了,您想创建一个自动聊天回复的自动化工作流。为了生成准确且符合场景的文字内容,我需要了解以下信息:
-
-需要的信息:
-需要 2 张图片:好友头像截图、我的头像截图
-需要 1 个文字参考:输入框位置
-**场景信息**(重要):
-   - 场景背景:这个聊天是在什么场景下进行的?(例如:微信聊天、QQ群聊、客服对话等)
-   - 您的角色:您在这个场景中扮演什么角色?(例如:刚认识女方的男生、客服人员、产品推广者等)
-
-{
-  "needsImages": 2,
-  "needsTexts": 1,
-  "imageDescriptions": ["好友头像", "我的头像"],
-  "textDescriptions": ["输入框位置"]
-}
-
-如果用户的话题与工作流无关,请友好地自然回复,不需要返回JSON。如果用户是在询问如何使用、或者其他一般性问题,请用自然语言友好地回答。`;
+const REQUIREMENTS_PROMPT = `分析用户需求,只返回需要准备的素材信息,言简意赅。
+
+这是一个通用的手机应用自动化工作流生成工具,可以自动化操作任意手机应用(如微信、小红书、抖音、游戏等)。
+
+我们有一套自己做的语言包含以下功能:
+
+【基础语法】
+- schedule: 定时执行,condition.interval为间隔,condition.repeat为重复次数(-1无限循环)
+- if: 条件判断,condition为条件表达式,ture/false为分支操作数组
+- while: 循环执行,condition为循环条件,ture为循环体
+
+【ADB操作】
+- adb input: 输入文本,inVars[0]为文本内容
+- adb click: 点击坐标,inVars[0]为位置("x,y"或"{\"x\":123,\"y\":456}")
+- adb locate: 定位图片或文字,inVars[0]为图片路径/文字,outVars[0]保存位置
+- adb swipe: 滑动屏幕,inVars[0]为方向(up-down/down-up/left-right/right-left)
+- adb scroll: 滚动屏幕,inVars[0]为方向(up/down)
+- adb press: 图像匹配并点击,inVars[0]为图片路径
+- adb string-press: 文字识别并点击,inVars[0]为文字内容
+- adb keyevent: 发送系统按键,inVars[0]为按键代码("4"返回键,"3"Home键,"187"最近任务键)
+
+【内置操作】
+- delay: 延迟执行,value为延迟时间("2s"/"1m")
+- set: 设置变量,variable为变量名,value为变量值
+- echo: 打印信息,value为文本(支持{{变量}}),或inVars为变量数组
+- random: 生成随机数,variable为变量名,min/max为范围,integer为是否整数
+
+【扩展标签】
+- image-center-location: 图像中心点定位,inVars[0]为模板图片路径,outVars[0]保存坐标
+- image-region-location: 图像区域定位,inVars[0]为截图路径,inVars[1]为区域图片路径,outVars[0]保存区域坐标
+- image-area-cropping: 图像区域裁剪,inVars[0]为区域坐标JSON,inVars[1]为保存路径
+- read-txt: 读取文本文件,inVars[0]为文件路径,outVars[0]保存内容
+- save-txt: 保存文本文件,inVars[0]为内容,inVars[1]为文件路径
+- string-reg-location: 文字区域定位,inVars[0]为文字内容,outVars[0]保存坐标
+- ai-generate: 请求AI生成内容,prompt为提示词(支持{变量}),inVars为变量数组,outVars[0]保存结果
+- ocr-chat: OCR识别对话内容,inVars[0]为好友RGB颜色,inVars[1]为我的RGB颜色,inVars[2]为区域坐标,outVars[0]保存聊天记录
+- read-last-message: 读取最后一条消息,inVars[0]为聊天记录(可选),outVars[0]保存消息文本,outVars[1]保存发送者
+- smart-chat-append: 智能合并聊天记录,inVars[0]为历史记录,inVars[1]为当前记录,outVars[0]保存合并结果
+
+分析需求,判断需要哪些素材:
+- 图片(needsImages):用于图像匹配定位的截图(如按钮、图标、界面元素等)
+- 文字参考(needsTexts):用于文字识别定位的文字内容
+
+如果用户话题与工作流无关,返回:{"error": "请描述工作流需求"}。`;
 
 /**
  * 分析用户需求,返回需要准备的素材信息

+ 7 - 15
src/pages/Processing/Processing.js

@@ -1,5 +1,5 @@
 import { useState, useEffect, useRef } from 'react';
-import { parseWorkflow, parseActions, executeActionSequence } from './action-parser.js';
+import { parseWorkflow, parseActions, executeActionSequence } from './ef-compiler.js';
 
 export function useHistory() {
   const [folders, setFolders] = useState([]);
@@ -164,24 +164,16 @@ export function useHistory() {
         return;
       }
 
-      // 解析工作流(支持新旧格式)
+      // 解析工作流
+      const workflow = parseWorkflow(processingData);
       let actions = [];
-      if (processingData.actions) {
-        // 新格式:尝试解析为工作流
-        const workflow = parseWorkflow(processingData);
-        if (workflow && workflow.actions) {
-          actions = workflow.actions;
-        } else {
-          // 旧格式:直接解析actions数组
-          actions = parseActions(processingData.actions);
-        }
-      } else if (Array.isArray(processingData)) {
-        // 向后兼容:如果直接是数组
-        actions = parseActions(processingData);
+      
+      if (workflow && workflow.actions) {
+        actions = workflow.actions;
       }
       
       if (actions.length === 0) {
-        console.error('没有有效的操作');
+        console.error('没有有效的操作,请确保工作流包含 execute 字段');
         setPlayingIndex(null);
         playingIndexRef.current = null;
         return;

+ 0 - 3287
src/pages/Processing/action-parser.js

@@ -1,3287 +0,0 @@
-// 工作流任务解析和执行器
-// 配置参数
-const DEFAULT_STEP_INTERVAL = 1000; // 默认步骤间隔1秒
-const DEFAULT_SCROLL_DISTANCE = 100; // 默认每次滚动距离(像素)
-
-// 变量上下文(用于存储变量值)
-let variableContext = {};
-
-// 变量上下文是否已初始化(防止循环中重置变量)
-let variableContextInitialized = false;
-
-// 全局步骤计数器(用于连续的步骤编号)
-let globalStepCounter = 0;
-
-// 当前工作流文件夹路径(用于日志记录)
-let currentWorkflowFolderPath = null;
-
-/**
- * 记录日志(同时输出到 console 和文件)
- * @param {string} message - 日志消息
- * @param {string} folderPath - 工作流文件夹路径(可选,如果不提供则使用 currentWorkflowFolderPath)
- */
-async function logMessage(message, folderPath = null) {
-  // 尝试写入日志文件(异步,不阻塞主流程)
-  // 注意:不输出到 console,只写入日志文件
-  try {
-    const targetFolderPath = folderPath || currentWorkflowFolderPath;
-    if (targetFolderPath && window.electronAPI && window.electronAPI.appendLog) {
-      // 异步写入,不等待完成
-      window.electronAPI.appendLog(targetFolderPath, message).catch(err => {
-        // 静默失败,不影响主流程
-      });
-    }
-  } catch (error) {
-    // 静默失败,不影响主流程
-  }
-}
-
-// 打印所有 outVars 的值
-async function logOutVars(action, variableContext, folderPath = null) {
-  if (!action.outVars || !Array.isArray(action.outVars) || action.outVars.length === 0) {
-    return;
-  }
-  
-  const outVarsInfo = action.outVars.map((varName, index) => {
-    const varNameClean = extractVarName(varName);
-    const value = variableContext[varNameClean];
-    // 如果值太长,截断显示(超过100字符)
-    let displayValue = value;
-    if (typeof value === 'string' && value.length > 100) {
-      displayValue = value.substring(0, 100) + '...';
-    }
-    return `${varNameClean}: ${JSON.stringify(displayValue)}`;
-  });
-  
-  const logMsg = `输出变量: ${outVarsInfo.join(', ')}`;
-  await logMessage(logMsg, folderPath);
-}
-
-// 导入聊天历史记录管理模块
-import { generateHistorySummary, getHistorySummary } from './func/chat/chat-history.js';
-// 导入 ocr-chat 执行函数
-import { executeOcrChat } from './func/chat/ocr-chat.js';
-// 导入 image-region-location 执行函数
-import { executeImageRegionLocation } from './func/image-region-location.js';
-// 导入 image-center-location 执行函数
-import { executeImageCenterLocation } from './func/image-center-location.js';
-// 导入 image-area-cropping 执行函数
-import { executeImageAreaCropping } from './func/image-area-cropping.js';
-// 导入 read-last-message 执行函数
-import { executeReadLastMessage } from './func/chat/read-last-message.js';
-// 导入 read-txt 执行函数
-import { executeReadTxt } from './func/read-txt.js';
-// 导入 save-txt 执行函数
-import { executeSaveTxt } from './func/save-txt.js';
-// 导入 smart-chat-append 执行函数
-import { executeSmartChatAppend } from './func/chat/smart-chat-append.js';
-
-/**
- * 解析时间字符串(格式:2026/1/13 02:09)
- * @param {string} timeStr - 时间字符串
- * @returns {Date|null} 解析后的日期对象,失败返回null
- */
-function parseTimeString(timeStr) {
-  if (!timeStr || timeStr.trim() === '') {
-    return null;
-  }
-  
-  try {
-    // 支持格式:2026/1/13 02:09 或 2026/01/13 02:09
-    const parts = timeStr.trim().split(' ');
-    if (parts.length !== 2) {
-      return null;
-    }
-    
-    const datePart = parts[0].split('/');
-    const timePart = parts[1].split(':');
-    
-    if (datePart.length !== 3 || timePart.length !== 2) {
-      return null;
-    }
-    
-    const year = parseInt(datePart[0], 10);
-    const month = parseInt(datePart[1], 10) - 1; // 月份从0开始
-    const day = parseInt(datePart[2], 10);
-    const hour = parseInt(timePart[0], 10);
-    const minute = parseInt(timePart[1], 10);
-    
-    const date = new Date(year, month, day, hour, minute, 0, 0);
-    
-    // 验证日期是否有效
-    if (isNaN(date.getTime())) {
-      return null;
-    }
-    
-    return date;
-  } catch (error) {
-    return null;
-  }
-}
-
-/**
- * 解析延迟字符串(格式:10s, 5m, 2h)
- * @param {string} delayStr - 延迟字符串
- * @returns {number|null} 延迟的毫秒数,失败返回null
- */
-function parseDelayString(delayStr) {
-  if (!delayStr || delayStr.trim() === '') {
-    return 0; // 空字符串表示不延迟
-  }
-  
-  try {
-    const trimmed = delayStr.trim();
-    const unit = trimmed.slice(-1).toLowerCase();
-    const value = parseInt(trimmed.slice(0, -1), 10);
-    
-    if (isNaN(value) || value < 0) {
-      return null;
-    }
-    
-    switch (unit) {
-      case 's':
-        return value * 1000; // 秒转毫秒
-      case 'm':
-        return value * 60 * 1000; // 分钟转毫秒
-      case 'h':
-        return value * 60 * 60 * 1000; // 小时转毫秒
-      default:
-        return null;
-    }
-  } catch (error) {
-    return null;
-  }
-}
-
-/**
- * 计算需要等待的时间(毫秒)
- * @param {string} data - 执行时间字符串(格式:2026/1/13 02:09)
- * @param {string} delay - 延迟字符串(格式:10s, 5m, 2h)
- * @returns {number} 需要等待的毫秒数
- */
-function calculateWaitTime(data, delay) {
-  // 始终返回 0,不等待,立即执行
-  return 0;
-}
-
-/**
- * 从变量名中提取变量名(去除 {variable} 格式的包裹)
- * @param {string} varName - 变量名(可能包含 {})
- * @returns {string} 提取后的变量名
- */
-function extractVarName(varName) {
-  if (typeof varName === 'string' && varName.startsWith('{') && varName.endsWith('}')) {
-    return varName.slice(1, -1);
-  }
-  return varName;
-}
-
-/**
- * 替换字符串中的变量(只支持 {{variable}} 格式,用于字符串拼接)
- * @param {string} str - 原始字符串
- * @param {Object} context - 变量上下文
- * @returns {string} 替换后的字符串
- */
-function replaceVariablesInString(str, context = variableContext) {
-  if (typeof str !== 'string') {
-    return str;
-  }
-  
-  let result = str;
-  
-  // 只替换 {{variable}} 格式的变量(双花括号,用于字符串拼接)
-  // 支持变量名中包含连字符,如 {{chat-history}}
-  const doubleBracePattern = /\{\{([\w-]+)\}\}/g;
-  result = result.replace(doubleBracePattern, (match, varName) => {
-    const varValue = context[varName];
-    if (varValue === undefined || varValue === null) {
-      return '';
-    }
-    // 如果值是空字符串,返回空字符串
-    if (varValue === '') {
-      return '';
-    }
-    // 如果值是字符串 "undefined" 或 "null",视为空
-    if (varValue === 'undefined' || varValue === 'null') {
-      return '';
-    }
-    // 如果是字符串,尝试判断是否是 JSON 数组字符串
-    if (typeof varValue === 'string') {
-      try {
-        const parsed = JSON.parse(varValue);
-        if (Array.isArray(parsed)) {
-          // 如果是空数组,返回空字符串
-          if (parsed.length === 0) {
-            return '';
-          }
-          // 如果不是空数组,返回原始 JSON 字符串
-          return varValue;
-        }
-      } catch (e) {
-        // 不是 JSON,按普通字符串处理
-      }
-    }
-    // 如果是数组或对象,转换为 JSON 字符串
-    if (Array.isArray(varValue) || typeof varValue === 'object') {
-      try {
-        return JSON.stringify(varValue);
-      } catch (e) {
-        return String(varValue);
-      }
-    }
-    return String(varValue);
-  });
-  
-  return result;
-}
-
-/**
- * 解析变量值(支持 {variable} 格式)
- * @param {any} value - 原始值
- * @param {Object} context - 变量上下文
- * @returns {any} 解析后的值
- */
-function resolveValue(value, context = variableContext) {
-  if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
-    const varName = value.slice(1, -1);
-    return context[varName] !== undefined ? context[varName] : value;
-  }
-  if (Array.isArray(value)) {
-    return value.map(item => resolveValue(item, context));
-  }
-  if (typeof value === 'object' && value !== null) {
-    const resolved = {};
-    for (const key in value) {
-      resolved[key] = resolveValue(value[key], context);
-    }
-    return resolved;
-  }
-  return value;
-}
-
-/**
- * 手动解析并计算算术表达式(不使用 eval 或 Function)
- * 支持 +, -, *, / 和括号
- * @param {string} expr - 表达式字符串,如 "1+2*3"
- * @returns {number} 计算结果
- */
-function parseArithmeticExpression(expr) {
-  let index = 0;
-  
-  // 跳过空格
-  const skipWhitespace = () => {
-    while (index < expr.length && /\s/.test(expr[index])) {
-      index++;
-    }
-  };
-  
-  // 解析数字
-  const parseNumber = () => {
-    skipWhitespace();
-    let numStr = '';
-    let hasDot = false;
-    
-    while (index < expr.length) {
-      const char = expr[index];
-      if (char >= '0' && char <= '9') {
-        numStr += char;
-        index++;
-      } else if (char === '.' && !hasDot) {
-        numStr += '.';
-        hasDot = true;
-        index++;
-      } else {
-        break;
-      }
-    }
-    
-    if (numStr === '') {
-      throw new Error('期望数字');
-    }
-    
-    const num = parseFloat(numStr);
-    if (isNaN(num)) {
-      throw new Error(`无效的数字: ${numStr}`);
-    }
-    return num;
-  };
-  
-  // 解析因子(数字或括号表达式)
-  const parseFactor = () => {
-    skipWhitespace();
-    
-    if (index >= expr.length) {
-      throw new Error('表达式不完整');
-    }
-    
-    // 处理负号(一元运算符)
-    let isNegative = false;
-    if (expr[index] === '-') {
-      isNegative = true;
-      index++;
-      skipWhitespace();
-    } else if (expr[index] === '+') {
-      // 正号可以忽略
-      index++;
-      skipWhitespace();
-    }
-    
-    let result;
-    if (expr[index] === '(') {
-      index++; // 跳过 '('
-      result = parseExpression();
-      skipWhitespace();
-      if (index >= expr.length || expr[index] !== ')') {
-        throw new Error('缺少右括号');
-      }
-      index++; // 跳过 ')'
-    } else {
-      result = parseNumber();
-    }
-    
-    return isNegative ? -result : result;
-  };
-  
-  // 解析项(处理 * 和 /)
-  const parseTerm = () => {
-    let result = parseFactor();
-    
-    skipWhitespace();
-    while (index < expr.length) {
-      const op = expr[index];
-      if (op === '*') {
-        index++;
-        result *= parseFactor();
-      } else if (op === '/') {
-        index++;
-        const divisor = parseFactor();
-        if (divisor === 0) {
-          throw new Error('除以零');
-        }
-        result /= divisor;
-      } else {
-        break;
-      }
-      skipWhitespace();
-    }
-    
-    return result;
-  };
-  
-  // 解析表达式(处理 + 和 -)
-  const parseExpression = () => {
-    let result = parseTerm();
-    
-    skipWhitespace();
-    while (index < expr.length) {
-      const op = expr[index];
-      if (op === '+') {
-        index++;
-        result += parseTerm();
-      } else if (op === '-') {
-        index++;
-        result -= parseTerm();
-      } else {
-        break;
-      }
-      skipWhitespace();
-    }
-    
-    return result;
-  };
-  
-  try {
-    const result = parseExpression();
-    skipWhitespace();
-    if (index < expr.length) {
-      throw new Error(`表达式解析不完整,剩余: ${expr.substring(index)}`);
-    }
-    return result;
-  } catch (error) {
-    throw new Error(`表达式解析失败: ${error.message}`);
-  }
-}
-
-/**
- * 评估算术表达式(支持 +, -, *, / 运算)
- * 只处理 {variable} 格式(单花括号),用于数字运算
- * @param {string} expression - 表达式字符串,如 "{turn} + 1"
- * @param {Object} context - 变量上下文
- * @returns {any} 计算结果
- */
-function evaluateExpression(expression, context = variableContext) {
-  if (typeof expression !== 'string') {
-    return expression;
-  }
-  
-  try {
-    // 替换变量(只处理 {variable} 格式,单花括号用于数字运算)
-    let expr = expression.trim();
-    // 只匹配 {variable} 格式,不匹配 {{variable}} 格式
-    // 使用负向前瞻确保不会匹配双花括号的开始部分
-    const varPattern = /\{(\w+)\}(?!\})/g;
-    const originalExpr = expr;
-    let hasVariables = false;
-    
-    expr = expr.replace(varPattern, (match, varName) => {
-      hasVariables = true;
-      const value = context[varName];
-      if (value === undefined || value === null) {
-        return '0';
-      }
-      // 数字类型直接转换
-      if (typeof value === 'number') {
-        return String(value);
-      }
-      // 布尔类型转换
-      if (typeof value === 'boolean') {
-        return value ? '1' : '0';
-      }
-      // 尝试将字符串转换为数字
-      if (typeof value === 'string') {
-        const numValue = Number(value);
-        // 如果字符串可以转换为数字,且不是空字符串,使用数字
-        if (!isNaN(numValue) && value.trim() !== '') {
-          return String(numValue);
-        }
-        // 如果无法转换为数字,返回 0 避免错误
-        return '0';
-      }
-      // 其他类型尝试转换为数字
-      const numValue = Number(value);
-      if (!isNaN(numValue)) {
-        return String(numValue);
-      }
-      return '0';
-    });
-    
-    // 如果没有变量且没有运算符,直接返回原值
-    if (!hasVariables && !/[+\-*/]/.test(expr)) {
-      const numValue = Number(expr);
-      if (!isNaN(numValue) && expr.trim() !== '') {
-        return numValue;
-      }
-      return expr;
-    }
-    
-    // 检查是否包含算术运算符
-    if (!/[+\-*/]/.test(expr)) {
-      // 没有运算符,直接返回解析后的值
-      const numValue = Number(expr);
-      if (!isNaN(numValue) && expr.trim() !== '') {
-        return numValue;
-      }
-      return expr;
-    }
-    
-    // 手动解析简单的算术表达式(只支持 +, -, *, /)
-    // 移除所有空格
-    expr = expr.replace(/\s+/g, '');
-    
-    // 检查是否只包含数字、运算符、小数点和括号
-    if (!/^[0-9+\-*/().]+$/.test(expr)) {
-      // 包含不允许的字符,返回原值
-      return resolveValue(originalExpr, context);
-    }
-    
-    // 验证表达式格式(防止注入)
-    // 确保表达式以数字或括号开头,以数字或括号结尾
-    if (!/^[0-9(]/.test(expr) || !/[0-9)]$/.test(expr)) {
-      return resolveValue(originalExpr, context);
-    }
-    
-    // 手动解析并计算表达式(不使用 eval 或 Function)
-    const result = parseArithmeticExpression(expr);
-    
-    // 如果结果是数字,返回数字类型
-    if (typeof result === 'number' && !isNaN(result) && isFinite(result)) {
-      return result;
-    }
-    
-    // 如果结果不是有效数字,返回原值
-    return resolveValue(originalExpr, context);
-  } catch (error) {
-    // 如果计算失败,尝试直接解析变量
-    return resolveValue(expression, context);
-  }
-}
-
-/**
- * 评估条件表达式(不使用eval,手动解析)
- * @param {string} condition - 条件表达式
- * @param {Object} context - 变量上下文
- * @returns {boolean} 条件结果
- */
-function evaluateCondition(condition, context = variableContext) {
-  if (!condition) return true;
-
-  try {
-    // 替换变量
-    // 支持变量名中包含连字符,如 {chat-history}
-    // 使用 [\w-]+ 来匹配字母、数字、下划线和连字符
-    let expr = condition;
-    const varPattern = /\{([\w-]+)\}/g;
-    expr = expr.replace(varPattern, (match, varName) => {
-      const value = context[varName];
-      // 如果变量不存在,视为空字符串(所有变量都是 string 或 int 类型)
-      if (value === undefined || value === null) {
-        return '""';
-      }
-      // 如果值是空字符串,也返回 '""'
-      if (value === '') {
-        return '""';
-      }
-      // 如果值是字符串 "undefined" 或 "null",也视为空字符串
-      if (value === 'undefined' || value === 'null') {
-        return '""';
-      }
-      // 确保字符串类型
-      if (typeof value === 'string') {
-        // 尝试判断是否是 JSON 字符串
-        try {
-          const parsed = JSON.parse(value);
-          if (Array.isArray(parsed)) {
-            // 如果是 JSON 数组字符串,转换为 JSON 字符串用于比较(保持原格式)
-            const escaped = value.replace(/"/g, '\\"');
-            return `"${escaped}"`;
-          }
-        } catch (e) {
-          // 不是 JSON,按普通字符串处理
-        }
-        // 转义字符串中的引号
-        const escaped = value.replace(/"/g, '\\"');
-        return `"${escaped}"`;
-      }
-      if (Array.isArray(value)) {
-        // 数组转换为 JSON 字符串(与 chat-history 和 currentMessage 格式一致)
-        try {
-          const jsonStr = JSON.stringify(value);
-          const escaped = jsonStr.replace(/"/g, '\\"');
-          return `"${escaped}"`;
-        } catch (e) {
-          return `"[]"`;
-        }
-      }
-      if (typeof value === 'number') return value;
-      if (typeof value === 'boolean') return value;
-      // 其他类型转为字符串
-      return `"${String(value)}"`;
-    });
-    
-    // 手动解析简单的条件表达式(不使用eval)
-    // 支持: ==, !=, >, <, >=, <=, &&, ||
-    const result = parseConditionExpression(expr);
-    return result;
-  } catch (error) {
-    return false;
-  }
-}
-
-/**
- * 手动解析条件表达式(避免使用eval)
- * @param {string} expr - 表达式字符串
- * @returns {boolean} 结果
- */
-function parseConditionExpression(expr) {
-  // 去除空格
-  expr = expr.trim();
-  
-  // 处理逻辑运算符(从低优先级到高优先级)
-  // 先处理 ||
-  if (expr.includes('||')) {
-    const parts = expr.split('||').map(p => p.trim());
-    return parts.some(part => parseConditionExpression(part));
-  }
-  
-  // 处理 &&
-  if (expr.includes('&&')) {
-    const parts = expr.split('&&').map(p => p.trim());
-    return parts.every(part => parseConditionExpression(part));
-  }
-  
-  // 处理比较运算符
-  const operators = [
-    { op: '!=', fn: (a, b) => a != b },
-    { op: '==', fn: (a, b) => {
-      // 确保类型一致:如果一边是字符串,另一边也转为字符串
-      if (typeof a === 'string' && typeof b !== 'string') {
-        b = String(b);
-      } else if (typeof b === 'string' && typeof a !== 'string') {
-        a = String(a);
-      }
-      // 特殊处理:空字符串比较
-      // 如果两边都是空字符串,直接返回 true
-      if (a === '' && b === '') {
-        return true;
-      }
-      // 特殊处理:如果一边是空字符串,另一边是 JSON 数组字符串,判断数组是否为空
-      if ((a === '' && typeof b === 'string') || (b === '' && typeof a === 'string')) {
-        const jsonStr = a === '' ? b : a;
-        try {
-          const parsed = JSON.parse(jsonStr);
-          if (Array.isArray(parsed)) {
-            return parsed.length === 0; // 空数组 == 空字符串
-          }
-        } catch (e) {
-          // 不是 JSON,按普通比较
-        }
-      }
-      return a == b;
-    }},
-    { op: '>=', fn: (a, b) => Number(a) >= Number(b) },
-    { op: '<=', fn: (a, b) => Number(a) <= Number(b) },
-    { op: '>', fn: (a, b) => Number(a) > Number(b) },
-    { op: '<', fn: (a, b) => Number(a) < Number(b) }
-  ];
-  
-  for (const { op, fn } of operators) {
-    if (expr.includes(op)) {
-      const parts = expr.split(op).map(p => p.trim());
-      if (parts.length === 2) {
-        const left = parseValue(parts[0]);
-        const right = parseValue(parts[1]);
-        // 调试:如果是 relationBg 相关的条件,输出调试信息
-        if (expr.includes('relationBg')) {
-          // 只在开发时输出,不影响生产
-        }
-        return fn(left, right);
-      }
-    }
-  }
-  
-  // 如果没有运算符,尝试解析为布尔值
-  const value = parseValue(expr);
-  if (typeof value === 'boolean') return value;
-  if (typeof value === 'string') {
-    // 空字符串为false
-    if (value === '' || value === 'undefined') return false;
-    // 尝试解析 JSON 字符串,如果是空数组 "[]",返回 false
-    try {
-      const parsed = JSON.parse(value);
-      if (Array.isArray(parsed)) {
-        return parsed.length > 0;
-      }
-      // 如果是空对象 "{}",返回 false
-      if (typeof parsed === 'object' && Object.keys(parsed).length === 0) {
-        return false;
-      }
-    } catch (e) {
-      // 不是 JSON,按普通字符串处理
-    }
-    // 非空字符串为true
-    return true;
-  }
-  // 数字:0为false,非0为true
-  if (typeof value === 'number') return value !== 0;
-  
-  return Boolean(value);
-}
-
-/**
- * 解析值(字符串、数字、布尔值)
- * @param {string} str - 字符串
- * @returns {any} 解析后的值
- */
-function parseValue(str) {
-  str = str.trim();
-  
-  // 布尔值
-  if (str === 'true') return true;
-  if (str === 'false') return false;
-  
-  // 字符串(带引号)
-  if ((str.startsWith('"') && str.endsWith('"')) || 
-      (str.startsWith("'") && str.endsWith("'"))) {
-    return str.slice(1, -1).replace(/\\"/g, '"').replace(/\\'/g, "'");
-  }
-  
-  // 数字
-  if (/^-?\d+(\.\d+)?$/.test(str)) {
-    return parseFloat(str);
-  }
-  
-  // undefined
-  if (str === 'undefined') return undefined;
-  
-  // 默认返回字符串
-  return str;
-}
-
-/**
- * 解析新的工作流格式(支持 variables, execute/actions)
- * @param {Object} workflow - 工作流配置对象
- * @returns {Object} 解析后的工作流
- */
-export function parseWorkflow(workflow) {
-  if (!workflow || typeof workflow !== 'object') {
-    return null;
-  }
-
-  // 初始化变量上下文(仅在未初始化时初始化,避免循环中重置)
-  if (!variableContextInitialized) {
-    variableContext = workflow.variables ? {} : {};
-    if (workflow.variables) {
-      // 转换变量类型:只允许 number 或 string
-      for (const key in workflow.variables) {
-        const value = workflow.variables[key];
-        if (value === null || value === undefined) {
-          variableContext[key] = ''; // null/undefined 转换为空字符串
-        } else if (typeof value === 'boolean') {
-          variableContext[key] = value ? '1' : '0'; // boolean 转换为字符串 '1' 或 '0'
-        } else if (typeof value === 'number') {
-          variableContext[key] = value; // number 保持不变
-        } else if (typeof value === 'string') {
-          variableContext[key] = value; // string 保持不变
-        } else {
-          variableContext[key] = String(value); // 其他类型转换为字符串
-        }
-      }
-    }
-    variableContextInitialized = true;
-  } else {
-    // 如果已初始化,只更新新增的变量(保留已有变量值)
-    if (workflow.variables) {
-      for (const key in workflow.variables) {
-        // 如果变量已存在且不为空,则保留原值;否则使用新值
-        if (variableContext[key] === undefined || variableContext[key] === null || variableContext[key] === '') {
-          const value = workflow.variables[key];
-          // 转换变量类型:只允许 number 或 string
-          if (value === null || value === undefined) {
-            variableContext[key] = ''; // null/undefined 转换为空字符串
-          } else if (typeof value === 'boolean') {
-            variableContext[key] = value ? '1' : '0'; // boolean 转换为字符串 '1' 或 '0'
-          } else if (typeof value === 'number') {
-            variableContext[key] = value; // number 保持不变
-          } else if (typeof value === 'string') {
-            variableContext[key] = value; // string 保持不变
-          } else {
-            variableContext[key] = String(value); // 其他类型转换为字符串
-          }
-        }
-      }
-    }
-  }
-  
-  // 解析 actions(支持新格式和旧格式)
-  let actions = [];
-  
-  // 新格式:优先支持 execute 字段,excuse 作为向后兼容
-  if (workflow.execute && Array.isArray(workflow.execute)) {
-    actions = parseActions(workflow.execute);
-  } else if (workflow.excuse && Array.isArray(workflow.excuse)) {
-    // 向后兼容:支持旧的 excuse 字段名
-    actions = parseActions(workflow.excuse);
-  } else if (workflow.actions && Array.isArray(workflow.actions)) {
-    actions = parseActions(workflow.actions);
-  } else if (Array.isArray(workflow)) {
-    // 向后兼容:如果直接是数组,按旧格式解析
-    actions = parseActions(workflow);
-  }
-
-  // 向后兼容:支持旧格式的顶层 schedule
-  let schedule = workflow.schedule !== undefined ? workflow.schedule : (workflow.triggers || {});
-  
-  // 如果 schedule 是数组(旧格式),转换为对象格式(取第一个元素)
-  if (Array.isArray(schedule)) {
-    schedule = schedule.length > 0 ? schedule[0] : {};
-  }
-  
-  // 如果 schedule 是对象但包含嵌套的 schedule 字段(旧格式),提取内部对象
-  if (schedule && typeof schedule === 'object' && schedule.schedule) {
-    schedule = schedule.schedule;
-  }
-
-  // 处理 repeat 字段:将 "forever" 转换为 -1,保持向后兼容
-  if (schedule && typeof schedule === 'object') {
-    if (schedule.repeat === 'forever' || schedule.repeat === 'Forever') {
-      schedule.repeat = -1;
-    }
-  }
-
-  return {
-    schedule: schedule,
-    triggers: schedule, // 向后兼容:同时保留 triggers 字段
-    variables: variableContext,
-    actions: actions
-  };
-}
-
-/**
- * 解析操作数组(支持新旧格式)
- * @param {Array} actions - 操作数组
- * @returns {Array} 解析后的操作列表
- */
-export function parseActions(actions) {
-  if (!Array.isArray(actions)) {
-    return [];
-  }
-
-  const parsedActions = [];
-  
-  for (const action of actions) {
-    if (typeof action !== 'object' || action === null) {
-      continue;
-    }
-
-    // 新格式:使用 type 字段
-    if (action.type) {
-      parsedActions.push(parseNewFormatAction(action));
-    }
-    // 旧格式:向后兼容
-    else {
-      parsedActions.push(parseOldFormatAction(action));
-    }
-  }
-
-  return parsedActions;
-}
-
-/**
- * 解析新格式操作
- * @param {Object} action - 操作对象
- * @returns {Object} 解析后的操作
- */
-function parseNewFormatAction(action) {
-  const parsed = {
-    type: action.type,
-    method: action.method,
-    // target和value保留原始值,在执行时再解析(因为变量可能在执行时才被赋值)
-    target: action.target,
-    value: action.value,
-    variable: action.variable,
-    condition: action.condition,
-    delay: action.delay || '',
-    timeout: action.timeout,
-    retry: action.retry
-  };
-
-  // 根据类型添加特定字段
-  switch (action.type) {
-    case 'adb':
-      // 统一 ADB 操作,通过 method 区分
-      parsed.method = action.method;
-      // 支持新的 inVars/outVars 格式(都可以为空)
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-      } else {
-        parsed.inVars = [];
-      }
-      if (action.outVars && Array.isArray(action.outVars)) {
-        parsed.outVars = action.outVars.map(v => extractVarName(v));
-      } else {
-        parsed.outVars = [];
-      }
-      // 向后兼容:保留旧字段
-      parsed.target = action.target;
-      parsed.value = action.value;
-      parsed.variable = action.variable;
-      parsed.clear = action.clear || false;
-      break;
-    case 'locate':
-      // locate 操作(向后兼容)
-      break;
-    case 'click':
-      // click 操作(向后兼容)
-      break;
-    case 'input':
-      parsed.clear = action.clear || false;
-      break;
-    case 'ocr':
-      parsed.area = action.area;
-      parsed.avatar = resolveValue(action.avatar);
-      break;
-    case 'extract-messages':
-    case 'ocr-chat':
-    case 'ocr-chat-history': // 向后兼容
-    case 'extract-chat-history': // 向后兼容
-      // 支持新的 inVars/outVars 格式(都可以为空)
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-        if (action.inVars.length >= 2) {
-          parsed.avatar1 = action.inVars[0];
-          parsed.avatar2 = action.inVars[1];
-        } else if (action.inVars.length === 1) {
-          parsed.avatar1 = action.inVars[0];
-          parsed.avatar2 = action.avatar2;
-        }
-      } else {
-        parsed.inVars = [];
-        parsed.avatar1 = action.avatar1;
-        parsed.avatar2 = action.avatar2;
-      }
-      if (action.outVars && Array.isArray(action.outVars)) {
-        parsed.outVars = action.outVars.map(v => extractVarName(v));
-        if (action.outVars.length > 0) {
-          parsed.variable = extractVarName(action.outVars[0]);
-        }
-      } else {
-        parsed.outVars = [];
-        if (action.variable) {
-          parsed.variable = extractVarName(action.variable);
-        }
-      }
-      // 向后兼容:支持旧的 friendAvatar 和 myAvatar
-      if (action.friendAvatar && !parsed.avatar1) parsed.avatar1 = action.friendAvatar;
-      if (action.myAvatar && !parsed.avatar2) parsed.avatar2 = action.myAvatar;
-      break;
-    case 'save-messages':
-      // 保存消息记录到文件
-      break;
-    case 'generate-summary':
-      parsed.summaryVariable = action.summaryVariable;
-      break;
-    case 'ai-generate':
-      parsed.prompt = resolveValue(action.prompt);
-      parsed.model = action.model;
-      // 支持新的 inVars/outVars 格式(都可以为空)
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-      } else {
-        parsed.inVars = [];
-      }
-      if (action.outVars && Array.isArray(action.outVars)) {
-        parsed.outVars = action.outVars.map(v => extractVarName(v));
-        if (action.outVars.length > 0) {
-          parsed.variable = extractVarName(action.outVars[0]);
-        }
-      } else {
-        parsed.outVars = [];
-        if (action.variable) {
-          parsed.variable = extractVarName(action.variable);
-        }
-      }
-      break;
-    case 'schedule':
-      // schedule 操作:condition 定义调度条件,interval 定义要执行的动作
-      parsed.condition = action.condition || {};
-      // 处理 repeat 字段:将 "forever" 转换为 -1
-      if (parsed.condition && typeof parsed.condition === 'object') {
-        if (parsed.condition.repeat === 'forever' || parsed.condition.repeat === 'Forever') {
-          parsed.condition.repeat = -1;
-        }
-      }
-      // interval 字段包含要执行的动作数组
-      if (action.interval && Array.isArray(action.interval)) {
-        parsed.interval = parseActions(action.interval);
-      } else {
-        parsed.interval = [];
-      }
-      break;
-    case 'if':
-      // 支持 ture 作为 then 的别名(可能是拼写错误,但为了兼容性支持)
-      parsed.then = action.then || action.ture ? parseActions(action.then || action.ture) : [];
-      parsed.else = action.else ? parseActions(action.else) : [];
-      break;
-    case 'for':
-      parsed.variable = action.variable;
-      parsed.items = resolveValue(action.items);
-      parsed.body = action.body ? parseActions(action.body) : [];
-      break;
-    case 'while':
-      // 支持 ture 作为 body 的别名(可能是拼写错误,但为了兼容性支持)
-      parsed.body = action.body || action.ture ? parseActions(action.body || action.ture) : [];
-      break;
-    case 'delay':
-      parsed.value = action.value || action.delay || '0s';
-      break;
-    case 'set':
-      parsed.variable = action.variable;
-      parsed.value = resolveValue(action.value);
-      break;
-    case 'log':
-      // log 操作:支持 inVars 或 value 字段
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-      } else {
-        parsed.inVars = [];
-      }
-      // 支持 value 字段作为直接输出内容
-      if (action.value) {
-        parsed.value = action.value;
-      }
-      break;
-    case 'read-last-message':
-      // 支持新的 inVars/outVars 格式,也支持 inputVars/outputVars(大小写不同,都可以为空)
-      const inputVars = action.inVars || action.inputVars || [];
-      const outputVars = action.outVars || action.outputVars || [];
-      
-      parsed.inVars = inputVars.map(v => extractVarName(v));
-      parsed.outVars = outputVars.map(v => extractVarName(v));
-      
-      if (inputVars.length > 0) {
-        parsed.inputVar = extractVarName(inputVars[0]);
-      }
-      if (outputVars.length > 0) {
-        parsed.textVariable = extractVarName(outputVars[0]);
-      }
-      if (outputVars.length > 1) {
-        parsed.senderVariable = extractVarName(outputVars[1]);
-      }
-      
-      // 向后兼容:支持旧的 textVariable 和 senderVariable
-      if (!parsed.textVariable) parsed.textVariable = action.textVariable;
-      if (!parsed.senderVariable) parsed.senderVariable = action.senderVariable;
-      break;
-    case 'read-txt':
-    case 'read-text': // 向后兼容别名
-      // 支持新的 inVars/outVars 格式
-      if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 0) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-        parsed.filePath = action.inVars[0];
-      } else {
-        parsed.inVars = [];
-        parsed.filePath = action.filePath;
-      }
-      if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-        parsed.variable = extractVarName(action.outVars[0]);
-      } else if (action.variable) {
-        parsed.variable = extractVarName(action.variable);
-      }
-      break;
-    case 'save-txt':
-    case 'save-text': // 向后兼容别名
-      // 支持新的 inVars/outVars 格式
-      // 入参顺序:第一个参数是内容,第二个参数是文件路径
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-        if (action.inVars.length > 0) {
-          parsed.content = action.inVars[0]; // 第一个参数是内容
-        }
-        if (action.inVars.length > 1) {
-          parsed.filePath = action.inVars[1]; // 第二个参数是文件路径
-        }
-      } else {
-        parsed.inVars = [];
-        parsed.filePath = action.filePath;
-        parsed.content = action.content;
-      }
-      // save-txt 通常不需要 outVars,但为了兼容性支持
-      if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-        parsed.variable = extractVarName(action.outVars[0]);
-      }
-      break;
-    case 'image-region-location':
-      // 支持新的 inVars/outVars 格式(都可以为空)
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-        // 如果 inVars 有值,从变量中读取 screenshot 和 region
-        if (action.inVars.length > 0) parsed.screenshot = action.inVars[0];
-        if (action.inVars.length > 1) parsed.region = action.inVars[1];
-      } else {
-        parsed.inVars = [];
-        parsed.screenshot = action.screenshot;
-        parsed.region = action.region;
-      }
-      if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-        parsed.variable = extractVarName(action.outVars[0]);
-      } else if (action.variable) {
-        parsed.variable = extractVarName(action.variable);
-      }
-      break;
-    case 'image-center-location':
-      // 支持新的 inVars/outVars 格式(都可以为空)
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-        // 如果 inVars 有值,从变量中读取 template
-        if (action.inVars.length > 0) parsed.template = action.inVars[0];
-      } else {
-        parsed.inVars = [];
-        parsed.template = action.template;
-      }
-      if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-        parsed.variable = extractVarName(action.outVars[0]);
-      } else if (action.variable) {
-        parsed.variable = extractVarName(action.variable);
-      }
-      break;
-    case 'image-area-cropping':
-      // 支持新的 inVars/outVars 格式
-      if (action.inVars && Array.isArray(action.inVars)) {
-        parsed.inVars = action.inVars.map(v => extractVarName(v));
-        // 如果 inVars 有值,从变量中读取 area 和 savePath
-        if (action.inVars.length > 0) parsed.area = action.inVars[0];
-        if (action.inVars.length > 1) parsed.savePath = action.inVars[1];
-      } else {
-        parsed.inVars = [];
-        parsed.area = action.area;
-        parsed.savePath = action.savePath;
-      }
-      // image-area-cropping 通常不需要 outVars,但为了兼容性支持
-      if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-        parsed.variable = extractVarName(action.outVars[0]);
-      }
-      break;
-    case 'scroll':
-    case 'swipe':
-    case 'press':
-    case 'string-press':
-      // 保持原有逻辑(向后兼容)
-      break;
-  }
-
-  return parsed;
-}
-
-/**
- * 解析旧格式操作(向后兼容)
- * @param {Object} action - 操作对象
- * @returns {Object} 解析后的操作
- */
-function parseOldFormatAction(action) {
-    const times = action.times && action.times > 0 ? parseInt(action.times, 10) : 1;
-    const data = action.data || '';
-    const delay = action.delay || '';
-
-    // 检查 press 操作
-    if (action.press) {
-    return {
-        type: 'press',
-      value: action.press,
-        times: times,
-        data: data,
-        delay: delay,
-    };
-    }
-    // 检查 input 操作
-    else if (action.input !== undefined) {
-    return {
-        type: 'input',
-      value: action.input,
-        times: times,
-        data: data,
-        delay: delay,
-    };
-    }
-    // 检查 swipe 操作
-    else if (action.swipe) {
-      const swipeValue = action.swipe;
-      const validSwipeDirections = ['up-down', 'down-up', 'left-right', 'right-left'];
-      
-      if (!validSwipeDirections.includes(swipeValue)) {
-      return null;
-      }
-      
-    return {
-        type: 'swipe',
-      value: swipeValue,
-        times: times,
-        data: data,
-        delay: delay,
-    };
-    }
-    // 检查 string-press 操作
-    else if (action['string-press']) {
-    return {
-        type: 'string-press',
-      value: action['string-press'],
-        times: times,
-        data: data,
-        delay: delay,
-    };
-    }
-    // 检查 scroll 操作
-    else if (action.scroll) {
-      const scrollValue = action.scroll;
-      const validScrollDirections = ['up-down', 'down-up', 'left-right', 'right-left'];
-      
-      if (!validScrollDirections.includes(scrollValue)) {
-      return null;
-      }
-      
-    return {
-        type: 'scroll',
-      value: scrollValue,
-        times: times,
-        data: data,
-        delay: delay,
-    };
-    }
-    else {
-    return null;
-    }
-}
-
-/**
- * 获取操作名称(用于显示)
- * @param {Object} action - 操作对象
- * @returns {string} 操作名称
- */
-function getActionName(action) {
-  const typeNames = {
-    'schedule': '定时执行',
-    'adb': 'ADB操作',
-    'press': '点击图片',
-    'input': '输入文本',
-    'swipe': '滑动',
-    'string-press': '点击文字',
-    'scroll': '滚动',
-    'locate': '定位',
-    'click': '点击',
-    'ocr': '文字识别',
-    'extract-messages': '提取消息记录',
-    'save-messages': '保存消息记录',
-    'generate-summary': '生成总结',
-    'ocr-chat': 'OCR识别对话',
-    // 向后兼容
-    'ocr-chat-history': 'OCR提取消息记录',
-    'extract-chat-history': '提取消息记录', // 向后兼容
-    'generate-history-summary': '生成总结',
-    'image-region-location': '图像区域定位',
-    'image-center-location': '图像中心点定位',
-    'image-area-cropping': '裁剪图片区域',
-    'read-last-message': '读取最后一条消息',
-    'read-txt': '读取文本文件',
-    'read-text': '读取文本文件', // 向后兼容别名
-    'save-txt': '保存文本文件',
-    'save-text': '保存文本文件', // 向后兼容别名
-    'smart-chat-append': '智能合并聊天记录',
-    'ai-generate': 'AI生成',
-    'if': '条件判断',
-    'for': '循环',
-    'while': '循环',
-    'delay': '延迟',
-    'set': '设置变量',
-    'echo': '打印信息',
-    'log': '打印信息' // 向后兼容
-  };
-  
-  const typeName = typeNames[action.type] || action.type;
-  const value = action.value || action.target || '';
-  const displayValue = typeof value === 'string' ? value : JSON.stringify(value);
-  
-  if (action.type === 'schedule') {
-    const condition = action.condition || {};
-    const interval = condition.interval || '0s';
-    const repeat = condition.repeat !== undefined ? condition.repeat : 1;
-    const repeatText = repeat === -1 ? '无限循环' : `重复${repeat}次`;
-    return `${typeName}: ${interval}, ${repeatText}`;
-  } else if (action.type === 'input') {
-    return `${typeName}: ${displayValue.length > 20 ? displayValue.substring(0, 20) + '...' : displayValue}`;
-  } else if (action.type === 'string-press' || action.type === 'click') {
-    return `${typeName}: ${displayValue.length > 20 ? displayValue.substring(0, 20) + '...' : displayValue}`;
-  } else if (action.type === 'if') {
-    return `${typeName}: ${action.condition || ''}`;
-  } else if (action.type === 'for') {
-    return `${typeName}: ${action.variable || ''}`;
-  } else if (action.type === 'set') {
-    return `${typeName}: ${action.variable || ''}`;
-  } else {
-    return `${typeName}: ${displayValue}`;
-  }
-}
-
-/**
- * 计算滑动操作的坐标
- * @param {string} direction - 滑动方向: up-down, down-up, left-right, right-left
- * @param {number} width - 设备宽度
- * @param {number} height - 设备高度
- * @returns {Object} 包含起始和结束坐标的对象 {x1, y1, x2, y2}
- */
-export function calculateSwipeCoordinates(direction, width, height) {
-  // 滑动距离为屏幕的 70%,起始和结束位置各留 15% 的边距
-  const margin = 0.15;
-  const swipeDistance = 0.7;
-
-  let x1, y1, x2, y2;
-
-  switch (direction) {
-    case 'up-down':
-      // 从上往下滑动
-      x1 = x2 = Math.round(width / 2);
-      y1 = Math.round(height * margin);
-      y2 = Math.round(height * (margin + swipeDistance));
-      break;
-    
-    case 'down-up':
-      // 从下往上滑动
-      x1 = x2 = Math.round(width / 2);
-      y1 = Math.round(height * (margin + swipeDistance));
-      y2 = Math.round(height * margin);
-      break;
-    
-    case 'left-right':
-      // 从左往右滑动
-      y1 = y2 = Math.round(height / 2);
-      x1 = Math.round(width * margin);
-      x2 = Math.round(width * (margin + swipeDistance));
-      break;
-    
-    case 'right-left':
-      // 从右往左滑动
-      y1 = y2 = Math.round(height / 2);
-      x1 = Math.round(width * (margin + swipeDistance));
-      x2 = Math.round(width * margin);
-      break;
-    
-    default:
-      throw new Error(`未知的滑动方向: ${direction}`);
-  }
-
-  return { x1, y1, x2, y2 };
-}
-
-
-/**
- * 执行单个操作
- * @param {Object} action - 操作对象
- * @param {string} device - 设备 ID
- * @param {string} folderPath - 文件夹路径(用于 press 操作查找图片)
- * @param {Object} resolution - 设备分辨率 {width, height}
- * @returns {Promise<Object>} 执行结果 {success, error?, result?}
- */
-export async function executeAction(action, device, folderPath, resolution) {
-  try {
-    // 检查条件
-    if (action.condition && !evaluateCondition(action.condition)) {
-      return { success: true, skipped: true };
-    }
-
-    switch (action.type) {
-      case 'adb': {
-        // 统一 ADB 操作
-        const method = action.method;
-        if (!method) {
-          return { success: false, error: 'adb 操作缺少 method 参数' };
-        }
-
-        // 从 inVars 读取参数
-        const inVars = action.inVars || [];
-        const outVars = action.outVars || [];
-
-        switch (method) {
-          case 'input': {
-            // 输入文本:inVars[0] 是输入文本
-            let inputValue = null;
-            if (inVars.length > 0) {
-              const inputVar = extractVarName(inVars[0]);
-              inputValue = variableContext[inputVar];
-            } else if (action.value) {
-              inputValue = resolveValue(action.value);
-            }
-
-            if (!inputValue) {
-              return { success: false, error: 'input 操作缺少输入内容' };
-            }
-
-            // 如果设置了clear,先清空输入框
-            if (action.clear) {
-              for (let i = 0; i < 200; i++) {
-                const clearResult = await window.electronAPI.sendKeyEvent(device, '67');
-                if (!clearResult.success) break;
-                await new Promise(resolve => setTimeout(resolve, 10));
-              }
-              await new Promise(resolve => setTimeout(resolve, 200));
-            }
-
-            if (!window.electronAPI || !window.electronAPI.sendText) {
-              return { success: false, error: '输入 API 不可用' };
-            }
-
-            const textResult = await window.electronAPI.sendText(device, String(inputValue));
-            if (!textResult.success) {
-              return { success: false, error: `输入失败: ${textResult.error}` };
-            }
-
-            return { success: true };
-          }
-
-          case 'click': {
-            // 点击操作:inVars[0] 是位置(坐标对象或变量名)
-            let position = null;
-            if (inVars.length > 0) {
-              const posVar = extractVarName(inVars[0]);
-              position = variableContext[posVar];
-            } else if (action.target) {
-              position = resolveValue(action.target);
-            }
-
-            if (!position) {
-              return { success: false, error: 'click 操作缺少位置参数' };
-            }
-
-            // 如果 position 是字符串,尝试解析为坐标对象
-            if (typeof position === 'string') {
-              if (position === '') {
-                return { success: false, error: 'click 操作缺少位置参数(位置变量为空)' };
-              }
-              try {
-                // 尝试解析 JSON 字符串(例如:{"x":123,"y":456})
-                position = JSON.parse(position);
-              } catch (e) {
-                // 如果不是 JSON,可能是 "x,y" 格式,尝试解析
-                const parts = position.split(',');
-                if (parts.length === 2) {
-                  const x = parseFloat(parts[0].trim());
-                  const y = parseFloat(parts[1].trim());
-                  if (!isNaN(x) && !isNaN(y)) {
-                    position = { x: Math.round(x), y: Math.round(y) };
-                  } else {
-                    return { success: false, error: `click 操作的位置格式错误,无法解析字符串: ${position}` };
-                  }
-                } else {
-                  return { success: false, error: `click 操作的位置格式错误,无法解析字符串: ${position}` };
-                }
-              }
-            }
-
-            // 如果 position 是数组 [x, y],转换为对象
-            if (Array.isArray(position) && position.length >= 2) {
-              position = { x: position[0], y: position[1] };
-            }
-
-            // 如果 position 是 corners 对象(四个顶点),计算中心点
-            if (position && typeof position === 'object' && position.topLeft && position.bottomRight) {
-              const centerX = Math.round((position.topLeft.x + position.bottomRight.x) / 2);
-              const centerY = Math.round((position.topLeft.y + position.bottomRight.y) / 2);
-              position = { x: centerX, y: centerY };
-            }
-
-            if (!position || typeof position !== 'object' || position.x === undefined || position.y === undefined) {
-              return { success: false, error: 'click 操作的位置格式错误,需要 {x, y} 对象' };
-            }
-
-            if (!window.electronAPI || !window.electronAPI.sendTap) {
-              return { success: false, error: '点击 API 不可用' };
-            }
-
-            const tapResult = await window.electronAPI.sendTap(device, position.x, position.y);
-            if (!tapResult.success) {
-              return { success: false, error: `点击失败: ${tapResult.error}` };
-            }
-
-            return { success: true };
-          }
-
-          case 'locate': {
-            // 定位操作:inVars[0] 是目标(图片路径或文字),outVars[0] 保存位置
-            // 支持通过 action.method 或 action.targetMethod 指定定位方法
-            const locateMethod = action.method || action.targetMethod || 'image'; // image, text, coordinate
-            let position = null;
-
-            if (locateMethod === 'image') {
-              let imagePath = null;
-              if (inVars.length > 0) {
-                const imageVar = extractVarName(inVars[0]);
-                imagePath = variableContext[imageVar] || imageVar;
-              } else if (action.target) {
-                imagePath = action.target;
-              }
-
-              if (!imagePath) {
-                return { success: false, error: 'locate 操作(image)缺少图片路径' };
-              }
-
-              // resources 作为根目录
-              const fullPath = imagePath.startsWith('/') || imagePath.includes(':')
-                ? imagePath
-                : `${folderPath}/resources/${imagePath}`;
-
-              if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
-                return { success: false, error: '图像匹配 API 不可用' };
-              }
-
-              const matchResult = await window.electronAPI.matchImageAndGetCoordinate(device, fullPath);
-              if (!matchResult.success) {
-                return { success: false, error: `图像匹配失败: ${matchResult.error}` };
-              }
-              position = matchResult.clickPosition;
-            } else if (locateMethod === 'text') {
-              let targetText = null;
-              if (inVars.length > 0) {
-                const textVar = extractVarName(inVars[0]);
-                targetText = variableContext[textVar] || textVar;
-              } else if (action.target) {
-                targetText = action.target;
-              }
-
-              if (!targetText) {
-                return { success: false, error: 'locate 操作(text)缺少文字内容' };
-              }
-
-              if (!window.electronAPI || !window.electronAPI.findTextAndGetCoordinate) {
-                return { success: false, error: '文字识别 API 不可用' };
-              }
-
-              const matchResult = await window.electronAPI.findTextAndGetCoordinate(device, targetText);
-              if (!matchResult.success) {
-                return { success: false, error: `文字识别失败: ${matchResult.error}` };
-              }
-              position = matchResult.clickPosition;
-            } else if (locateMethod === 'coordinate') {
-              let coord = null;
-              if (inVars.length > 0) {
-                const coordVar = extractVarName(inVars[0]);
-                coord = variableContext[coordVar];
-              } else if (action.target) {
-                coord = resolveValue(action.target);
-              }
-
-              if (!coord) {
-                return { success: false, error: 'locate 操作(coordinate)缺少坐标' };
-              }
-
-              position = Array.isArray(coord) ? { x: coord[0], y: coord[1] } : coord;
-            }
-
-            // 保存到变量
-            if (outVars.length > 0) {
-              const outputVar = extractVarName(outVars[0]);
-              variableContext[outputVar] = position;
-              await logOutVars(action, variableContext, folderPath);
-            } else if (action.variable) {
-              variableContext[action.variable] = position;
-            }
-
-            return { success: true, result: position };
-          }
-
-          case 'swipe': {
-            // 滑动操作:inVars[0] 是方向,inVars[1] 和 inVars[2] 可选(起始和结束位置)
-            let direction = null;
-            if (inVars.length > 0) {
-              const dirVar = extractVarName(inVars[0]);
-              direction = variableContext[dirVar] || dirVar;
-            } else if (action.value) {
-              direction = resolveValue(action.value);
-            }
-
-            if (!direction) {
-              return { success: false, error: 'swipe 操作缺少方向参数' };
-            }
-
-            let x1, y1, x2, y2;
-            if (inVars.length >= 3) {
-              // 从 inVars 读取起始和结束位置
-              const startVar = extractVarName(inVars[1]);
-              const endVar = extractVarName(inVars[2]);
-              const start = variableContext[startVar];
-              const end = variableContext[endVar];
-              if (start && end) {
-                x1 = start.x || start[0];
-                y1 = start.y || start[1];
-                x2 = end.x || end[0];
-                y2 = end.y || end[1];
-              }
-            }
-
-            // 如果没有提供具体坐标,使用方向计算坐标
-            if (x1 === undefined || y1 === undefined || x2 === undefined || y2 === undefined) {
-              const coords = calculateSwipeCoordinates(direction, resolution.width, resolution.height);
-              x1 = coords.x1;
-              y1 = coords.y1;
-              x2 = coords.x2;
-              y2 = coords.y2;
-            }
-
-            if (!window.electronAPI || !window.electronAPI.sendSwipe) {
-              return { success: false, error: '滑动 API 不可用' };
-            }
-
-            const swipeResult = await window.electronAPI.sendSwipe(device, x1, y1, x2, y2, 300);
-            if (!swipeResult.success) {
-              return { success: false, error: `滑动失败: ${swipeResult.error}` };
-            }
-
-            return { success: true };
-          }
-
-          case 'scroll': {
-            // 滚动操作:inVars[0] 是方向
-            let direction = null;
-            if (inVars.length > 0) {
-              const dirVar = extractVarName(inVars[0]);
-              direction = variableContext[dirVar] || dirVar;
-            } else if (action.value) {
-              direction = resolveValue(action.value);
-            }
-
-            if (!direction) {
-              return { success: false, error: 'scroll 操作缺少方向参数' };
-            }
-
-            if (!window.electronAPI || !window.electronAPI.sendScroll) {
-              return { success: false, error: '滚动 API 不可用' };
-            }
-
-            const scrollResult = await window.electronAPI.sendScroll(
-              device,
-              direction,
-              resolution.width,
-              resolution.height,
-              DEFAULT_SCROLL_DISTANCE,
-              500
-            );
-
-            if (!scrollResult.success) {
-              return { success: false, error: `滚动失败: ${scrollResult.error}` };
-            }
-
-            return { success: true };
-          }
-
-          case 'press': {
-            // 图像匹配并点击:inVars[0] 是图片路径
-            let imagePath = null;
-            if (inVars.length > 0) {
-              const imageVar = extractVarName(inVars[0]);
-              imagePath = variableContext[imageVar] || imageVar;
-            } else if (action.value) {
-              imagePath = action.value;
-            }
-
-            if (!imagePath) {
-              return { success: false, error: 'press 操作缺少图片路径' };
-            }
-
-            const fullPath = imagePath.startsWith('/') || imagePath.includes(':')
-              ? imagePath
-              : `${folderPath}/${imagePath}`;
-
-            if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
-              return { success: false, error: '图像匹配 API 不可用' };
-            }
-
-            const matchResult = await window.electronAPI.matchImageAndGetCoordinate(device, fullPath);
-            if (!matchResult.success) {
-              return { success: false, error: `图像匹配失败: ${matchResult.error}` };
-            }
-
-            const { clickPosition } = matchResult;
-            const { x, y } = clickPosition;
-
-            if (!window.electronAPI || !window.electronAPI.sendTap) {
-              return { success: false, error: '点击 API 不可用' };
-            }
-
-            const tapResult = await window.electronAPI.sendTap(device, x, y);
-            if (!tapResult.success) {
-              return { success: false, error: `点击失败: ${tapResult.error}` };
-            }
-
-            return { success: true };
-          }
-
-          case 'string-press': {
-            // 文字识别并点击:inVars[0] 是文字
-            let targetText = null;
-            if (inVars.length > 0) {
-              const textVar = extractVarName(inVars[0]);
-              targetText = variableContext[textVar] || textVar;
-            } else if (action.value) {
-              targetText = action.value;
-            }
-
-            if (!targetText) {
-              return { success: false, error: 'string-press 操作缺少文字内容' };
-            }
-
-            if (!window.electronAPI || !window.electronAPI.findTextAndGetCoordinate) {
-              return { success: false, error: '文字识别 API 不可用' };
-            }
-
-            const matchResult = await window.electronAPI.findTextAndGetCoordinate(device, targetText);
-            if (!matchResult.success) {
-              return { success: false, error: `文字识别失败: ${matchResult.error}` };
-            }
-
-            const { clickPosition } = matchResult;
-            const { x, y } = clickPosition;
-
-            if (!window.electronAPI || !window.electronAPI.sendTap) {
-              return { success: false, error: '点击 API 不可用' };
-            }
-
-            const tapResult = await window.electronAPI.sendTap(device, x, y);
-            if (!tapResult.success) {
-              return { success: false, error: `点击失败: ${tapResult.error}` };
-            }
-
-            return { success: true };
-          }
-
-          default:
-            return { success: false, error: `未知的 adb method: ${method}` };
-        }
-      }
-
-      case 'locate': {
-        // 定位操作
-        const method = action.method || 'image';
-        let position = null;
-
-        if (method === 'image') {
-          const imagePath = action.target.startsWith('/') || action.target.includes(':') 
-            ? action.target 
-            : `${folderPath}/${action.target}`;
-          
-          if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
-            return { success: false, error: '图像匹配 API 不可用' };
-          }
-
-          const matchResult = await window.electronAPI.matchImageAndGetCoordinate(device, imagePath);
-          if (!matchResult.success) {
-            return { success: false, error: `图像匹配失败: ${matchResult.error}` };
-          }
-          position = matchResult.clickPosition;
-        } else if (method === 'text') {
-          if (!window.electronAPI || !window.electronAPI.findTextAndGetCoordinate) {
-            return { success: false, error: '文字识别 API 不可用' };
-          }
-
-          const matchResult = await window.electronAPI.findTextAndGetCoordinate(device, action.target);
-          if (!matchResult.success) {
-            return { success: false, error: `文字识别失败: ${matchResult.error}` };
-          }
-          position = matchResult.clickPosition;
-        } else if (method === 'coordinate') {
-          position = Array.isArray(action.target) 
-            ? { x: action.target[0], y: action.target[1] }
-            : action.target;
-        }
-
-        // 保存到变量
-        if (action.variable && position) {
-          variableContext[action.variable] = position;
-        }
-
-        return { success: true, result: position };
-      }
-
-      case 'click': {
-        // 点击操作
-        const method = action.method || 'position';
-        let position = null;
-
-        if (method === 'position') {
-          position = resolveValue(action.target);
-        } else if (method === 'image') {
-          const imagePath = action.target.startsWith('/') || action.target.includes(':')
-            ? action.target
-            : `${folderPath}/${action.target}`;
-          
-          if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
-            return { success: false, error: '图像匹配 API 不可用' };
-          }
-
-          const matchResult = await window.electronAPI.matchImageAndGetCoordinate(device, imagePath);
-          if (!matchResult.success) {
-            return { success: false, error: `图像匹配失败: ${matchResult.error}` };
-          }
-          position = matchResult.clickPosition;
-        } else if (method === 'text') {
-          if (!window.electronAPI || !window.electronAPI.findTextAndGetCoordinate) {
-            return { success: false, error: '文字识别 API 不可用' };
-          }
-
-          const matchResult = await window.electronAPI.findTextAndGetCoordinate(device, action.target);
-          if (!matchResult.success) {
-            return { success: false, error: `文字识别失败: ${matchResult.error}` };
-          }
-          position = matchResult.clickPosition;
-        }
-
-        if (!position || !position.x || !position.y) {
-          return { success: false, error: '无法获取点击位置' };
-        }
-
-        if (!window.electronAPI || !window.electronAPI.sendTap) {
-          return { success: false, error: '点击 API 不可用' };
-        }
-
-        const tapResult = await window.electronAPI.sendTap(device, position.x, position.y);
-        if (!tapResult.success) {
-          return { success: false, error: `点击失败: ${tapResult.error}` };
-        }
-
-        return { success: true };
-      }
-
-      case 'press': {
-        // 向后兼容:图像匹配并点击
-        // resources 作为根目录
-        const imagePath = `${folderPath}/resources/${action.value}`;
-        
-        if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
-          return { success: false, error: '图像匹配 API 不可用' };
-        }
-
-        const matchResult = await window.electronAPI.matchImageAndGetCoordinate(device, imagePath);
-        
-        if (!matchResult.success) {
-          return { success: false, error: `图像匹配失败: ${matchResult.error}` };
-        }
-
-        const { clickPosition } = matchResult;
-        const { x, y } = clickPosition;
-
-        if (!window.electronAPI || !window.electronAPI.sendTap) {
-          return { success: false, error: '点击 API 不可用' };
-        }
-
-        const tapResult = await window.electronAPI.sendTap(device, x, y);
-        
-        if (!tapResult.success) {
-          return { success: false, error: `点击失败: ${tapResult.error}` };
-        }
-
-        return { success: true };
-      }
-
-      case 'input': {
-        // 输入文本
-        const inputStartTime = Date.now();
-        
-        // 先解析value(可能在运行时变量才被赋值,需要重新解析)
-        let inputValue = resolveValue(action.value);
-        
-        // 如果value为空或undefined,且target存在,使用target(用于向后兼容,target可能也是定位文字)
-        if (!inputValue && action.target) {
-          // 如果target看起来像定位文字(不是变量引用),就不使用它
-          const resolvedTarget = resolveValue(action.target);
-          // 只有当target是变量引用时才使用它作为输入值
-          if (resolvedTarget !== action.target || !action.target.includes(' ')) {
-            inputValue = resolvedTarget;
-          }
-        }
-        
-        // 如果还是没有值,报错
-        if (!inputValue) {
-          return { success: false, error: '输入内容为空' };
-        }
-
-        // 如果target是定位方式,先定位输入框(暂未实现,直接输入文本)
-        if (action.method === 'locate' && action.target) {
-          // 这里可以添加定位输入框的逻辑
-          // 暂时直接使用 sendText
-        }
-
-        if (!window.electronAPI || !window.electronAPI.sendText) {
-          return { success: false, error: '输入 API 不可用' };
-        }
-
-        // 如果设置了clear,先清空输入框(通过发送退格键)
-        if (action.clear) {
-          // 发送退格键清空输入框(假设最多200个字符)
-          // 使用Android的KEYCODE_DEL,值为67
-          for (let i = 0; i < 200; i++) {
-            const clearResult = await window.electronAPI.sendKeyEvent(device, '67');
-            if (!clearResult.success) {
-              break;
-            }
-            await new Promise(resolve => setTimeout(resolve, 10));
-          }
-          // 等待清空完成
-          await new Promise(resolve => setTimeout(resolve, 200));
-        }
-
-        const textResult = await window.electronAPI.sendText(device, inputValue);
-        
-        if (!textResult.success) {
-          return { success: false, error: `输入失败: ${textResult.error}` };
-        }
-
-        // 确保正确显示UTF-8编码的中文
-        try {
-          const displayValue = Buffer.isBuffer(inputValue) 
-            ? inputValue.toString('utf8') 
-            : String(inputValue);
-          // 输入成功,不打印日志
-        } catch (e) {
-          // 输入成功,不打印日志
-        }
-        return { success: true };
-      }
-
-      case 'ocr': {
-        // OCR识别
-        if (!window.electronAPI || !window.electronAPI.ocrLastMessage) {
-          return { success: false, error: 'OCR API 不可用' };
-        }
-
-        const method = action.method || 'full-screen';
-        let avatarPath = null;
-        const area = action.area;
-
-        // 如果是by-avatar方法,需要获取头像路径
-        if (method === 'by-avatar' && action.avatar) {
-          const avatarName = resolveValue(action.avatar);
-          if (avatarName) {
-            // 头像路径:从工作流文件夹路径中提取文件夹名,然后拼接头像文件名
-            // folderPath格式可能是:C:\...\static\processing\工作流名称
-            // 我们需要传递:工作流名称/头像文件名
-            const folderName = folderPath.split(/[/\\]/).pop();
-            avatarPath = `${folderName}/${avatarName}`;
-          }
-        }
-
-        // 调用OCR API,传递工作流文件夹路径
-        const ocrResult = await window.electronAPI.ocrLastMessage(device, method, avatarPath, area, folderPath);
-        
-        if (!ocrResult.success) {
-          return { success: false, error: `OCR识别失败: ${ocrResult.error}` };
-        }
-
-        // 保存识别结果到变量
-        if (action.variable) {
-          variableContext[action.variable] = ocrResult.text || '';
-          // 确保正确显示UTF-8编码的中文
-          const displayText = ocrResult.text || '';
-          try {
-            // 如果text是Buffer,转换为字符串
-            const textStr = Buffer.isBuffer(displayText) 
-              ? displayText.toString('utf8') 
-              : String(displayText);
-            // OCR识别结果已保存到变量
-          } catch (e) {
-            // 如果转换失败,直接输出
-            // OCR识别结果已保存到变量
-          }
-        }
-
-        return { 
-          success: true, 
-          text: ocrResult.text,
-          position: ocrResult.position
-        };
-      }
-
-      case 'extract-messages':
-      case 'ocr-chat':
-      case 'ocr-chat-history': // 向后兼容
-      case 'extract-chat-history': { // 向后兼容
-        // 提取消息记录
-        // 获取头像路径(支持新的 inVars 格式,也支持旧参数 avatar1/avatar2 和 friendAvatar/myAvatar)
-        const folderName = folderPath.split(/[/\\]/).pop();
-        let avatar1Path = null;
-        let avatar2Path = null;
-
-        // 优先使用新的 inVars 格式
-        let avatar1Name, avatar2Name, regionArea = null;
-        let friendRgb = null, myRgb = null;
-        
-        if (action.inVars && Array.isArray(action.inVars)) {
-          if (action.inVars.length >= 3) {
-            // 三个参数:可能是 RGB格式 或 头像+区域格式
-            const param1 = resolveValue(action.inVars[0]);
-            const param2 = resolveValue(action.inVars[1]);
-            
-            // 检查是否是RGB格式(格式:"(r,g,b)")
-            const rgbPattern = /^\((\d+),(\d+),(\d+)\)$/;
-            if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) &&
-                typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
-              // RGB格式:第一个是好友RGB,第二个是我的RGB,第三个是区域
-              friendRgb = param1.trim();
-              myRgb = param2.trim();
-              
-              // 第三个参数是区域
-              const regionVar = extractVarName(action.inVars[2]);
-              regionArea = variableContext[regionVar];
-              if (regionArea === undefined) {
-                const regionResolved = resolveValue(action.inVars[2]);
-                if (regionResolved && typeof regionResolved === 'object') {
-                  regionArea = regionResolved;
-                }
-              }
-            } else {
-              // 头像格式:头像1、头像2、区域
-              avatar1Name = action.inVars[0];
-              avatar2Name = action.inVars[1];
-              const regionVar = extractVarName(action.inVars[2]);
-              regionArea = variableContext[regionVar];
-              if (regionArea === undefined) {
-                const regionResolved = resolveValue(action.inVars[2]);
-                if (regionResolved && typeof regionResolved === 'object') {
-                  regionArea = regionResolved;
-                }
-              }
-            }
-            
-            // 验证区域格式
-            if (regionArea) {
-              if (typeof regionArea === 'string') {
-                try {
-                  regionArea = JSON.parse(regionArea);
-                } catch (e) {
-                  regionArea = null;
-                }
-              }
-              if (regionArea && typeof regionArea === 'object') {
-                if (!regionArea.topLeft || !regionArea.bottomRight) {
-                  regionArea = null;
-                }
-              }
-            }
-          } else if (action.inVars.length >= 2) {
-            // 两个参数:可能是 RGB格式 或 头像格式
-            const param1 = resolveValue(action.inVars[0]);
-            const param2 = resolveValue(action.inVars[1]);
-            
-            // 检查是否是RGB格式
-            const rgbPattern = /^\((\d+),(\d+),(\d+)\)$/;
-            if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) &&
-                typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
-              // RGB格式:第一个是好友RGB,第二个是我的RGB
-              friendRgb = param1.trim();
-              myRgb = param2.trim();
-            } else {
-              // 头像格式:头像1、头像2(无区域,使用全屏)
-              avatar1Name = action.inVars[0];
-              avatar2Name = action.inVars[1];
-            }
-          } else if (action.inVars.length === 1) {
-            // 一个参数:头像1(向后兼容)
-            avatar1Name = action.inVars[0];
-            avatar2Name = action.avatar2 || action.myAvatar;
-          }
-        } else {
-          // 使用旧参数
-          avatar1Name = action.avatar1 || action.friendAvatar;
-          avatar2Name = action.avatar2 || action.myAvatar;
-        }
-
-        if (avatar1Name) {
-          const avatar1Resolved = resolveValue(avatar1Name);
-          if (avatar1Resolved) {
-            // resources 作为根目录
-            avatar1Path = `${folderName}/resources/${avatar1Resolved}`;
-          }
-        }
-
-        if (avatar2Name) {
-          const avatar2Resolved = resolveValue(avatar2Name);
-          if (avatar2Resolved) {
-            // resources 作为根目录
-            avatar2Path = `${folderName}/resources/${avatar2Resolved}`;
-          }
-        }
-
-        // 调用 Func 目录下的执行函数
-        // 确保 regionArea 是对象格式(如果是字符串,尝试解析为 JSON)
-        let regionParam = regionArea;
-        if (regionArea && typeof regionArea === 'string') {
-          try {
-            regionParam = JSON.parse(regionArea);
-          } catch (e) {
-            // 解析失败,使用 null(函数内部会处理)
-            regionParam = null;
-          }
-        }
-        
-        const chatResult = await executeOcrChat({
-          device,
-          avatar1: avatar1Path,
-          avatar2: avatar2Path,
-          folderPath,
-          region: regionParam,  // 传递区域参数(对象格式,如果提供)
-          friendRgb: friendRgb,  // 传递好友RGB(如果提供)
-          myRgb: myRgb  // 传递我的RGB(如果提供)
-        });
-        
-        if (!chatResult.success) {
-          return { success: false, error: `提取消息记录失败: ${chatResult.error}` };
-        }
-
-        // 保存消息记录到变量(支持新的 outVars 格式)
-        let outputVarName = null;
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          outputVarName = extractVarName(action.outVars[0]);
-        } else if (action.variable) {
-          outputVarName = extractVarName(action.variable);
-        }
-
-        if (outputVarName) {
-          // 使用JSON字符串格式的消息记录
-          const messagesJson = chatResult.messagesJson || JSON.stringify(chatResult.messages || []);
-          variableContext[outputVarName] = messagesJson;
-          await logOutVars(action, variableContext, folderPath);
-        }
-
-        return { 
-          success: true, 
-          messages: chatResult.messages || [],
-          messagesJson: chatResult.messagesJson || JSON.stringify(chatResult.messages || []),
-          lastMessage: chatResult.messages && chatResult.messages.length > 0 ? chatResult.messages[chatResult.messages.length - 1] : null
-        };
-      }
-
-      case 'ai-generate': {
-        // AI生成
-        let prompt = resolveValue(action.prompt);
-        
-        // 如果提供了 inVars,替换 prompt 中的变量
-        if (action.inVars && Array.isArray(action.inVars)) {
-          for (let i = 0; i < action.inVars.length; i++) {
-            const varName = extractVarName(action.inVars[i]);
-            const varValue = variableContext[varName];
-            
-            // 替换 prompt 中的变量占位符(例如 {currentMessage})
-            if (varValue !== undefined && varValue !== null) {
-              const placeholder = `{${varName}}`;
-              // 检查是否是空数组 JSON 字符串
-              let replaceValue = String(varValue);
-              if (typeof varValue === 'string' && varValue.trim() === '[]') {
-                try {
-                  const parsed = JSON.parse(varValue);
-                  if (Array.isArray(parsed) && parsed.length === 0) {
-                    replaceValue = '';
-                  }
-                } catch (e) {
-                  // 不是 JSON,继续使用原值
-                }
-              }
-              prompt = prompt.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), replaceValue);
-            }
-          }
-        }
-        
-        // 替换 prompt 中的变量(包括 historySummary 和其他变量)
-        // 注意:这里需要先替换 historySummary,因为它可能包含在 prompt 模板中
-        if (prompt.includes('{historySummary}')) {
-          let historySummary = variableContext['historySummary'] || '';
-          
-          // 如果变量中没有历史总结,尝试从文件中读取
-          if (!historySummary) {
-            historySummary = await getHistorySummary(folderPath);
-            // 如果从文件读取成功,也更新变量上下文
-            if (historySummary) {
-              variableContext['historySummary'] = historySummary;
-            }
-          }
-          
-          prompt = prompt.replace(/{historySummary}/g, historySummary);
-        }
-        
-        // 替换 prompt 中所有剩余的变量占位符(支持 {{variable}} 和 {variable} 格式)
-        prompt = replaceVariablesInString(prompt, variableContext);
-        
-        try {
-          // 调用AI API(使用现有的GPT API)
-          const requestBody = {
-            prompt: prompt,
-            modelName: action.model || 'gpt-5-nano-ca'
-          };
-          
-          const response = await fetch('https://ai-anim.com/api/text2textByModel', {
-            method: 'POST',
-            headers: { 'Content-Type': 'application/json' },
-            body: JSON.stringify(requestBody)
-          });
-
-          if (!response.ok) {
-            const errorText = await response.text();
-            return { success: false, error: `AI请求失败: ${response.statusText}` };
-          }
-
-          const data = await response.json();
-          
-          // API 返回格式:{ success: true, data: { output_text: "..." } }
-          // 或者:{ data: { output_text: "..." } }
-          // 优先从 data.data.output_text 提取,然后是 data.output_text,最后是其他字段
-          let rawResult = '';
-          if (data.data && typeof data.data === 'object' && data.data.output_text) {
-            rawResult = data.data.output_text;
-          } else if (data.output_text) {
-            rawResult = data.output_text;
-          } else if (data.text) {
-            rawResult = data.text;
-          } else if (data.content) {
-            rawResult = data.content;
-          } else if (data.data && typeof data.data === 'string') {
-            rawResult = data.data;
-          } else {
-            // 如果都没有,尝试从整个响应中提取
-            rawResult = JSON.stringify(data);
-          }
-          
-          // 确保 rawResult 是字符串
-          rawResult = rawResult ? String(rawResult) : '';
-          
-          // 解析AI返回的JSON格式回复
-          let result = rawResult;
-          try {
-            // 尝试从返回文本中提取JSON
-            // 方法1: 尝试直接解析整个文本
-            try {
-              const jsonResult = JSON.parse(rawResult.trim());
-              if (jsonResult.reply) {
-                result = jsonResult.reply;
-              }
-            } catch (e) {
-              // 方法2: 尝试从代码块中提取JSON
-              const codeBlockMatch = rawResult.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
-              if (codeBlockMatch) {
-                try {
-                  const jsonResult = JSON.parse(codeBlockMatch[1]);
-                  if (jsonResult.reply) {
-                    result = jsonResult.reply;
-                  }
-                } catch (e2) {
-                  // JSON解析失败,继续使用原始文本
-                }
-              } else {
-                // 方法3: 尝试从文本中查找JSON对象
-                const jsonMatch = rawResult.match(/\{\s*"reply"\s*:\s*"([^"]+)"\s*\}/);
-                if (jsonMatch) {
-                  result = jsonMatch[1];
-                } else {
-                  // 方法4: 尝试查找单行的JSON格式
-                  const lines = rawResult.split('\n').map(line => line.trim()).filter(line => line);
-                  for (const line of lines) {
-                    if (line.startsWith('{') && line.includes('"reply"')) {
-                      try {
-                        const jsonResult = JSON.parse(line);
-                        if (jsonResult.reply) {
-                          result = jsonResult.reply;
-                          break;
-                        }
-                      } catch (e3) {
-                        // 继续尝试下一行
-                      }
-                    }
-                  }
-                }
-              }
-            }
-          } catch (parseError) {
-            // 如果解析失败,使用原始文本
-          }
-          
-          // 保存到变量(支持 outVars 格式)
-          // outVars[0] 是 AI 生成的结果,outVars[1](如果有)是 aiCallBack 变量
-          if (action.outVars && Array.isArray(action.outVars)) {
-            // 第一个输出变量保存 AI 生成结果
-            if (action.outVars.length > 0) {
-              const outputVarName = extractVarName(action.outVars[0]);
-              if (outputVarName) {
-                variableContext[outputVarName] = result;
-              }
-            }
-            // 第二个输出变量(如果有)设置为 1 表示 AI 生成完成(aiCallBack)
-            if (action.outVars.length > 1) {
-              const callbackVarName = extractVarName(action.outVars[1]);
-              if (callbackVarName) {
-                variableContext[callbackVarName] = 1;
-              }
-            }
-            await logOutVars(action, variableContext, folderPath);
-          } else if (action.variable) {
-            // 向后兼容:使用旧的 variable 字段
-            const outputVarName = extractVarName(action.variable);
-            if (outputVarName) {
-              variableContext[outputVarName] = result;
-            }
-          }
-          
-          // 向后兼容:如果 aiCallBack 在 inVars 中提供,也设置它(但优先使用 outVars)
-          if (!action.outVars || !Array.isArray(action.outVars) || action.outVars.length <= 1) {
-            if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 1) {
-              const callbackVarName = extractVarName(action.inVars[1]);
-              if (callbackVarName) {
-                variableContext[callbackVarName] = 1;
-              }
-            }
-          }
-
-          return { success: true, result };
-        } catch (error) {
-          return { success: false, error: `AI生成失败: ${error.message}` };
-        }
-      }
-
-      case 'save-messages':
-
-      case 'generate-summary':
-      case 'generate-history-summary': { // 向后兼容
-        // 生成消息记录的AI总结
-        if (!action.variable) {
-          return { success: false, error: '缺少变量名' };
-        }
-
-        const messages = variableContext[action.variable];
-        if (!messages) {
-          return { success: false, error: `变量 ${action.variable} 不存在或为空` };
-        }
-
-        const modelName = action.model || 'gpt-5-nano-ca';
-        const result = await generateHistorySummary(messages, folderPath, modelName);
-        
-        if (!result.success) {
-          return { success: false, error: `生成消息记录总结失败: ${result.error}` };
-        }
-
-        // 保存总结到变量
-        if (action.summaryVariable) {
-          variableContext[action.summaryVariable] = result.summary;
-          // 消息记录总结已保存到变量
-        }
-
-        return { success: true, summary: result.summary };
-      }
-
-      case 'set': {
-        // 设置变量
-        if (action.variable) {
-          const varName = extractVarName(action.variable);
-          // 如果 value 是字符串且包含算术运算符,尝试计算表达式
-          let finalValue = action.value;
-          if (typeof action.value === 'string' && /[+\-*/]/.test(action.value)) {
-            finalValue = evaluateExpression(action.value, variableContext);
-          } else {
-            // 否则使用 resolveValue 解析变量引用
-            finalValue = resolveValue(action.value, variableContext);
-          }
-          variableContext[varName] = finalValue;
-        }
-        return { success: true };
-      }
-
-      case 'echo':
-      case 'log': { // 向后兼容
-        // 打印信息到 console.log 和 UI
-        // 支持 inVars 或 value 字段
-        let message = '';
-        
-        if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 0) {
-          // 从 inVars 中读取变量值
-          const messages = action.inVars.map(varWithBraces => {
-            const varName = extractVarName(varWithBraces);
-            const varValue = variableContext[varName];
-            // 如果变量包含 {variable} 或 {{variable}} 格式,尝试解析为变量名
-            if (varWithBraces.startsWith('{') && varWithBraces.endsWith('}')) {
-              return varValue !== undefined ? String(varValue) : varWithBraces;
-            }
-            // 如果不是变量格式,直接使用原值
-            return varWithBraces;
-          });
-          message = messages.join(' ');
-        } else if (action.value) {
-          // 使用 value 字段,支持变量替换(只支持 {{variable}} 格式,用于字符串拼接)
-          message = replaceVariablesInString(action.value, variableContext);
-        } else {
-          // 如果没有提供任何内容,输出空字符串
-          message = '';
-        }
-        
-        // 输出到 console.log
-        console.log(message);
-        
-        // 发送 log 消息事件到 UI
-        const logEvent = new CustomEvent('log-message', {
-          detail: {
-            message: message
-          }
-        });
-        window.dispatchEvent(logEvent);
-        
-        return { success: true };
-      }
-
-      case 'image-region-location': {
-        // 图像区域定位
-        // 支持新的 inVars/outVars 格式(都可以为空)
-        let screenshotPath = action.screenshot;
-        let regionPath = action.region;
-        
-        // 如果提供了 inVars,从变量中读取或直接使用
-        if (action.inVars && Array.isArray(action.inVars)) {
-          if (action.inVars.length === 1) {
-            // 如果只有一个参数,将其作为 region(区域图片)
-            const firstVar = extractVarName(action.inVars[0]);
-            const firstValue = variableContext[firstVar];
-            if (firstValue && typeof firstValue === 'string' && !firstValue.includes('{')) {
-              regionPath = firstValue;
-            } else {
-              // 如果变量不存在,直接使用 inVars[0] 作为路径
-              regionPath = action.inVars[0];
-            }
-            // screenshot 需要自动从设备获取(传递 null)
-            screenshotPath = null;
-          } else if (action.inVars.length >= 2) {
-            // 如果有两个参数,第一个是 screenshot,第二个是 region
-            const screenshotVar = extractVarName(action.inVars[0]);
-            const screenshotValue = variableContext[screenshotVar];
-            if (screenshotValue && typeof screenshotValue === 'string' && !screenshotValue.includes('{')) {
-              screenshotPath = screenshotValue;
-            } else {
-              screenshotPath = action.inVars[0];
-            }
-            
-            const regionVar = extractVarName(action.inVars[1]);
-            const regionValue = variableContext[regionVar];
-            if (regionValue && typeof regionValue === 'string' && !regionValue.includes('{')) {
-              regionPath = regionValue;
-            } else {
-              regionPath = action.inVars[1];
-            }
-          }
-        }
-        
-        // 如果没有提供路径,使用默认值或从 action 中读取
-        if (screenshotPath !== null && !screenshotPath) screenshotPath = action.screenshot;
-        if (!regionPath) regionPath = action.region;
-        
-        if (!regionPath) {
-          return { success: false, error: '缺少区域截图路径' };
-        }
-        
-        // 如果 screenshotPath 为 null,需要自动从设备获取
-        if (screenshotPath === null && !device) {
-          return { success: false, error: '缺少完整截图路径,且无法自动获取设备截图(缺少设备ID)' };
-        }
-
-        // 调用 Func 目录下的执行函数
-        const result = await executeImageRegionLocation({
-          device,
-          screenshot: screenshotPath,
-          region: regionPath,
-          folderPath
-        });
-
-        if (!result.success) {
-          return { success: false, error: `图像区域定位失败: ${result.error}` };
-        }
-
-        // 保存结果到变量(支持 outVars 格式)
-        // image-region-location 返回四个顶点坐标
-        let outputVarName = null;
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          outputVarName = extractVarName(action.outVars[0]);
-        } else if (action.variable) {
-          outputVarName = extractVarName(action.variable);
-        }
-        
-        if (outputVarName) {
-          // 将对象转换为 JSON 字符串(只允许 string 或 int 类型)
-          if (result.corners && typeof result.corners === 'object') {
-            variableContext[outputVarName] = JSON.stringify(result.corners);
-          } else {
-            variableContext[outputVarName] = '';
-          }
-          // 图像区域定位结果已保存到变量
-          await logOutVars(action, variableContext, folderPath);
-        }
-
-        return { success: true, result: result.corners };
-      }
-
-      case 'image-center-location': {
-        // 图像中心点定位
-        // 支持新的 inVars/outVars 格式(都可以为空)
-        let templatePath = action.template;
-        
-        // 如果提供了 inVars,从变量中读取或直接使用
-        if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 0) {
-          const templateVar = extractVarName(action.inVars[0]);
-          const templateValue = variableContext[templateVar];
-          if (templateValue && typeof templateValue === 'string' && !templateValue.includes('{')) {
-            templatePath = templateValue;
-          } else {
-            // 如果变量不存在,直接使用 inVars[0] 作为路径
-            templatePath = action.inVars[0];
-          }
-        }
-        
-        // 如果没有提供路径,使用默认值或从 action 中读取
-        if (!templatePath) templatePath = action.template;
-        
-        if (!templatePath) {
-          return { success: false, error: '缺少模板图片路径' };
-        }
-        
-        if (!device) {
-          return { success: false, error: '缺少设备 ID,无法自动获取截图' };
-        }
-
-        // 调用 Func 目录下的执行函数
-        const result = await executeImageCenterLocation({
-          device,
-          template: templatePath,
-          folderPath
-        });
-
-        if (!result.success) {
-          return { success: false, error: `图像中心点定位失败: ${result.error}` };
-        }
-
-        // 保存结果到变量(支持 outVars 格式)
-        let outputVarName = null;
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          outputVarName = extractVarName(action.outVars[0]);
-        } else if (action.variable) {
-          outputVarName = extractVarName(action.variable);
-        }
-        
-        if (outputVarName) {
-          // 保存中心点坐标为字符串(JSON 格式):只允许 number 或 string
-          if (result.center && typeof result.center === 'object' && result.center.x !== undefined && result.center.y !== undefined) {
-            variableContext[outputVarName] = JSON.stringify({ x: result.center.x, y: result.center.y });
-          } else {
-            variableContext[outputVarName] = '';
-          }
-          // 图像中心点定位结果已保存到变量
-          await logOutVars(action, variableContext, folderPath);
-        }
-
-        return { success: true, result: result.center };
-      }
-
-      case 'image-area-cropping': {
-        // 裁剪图片区域
-        // 支持新的 inVars 格式
-        console.log('[action-parser] image-area-cropping 开始处理,action:', action);
-        let area = action.area;
-        let savePath = action.savePath;
-        
-        // 如果提供了 inVars,从变量中读取或直接使用
-        if (action.inVars && Array.isArray(action.inVars)) {
-          console.log('[action-parser] image-area-cropping inVars:', action.inVars);
-          if (action.inVars.length > 0) {
-            const areaVar = extractVarName(action.inVars[0]);
-            console.log('[action-parser] image-area-cropping 提取area变量名:', areaVar);
-            const areaValue = variableContext[areaVar];
-            console.log('[action-parser] image-area-cropping area变量值:', areaValue ? (typeof areaValue === 'string' ? areaValue.substring(0, 200) : areaValue) : 'undefined');
-            if (areaValue !== undefined) {
-              area = areaValue;
-            } else {
-              area = resolveValue(action.inVars[0]);
-              console.log('[action-parser] image-area-cropping resolveValue结果:', area ? (typeof area === 'string' ? area.substring(0, 200) : area) : 'null');
-            }
-          }
-          
-          if (action.inVars.length > 1) {
-            const savePathVar = extractVarName(action.inVars[1]);
-            console.log('[action-parser] image-area-cropping 提取savePath变量名:', savePathVar);
-            const savePathValue = variableContext[savePathVar];
-            console.log('[action-parser] image-area-cropping savePath变量值:', savePathValue);
-            if (savePathValue !== undefined) {
-              savePath = savePathValue;
-            } else {
-              savePath = resolveValue(action.inVars[1]);
-              console.log('[action-parser] image-area-cropping resolveValue savePath结果:', savePath);
-            }
-          }
-        }
-
-        console.log('[action-parser] image-area-cropping 最终参数:', {
-          area: area ? (typeof area === 'string' ? area.substring(0, 200) : area) : 'null',
-          savePath,
-          folderPath
-        });
-
-        if (!area) {
-          console.error('[action-parser] image-area-cropping 缺少 area 参数');
-          return { success: false, error: 'image-area-cropping 缺少 area 参数' };
-        }
-
-        if (!savePath) {
-          console.error('[action-parser] image-area-cropping 缺少 savePath 参数');
-          return { success: false, error: 'image-area-cropping 缺少 savePath 参数' };
-        }
-
-        const result = await executeImageAreaCropping({
-          area,
-          savePath,
-          folderPath,
-          device // 传递设备ID,用于获取最新截图
-        });
-
-        if (!result.success) {
-          // 输出详细错误信息以便调试
-          const errorMsg = `image-area-cropping 失败: ${result.error}`;
-          await logMessage(errorMsg, folderPath);
-          return { success: false, error: result.error };
-        }
-        
-        // 成功时也记录日志
-        await logMessage(`image-area-cropping 成功: 已保存到 ${savePath}`, folderPath);
-
-        // 如果提供了 outVars,可以将结果保存到变量
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          const outputVarName = extractVarName(action.outVars[0]);
-          if (outputVarName) {
-            // 只允许 string 或 int 类型,保存成功标志为 "1" 或 1
-            variableContext[outputVarName] = result.success ? '1' : '0';
-          }
-        }
-
-        await logOutVars(action, variableContext, folderPath);
-        return { success: true };
-      }
-
-      case 'read-last-message': {
-        // 读取最后一条消息
-        // 支持新的 inVars/outVars 格式,也支持 inputVars/outputVars
-        const inputVars = action.inVars || action.inputVars || [];
-        const outputVars = action.outVars || action.outputVars || [];
-        
-        let textVar = action.textVariable;
-        let senderVar = action.senderVariable;
-        let inputVar = null;
-        
-        if (outputVars.length > 0) {
-          textVar = extractVarName(outputVars[0]);
-        }
-        if (outputVars.length > 1) {
-          senderVar = extractVarName(outputVars[1]);
-        }
-        if (inputVars.length > 0) {
-          inputVar = extractVarName(inputVars[0]);
-        }
-        
-        if (!textVar && !senderVar) {
-          return { success: false, error: 'read-last-message 缺少 textVariable 或 senderVariable 参数' };
-        }
-
-        // 如果提供了 inputVar,从变量中读取数据
-        let inputData = null;
-        if (inputVar && variableContext[inputVar] !== undefined) {
-          inputData = variableContext[inputVar];
-        }
-
-        // 确保 inputData 是字符串类型(如果是对象或数组,转换为 JSON 字符串)
-        let inputDataString = inputData;
-        if (inputData !== null && inputData !== undefined) {
-          if (typeof inputData === 'string') {
-            inputDataString = inputData;
-          } else if (Array.isArray(inputData) || typeof inputData === 'object') {
-            inputDataString = JSON.stringify(inputData);
-          } else {
-            inputDataString = String(inputData);
-          }
-        } else {
-          inputDataString = null; // null 表示从文件读取
-        }
-        
-        const result = await executeReadLastMessage({
-          folderPath,
-          inputData: inputDataString, // 确保是字符串类型,如果为 null,则从 history 文件夹读取
-          textVariable: textVar,
-          senderVariable: senderVar
-        });
-
-        if (!result.success) {
-          return { success: false, error: result.error };
-        }
-
-        // 保存消息文本和发送者到变量
-        if (textVar) {
-          variableContext[textVar] = result.text;
-          // 最后一条消息文本已保存到变量
-        }
-        if (senderVar) {
-          variableContext[senderVar] = result.sender;
-          // 最后一条消息发送者已保存到变量
-        }
-        await logOutVars(action, variableContext, folderPath);
-
-        return { success: true, text: result.text, sender: result.sender };
-      }
-
-      case 'read-txt':
-      case 'read-text': { // 向后兼容别名
-        // 读取根目录下的文本文件
-        // 支持新的 inVars/outVars 格式
-        let filePath = action.filePath;
-        let varName = action.variable;
-
-        if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 0) {
-          // 从 inVars 读取文件路径(可能是变量或直接路径)
-          const filePathVar = extractVarName(action.inVars[0]);
-          const filePathValue = variableContext[filePathVar];
-          if (filePathValue !== undefined) {
-            filePath = filePathValue;
-          } else {
-            filePath = resolveValue(action.inVars[0]);
-          }
-        }
-
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          varName = extractVarName(action.outVars[0]);
-        } else if (action.variable) {
-          varName = extractVarName(action.variable);
-        }
-
-        if (!filePath) {
-          return { success: false, error: 'read-txt 缺少 filePath 参数' };
-        }
-
-        if (!varName) {
-          return { success: false, error: 'read-txt 缺少 variable 参数' };
-        }
-
-        const result = await executeReadTxt({
-          filePath,
-          folderPath
-        });
-
-        if (!result.success) {
-          return { success: false, error: result.error };
-        }
-
-        // 保存文件内容到变量(确保始终是字符串类型,即使是空字符串)
-        const content = result.content || '';
-        variableContext[varName] = typeof content === 'string' ? content : String(content);
-        // 确保变量始终是字符串类型(即使是空字符串)
-        if (variableContext[varName] === undefined || variableContext[varName] === null) {
-          variableContext[varName] = '';
-        }
-        await logOutVars(action, variableContext, folderPath);
-
-        return { success: true, content: result.content };
-      }
-
-      case 'smart-chat-append': {
-        // 智能合并历史聊天记录和当前聊天记录,自动检测并去除连续重合部分后返回新的聊天记录字符串
-        // 支持新的 inVars/outVars 格式
-        let history = action.history;
-        let current = action.current;
-
-        if (action.inVars && Array.isArray(action.inVars)) {
-          // 从 inVars 读取历史记录和当前记录(可能是变量或直接值)
-          if (action.inVars.length > 0) {
-            const historyVar = extractVarName(action.inVars[0]);
-            const historyValue = variableContext[historyVar];
-            if (historyValue !== undefined) {
-              history = historyValue;
-            } else {
-              history = resolveValue(action.inVars[0]);
-            }
-          }
-          
-          if (action.inVars.length > 1) {
-            const currentVar = extractVarName(action.inVars[1]);
-            const currentValue = variableContext[currentVar];
-            if (currentValue !== undefined) {
-              current = currentValue;
-            } else {
-              current = resolveValue(action.inVars[1]);
-            }
-          }
-        }
-
-        if (history === undefined || history === null) {
-          history = '';
-        }
-        if (current === undefined || current === null) {
-          current = '';
-        }
-
-        const result = await executeSmartChatAppend({
-          history: typeof history === 'string' ? history : String(history),
-          current: typeof current === 'string' ? current : String(current)
-        });
-
-        if (!result.success) {
-          return { success: false, error: result.error };
-        }
-
-        // 保存结果到变量
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          const outputVarName = extractVarName(action.outVars[0]);
-          if (outputVarName && result.result) {
-            variableContext[outputVarName] = result.result;
-          }
-        } else if (action.variable) {
-          const varName = extractVarName(action.variable);
-          if (varName && result.result) {
-            variableContext[varName] = result.result;
-          }
-        }
-
-        return { success: true, result: result.result };
-      }
-
-      case 'save-txt':
-      case 'save-text': { // 向后兼容别名
-        // 保存字符串为文本文件
-        // 支持新的 inVars/outVars 格式
-        let filePath = action.filePath;
-        let content = action.content;
-
-        if (action.inVars && Array.isArray(action.inVars)) {
-          // 从 inVars 读取内容和文件路径(可能是变量或直接值)
-          // 入参顺序:第一个参数是内容,第二个参数是文件路径
-          if (action.inVars.length > 0) {
-            // 第一个参数是内容
-            const contentVar = extractVarName(action.inVars[0]);
-            const contentValue = variableContext[contentVar];
-            if (contentValue !== undefined) {
-              content = contentValue;
-            } else {
-              content = resolveValue(action.inVars[0]);
-            }
-          }
-          
-          if (action.inVars.length > 1) {
-            // 第二个参数是文件路径
-            const filePathVar = extractVarName(action.inVars[1]);
-            const filePathValue = variableContext[filePathVar];
-            if (filePathValue !== undefined) {
-              filePath = filePathValue;
-            } else {
-              filePath = resolveValue(action.inVars[1]);
-            }
-          }
-        }
-
-        if (!filePath) {
-          return { success: false, error: 'save-txt 缺少 filePath 参数' };
-        }
-
-        if (content === undefined || content === null) {
-          return { success: false, error: 'save-txt 缺少 content 参数' };
-        }
-
-        const result = await executeSaveTxt({
-          filePath,
-          content,
-          folderPath
-        });
-
-        if (!result.success) {
-          return { success: false, error: result.error };
-        }
-
-        // 如果提供了 outVars,可以将结果保存到变量(虽然通常不需要)
-        if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
-          const outputVarName = extractVarName(action.outVars[0]);
-          if (outputVarName) {
-            // 只允许 string 或 int 类型,保存成功标志为 "1" 或 1
-            variableContext[outputVarName] = result.success ? '1' : '0';
-          }
-        }
-
-        await logOutVars(action, variableContext, folderPath);
-        return { success: true };
-      }
-
-      case 'delay': {
-        // 延迟
-        const delayMs = parseDelayString(action.value || action.delay || '0s');
-        if (delayMs > 0) {
-          await new Promise(resolve => setTimeout(resolve, delayMs));
-        }
-        return { success: true };
-      }
-
-      case 'swipe': {
-        // 滑动操作
-        if (!window.electronAPI || !window.electronAPI.sendSwipe) {
-          return { success: false, error: '滑动 API 不可用' };
-        }
-
-        const { x1, y1, x2, y2 } = calculateSwipeCoordinates(
-          action.value,
-          resolution.width,
-          resolution.height
-        );
-
-        const swipeResult = await window.electronAPI.sendSwipe(device, x1, y1, x2, y2, 300);
-        
-        if (!swipeResult.success) {
-          return { success: false, error: `滑动失败: ${swipeResult.error}` };
-        }
-
-        // 成功滑动
-        return { success: true };
-      }
-
-      case 'string-press': {
-        // 向后兼容:文字识别并点击
-        if (!window.electronAPI || !window.electronAPI.findTextAndGetCoordinate) {
-          return { success: false, error: '文字识别 API 不可用' };
-        }
-
-        const matchResult = await window.electronAPI.findTextAndGetCoordinate(device, action.value);
-        
-        if (!matchResult.success) {
-          return { success: false, error: `文字识别失败: ${matchResult.error}` };
-        }
-
-        const { clickPosition } = matchResult;
-        const { x, y } = clickPosition;
-
-        if (!window.electronAPI || !window.electronAPI.sendTap) {
-          return { success: false, error: '点击 API 不可用' };
-        }
-
-        const tapResult = await window.electronAPI.sendTap(device, x, y);
-        
-        if (!tapResult.success) {
-          return { success: false, error: `点击失败: ${tapResult.error}` };
-        }
-
-        // 成功点击文字
-        return { success: true };
-      }
-
-      case 'scroll': {
-        // 滚动操作(小幅度滚动)
-        if (!window.electronAPI || !window.electronAPI.sendScroll) {
-          return { success: false, error: '滚动 API 不可用' };
-        }
-
-        const scrollResult = await window.electronAPI.sendScroll(
-          device,
-          action.value,
-          resolution.width,
-          resolution.height,
-          DEFAULT_SCROLL_DISTANCE,
-          500
-        );
-        
-        if (!scrollResult.success) {
-          return { success: false, error: `滚动失败: ${scrollResult.error}` };
-        }
-
-        // 成功滚动
-        return { success: true };
-      }
-
-      default:
-        return { success: false, error: `未知的操作类型: ${action.type}` };
-    }
-  } catch (error) {
-    return { success: false, error: error.message };
-  }
-}
-
-/**
- * 执行操作序列(支持嵌套和条件)
- * @param {Array} actions - 解析后的操作列表
- * @param {string} device - 设备 ID
- * @param {string} folderPath - 文件夹路径
- * @param {Object} resolution - 设备分辨率
- * @param {number} stepInterval - 步骤间隔时间(毫秒),默认1秒
- * @param {Function} onStepComplete - 每步完成后的回调函数
- * @param {Function} shouldStop - 检查是否应该停止的函数
- * @param {number} depth - 嵌套深度(用于递归)
- * @returns {Promise<Object>} 执行结果 {success, error?, completedSteps}
- */
-export async function executeActionSequence(
-  actions,
-  device,
-  folderPath,
-  resolution,
-  stepInterval = DEFAULT_STEP_INTERVAL,
-  onStepComplete = null,
-  shouldStop = null,
-  depth = 0
-) {
-  // 如果是顶层(depth === 0),重置全局步骤计数器和变量初始化标志
-  if (depth === 0) {
-    globalStepCounter = 0;
-    // 重置变量初始化标志,允许在新工作流开始时重新初始化
-    variableContextInitialized = false;
-    // 保存当前工作流文件夹路径,用于日志记录
-    currentWorkflowFolderPath = folderPath;
-    // 记录工作流开始执行
-    await logMessage('==================== 工作流开始执行 ====================', folderPath);
-  }
-  
-  let completedSteps = 0;
-  const stepPrefix = depth > 0 ? '  '.repeat(depth) : '';
-
-  for (let i = 0; i < actions.length; i++) {
-    // 检查是否应该停止
-    if (shouldStop && shouldStop()) {
-      // 执行被停止
-      return { success: false, error: '执行被停止', completedSteps };
-    }
-
-    const action = actions[i];
-    
-    // 处理特殊操作类型
-    if (action.type === 'schedule') {
-      // schedule 操作:根据 condition 中的 interval 和 repeat 执行动作
-      const condition = action.condition || {};
-      const intervalStr = condition.interval || '0s';
-      const repeat = condition.repeat !== undefined ? condition.repeat : 1;
-      const actionsToExecute = action.interval || [];
-      
-      // 解析间隔时间
-      const intervalMs = parseDelayString(intervalStr) || 0;
-      
-      // 确定循环次数(-1 表示无限循环)
-      const maxIterations = repeat === -1 ? Infinity : (typeof repeat === 'number' ? repeat : 1);
-      let iteration = 0;
-      
-      while (iteration < maxIterations) {
-        if (shouldStop && shouldStop()) {
-          return { success: false, error: '执行被停止', completedSteps };
-        }
-        
-        iteration++;
-        
-        // 如果不是第一次迭代,等待间隔时间
-        if (iteration > 1 && intervalMs > 0) {
-          await logMessage(`${stepPrefix}等待 ${intervalMs}ms 后执行第 ${iteration} 次循环...`, folderPath);
-          let remainingTime = intervalMs;
-          const countdownInterval = 100;
-          
-          while (remainingTime > 0) {
-            if (shouldStop && shouldStop()) {
-              return { success: false, error: '执行被停止', completedSteps };
-            }
-            const waitTime = Math.min(countdownInterval, remainingTime);
-            await new Promise(resolve => setTimeout(resolve, waitTime));
-            remainingTime -= waitTime;
-          }
-        }
-        
-        // 执行动作序列
-        if (actionsToExecute.length > 0) {
-          const result = await executeActionSequence(
-            actionsToExecute,
-            device,
-            folderPath,
-            resolution,
-            stepInterval,
-            onStepComplete,
-            shouldStop,
-            depth + 1
-          );
-          
-          if (!result.success) {
-            return result;
-          }
-          completedSteps += result.completedSteps || 0;
-        }
-      }
-      
-      continue;
-    }
-
-    if (action.type === 'if') {
-      const conditionResult = evaluateCondition(action.condition);
-      
-      // 支持 ture(拼写错误)和 false 作为 then 和 else 的别名
-      const actionsToExecute = conditionResult ? (action.then || action.ture || []) : (action.else || action.false || []);
-      
-      if (actionsToExecute.length > 0) {
-        const result = await executeActionSequence(
-          actionsToExecute,
-          device,
-          folderPath,
-          resolution,
-          stepInterval,
-          onStepComplete,
-          shouldStop,
-          depth + 1
-        );
-        
-        if (!result.success) {
-          return result;
-        }
-        completedSteps += result.completedSteps || 0;
-      }
-      continue;
-    }
-
-    if (action.type === 'for') {
-      const items = Array.isArray(action.items) ? action.items : [];
-      
-      for (const item of items) {
-        if (shouldStop && shouldStop()) {
-          return { success: false, error: '执行被停止', completedSteps };
-        }
-
-        // 设置循环变量
-        if (action.variable) {
-          variableContext[action.variable] = item;
-        }
-
-        if (action.body && action.body.length > 0) {
-          const result = await executeActionSequence(
-            action.body,
-            device,
-            folderPath,
-            resolution,
-            stepInterval,
-            onStepComplete,
-            shouldStop,
-            depth + 1
-          );
-          
-          if (!result.success) {
-            return result;
-          }
-          completedSteps += result.completedSteps || 0;
-        }
-      }
-      continue;
-    }
-
-    if (action.type === 'while') {
-      while (evaluateCondition(action.condition)) {
-        if (shouldStop && shouldStop()) {
-          return { success: false, error: '执行被停止', completedSteps };
-        }
-
-        if (action.body && action.body.length > 0) {
-          const result = await executeActionSequence(
-            action.body,
-            device,
-            folderPath,
-            resolution,
-            stepInterval,
-            onStepComplete,
-            shouldStop,
-            depth + 1
-          );
-          
-          if (!result.success) {
-            return result;
-          }
-          completedSteps += result.completedSteps || 0;
-        }
-      }
-      continue;
-    }
-
-    // 普通操作
-    const times = action.times || 1;
-    
-    // 发送步骤开始执行事件
-    if (onStepComplete) {
-      const stepName = getActionName(action);
-      onStepComplete(i + 1, actions.length, stepName, 0, times, 0);
-    }
-    
-    // 计算等待时间(根据 data 和 delay)
-    const waitTime = calculateWaitTime(action.data, action.delay);
-    
-    if (waitTime > 0) {
-      const waitSeconds = Math.round(waitTime / 1000);
-      await logMessage(`${stepPrefix}步骤 ${i + 1}/${actions.length} 等待 ${waitSeconds} 秒后执行...`, folderPath);
-      
-      // 在等待期间也更新倒计时
-      let remainingTime = waitTime;
-      const countdownInterval = 100;
-      const stepName = getActionName(action);
-      
-      while (remainingTime > 0) {
-        if (shouldStop && shouldStop()) {
-          return { success: false, error: '执行被停止', completedSteps };
-        }
-        
-        if (onStepComplete) {
-          onStepComplete(i + 1, actions.length, stepName, remainingTime, times, 0);
-        }
-        
-        const waitTimeChunk = Math.min(countdownInterval, remainingTime);
-        await new Promise(resolve => setTimeout(resolve, waitTimeChunk));
-        remainingTime -= waitTimeChunk;
-      }
-    }
-
-    // 根据 times 重复执行操作
-    for (let t = 0; t < times; t++) {
-      // 检查是否应该停止
-      if (shouldStop && shouldStop()) {
-        await logMessage(`${stepPrefix}执行被停止`, folderPath);
-        return { success: false, error: '执行被停止', completedSteps };
-      }
-
-      // 发送步骤执行中事件(包含当前执行次数)
-      if (onStepComplete) {
-        const stepName = getActionName(action);
-        onStepComplete(i + 1, actions.length, stepName, 0, times, t + 1);
-      }
-
-      // 使用全局步骤计数器
-      globalStepCounter++;
-      const currentStepNumber = globalStepCounter;
-      
-      // 记录步骤开始时间
-      const stepStartTime = Date.now();
-      const startTimeStr = new Date(stepStartTime).toLocaleString('zh-CN', {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        hour: '2-digit',
-        minute: '2-digit',
-        second: '2-digit',
-        hour12: false
-      });
-      
-      // 获取操作类型名称
-      const typeName = getActionName(action);
-      
-      // 打印步骤开始执行(包含系统时间)
-      await logMessage(`开始执行:${typeName} [系统时间: ${startTimeStr}]`, folderPath);
-
-      // 执行操作
-      const result = await executeAction(action, device, folderPath, resolution);
-
-      // 记录步骤结束时间
-      const stepEndTime = Date.now();
-      const endTimeStr = new Date(stepEndTime).toLocaleString('zh-CN', {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        hour: '2-digit',
-        minute: '2-digit',
-        second: '2-digit',
-        hour12: false
-      });
-      const stepDuration = (stepEndTime - stepStartTime) / 1000; // 转换为秒
-      
-      // 打印步骤执行完成(包含系统时间和执行时长)
-      // 如果时长超过 0.1 秒,才打印结束日志
-      if (stepDuration > 0.1) {
-        const endMessage = `结束执行:${typeName} 时长:${stepDuration.toFixed(2)}秒 [系统时间: ${endTimeStr}]`;
-        await logMessage(endMessage, folderPath);
-      }
-
-      if (!result.success) {
-        // 失败时,如果时长超过 0.1 秒才打印结束日志
-        if (stepDuration > 0.1) {
-          await logMessage(`结束执行:${typeName} 时长:${stepDuration.toFixed(2)}秒`, folderPath);
-        }
-        return { success: false, error: result.error, completedSteps: i };
-      }
-
-      // 如果不是最后一次重复,等待一小段时间
-      if (t < times - 1) {
-        await new Promise(resolve => setTimeout(resolve, 500));
-      }
-    }
-
-    completedSteps++;
-
-    // 调用完成回调
-    if (onStepComplete) {
-      const stepName = getActionName(action);
-      onStepComplete(i + 1, actions.length, stepName, 0, times, times);
-    }
-
-    // 如果不是最后一步,等待间隔时间
-    if (i < actions.length - 1) {
-      let remainingTime = stepInterval;
-      const countdownInterval = 100;
-      const nextStepName = getActionName(actions[i + 1]);
-      const nextTimes = actions[i + 1].times || 1;
-      
-      while (remainingTime > 0) {
-        if (shouldStop && shouldStop()) {
-          return { success: false, error: '执行被停止', completedSteps };
-        }
-        
-        if (onStepComplete) {
-          onStepComplete(i + 1, actions.length, nextStepName, remainingTime, nextTimes, 0);
-        }
-        
-        const waitTime = Math.min(countdownInterval, remainingTime);
-        await new Promise(resolve => setTimeout(resolve, waitTime));
-        remainingTime -= waitTime;
-      }
-    }
-  }
-
-  if (depth === 0) {
-    await logMessage('所有操作执行完成', folderPath);
-    await logMessage('==================== 工作流执行完成 ====================', folderPath);
-  }
-  return { success: true, completedSteps };
-}