|
@@ -1,20 +1,40 @@
|
|
|
/**
|
|
/**
|
|
|
* fun 解析与执行 + 执行入口:registry/executeAction 由 ctx 传入(来自 workflow-json-parser),本模块负责 parse/runAction/run/supports
|
|
* fun 解析与执行 + 执行入口:registry/executeAction 由 ctx 传入(来自 workflow-json-parser),本模块负责 parse/runAction/run/supports
|
|
|
|
|
+ * 简易结点由 fun-node-registry.js 配置,只需新建脚本 + 在注册表加一条即可。
|
|
|
*/
|
|
*/
|
|
|
const path = require('path')
|
|
const path = require('path')
|
|
|
const variableParser = require('../variable-parser.js')
|
|
const variableParser = require('../variable-parser.js')
|
|
|
|
|
+const FUN_NODE_REGISTRY = require('./fun-node-registry.js')
|
|
|
|
|
|
|
|
-const FUN_REGISTRY_TYPES = [
|
|
|
|
|
- 'fun',
|
|
|
|
|
|
|
+const LEGACY_FUN_TYPES = [
|
|
|
|
|
+ 'fun', 'ai',
|
|
|
'read-txt', 'read-text', 'save-txt', 'save-text',
|
|
'read-txt', 'read-text', 'save-txt', 'save-text',
|
|
|
'img-bounding-box-location', 'img-center-point-location', 'img-cropping',
|
|
'img-bounding-box-location', 'img-center-point-location', 'img-cropping',
|
|
|
'read-last-message', 'smart-chat-append',
|
|
'read-last-message', 'smart-chat-append',
|
|
|
'extract-messages', 'ocr-chat', 'ocr-chat-history', 'extract-chat-history',
|
|
'extract-messages', 'ocr-chat', 'ocr-chat-history', 'extract-chat-history',
|
|
|
'save-messages', 'generate-summary', 'generate-history-summary',
|
|
'save-messages', 'generate-summary', 'generate-history-summary',
|
|
|
- 'ai-generate', 'string-press','download',
|
|
|
|
|
|
|
+ 'ai-generate', 'string-press',
|
|
|
]
|
|
]
|
|
|
-
|
|
|
|
|
|
|
+const REGISTERED_TYPES = (FUN_NODE_REGISTRY && Array.isArray(FUN_NODE_REGISTRY)) ? FUN_NODE_REGISTRY.map((r) => r.type) : []
|
|
|
|
|
+const FUN_REGISTRY_TYPES = LEGACY_FUN_TYPES.concat(REGISTERED_TYPES)
|
|
|
const types = FUN_REGISTRY_TYPES
|
|
const types = FUN_REGISTRY_TYPES
|
|
|
|
|
+const REGISTRY_BY_TYPE = new Map((FUN_NODE_REGISTRY || []).map((r) => [r.type, r]))
|
|
|
|
|
+const scriptCache = new Map()
|
|
|
|
|
+
|
|
|
|
|
+function getRegistryScript(funcDir, type) {
|
|
|
|
|
+ const def = REGISTRY_BY_TYPE.get(type)
|
|
|
|
|
+ if (!def) return null
|
|
|
|
|
+ const key = `${funcDir}:${type}`
|
|
|
|
|
+ if (!scriptCache.has(key)) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const scriptPath = path.join(funcDir, def.script || def.type + '.js')
|
|
|
|
|
+ scriptCache.set(key, require(scriptPath))
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ scriptCache.set(key, null)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return scriptCache.get(key)
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
function parse(action, parseContext) {
|
|
function parse(action, parseContext) {
|
|
|
const { extractVarName, resolveValue } = parseContext
|
|
const { extractVarName, resolveValue } = parseContext
|
|
@@ -32,6 +52,24 @@ function parse(action, parseContext) {
|
|
|
}
|
|
}
|
|
|
Object.assign(parsed, action)
|
|
Object.assign(parsed, action)
|
|
|
|
|
|
|
|
|
|
+ const regDef = REGISTRY_BY_TYPE.get(action.type)
|
|
|
|
|
+ if (regDef) {
|
|
|
|
|
+ const funcDir = (parseContext.compilerConfig && parseContext.compilerConfig.funcDir) || path.join(__dirname, 'fun')
|
|
|
|
|
+ if (regDef.customParse) {
|
|
|
|
|
+ const script = getRegistryScript(funcDir, action.type)
|
|
|
|
|
+ if (script && typeof script.parseNode === 'function') return script.parseNode(action, parseContext)
|
|
|
|
|
+ }
|
|
|
|
|
+ parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map((v) => extractVarName(v)) : []
|
|
|
|
|
+ const inKeys = regDef.in || []
|
|
|
|
|
+ const inAlt = regDef.inAlt || {}
|
|
|
|
|
+ inKeys.forEach((key, i) => {
|
|
|
|
|
+ const altKey = inAlt[key]
|
|
|
|
|
+ parsed[key] = action.inVars?.[i] ?? action[key] ?? (altKey ? action[altKey] : undefined)
|
|
|
|
|
+ })
|
|
|
|
|
+ parsed.variable = action.outVars?.[0] ? extractVarName(action.outVars[0]) : undefined
|
|
|
|
|
+ return parsed
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
switch (action.type) {
|
|
switch (action.type) {
|
|
|
case 'fun':
|
|
case 'fun':
|
|
|
parsed.method = action.method
|
|
parsed.method = action.method
|
|
@@ -113,9 +151,6 @@ function parse(action, parseContext) {
|
|
|
parsed.savePath = action.inVars?.[1] ?? action.savePath
|
|
parsed.savePath = action.inVars?.[1] ?? action.savePath
|
|
|
if (action.outVars && action.outVars.length > 0) parsed.variable = extractVarName(action.outVars[0])
|
|
if (action.outVars && action.outVars.length > 0) parsed.variable = extractVarName(action.outVars[0])
|
|
|
break
|
|
break
|
|
|
- case 'download':
|
|
|
|
|
- parsed.variable = action.outVars?.[0] ? extractVarName(action.outVars[0]) : extractVarName(action.variable)
|
|
|
|
|
- break
|
|
|
|
|
default:
|
|
default:
|
|
|
parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
|
|
parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
|
|
|
parsed.outVars = action.outVars && Array.isArray(action.outVars) ? action.outVars.map(v => extractVarName(v)) : []
|
|
parsed.outVars = action.outVars && Array.isArray(action.outVars) ? action.outVars.map(v => extractVarName(v)) : []
|
|
@@ -139,6 +174,9 @@ async function runAction(action, device, folderPath, resolution, ctx) {
|
|
|
if (resolvedAction.type === 'fun' && resolvedAction.method) {
|
|
if (resolvedAction.type === 'fun' && resolvedAction.method) {
|
|
|
return run(resolvedAction.method, resolvedAction, ctx, device, folderPath)
|
|
return run(resolvedAction.method, resolvedAction, ctx, device, folderPath)
|
|
|
}
|
|
}
|
|
|
|
|
+ if (resolvedAction.type === 'ai' && resolvedAction.method) {
|
|
|
|
|
+ return run(resolvedAction.method, resolvedAction, ctx, device, folderPath)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (supports(resolvedAction.type)) {
|
|
if (supports(resolvedAction.type)) {
|
|
|
return run(resolvedAction.type, resolvedAction, ctx, device, folderPath)
|
|
return run(resolvedAction.type, resolvedAction, ctx, device, folderPath)
|
|
@@ -192,6 +230,13 @@ function get(funcDir, category) {
|
|
|
executeSmartChatAppend: require(path.join(funcDir, 'chat', 'smart-chat-append.js')).executeSmartChatAppend,
|
|
executeSmartChatAppend: require(path.join(funcDir, 'chat', 'smart-chat-append.js')).executeSmartChatAppend,
|
|
|
executeSaveTxt: require(path.join(funcDir, 'save-txt.js')).executeSaveTxt,
|
|
executeSaveTxt: require(path.join(funcDir, 'save-txt.js')).executeSaveTxt,
|
|
|
}
|
|
}
|
|
|
|
|
+ ;(FUN_NODE_REGISTRY || []).filter((r) => r.category === 'io').forEach((def) => {
|
|
|
|
|
+ const scriptPath = path.join(funcDir, def.script || def.type + '.js')
|
|
|
|
|
+ try {
|
|
|
|
|
+ const m = require(scriptPath)
|
|
|
|
|
+ if (m[def.execute]) mod[def.execute] = m[def.execute]
|
|
|
|
|
+ } catch (e) { /* skip missing script */ }
|
|
|
|
|
+ })
|
|
|
break
|
|
break
|
|
|
case 'chat':
|
|
case 'chat':
|
|
|
mod = (() => {
|
|
mod = (() => {
|
|
@@ -227,7 +272,7 @@ function parseRegion(regionArea) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function run(actionType, action, ctx, device, folderPath) {
|
|
async function run(actionType, action, ctx, device, folderPath) {
|
|
|
- const { variableContext, extractVarName, resolveValue, replaceVariablesInString, logOutVars } = ctx
|
|
|
|
|
|
|
+ const { variableContext, extractVarName, resolveValue, replaceVariablesInString, logOutVars, logMessage } = ctx
|
|
|
const funcDir = ctx.compilerConfig && ctx.compilerConfig.funcDir
|
|
const funcDir = ctx.compilerConfig && ctx.compilerConfig.funcDir
|
|
|
if (!funcDir) return { success: false, error: 'compilerConfig.funcDir 未提供' }
|
|
if (!funcDir) return { success: false, error: 'compilerConfig.funcDir 未提供' }
|
|
|
|
|
|
|
@@ -238,13 +283,13 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
let regionPath = action.region
|
|
let regionPath = action.region
|
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
|
if (action.inVars.length === 1) {
|
|
if (action.inVars.length === 1) {
|
|
|
- const firstValue = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
|
|
+ const firstValue = action.inVars[0]
|
|
|
regionPath = firstValue != null && typeof firstValue === 'string' && !String(firstValue).includes('{') ? firstValue : action.inVars[0]
|
|
regionPath = firstValue != null && typeof firstValue === 'string' && !String(firstValue).includes('{') ? firstValue : action.inVars[0]
|
|
|
screenshotPath = null
|
|
screenshotPath = null
|
|
|
} else if (action.inVars.length >= 2) {
|
|
} else if (action.inVars.length >= 2) {
|
|
|
- const sv = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
|
|
+ const sv = action.inVars[0]
|
|
|
screenshotPath = sv != null && typeof sv === 'string' && !sv.includes('{') ? sv : action.inVars[0]
|
|
screenshotPath = sv != null && typeof sv === 'string' && !sv.includes('{') ? sv : action.inVars[0]
|
|
|
- const rv = variableContext[extractVarName(action.inVars[1])] ?? resolveValue(action.inVars[1], variableContext)
|
|
|
|
|
|
|
+ const rv = action.inVars[1]
|
|
|
regionPath = rv != null && typeof rv === 'string' && !rv.includes('{') ? rv : action.inVars[1]
|
|
regionPath = rv != null && typeof rv === 'string' && !rv.includes('{') ? rv : action.inVars[1]
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -254,7 +299,7 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
if (screenshotPath === null && !device) return { success: false, error: '缺少完整截图路径,且无法自动获取设备截图(缺少设备ID)' }
|
|
if (screenshotPath === null && !device) return { success: false, error: '缺少完整截图路径,且无法自动获取设备截图(缺少设备ID)' }
|
|
|
const result = await executeImgBoundingBoxLocation({ device, screenshot: screenshotPath, region: regionPath, folderPath })
|
|
const result = await executeImgBoundingBoxLocation({ device, screenshot: screenshotPath, region: regionPath, folderPath })
|
|
|
if (!result.success) return { success: false, error: `图像区域定位失败: ${result.error}` }
|
|
if (!result.success) return { success: false, error: `图像区域定位失败: ${result.error}` }
|
|
|
- const outputVarName = action.outVars?.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : null)
|
|
|
|
|
|
|
+ const outputVarName = action.outVars?.[0] != null ? String(action.outVars[0]).trim() : (action.variable ? extractVarName(action.variable) : null)
|
|
|
if (outputVarName) {
|
|
if (outputVarName) {
|
|
|
variableContext[outputVarName] = result.corners && typeof result.corners === 'object' ? JSON.stringify(result.corners) : ''
|
|
variableContext[outputVarName] = result.corners && typeof result.corners === 'object' ? JSON.stringify(result.corners) : ''
|
|
|
await logOutVars(action, variableContext, folderPath)
|
|
await logOutVars(action, variableContext, folderPath)
|
|
@@ -266,7 +311,7 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
const { executeImgCenterPointLocation } = get(funcDir, 'img')
|
|
const { executeImgCenterPointLocation } = get(funcDir, 'img')
|
|
|
let templatePath = action.template
|
|
let templatePath = action.template
|
|
|
if (action.inVars?.length > 0) {
|
|
if (action.inVars?.length > 0) {
|
|
|
- const templateValue = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
|
|
+ const templateValue = action.inVars[0]
|
|
|
templatePath = templateValue != null && typeof templateValue === 'string' && !String(templateValue).includes('{') ? templateValue : action.inVars[0]
|
|
templatePath = templateValue != null && typeof templateValue === 'string' && !String(templateValue).includes('{') ? templateValue : action.inVars[0]
|
|
|
}
|
|
}
|
|
|
if (!templatePath) templatePath = action.template
|
|
if (!templatePath) templatePath = action.template
|
|
@@ -274,7 +319,7 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
if (!device) return { success: false, error: '缺少设备 ID,无法自动获取截图' }
|
|
if (!device) return { success: false, error: '缺少设备 ID,无法自动获取截图' }
|
|
|
const result = await executeImgCenterPointLocation({ device, template: templatePath, folderPath })
|
|
const result = await executeImgCenterPointLocation({ device, template: templatePath, folderPath })
|
|
|
if (!result.success) return { success: false, error: `图像中心点定位失败: ${result.error}` }
|
|
if (!result.success) return { success: false, error: `图像中心点定位失败: ${result.error}` }
|
|
|
- const outputVarName = action.outVars?.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : null)
|
|
|
|
|
|
|
+ const outputVarName = action.outVars?.[0] != null ? String(action.outVars[0]).trim() : (action.variable ? extractVarName(action.variable) : null)
|
|
|
if (outputVarName) {
|
|
if (outputVarName) {
|
|
|
variableContext[outputVarName] = result.center && typeof result.center === 'object' && result.center.x !== undefined && result.center.y !== undefined
|
|
variableContext[outputVarName] = result.center && typeof result.center === 'object' && result.center.x !== undefined && result.center.y !== undefined
|
|
|
? JSON.stringify({ x: result.center.x, y: result.center.y }) : ''
|
|
? JSON.stringify({ x: result.center.x, y: result.center.y }) : ''
|
|
@@ -288,15 +333,15 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
let area = action.area
|
|
let area = action.area
|
|
|
let savePath = action.savePath
|
|
let savePath = action.savePath
|
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
|
- if (action.inVars.length > 0) area = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
- if (action.inVars.length > 1) savePath = variableContext[extractVarName(action.inVars[1])] ?? resolveValue(action.inVars[1], variableContext)
|
|
|
|
|
|
|
+ if (action.inVars.length > 0) area = action.inVars[0]
|
|
|
|
|
+ if (action.inVars.length > 1) savePath = action.inVars[1]
|
|
|
}
|
|
}
|
|
|
if (!area) return { success: false, error: 'img-cropping 缺少 area 参数' }
|
|
if (!area) return { success: false, error: 'img-cropping 缺少 area 参数' }
|
|
|
if (!savePath) return { success: false, error: 'img-cropping 缺少 savePath 参数' }
|
|
if (!savePath) return { success: false, error: 'img-cropping 缺少 savePath 参数' }
|
|
|
const result = await executeImgCropping({ area, savePath, folderPath, device })
|
|
const result = await executeImgCropping({ area, savePath, folderPath, device })
|
|
|
if (!result.success) return { success: false, error: result.error }
|
|
if (!result.success) return { success: false, error: result.error }
|
|
|
- if (action.outVars?.length > 0) {
|
|
|
|
|
- const outputVarName = extractVarName(action.outVars[0])
|
|
|
|
|
|
|
+ if (action.outVars?.[0] != null) {
|
|
|
|
|
+ const outputVarName = String(action.outVars[0]).trim()
|
|
|
if (outputVarName) variableContext[outputVarName] = result.success ? '1' : '0'
|
|
if (outputVarName) variableContext[outputVarName] = result.success ? '1' : '0'
|
|
|
}
|
|
}
|
|
|
await logOutVars(action, variableContext, folderPath)
|
|
await logOutVars(action, variableContext, folderPath)
|
|
@@ -307,17 +352,10 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
const { executeReadLastMessage } = get(funcDir, 'io')
|
|
const { executeReadLastMessage } = get(funcDir, 'io')
|
|
|
const inputVars = action.inVars || action.inputVars || []
|
|
const inputVars = action.inVars || action.inputVars || []
|
|
|
const outputVars = action.outVars || action.outputVars || []
|
|
const outputVars = action.outVars || action.outputVars || []
|
|
|
- let textVar = outputVars.length > 0 ? extractVarName(outputVars[0]) : action.textVariable
|
|
|
|
|
- let senderVar = outputVars.length > 1 ? extractVarName(outputVars[1]) : action.senderVariable
|
|
|
|
|
- const inputVar = inputVars.length > 0 ? extractVarName(inputVars[0]) : null
|
|
|
|
|
|
|
+ let textVar = outputVars.length > 0 ? String(outputVars[0]).trim() : action.textVariable
|
|
|
|
|
+ let senderVar = outputVars.length > 1 ? String(outputVars[1]).trim() : action.senderVariable
|
|
|
|
|
+ let inputDataString = inputVars.length > 0 ? (inputVars[0] != null ? (typeof inputVars[0] === 'string' ? inputVars[0] : (Array.isArray(inputVars[0]) || typeof inputVars[0] === 'object' ? JSON.stringify(inputVars[0]) : String(inputVars[0]))) : null) : null
|
|
|
if (!textVar && !senderVar) return { success: false, error: 'read-last-message 缺少 textVariable 或 senderVariable 参数' }
|
|
if (!textVar && !senderVar) return { success: false, error: 'read-last-message 缺少 textVariable 或 senderVariable 参数' }
|
|
|
- let inputDataString = null
|
|
|
|
|
- if (inputVar && variableContext[inputVar] !== undefined) {
|
|
|
|
|
- const inputData = variableContext[inputVar]
|
|
|
|
|
- if (typeof inputData === 'string') inputDataString = inputData
|
|
|
|
|
- else if (Array.isArray(inputData) || typeof inputData === 'object') inputDataString = JSON.stringify(inputData)
|
|
|
|
|
- else inputDataString = String(inputData)
|
|
|
|
|
- }
|
|
|
|
|
const result = await executeReadLastMessage({ folderPath, inputData: inputDataString, textVariable: textVar, senderVariable: senderVar })
|
|
const result = await executeReadLastMessage({ folderPath, inputData: inputDataString, textVariable: textVar, senderVariable: senderVar })
|
|
|
if (!result.success) return { success: false, error: result.error }
|
|
if (!result.success) return { success: false, error: result.error }
|
|
|
if (textVar) variableContext[textVar] = result.text
|
|
if (textVar) variableContext[textVar] = result.text
|
|
@@ -331,9 +369,8 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
const { executeReadTxt } = get(funcDir, 'io')
|
|
const { executeReadTxt } = get(funcDir, 'io')
|
|
|
let filePath = action.filePath
|
|
let filePath = action.filePath
|
|
|
let varName = action.variable
|
|
let varName = action.variable
|
|
|
- if (action.inVars?.length > 0) filePath = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
- if (action.outVars?.length > 0) varName = extractVarName(action.outVars[0])
|
|
|
|
|
- else if (action.variable) varName = extractVarName(action.variable)
|
|
|
|
|
|
|
+ if (action.inVars?.length > 0) filePath = action.inVars[0]
|
|
|
|
|
+ if (action.outVars?.length > 0) varName = String(action.outVars[0]).trim()
|
|
|
else if (action.variable) varName = extractVarName(action.variable)
|
|
else if (action.variable) varName = extractVarName(action.variable)
|
|
|
if (!filePath) return { success: false, error: 'read-txt 缺少 filePath 参数' }
|
|
if (!filePath) return { success: false, error: 'read-txt 缺少 filePath 参数' }
|
|
|
if (!varName) return { success: false, error: 'read-txt 缺少 variable 参数' }
|
|
if (!varName) return { success: false, error: 'read-txt 缺少 variable 参数' }
|
|
@@ -351,8 +388,8 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
let history = action.history
|
|
let history = action.history
|
|
|
let current = action.current
|
|
let current = action.current
|
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
|
- if (action.inVars.length > 0) history = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
- if (action.inVars.length > 1) current = variableContext[extractVarName(action.inVars[1])] ?? resolveValue(action.inVars[1], variableContext)
|
|
|
|
|
|
|
+ if (action.inVars.length > 0) history = action.inVars[0]
|
|
|
|
|
+ if (action.inVars.length > 1) current = action.inVars[1]
|
|
|
}
|
|
}
|
|
|
if (history === undefined || history === null) history = ''
|
|
if (history === undefined || history === null) history = ''
|
|
|
if (current === undefined || current === null) current = ''
|
|
if (current === undefined || current === null) current = ''
|
|
@@ -361,7 +398,7 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
current: typeof current === 'string' ? current : String(current),
|
|
current: typeof current === 'string' ? current : String(current),
|
|
|
})
|
|
})
|
|
|
if (!result.success) return { success: false, error: result.error }
|
|
if (!result.success) return { success: false, error: result.error }
|
|
|
- const outputVarName = action.outVars?.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : null)
|
|
|
|
|
|
|
+ const outputVarName = action.outVars?.[0] != null ? String(action.outVars[0]).trim() : (action.variable ? extractVarName(action.variable) : null)
|
|
|
if (outputVarName && result.result) variableContext[outputVarName] = result.result
|
|
if (outputVarName && result.result) variableContext[outputVarName] = result.result
|
|
|
return { success: true, result: result.result }
|
|
return { success: true, result: result.result }
|
|
|
}
|
|
}
|
|
@@ -372,15 +409,15 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
let filePath = action.filePath
|
|
let filePath = action.filePath
|
|
|
let content = action.content
|
|
let content = action.content
|
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
|
- if (action.inVars.length > 0) content = variableContext[extractVarName(action.inVars[0])] ?? resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
- if (action.inVars.length > 1) filePath = variableContext[extractVarName(action.inVars[1])] ?? resolveValue(action.inVars[1], variableContext)
|
|
|
|
|
|
|
+ if (action.inVars.length > 0) content = action.inVars[0]
|
|
|
|
|
+ if (action.inVars.length > 1) filePath = action.inVars[1]
|
|
|
}
|
|
}
|
|
|
if (!filePath) return { success: false, error: 'save-txt 缺少 filePath 参数' }
|
|
if (!filePath) return { success: false, error: 'save-txt 缺少 filePath 参数' }
|
|
|
if (content === undefined || content === null) return { success: false, error: 'save-txt 缺少 content 参数' }
|
|
if (content === undefined || content === null) return { success: false, error: 'save-txt 缺少 content 参数' }
|
|
|
const result = await executeSaveTxt({ filePath, content, folderPath })
|
|
const result = await executeSaveTxt({ filePath, content, folderPath })
|
|
|
if (!result.success) return { success: false, error: result.error }
|
|
if (!result.success) return { success: false, error: result.error }
|
|
|
- if (action.outVars?.length > 0) {
|
|
|
|
|
- const outputVarName = extractVarName(action.outVars[0])
|
|
|
|
|
|
|
+ if (action.outVars?.[0] != null) {
|
|
|
|
|
+ const outputVarName = String(action.outVars[0]).trim()
|
|
|
if (outputVarName) variableContext[outputVarName] = result.success ? '1' : '0'
|
|
if (outputVarName) variableContext[outputVarName] = result.success ? '1' : '0'
|
|
|
}
|
|
}
|
|
|
await logOutVars(action, variableContext, folderPath)
|
|
await logOutVars(action, variableContext, folderPath)
|
|
@@ -400,21 +437,21 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
|
|
|
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
if (action.inVars && Array.isArray(action.inVars)) {
|
|
|
if (action.inVars.length >= 3) {
|
|
if (action.inVars.length >= 3) {
|
|
|
- const param1 = resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
- const param2 = resolveValue(action.inVars[1], variableContext)
|
|
|
|
|
|
|
+ const param1 = action.inVars[0]
|
|
|
|
|
+ const param2 = action.inVars[1]
|
|
|
if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) && typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
|
|
if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) && typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
|
|
|
friendRgb = param1.trim()
|
|
friendRgb = param1.trim()
|
|
|
myRgb = param2.trim()
|
|
myRgb = param2.trim()
|
|
|
- regionArea = variableContext[extractVarName(action.inVars[2])] ?? resolveValue(action.inVars[2], variableContext)
|
|
|
|
|
|
|
+ regionArea = action.inVars[2]
|
|
|
} else {
|
|
} else {
|
|
|
avatar1Name = action.inVars[0]
|
|
avatar1Name = action.inVars[0]
|
|
|
avatar2Name = action.inVars[1]
|
|
avatar2Name = action.inVars[1]
|
|
|
- regionArea = variableContext[extractVarName(action.inVars[2])] ?? resolveValue(action.inVars[2], variableContext)
|
|
|
|
|
|
|
+ regionArea = action.inVars[2]
|
|
|
}
|
|
}
|
|
|
regionArea = parseRegion(regionArea)
|
|
regionArea = parseRegion(regionArea)
|
|
|
} else if (action.inVars.length >= 2) {
|
|
} else if (action.inVars.length >= 2) {
|
|
|
- const param1 = resolveValue(action.inVars[0], variableContext)
|
|
|
|
|
- const param2 = resolveValue(action.inVars[1], variableContext)
|
|
|
|
|
|
|
+ const param1 = action.inVars[0]
|
|
|
|
|
+ const param2 = action.inVars[1]
|
|
|
if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) && typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
|
|
if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) && typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
|
|
|
friendRgb = param1.trim()
|
|
friendRgb = param1.trim()
|
|
|
myRgb = param2.trim()
|
|
myRgb = param2.trim()
|
|
@@ -444,7 +481,7 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
const chatResult = await executeOcrChat({ device, avatar1: avatar1Path, avatar2: avatar2Path, folderPath, region: regionParam, friendRgb, myRgb })
|
|
const chatResult = await executeOcrChat({ device, avatar1: avatar1Path, avatar2: avatar2Path, folderPath, region: regionParam, friendRgb, myRgb })
|
|
|
if (!chatResult.success) return { success: false, error: `提取消息记录失败: ${chatResult.error}` }
|
|
if (!chatResult.success) return { success: false, error: `提取消息记录失败: ${chatResult.error}` }
|
|
|
|
|
|
|
|
- const outputVarName = action.outVars?.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : null)
|
|
|
|
|
|
|
+ const outputVarName = action.outVars?.[0] != null ? String(action.outVars[0]).trim() : (action.variable ? extractVarName(action.variable) : null)
|
|
|
if (outputVarName) {
|
|
if (outputVarName) {
|
|
|
variableContext[outputVarName] = chatResult.messagesJson || JSON.stringify(chatResult.messages || [])
|
|
variableContext[outputVarName] = chatResult.messagesJson || JSON.stringify(chatResult.messages || [])
|
|
|
await logOutVars(action, variableContext, folderPath)
|
|
await logOutVars(action, variableContext, folderPath)
|
|
@@ -474,22 +511,6 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
case 'ai-generate': {
|
|
case 'ai-generate': {
|
|
|
const { getHistorySummary } = get(funcDir, 'chat')
|
|
const { getHistorySummary } = get(funcDir, 'chat')
|
|
|
let prompt = resolveValue(action.prompt, variableContext)
|
|
let prompt = resolveValue(action.prompt, variableContext)
|
|
|
- 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]
|
|
|
|
|
- if (varValue !== undefined && varValue !== null) {
|
|
|
|
|
- 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) {}
|
|
|
|
|
- }
|
|
|
|
|
- prompt = (prompt || '').replace(new RegExp(`\\{${varName.replace(/[{}]/g, '\\$&')}\\}`, 'g'), replaceValue)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
if (prompt && prompt.includes('{historySummary}') && getHistorySummary) {
|
|
if (prompt && prompt.includes('{historySummary}') && getHistorySummary) {
|
|
|
let historySummary = variableContext['historySummary'] || ''
|
|
let historySummary = variableContext['historySummary'] || ''
|
|
|
if (!historySummary) {
|
|
if (!historySummary) {
|
|
@@ -549,11 +570,11 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
|
|
|
|
|
if (action.outVars?.length > 0) {
|
|
if (action.outVars?.length > 0) {
|
|
|
if (action.outVars.length > 0) {
|
|
if (action.outVars.length > 0) {
|
|
|
- const outputVarName = extractVarName(action.outVars[0])
|
|
|
|
|
|
|
+ const outputVarName = action.outVars?.[0] != null ? String(action.outVars[0]).trim() : null
|
|
|
if (outputVarName) variableContext[outputVarName] = result
|
|
if (outputVarName) variableContext[outputVarName] = result
|
|
|
}
|
|
}
|
|
|
if (action.outVars.length > 1) {
|
|
if (action.outVars.length > 1) {
|
|
|
- const callbackVarName = extractVarName(action.outVars[1])
|
|
|
|
|
|
|
+ const callbackVarName = action.outVars?.[1] != null ? String(action.outVars[1]).trim() : null
|
|
|
if (callbackVarName) variableContext[callbackVarName] = 1
|
|
if (callbackVarName) variableContext[callbackVarName] = 1
|
|
|
}
|
|
}
|
|
|
await logOutVars(action, variableContext, folderPath)
|
|
await logOutVars(action, variableContext, folderPath)
|
|
@@ -562,8 +583,8 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
if (outputVarName) variableContext[outputVarName] = result
|
|
if (outputVarName) variableContext[outputVarName] = result
|
|
|
}
|
|
}
|
|
|
if (!action.outVars || action.outVars.length <= 1) {
|
|
if (!action.outVars || action.outVars.length <= 1) {
|
|
|
- if (action.inVars?.length > 1) {
|
|
|
|
|
- const callbackVarName = extractVarName(action.inVars[1])
|
|
|
|
|
|
|
+ if (action.inVars?.length > 1 && action.inVars[1] != null) {
|
|
|
|
|
+ const callbackVarName = String(action.inVars[1]).trim()
|
|
|
if (callbackVarName) variableContext[callbackVarName] = 1
|
|
if (callbackVarName) variableContext[callbackVarName] = 1
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -576,7 +597,7 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
case 'string-press': {
|
|
case 'string-press': {
|
|
|
const api = ctx.electronAPI
|
|
const api = ctx.electronAPI
|
|
|
const inVars = action.inVars || []
|
|
const inVars = action.inVars || []
|
|
|
- const targetText = inVars.length > 0 ? (variableContext[extractVarName(inVars[0])] ?? resolveValue(inVars[0], variableContext) ?? action.value) : (action.value ?? '')
|
|
|
|
|
|
|
+ const targetText = inVars.length > 0 ? (inVars[0] ?? action.value) : (action.value ?? '')
|
|
|
if (!targetText) return { success: false, error: 'string-press 操作缺少文字内容' }
|
|
if (!targetText) return { success: false, error: 'string-press 操作缺少文字内容' }
|
|
|
if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' }
|
|
if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' }
|
|
|
const matchResult = await api.findTextAndGetCoordinate(device, targetText)
|
|
const matchResult = await api.findTextAndGetCoordinate(device, targetText)
|
|
@@ -588,19 +609,70 @@ async function run(actionType, action, ctx, device, folderPath) {
|
|
|
return { success: true }
|
|
return { success: true }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- default:
|
|
|
|
|
|
|
+ default: {
|
|
|
|
|
+ const regDef = REGISTRY_BY_TYPE.get(actionType)
|
|
|
|
|
+ if (regDef) {
|
|
|
|
|
+ if (regDef.customRun) {
|
|
|
|
|
+ const script = getRegistryScript(funcDir, actionType)
|
|
|
|
|
+ if (script && typeof script.runNode === 'function') {
|
|
|
|
|
+ const runCtx = { ...ctx, get, funcDir, folderPath, device }
|
|
|
|
|
+ const result = await script.runNode(action, runCtx)
|
|
|
|
|
+ if (result && result.success !== false) await logOutVars(action, variableContext, folderPath)
|
|
|
|
|
+ return result != null ? result : { success: true }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const inKeys = regDef.in || []
|
|
|
|
|
+ const inAlt = regDef.inAlt || {}
|
|
|
|
|
+ const input = {}
|
|
|
|
|
+ inKeys.forEach((key, i) => {
|
|
|
|
|
+ let val = action[key]
|
|
|
|
|
+ if (action.inVars && action.inVars[i] !== undefined) val = action.inVars[i]
|
|
|
|
|
+ if (val === undefined && inAlt[key]) val = action[inAlt[key]]
|
|
|
|
|
+ input[key] = val != null ? String(val).trim() : val
|
|
|
|
|
+ })
|
|
|
|
|
+ input.folderPath = folderPath
|
|
|
|
|
+ const mod = get(funcDir, regDef.category)
|
|
|
|
|
+ const fn = mod[regDef.execute]
|
|
|
|
|
+ if (!fn) {
|
|
|
|
|
+ const errMsg = `fun-parser: ${regDef.execute} not found`
|
|
|
|
|
+ if (logMessage) await logMessage(`[${actionType}] failed: ${errMsg}`, folderPath).catch(() => {})
|
|
|
|
|
+ return { success: false, error: errMsg }
|
|
|
|
|
+ }
|
|
|
|
|
+ let result
|
|
|
|
|
+ try {
|
|
|
|
|
+ result = await fn(input)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ const errMsg = (e && (e.message || String(e))) || 'execute threw'
|
|
|
|
|
+ if (logMessage) await logMessage(`[${actionType}] failed: ${errMsg}`, folderPath).catch(() => {})
|
|
|
|
|
+ return { success: false, error: errMsg }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!result || !result.success) {
|
|
|
|
|
+ const errMsg = (result && result.error) || 'execute failed'
|
|
|
|
|
+ if (logMessage) await logMessage(`[${actionType}] failed: ${errMsg}`, folderPath).catch(() => {})
|
|
|
|
|
+ return { success: false, error: errMsg }
|
|
|
|
|
+ }
|
|
|
|
|
+ const outputVarName = action.outVars?.[0] != null ? String(action.outVars[0]).trim() : null
|
|
|
|
|
+ if (outputVarName && result != null) {
|
|
|
|
|
+ const outVal = result.path ?? result.value ?? result.result
|
|
|
|
|
+ if (outVal !== undefined && outVal !== null) variableContext[outputVarName] = typeof outVal === 'string' ? outVal : String(outVal)
|
|
|
|
|
+ }
|
|
|
|
|
+ await logOutVars(action, variableContext, folderPath)
|
|
|
|
|
+ return { success: true, ...result }
|
|
|
|
|
+ }
|
|
|
return { success: false, error: `fun-parser 不支持的 type: ${actionType}` }
|
|
return { success: false, error: `fun-parser 不支持的 type: ${actionType}` }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const FUN_TYPES = new Set([
|
|
const FUN_TYPES = new Set([
|
|
|
|
|
+ 'ai',
|
|
|
'img-bounding-box-location', 'img-center-point-location', 'img-cropping',
|
|
'img-bounding-box-location', 'img-center-point-location', 'img-cropping',
|
|
|
'read-last-message', 'read-txt', 'read-text', 'smart-chat-append', 'save-txt', 'save-text',
|
|
'read-last-message', 'read-txt', 'read-text', 'smart-chat-append', 'save-txt', 'save-text',
|
|
|
'extract-messages', 'ocr-chat', 'ocr-chat-history', 'extract-chat-history',
|
|
'extract-messages', 'ocr-chat', 'ocr-chat-history', 'extract-chat-history',
|
|
|
'save-messages', 'generate-summary', 'generate-history-summary',
|
|
'save-messages', 'generate-summary', 'generate-history-summary',
|
|
|
'ai-generate',
|
|
'ai-generate',
|
|
|
'string-press',
|
|
'string-press',
|
|
|
-])
|
|
|
|
|
|
|
+].concat(REGISTERED_TYPES))
|
|
|
|
|
|
|
|
function supports(type) {
|
|
function supports(type) {
|
|
|
return FUN_TYPES.has(type)
|
|
return FUN_TYPES.has(type)
|