|
@@ -6,13 +6,62 @@ const DEFAULT_SCROLL_DISTANCE = 100; // 默认每次滚动距离(像素)
|
|
|
// 变量上下文(用于存储变量值)
|
|
// 变量上下文(用于存储变量值)
|
|
|
let variableContext = {};
|
|
let variableContext = {};
|
|
|
|
|
|
|
|
|
|
+// 变量上下文是否已初始化(防止循环中重置变量)
|
|
|
|
|
+let variableContextInitialized = false;
|
|
|
|
|
+
|
|
|
// 全局步骤计数器(用于连续的步骤编号)
|
|
// 全局步骤计数器(用于连续的步骤编号)
|
|
|
let globalStepCounter = 0;
|
|
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 => {
|
|
|
|
|
+ // 静默失败,不影响主流程
|
|
|
|
|
+ console.error('写入日志失败:', 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 { saveChatHistory, generateHistorySummary, getHistorySummary } from './func/chat/chat-history.js';
|
|
import { saveChatHistory, generateHistorySummary, getHistorySummary } from './func/chat/chat-history.js';
|
|
|
-// 导入 ocr-chat-history 执行函数
|
|
|
|
|
-import { executeOcrChatHistory } from './func/chat/ocr-chat-history.js';
|
|
|
|
|
|
|
+// 导入 ocr-chat 执行函数
|
|
|
|
|
+import { executeOcrChat } from './func/chat/ocr-chat.js';
|
|
|
// 导入 image-region-location 执行函数
|
|
// 导入 image-region-location 执行函数
|
|
|
import { executeImageRegionLocation } from './func/image-region-location.js';
|
|
import { executeImageRegionLocation } from './func/image-region-location.js';
|
|
|
// 导入 image-center-location 执行函数
|
|
// 导入 image-center-location 执行函数
|
|
@@ -125,6 +174,59 @@ function extractVarName(varName) {
|
|
|
return varName;
|
|
return varName;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 替换字符串中的变量(支持 {{variable}} 和 {variable} 格式)
|
|
|
|
|
+ * @param {string} str - 原始字符串
|
|
|
|
|
+ * @param {Object} context - 变量上下文
|
|
|
|
|
+ * @returns {string} 替换后的字符串
|
|
|
|
|
+ */
|
|
|
|
|
+function replaceVariablesInString(str, context = variableContext) {
|
|
|
|
|
+ if (typeof str !== 'string') {
|
|
|
|
|
+ return str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let result = str;
|
|
|
|
|
+
|
|
|
|
|
+ // 优先替换 {{variable}} 格式的变量(双花括号)
|
|
|
|
|
+ const doubleBracePattern = /\{\{(\w+)\}\}/g;
|
|
|
|
|
+ result = result.replace(doubleBracePattern, (match, varName) => {
|
|
|
|
|
+ const varValue = context[varName];
|
|
|
|
|
+ if (varValue === undefined || varValue === null) {
|
|
|
|
|
+ return match;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果是数组或对象,转换为 JSON 字符串
|
|
|
|
|
+ if (Array.isArray(varValue) || typeof varValue === 'object') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.stringify(varValue);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return String(varValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return String(varValue);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 也支持 {variable} 格式的变量(单花括号,向后兼容)
|
|
|
|
|
+ // 注意:使用负向前瞻确保不会匹配已经是 {{}} 格式的开始部分
|
|
|
|
|
+ const varPattern = /\{(\w+)\}(?!\})/g;
|
|
|
|
|
+ result = result.replace(varPattern, (match, varName) => {
|
|
|
|
|
+ const varValue = context[varName];
|
|
|
|
|
+ if (varValue === undefined || varValue === null) {
|
|
|
|
|
+ return match;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果是数组或对象,转换为 JSON 字符串
|
|
|
|
|
+ if (Array.isArray(varValue) || typeof varValue === 'object') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.stringify(varValue);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return String(varValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return String(varValue);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 解析变量值(支持 {variable} 格式)
|
|
* 解析变量值(支持 {variable} 格式)
|
|
|
* @param {any} value - 原始值
|
|
* @param {any} value - 原始值
|
|
@@ -166,10 +268,31 @@ function evaluateCondition(condition, context = variableContext) {
|
|
|
const value = context[varName];
|
|
const value = context[varName];
|
|
|
if (value === undefined) return 'undefined';
|
|
if (value === undefined) return 'undefined';
|
|
|
if (typeof value === 'string') {
|
|
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, '\\"');
|
|
const escaped = value.replace(/"/g, '\\"');
|
|
|
return `"${escaped}"`;
|
|
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 === 'number') return value;
|
|
|
if (typeof value === 'boolean') return value;
|
|
if (typeof value === 'boolean') return value;
|
|
|
// 其他类型转为字符串
|
|
// 其他类型转为字符串
|
|
@@ -179,7 +302,6 @@ function evaluateCondition(condition, context = variableContext) {
|
|
|
// 手动解析简单的条件表达式(不使用eval)
|
|
// 手动解析简单的条件表达式(不使用eval)
|
|
|
// 支持: ==, !=, >, <, >=, <=, &&, ||
|
|
// 支持: ==, !=, >, <, >=, <=, &&, ||
|
|
|
const result = parseConditionExpression(expr);
|
|
const result = parseConditionExpression(expr);
|
|
|
- console.log(`条件评估: "${condition}" -> "${expr}" = ${result}`);
|
|
|
|
|
return result;
|
|
return result;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('条件评估失败:', error, condition);
|
|
console.error('条件评估失败:', error, condition);
|
|
@@ -212,7 +334,21 @@ function parseConditionExpression(expr) {
|
|
|
// 处理比较运算符
|
|
// 处理比较运算符
|
|
|
const operators = [
|
|
const operators = [
|
|
|
{ op: '!=', fn: (a, b) => a != b },
|
|
{ op: '!=', fn: (a, b) => a != b },
|
|
|
- { op: '==', fn: (a, b) => a == b },
|
|
|
|
|
|
|
+ { op: '==', fn: (a, b) => {
|
|
|
|
|
+ // 特殊处理:如果一边是空字符串,另一边是 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) },
|
|
|
{ op: '>', fn: (a, b) => Number(a) > Number(b) },
|
|
{ op: '>', fn: (a, b) => Number(a) > Number(b) },
|
|
@@ -234,8 +370,23 @@ function parseConditionExpression(expr) {
|
|
|
const value = parseValue(expr);
|
|
const value = parseValue(expr);
|
|
|
if (typeof value === 'boolean') return value;
|
|
if (typeof value === 'boolean') return value;
|
|
|
if (typeof value === 'string') {
|
|
if (typeof value === 'string') {
|
|
|
- // 空字符串为false,非空为true
|
|
|
|
|
- return value !== '' && value !== 'undefined';
|
|
|
|
|
|
|
+ // 空字符串为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
|
|
// 数字:0为false,非0为true
|
|
|
if (typeof value === 'number') return value !== 0;
|
|
if (typeof value === 'number') return value !== 0;
|
|
@@ -284,8 +435,50 @@ export function parseWorkflow(workflow) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 初始化变量上下文
|
|
|
|
|
- variableContext = workflow.variables ? { ...workflow.variables } : {};
|
|
|
|
|
|
|
+ // 初始化变量上下文(仅在未初始化时初始化,避免循环中重置)
|
|
|
|
|
+ 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(支持新格式和旧格式)
|
|
// 解析 actions(支持新格式和旧格式)
|
|
|
let actions = [];
|
|
let actions = [];
|
|
@@ -418,7 +611,8 @@ function parseNewFormatAction(action) {
|
|
|
parsed.avatar = resolveValue(action.avatar);
|
|
parsed.avatar = resolveValue(action.avatar);
|
|
|
break;
|
|
break;
|
|
|
case 'extract-messages':
|
|
case 'extract-messages':
|
|
|
- case 'ocr-chat-history':
|
|
|
|
|
|
|
+ case 'ocr-chat':
|
|
|
|
|
+ case 'ocr-chat-history': // 向后兼容
|
|
|
case 'extract-chat-history': // 向后兼容
|
|
case 'extract-chat-history': // 向后兼容
|
|
|
// 支持新的 inVars/outVars 格式(都可以为空)
|
|
// 支持新的 inVars/outVars 格式(都可以为空)
|
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
@@ -514,6 +708,18 @@ function parseNewFormatAction(action) {
|
|
|
parsed.variable = action.variable;
|
|
parsed.variable = action.variable;
|
|
|
parsed.value = resolveValue(action.value);
|
|
parsed.value = resolveValue(action.value);
|
|
|
break;
|
|
break;
|
|
|
|
|
+ case 'echo':
|
|
|
|
|
+ // echo 操作:支持 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':
|
|
case 'read-last-message':
|
|
|
// 支持新的 inVars/outVars 格式,也支持 inputVars/outputVars(大小写不同,都可以为空)
|
|
// 支持新的 inVars/outVars 格式,也支持 inputVars/outputVars(大小写不同,都可以为空)
|
|
|
const inputVars = action.inVars || action.inputVars || [];
|
|
const inputVars = action.inVars || action.inputVars || [];
|
|
@@ -717,6 +923,7 @@ function getActionName(action) {
|
|
|
'extract-messages': '提取消息记录',
|
|
'extract-messages': '提取消息记录',
|
|
|
'save-messages': '保存消息记录',
|
|
'save-messages': '保存消息记录',
|
|
|
'generate-summary': '生成总结',
|
|
'generate-summary': '生成总结',
|
|
|
|
|
+ 'ocr-chat': 'OCR识别对话',
|
|
|
// 向后兼容
|
|
// 向后兼容
|
|
|
'ocr-chat-history': 'OCR提取消息记录',
|
|
'ocr-chat-history': 'OCR提取消息记录',
|
|
|
'extract-chat-history': '提取消息记录', // 向后兼容
|
|
'extract-chat-history': '提取消息记录', // 向后兼容
|
|
@@ -733,7 +940,8 @@ function getActionName(action) {
|
|
|
'for': '循环',
|
|
'for': '循环',
|
|
|
'while': '循环',
|
|
'while': '循环',
|
|
|
'delay': '延迟',
|
|
'delay': '延迟',
|
|
|
- 'set': '设置变量'
|
|
|
|
|
|
|
+ 'set': '设置变量',
|
|
|
|
|
+ 'echo': '打印信息'
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const typeName = typeNames[action.type] || action.type;
|
|
const typeName = typeNames[action.type] || action.type;
|
|
@@ -873,7 +1081,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `输入失败: ${textResult.error}` };
|
|
return { success: false, error: `输入失败: ${textResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功输入文本: ${inputValue}`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -893,12 +1100,26 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
|
|
|
|
|
// 如果 position 是字符串,尝试解析为坐标对象
|
|
// 如果 position 是字符串,尝试解析为坐标对象
|
|
|
if (typeof position === 'string') {
|
|
if (typeof position === 'string') {
|
|
|
|
|
+ if (position === '') {
|
|
|
|
|
+ return { success: false, error: 'click 操作缺少位置参数(位置变量为空)' };
|
|
|
|
|
+ }
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // 尝试解析 JSON 字符串(例如:{"x":123,"y":456})
|
|
|
position = JSON.parse(position);
|
|
position = JSON.parse(position);
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- // 如果不是 JSON,可能是变量名,尝试从变量上下文读取
|
|
|
|
|
- const posVar = extractVarName(position);
|
|
|
|
|
- position = variableContext[posVar] || position;
|
|
|
|
|
|
|
+ // 如果不是 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}` };
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -927,7 +1148,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功点击坐标: (${position.x}, ${position.y})`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -950,9 +1170,10 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: 'locate 操作(image)缺少图片路径' };
|
|
return { success: false, error: 'locate 操作(image)缺少图片路径' };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // resources 作为根目录
|
|
|
const fullPath = imagePath.startsWith('/') || imagePath.includes(':')
|
|
const fullPath = imagePath.startsWith('/') || imagePath.includes(':')
|
|
|
? imagePath
|
|
? imagePath
|
|
|
- : `${folderPath}/${imagePath}`;
|
|
|
|
|
|
|
+ : `${folderPath}/resources/${imagePath}`;
|
|
|
|
|
|
|
|
if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
|
|
if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
|
|
|
return { success: false, error: '图像匹配 API 不可用' };
|
|
return { success: false, error: '图像匹配 API 不可用' };
|
|
@@ -1005,10 +1226,9 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
if (outVars.length > 0) {
|
|
if (outVars.length > 0) {
|
|
|
const outputVar = extractVarName(outVars[0]);
|
|
const outputVar = extractVarName(outVars[0]);
|
|
|
variableContext[outputVar] = position;
|
|
variableContext[outputVar] = position;
|
|
|
- console.log(`定位结果已保存到变量 ${outputVar}:`, position);
|
|
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath);
|
|
|
} else if (action.variable) {
|
|
} else if (action.variable) {
|
|
|
variableContext[action.variable] = position;
|
|
variableContext[action.variable] = position;
|
|
|
- console.log(`定位结果已保存到变量 ${action.variable}:`, position);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return { success: true, result: position };
|
|
return { success: true, result: position };
|
|
@@ -1061,7 +1281,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `滑动失败: ${swipeResult.error}` };
|
|
return { success: false, error: `滑动失败: ${swipeResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功滑动: ${direction} (${x1},${y1}) -> (${x2},${y2})`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1096,7 +1315,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `滚动失败: ${scrollResult.error}` };
|
|
return { success: false, error: `滚动失败: ${scrollResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功滚动: ${direction}`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1139,7 +1357,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功点击图片坐标: (${x}, ${y})`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1178,7 +1395,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功点击文字 "${targetText}" 的坐标: (${x}, ${y})`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1276,13 +1492,13 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功点击坐标: (${position.x}, ${position.y})`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
case 'press': {
|
|
case 'press': {
|
|
|
// 向后兼容:图像匹配并点击
|
|
// 向后兼容:图像匹配并点击
|
|
|
- const imagePath = `${folderPath}/${action.value}`;
|
|
|
|
|
|
|
+ // resources 作为根目录
|
|
|
|
|
+ const imagePath = `${folderPath}/resources/${action.value}`;
|
|
|
|
|
|
|
|
if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
|
|
if (!window.electronAPI || !window.electronAPI.matchImageAndGetCoordinate) {
|
|
|
return { success: false, error: '图像匹配 API 不可用' };
|
|
return { success: false, error: '图像匹配 API 不可用' };
|
|
@@ -1307,20 +1523,15 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功点击坐标: (${x}, ${y})`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
case 'input': {
|
|
case 'input': {
|
|
|
// 输入文本
|
|
// 输入文本
|
|
|
const inputStartTime = Date.now();
|
|
const inputStartTime = Date.now();
|
|
|
- console.log(`[input] 开始输入操作: ${new Date(inputStartTime).toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 })}`);
|
|
|
|
|
|
|
|
|
|
// 先解析value(可能在运行时变量才被赋值,需要重新解析)
|
|
// 先解析value(可能在运行时变量才被赋值,需要重新解析)
|
|
|
- let parseStartTime = Date.now();
|
|
|
|
|
let inputValue = resolveValue(action.value);
|
|
let inputValue = resolveValue(action.value);
|
|
|
- let parseEndTime = Date.now();
|
|
|
|
|
- console.log(`[input] 解析变量耗时: ${parseEndTime - parseStartTime}ms`);
|
|
|
|
|
|
|
|
|
|
// 如果value为空或undefined,且target存在,使用target(用于向后兼容,target可能也是定位文字)
|
|
// 如果value为空或undefined,且target存在,使用target(用于向后兼容,target可能也是定位文字)
|
|
|
if (!inputValue && action.target) {
|
|
if (!inputValue && action.target) {
|
|
@@ -1349,9 +1560,6 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
|
|
|
|
|
// 如果设置了clear,先清空输入框(通过发送退格键)
|
|
// 如果设置了clear,先清空输入框(通过发送退格键)
|
|
|
if (action.clear) {
|
|
if (action.clear) {
|
|
|
- const clearStartTime = Date.now();
|
|
|
|
|
- console.log(`[input] 开始清空输入框: ${new Date(clearStartTime).toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 })}`);
|
|
|
|
|
-
|
|
|
|
|
// 发送退格键清空输入框(假设最多200个字符)
|
|
// 发送退格键清空输入框(假设最多200个字符)
|
|
|
// 使用Android的KEYCODE_DEL,值为67
|
|
// 使用Android的KEYCODE_DEL,值为67
|
|
|
for (let i = 0; i < 200; i++) {
|
|
for (let i = 0; i < 200; i++) {
|
|
@@ -1364,37 +1572,23 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
}
|
|
}
|
|
|
// 等待清空完成
|
|
// 等待清空完成
|
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
-
|
|
|
|
|
- const clearEndTime = Date.now();
|
|
|
|
|
- console.log(`[input] 清空输入框完成,耗时: ${clearEndTime - clearStartTime}ms`);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const sendTextStartTime = Date.now();
|
|
|
|
|
- console.log(`[input] 开始发送文本: ${new Date(sendTextStartTime).toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 })}`);
|
|
|
|
|
- console.log(`[input] 待输入文本内容: ${inputValue}`);
|
|
|
|
|
-
|
|
|
|
|
const textResult = await window.electronAPI.sendText(device, inputValue);
|
|
const textResult = await window.electronAPI.sendText(device, inputValue);
|
|
|
|
|
|
|
|
- const sendTextEndTime = Date.now();
|
|
|
|
|
- console.log(`[input] 发送文本完成,耗时: ${sendTextEndTime - sendTextStartTime}ms`);
|
|
|
|
|
- console.log(`[input] sendText 返回结果:`, textResult);
|
|
|
|
|
-
|
|
|
|
|
if (!textResult.success) {
|
|
if (!textResult.success) {
|
|
|
console.error(`[input] 输入失败: ${textResult.error}`);
|
|
console.error(`[input] 输入失败: ${textResult.error}`);
|
|
|
return { success: false, error: `输入失败: ${textResult.error}` };
|
|
return { success: false, error: `输入失败: ${textResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const inputEndTime = Date.now();
|
|
|
|
|
- console.log(`[input] 输入操作总耗时: ${inputEndTime - inputStartTime}ms`);
|
|
|
|
|
-
|
|
|
|
|
// 确保正确显示UTF-8编码的中文
|
|
// 确保正确显示UTF-8编码的中文
|
|
|
try {
|
|
try {
|
|
|
const displayValue = Buffer.isBuffer(inputValue)
|
|
const displayValue = Buffer.isBuffer(inputValue)
|
|
|
? inputValue.toString('utf8')
|
|
? inputValue.toString('utf8')
|
|
|
: String(inputValue);
|
|
: String(inputValue);
|
|
|
- console.log('成功输入文本:', displayValue);
|
|
|
|
|
|
|
+ // 输入成功,不打印日志
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- console.log('成功输入文本:', inputValue);
|
|
|
|
|
|
|
+ // 输入成功,不打印日志
|
|
|
}
|
|
}
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
@@ -1438,10 +1632,10 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
const textStr = Buffer.isBuffer(displayText)
|
|
const textStr = Buffer.isBuffer(displayText)
|
|
|
? displayText.toString('utf8')
|
|
? displayText.toString('utf8')
|
|
|
: String(displayText);
|
|
: String(displayText);
|
|
|
- console.log(`OCR识别结果已保存到变量 ${action.variable}:`, textStr);
|
|
|
|
|
|
|
+ // OCR识别结果已保存到变量
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
// 如果转换失败,直接输出
|
|
// 如果转换失败,直接输出
|
|
|
- console.log(`OCR识别结果已保存到变量 ${action.variable}:`, displayText);
|
|
|
|
|
|
|
+ // OCR识别结果已保存到变量
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1453,7 +1647,8 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
case 'extract-messages':
|
|
case 'extract-messages':
|
|
|
- case 'ocr-chat-history':
|
|
|
|
|
|
|
+ case 'ocr-chat':
|
|
|
|
|
+ case 'ocr-chat-history': // 向后兼容
|
|
|
case 'extract-chat-history': { // 向后兼容
|
|
case 'extract-chat-history': { // 向后兼容
|
|
|
// 提取消息记录
|
|
// 提取消息记录
|
|
|
// 获取头像路径(支持新的 inVars 格式,也支持旧参数 avatar1/avatar2 和 friendAvatar/myAvatar)
|
|
// 获取头像路径(支持新的 inVars 格式,也支持旧参数 avatar1/avatar2 和 friendAvatar/myAvatar)
|
|
@@ -1475,19 +1670,21 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
if (avatar1Name) {
|
|
if (avatar1Name) {
|
|
|
const avatar1Resolved = resolveValue(avatar1Name);
|
|
const avatar1Resolved = resolveValue(avatar1Name);
|
|
|
if (avatar1Resolved) {
|
|
if (avatar1Resolved) {
|
|
|
- avatar1Path = `${folderName}/${avatar1Resolved}`;
|
|
|
|
|
|
|
+ // resources 作为根目录
|
|
|
|
|
+ avatar1Path = `${folderName}/resources/${avatar1Resolved}`;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (avatar2Name) {
|
|
if (avatar2Name) {
|
|
|
const avatar2Resolved = resolveValue(avatar2Name);
|
|
const avatar2Resolved = resolveValue(avatar2Name);
|
|
|
if (avatar2Resolved) {
|
|
if (avatar2Resolved) {
|
|
|
- avatar2Path = `${folderName}/${avatar2Resolved}`;
|
|
|
|
|
|
|
+ // resources 作为根目录
|
|
|
|
|
+ avatar2Path = `${folderName}/resources/${avatar2Resolved}`;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 调用 Func 目录下的执行函数
|
|
// 调用 Func 目录下的执行函数
|
|
|
- const chatResult = await executeOcrChatHistory({
|
|
|
|
|
|
|
+ const chatResult = await executeOcrChat({
|
|
|
device,
|
|
device,
|
|
|
avatar1: avatar1Path,
|
|
avatar1: avatar1Path,
|
|
|
avatar2: avatar2Path,
|
|
avatar2: avatar2Path,
|
|
@@ -1498,19 +1695,26 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `提取消息记录失败: ${chatResult.error}` };
|
|
return { success: false, error: `提取消息记录失败: ${chatResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 保存消息记录到变量
|
|
|
|
|
- if (action.variable) {
|
|
|
|
|
- // 使用格式化后的消息文本
|
|
|
|
|
- const messagesText = chatResult.messagesText || '';
|
|
|
|
|
- variableContext[action.variable] = messagesText;
|
|
|
|
|
|
|
+ // 保存消息记录到变量(支持新的 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;
|
|
|
const messageCount = chatResult.messages ? chatResult.messages.length : 0;
|
|
const messageCount = chatResult.messages ? chatResult.messages.length : 0;
|
|
|
- console.log(`消息记录已保存到变量 ${action.variable},共 ${messageCount} 条消息`);
|
|
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
success: true,
|
|
success: true,
|
|
|
messages: chatResult.messages || [],
|
|
messages: chatResult.messages || [],
|
|
|
- messagesText: chatResult.messagesText || '',
|
|
|
|
|
|
|
+ messagesJson: chatResult.messagesJson || JSON.stringify(chatResult.messages || []),
|
|
|
lastMessage: chatResult.messages && chatResult.messages.length > 0 ? chatResult.messages[chatResult.messages.length - 1] : null
|
|
lastMessage: chatResult.messages && chatResult.messages.length > 0 ? chatResult.messages[chatResult.messages.length - 1] : null
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -1550,12 +1754,8 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
prompt = prompt.replace(/{historySummary}/g, historySummary);
|
|
prompt = prompt.replace(/{historySummary}/g, historySummary);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 替换 prompt 中所有剩余的变量占位符
|
|
|
|
|
- const varPlaceholderRegex = /\{(\w+)\}/g;
|
|
|
|
|
- prompt = prompt.replace(varPlaceholderRegex, (match, varName) => {
|
|
|
|
|
- const varValue = variableContext[varName];
|
|
|
|
|
- return varValue !== undefined && varValue !== null ? String(varValue) : match;
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // 替换 prompt 中所有剩余的变量占位符(支持 {{variable}} 和 {variable} 格式)
|
|
|
|
|
+ prompt = replaceVariablesInString(prompt, variableContext);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
// 调用AI API(使用现有的GPT API)
|
|
// 调用AI API(使用现有的GPT API)
|
|
@@ -1627,29 +1827,41 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 保存到变量(支持 outVars 格式)
|
|
// 保存到变量(支持 outVars 格式)
|
|
|
- let outputVarName = null;
|
|
|
|
|
- if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
|
|
|
|
|
- outputVarName = extractVarName(action.outVars[0]);
|
|
|
|
|
|
|
+ // 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) {
|
|
} else if (action.variable) {
|
|
|
- outputVarName = extractVarName(action.variable);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (outputVarName) {
|
|
|
|
|
- variableContext[outputVarName] = result;
|
|
|
|
|
- console.log(`AI生成结果已保存到变量 ${outputVarName}: ${result}`);
|
|
|
|
|
|
|
+ // 向后兼容:使用旧的 variable 字段
|
|
|
|
|
+ const outputVarName = extractVarName(action.variable);
|
|
|
|
|
+ if (outputVarName) {
|
|
|
|
|
+ variableContext[outputVarName] = result;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 处理 aiCallBack 变量(如果提供了)
|
|
|
|
|
- if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 1) {
|
|
|
|
|
- const callbackVarName = extractVarName(action.inVars[1]);
|
|
|
|
|
- if (callbackVarName) {
|
|
|
|
|
- // 将 aiCallBack 设置为 1 表示 AI 生成完成
|
|
|
|
|
- variableContext[callbackVarName] = 1;
|
|
|
|
|
- console.log(`AI回调变量 ${callbackVarName} 已设置为 1`);
|
|
|
|
|
|
|
+ // 向后兼容:如果 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;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`AI生成成功: ${result}`);
|
|
|
|
|
return { success: true, result };
|
|
return { success: true, result };
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
return { success: false, error: `AI生成失败: ${error.message}` };
|
|
return { success: false, error: `AI生成失败: ${error.message}` };
|
|
@@ -1691,8 +1903,8 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
variableContext[outputVarName] = result.filePath;
|
|
variableContext[outputVarName] = result.filePath;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath);
|
|
|
|
|
|
|
|
- console.log(`消息记录已保存到 history 文件夹`);
|
|
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1718,7 +1930,7 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
// 保存总结到变量
|
|
// 保存总结到变量
|
|
|
if (action.summaryVariable) {
|
|
if (action.summaryVariable) {
|
|
|
variableContext[action.summaryVariable] = result.summary;
|
|
variableContext[action.summaryVariable] = result.summary;
|
|
|
- console.log(`消息记录总结已保存到变量 ${action.summaryVariable}`);
|
|
|
|
|
|
|
+ // 消息记录总结已保存到变量
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return { success: true, summary: result.summary };
|
|
return { success: true, summary: result.summary };
|
|
@@ -1727,9 +1939,42 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
case 'set': {
|
|
case 'set': {
|
|
|
// 设置变量
|
|
// 设置变量
|
|
|
if (action.variable) {
|
|
if (action.variable) {
|
|
|
- variableContext[action.variable] = action.value;
|
|
|
|
|
- console.log(`设置变量 ${action.variable} = ${action.value}`);
|
|
|
|
|
|
|
+ const varName = extractVarName(action.variable);
|
|
|
|
|
+ variableContext[varName] = action.value;
|
|
|
|
|
+ // 设置变量
|
|
|
|
|
+ }
|
|
|
|
|
+ return { success: true };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case 'echo': {
|
|
|
|
|
+ // 打印信息到 console.log
|
|
|
|
|
+ // 支持 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} 格式,尝试解析为变量名
|
|
|
|
|
+ if (varWithBraces.startsWith('{') && varWithBraces.endsWith('}')) {
|
|
|
|
|
+ return varValue !== undefined ? String(varValue) : varWithBraces;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果不是变量格式,直接使用原值
|
|
|
|
|
+ return varWithBraces;
|
|
|
|
|
+ });
|
|
|
|
|
+ message = messages.join(' ');
|
|
|
|
|
+ } else if (action.value) {
|
|
|
|
|
+ // 使用 value 字段,支持变量替换(支持 {{variable}} 和 {variable} 格式)
|
|
|
|
|
+ message = replaceVariablesInString(action.value, variableContext);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有提供任何内容,输出空字符串
|
|
|
|
|
+ message = '';
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 输出到 console.log
|
|
|
|
|
+ console.log(message);
|
|
|
|
|
+
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1809,7 +2054,8 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
|
|
|
|
|
if (outputVarName) {
|
|
if (outputVarName) {
|
|
|
variableContext[outputVarName] = result.corners;
|
|
variableContext[outputVarName] = result.corners;
|
|
|
- console.log(`图像区域定位结果已保存到变量 ${outputVarName}:`, result.corners);
|
|
|
|
|
|
|
+ // 图像区域定位结果已保存到变量
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return { success: true, result: result.corners };
|
|
return { success: true, result: result.corners };
|
|
@@ -1863,9 +2109,14 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (outputVarName) {
|
|
if (outputVarName) {
|
|
|
- // 保存中心点坐标
|
|
|
|
|
- variableContext[outputVarName] = result.center;
|
|
|
|
|
- console.log(`图像中心点定位结果已保存到变量 ${outputVarName}:`, result.center);
|
|
|
|
|
|
|
+ // 保存中心点坐标为字符串(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 };
|
|
return { success: true, result: result.center };
|
|
@@ -1899,7 +2150,7 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
let inputData = null;
|
|
let inputData = null;
|
|
|
if (inputVar && variableContext[inputVar] !== undefined) {
|
|
if (inputVar && variableContext[inputVar] !== undefined) {
|
|
|
inputData = variableContext[inputVar];
|
|
inputData = variableContext[inputVar];
|
|
|
- console.log(`从变量 ${inputVar} 读取数据,类型: ${typeof inputData}, 是否为数组: ${Array.isArray(inputData)}`);
|
|
|
|
|
|
|
+ // 从变量读取数据
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const result = await executeReadLastMessage({
|
|
const result = await executeReadLastMessage({
|
|
@@ -1916,12 +2167,13 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
// 保存消息文本和发送者到变量
|
|
// 保存消息文本和发送者到变量
|
|
|
if (textVar) {
|
|
if (textVar) {
|
|
|
variableContext[textVar] = result.text;
|
|
variableContext[textVar] = result.text;
|
|
|
- console.log(`最后一条消息文本已保存到变量 ${textVar}: ${result.text}`);
|
|
|
|
|
|
|
+ // 最后一条消息文本已保存到变量
|
|
|
}
|
|
}
|
|
|
if (senderVar) {
|
|
if (senderVar) {
|
|
|
variableContext[senderVar] = result.sender;
|
|
variableContext[senderVar] = result.sender;
|
|
|
- console.log(`最后一条消息发送者已保存到变量 ${senderVar}: ${result.sender}`);
|
|
|
|
|
|
|
+ // 最后一条消息发送者已保存到变量
|
|
|
}
|
|
}
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath);
|
|
|
|
|
|
|
|
return { success: true, text: result.text, sender: result.sender };
|
|
return { success: true, text: result.text, sender: result.sender };
|
|
|
}
|
|
}
|
|
@@ -1949,12 +2201,15 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: result.error };
|
|
return { success: false, error: result.error };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 保存消息数组到变量
|
|
|
|
|
- variableContext[varName] = result.messages;
|
|
|
|
|
- console.log(`聊天记录已读取并保存到变量 ${varName},共 ${result.fileCount || 0} 个文件,${result.totalMessages || 0} 条消息`);
|
|
|
|
|
|
|
+ // 保存消息数组为 JSON 字符串到变量(保持与 ocr-chat 输出格式一致)
|
|
|
|
|
+ const messagesJson = JSON.stringify(result.messages || []);
|
|
|
|
|
+ variableContext[varName] = messagesJson;
|
|
|
|
|
+ // 聊天记录已读取并保存到变量
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath);
|
|
|
return {
|
|
return {
|
|
|
success: true,
|
|
success: true,
|
|
|
messages: result.messages,
|
|
messages: result.messages,
|
|
|
|
|
+ messagesJson: messagesJson,
|
|
|
fileCount: result.fileCount || 0,
|
|
fileCount: result.fileCount || 0,
|
|
|
totalMessages: result.totalMessages || 0
|
|
totalMessages: result.totalMessages || 0
|
|
|
};
|
|
};
|
|
@@ -1987,7 +2242,7 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `滑动失败: ${swipeResult.error}` };
|
|
return { success: false, error: `滑动失败: ${swipeResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功滑动: ${action.value} (${x1},${y1}) -> (${x2},${y2})`);
|
|
|
|
|
|
|
+ // 成功滑动
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2016,7 +2271,7 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功点击文字 "${action.value}" 的坐标: (${x}, ${y})`);
|
|
|
|
|
|
|
+ // 成功点击文字
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2039,7 +2294,7 @@ export async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: `滚动失败: ${scrollResult.error}` };
|
|
return { success: false, error: `滚动失败: ${scrollResult.error}` };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log(`成功滚动: ${action.value}`);
|
|
|
|
|
|
|
+ // 成功滚动
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2074,9 +2329,15 @@ export async function executeActionSequence(
|
|
|
shouldStop = null,
|
|
shouldStop = null,
|
|
|
depth = 0
|
|
depth = 0
|
|
|
) {
|
|
) {
|
|
|
- // 如果是顶层(depth === 0),重置全局步骤计数器
|
|
|
|
|
|
|
+ // 如果是顶层(depth === 0),重置全局步骤计数器和变量初始化标志
|
|
|
if (depth === 0) {
|
|
if (depth === 0) {
|
|
|
globalStepCounter = 0;
|
|
globalStepCounter = 0;
|
|
|
|
|
+ // 重置变量初始化标志,允许在新工作流开始时重新初始化
|
|
|
|
|
+ variableContextInitialized = false;
|
|
|
|
|
+ // 保存当前工作流文件夹路径,用于日志记录
|
|
|
|
|
+ currentWorkflowFolderPath = folderPath;
|
|
|
|
|
+ // 记录工作流开始执行
|
|
|
|
|
+ await logMessage('==================== 工作流开始执行 ====================', folderPath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let completedSteps = 0;
|
|
let completedSteps = 0;
|
|
@@ -2085,7 +2346,7 @@ export async function executeActionSequence(
|
|
|
for (let i = 0; i < actions.length; i++) {
|
|
for (let i = 0; i < actions.length; i++) {
|
|
|
// 检查是否应该停止
|
|
// 检查是否应该停止
|
|
|
if (shouldStop && shouldStop()) {
|
|
if (shouldStop && shouldStop()) {
|
|
|
- console.log(`${stepPrefix}执行被停止`);
|
|
|
|
|
|
|
+ // 执行被停止
|
|
|
return { success: false, error: '执行被停止', completedSteps };
|
|
return { success: false, error: '执行被停止', completedSteps };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2115,7 +2376,7 @@ export async function executeActionSequence(
|
|
|
|
|
|
|
|
// 如果不是第一次迭代,等待间隔时间
|
|
// 如果不是第一次迭代,等待间隔时间
|
|
|
if (iteration > 1 && intervalMs > 0) {
|
|
if (iteration > 1 && intervalMs > 0) {
|
|
|
- console.log(`${stepPrefix}等待 ${intervalMs}ms 后执行第 ${iteration} 次循环...`);
|
|
|
|
|
|
|
+ await logMessage(`${stepPrefix}等待 ${intervalMs}ms 后执行第 ${iteration} 次循环...`, folderPath);
|
|
|
let remainingTime = intervalMs;
|
|
let remainingTime = intervalMs;
|
|
|
const countdownInterval = 100;
|
|
const countdownInterval = 100;
|
|
|
|
|
|
|
@@ -2251,7 +2512,7 @@ export async function executeActionSequence(
|
|
|
|
|
|
|
|
if (waitTime > 0) {
|
|
if (waitTime > 0) {
|
|
|
const waitSeconds = Math.round(waitTime / 1000);
|
|
const waitSeconds = Math.round(waitTime / 1000);
|
|
|
- console.log(`${stepPrefix}步骤 ${i + 1}/${actions.length} 等待 ${waitSeconds} 秒后执行...`);
|
|
|
|
|
|
|
+ await logMessage(`${stepPrefix}步骤 ${i + 1}/${actions.length} 等待 ${waitSeconds} 秒后执行...`, folderPath);
|
|
|
|
|
|
|
|
// 在等待期间也更新倒计时
|
|
// 在等待期间也更新倒计时
|
|
|
let remainingTime = waitTime;
|
|
let remainingTime = waitTime;
|
|
@@ -2277,7 +2538,7 @@ export async function executeActionSequence(
|
|
|
for (let t = 0; t < times; t++) {
|
|
for (let t = 0; t < times; t++) {
|
|
|
// 检查是否应该停止
|
|
// 检查是否应该停止
|
|
|
if (shouldStop && shouldStop()) {
|
|
if (shouldStop && shouldStop()) {
|
|
|
- console.log(`${stepPrefix}执行被停止`);
|
|
|
|
|
|
|
+ await logMessage(`${stepPrefix}执行被停止`, folderPath);
|
|
|
return { success: false, error: '执行被停止', completedSteps };
|
|
return { success: false, error: '执行被停止', completedSteps };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2293,26 +2554,50 @@ export async function executeActionSequence(
|
|
|
|
|
|
|
|
// 记录步骤开始时间
|
|
// 记录步骤开始时间
|
|
|
const stepStartTime = Date.now();
|
|
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
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- // 打印步骤开始执行
|
|
|
|
|
- if (times > 1) {
|
|
|
|
|
- console.log(`${stepPrefix}步骤 ${currentStepNumber} 开始执行: ${action.type} (第 ${t + 1}/${times} 次)`);
|
|
|
|
|
- } else {
|
|
|
|
|
- console.log(`${stepPrefix}步骤 ${currentStepNumber} 开始执行: ${action.type}`);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 获取操作类型名称
|
|
|
|
|
+ const typeName = getActionName(action);
|
|
|
|
|
+
|
|
|
|
|
+ // 打印步骤开始执行(包含系统时间)
|
|
|
|
|
+ await logMessage(`开始执行:${typeName} [系统时间: ${startTimeStr}]`, folderPath);
|
|
|
|
|
|
|
|
// 执行操作
|
|
// 执行操作
|
|
|
const result = await executeAction(action, device, folderPath, resolution);
|
|
const result = await executeAction(action, device, folderPath, resolution);
|
|
|
|
|
|
|
|
// 记录步骤结束时间
|
|
// 记录步骤结束时间
|
|
|
const stepEndTime = Date.now();
|
|
const stepEndTime = Date.now();
|
|
|
- const stepDuration = stepEndTime - stepStartTime;
|
|
|
|
|
|
|
+ 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; // 转换为秒
|
|
|
|
|
|
|
|
- // 打印步骤执行完成
|
|
|
|
|
- console.log(`${stepPrefix}步骤 ${currentStepNumber} 执行完成: ${action.type}, 耗时: ${stepDuration}ms (${(stepDuration / 1000).toFixed(2)}秒)`);
|
|
|
|
|
|
|
+ // 打印步骤执行完成(包含系统时间和执行时长)
|
|
|
|
|
+ // 如果时长超过 0.1 秒,才打印结束日志
|
|
|
|
|
+ if (stepDuration > 0.1) {
|
|
|
|
|
+ const endMessage = `结束执行:${typeName} 时长:${stepDuration.toFixed(2)}秒 [系统时间: ${endTimeStr}]`;
|
|
|
|
|
+ await logMessage(endMessage, folderPath);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
if (!result.success) {
|
|
|
- console.error(`${stepPrefix}步骤 ${currentStepNumber} 执行失败:`, result.error);
|
|
|
|
|
|
|
+ // 失败时,如果时长超过 0.1 秒才打印结束日志
|
|
|
|
|
+ if (stepDuration > 0.1) {
|
|
|
|
|
+ await logMessage(`结束执行:${typeName} 时长:${stepDuration.toFixed(2)}秒`, folderPath);
|
|
|
|
|
+ }
|
|
|
return { success: false, error: result.error, completedSteps: i };
|
|
return { success: false, error: result.error, completedSteps: i };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2354,7 +2639,8 @@ export async function executeActionSequence(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (depth === 0) {
|
|
if (depth === 0) {
|
|
|
- console.log('所有操作执行完成');
|
|
|
|
|
|
|
+ await logMessage('所有操作执行完成', folderPath);
|
|
|
|
|
+ await logMessage('==================== 工作流执行完成 ====================', folderPath);
|
|
|
}
|
|
}
|
|
|
return { success: true, completedSteps };
|
|
return { success: true, completedSteps };
|
|
|
}
|
|
}
|