| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- /**
- * fun 解析与执行 + 执行入口:registry/executeAction 由 ctx 传入(来自 workflow-json-parser),本模块负责 parse/runAction/run/supports
- */
- const path = require('path')
- const FUN_REGISTRY_TYPES = [
- 'fun',
- 'read-txt', 'read-text', 'save-txt', 'save-text',
- 'img-bounding-box-location', 'img-center-point-location', 'img-cropping',
- 'read-last-message', 'smart-chat-append',
- 'extract-messages', 'ocr-chat', 'ocr-chat-history', 'extract-chat-history',
- 'save-messages', 'generate-summary', 'generate-history-summary',
- 'ai-generate', 'string-press',
- ]
- const types = FUN_REGISTRY_TYPES
- function parse(action, parseContext) {
- const { extractVarName, resolveValue } = parseContext
- const variableContext = parseContext.variableContext || {}
- const parsed = {
- type: action.type,
- method: action.method,
- target: action.target,
- value: action.value,
- variable: action.variable,
- condition: action.condition,
- delay: action.delay || '',
- timeout: action.timeout,
- retry: action.retry,
- }
- Object.assign(parsed, action)
- switch (action.type) {
- case 'fun':
- parsed.method = action.method
- 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)) : []
- break
- case 'extract-messages':
- case 'ocr-chat':
- case 'ocr-chat-history':
- case 'extract-chat-history':
- parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
- parsed.avatar1 = action.inVars && action.inVars.length >= 2 ? action.inVars[0] : action.inVars?.[0]
- parsed.avatar2 = action.inVars && action.inVars.length >= 2 ? action.inVars[1] : action.avatar2
- parsed.outVars = action.outVars && Array.isArray(action.outVars) ? action.outVars.map(v => extractVarName(v)) : []
- if (parsed.outVars.length > 0) parsed.variable = extractVarName(action.outVars[0])
- else if (action.variable) parsed.variable = extractVarName(action.variable)
- 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':
- case 'generate-history-summary':
- parsed.summaryVariable = action.summaryVariable
- break
- case 'ai-generate':
- parsed.prompt = resolveValue(action.prompt, variableContext)
- parsed.model = action.model
- 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)) : []
- if (parsed.outVars.length > 0) parsed.variable = extractVarName(action.outVars[0])
- else if (action.variable) parsed.variable = extractVarName(action.variable)
- break
- case 'read-last-message': {
- 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])
- if (!parsed.textVariable) parsed.textVariable = action.textVariable
- if (!parsed.senderVariable) parsed.senderVariable = action.senderVariable
- break
- }
- case 'read-txt':
- case 'read-text':
- parsed.inVars = action.inVars && action.inVars.length > 0 ? action.inVars.map(v => extractVarName(v)) : []
- parsed.filePath = action.inVars && action.inVars.length > 0 ? action.inVars[0] : action.filePath
- parsed.variable = action.outVars && action.outVars.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : undefined)
- break
- case 'save-txt':
- case 'save-text':
- if (action.inVars && Array.isArray(action.inVars)) {
- parsed.inVars = action.inVars.map(v => extractVarName(v))
- parsed.content = action.inVars[0]
- parsed.filePath = action.inVars.length > 1 ? action.inVars[1] : action.filePath
- } else {
- parsed.inVars = []
- parsed.filePath = action.filePath
- parsed.content = action.content
- }
- if (action.outVars && action.outVars.length > 0) parsed.variable = extractVarName(action.outVars[0])
- break
- case 'img-bounding-box-location':
- parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
- parsed.screenshot = action.inVars?.[0]
- parsed.region = action.inVars?.[1] ?? action.region
- parsed.variable = action.outVars && action.outVars.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : undefined)
- break
- case 'img-center-point-location':
- parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
- parsed.template = action.inVars?.[0] ?? action.template
- parsed.variable = action.outVars && action.outVars.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : undefined)
- break
- case 'img-cropping':
- parsed.inVars = action.inVars && Array.isArray(action.inVars) ? action.inVars.map(v => extractVarName(v)) : []
- parsed.area = action.inVars?.[0] ?? action.area
- parsed.savePath = action.inVars?.[1] ?? action.savePath
- if (action.outVars && action.outVars.length > 0) parsed.variable = extractVarName(action.outVars[0])
- break
- default:
- 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)) : []
- break
- }
- return parsed
- }
- async function execute(action, ctx) {
- return { success: true }
- }
- async function runAction(action, device, folderPath, resolution, ctx) {
- const { variableContext, evaluateCondition, registry, executeAction } = ctx
- if (action.condition && !evaluateCondition(action.condition, variableContext)) {
- return { success: true, skipped: true }
- }
- if (action.type === 'fun' && action.method) {
- return run(action.method, action, ctx, device, folderPath)
- }
- // fun 类 type(如 img-center-point-location)必须走 run() 才会执行逻辑并写变量,不能走 registry 的 stub execute
- if (supports(action.type)) {
- return run(action.type, action, ctx, device, folderPath)
- }
- if (registry && registry[action.type]) {
- const execCtx = {
- device,
- folderPath,
- resolution,
- variableContext,
- api: ctx.electronAPI,
- extractVarName: ctx.extractVarName,
- resolveValue: ctx.resolveValue,
- replaceVariablesInString: ctx.replaceVariablesInString,
- evaluateCondition: ctx.evaluateCondition,
- evaluateExpression: ctx.evaluateExpression,
- getActionName: ctx.getActionName,
- logMessage: ctx.logMessage,
- logOutVars: ctx.logOutVars,
- parseDelayString: ctx.parseDelayString,
- calculateWaitTime: ctx.calculateWaitTime,
- DEFAULT_SCROLL_DISTANCE: ctx.DEFAULT_SCROLL_DISTANCE,
- }
- return await executeAction(action.type, action, execCtx)
- }
- return { success: false, error: `未知的操作类型: ${action.type}` }
- }
- const cache = new Map()
- function get(funcDir, category) {
- if (!funcDir) throw new Error('fun-parser: funcDir 未提供')
- const key = `${funcDir}:${category}`
- if (cache.has(key)) return cache.get(key)
- let mod
- switch (category) {
- case 'img':
- mod = {
- executeImgBoundingBoxLocation: require(path.join(funcDir, 'img-bounding-box-location.js')).executeImgBoundingBoxLocation,
- executeImgCenterPointLocation: require(path.join(funcDir, 'img-center-point-location.js')).executeImgCenterPointLocation,
- executeImgCropping: require(path.join(funcDir, 'img-cropping.js')).executeImgCropping,
- }
- break
- case 'io':
- mod = {
- executeReadLastMessage: require(path.join(funcDir, 'chat', 'read-last-message.js')).executeReadLastMessage,
- executeReadTxt: require(path.join(funcDir, 'read-txt.js')).executeReadTxt,
- executeSmartChatAppend: require(path.join(funcDir, 'chat', 'smart-chat-append.js')).executeSmartChatAppend,
- executeSaveTxt: require(path.join(funcDir, 'save-txt.js')).executeSaveTxt,
- }
- break
- case 'chat':
- mod = (() => {
- const chatHistory = require(path.join(funcDir, 'chat', 'chat-history.js'))
- const ocrChat = require(path.join(funcDir, 'chat', 'ocr-chat.js'))
- return {
- executeOcrChat: ocrChat.executeOcrChat,
- generateHistorySummary: chatHistory.generateHistorySummary,
- getHistorySummary: chatHistory.getHistorySummary,
- }
- })()
- break
- default:
- throw new Error(`fun-parser: 未知分类 ${category}`)
- }
- cache.set(key, mod)
- return mod
- }
- const rgbPattern = /^\((\d+),(\d+),(\d+)\)$/
- function parseRegion(regionArea) {
- if (!regionArea) return null
- if (typeof regionArea === 'string') {
- try {
- regionArea = JSON.parse(regionArea)
- } catch (e) {
- return null
- }
- }
- if (regionArea && typeof regionArea === 'object' && (!regionArea.topLeft || !regionArea.bottomRight)) return null
- return regionArea
- }
- async function run(actionType, action, ctx, device, folderPath) {
- const { variableContext, extractVarName, resolveValue, replaceVariablesInString, logOutVars } = ctx
- const funcDir = ctx.compilerConfig && ctx.compilerConfig.funcDir
- if (!funcDir) return { success: false, error: 'compilerConfig.funcDir 未提供' }
- switch (actionType) {
- case 'img-bounding-box-location': {
- const { executeImgBoundingBoxLocation } = get(funcDir, 'img')
- let screenshotPath = action.screenshot
- let regionPath = action.region
- if (action.inVars && Array.isArray(action.inVars)) {
- if (action.inVars.length === 1) {
- const firstVar = extractVarName(action.inVars[0])
- const firstValue = variableContext[firstVar]
- regionPath = firstValue && typeof firstValue === 'string' && !firstValue.includes('{') ? firstValue : action.inVars[0]
- screenshotPath = null
- } else if (action.inVars.length >= 2) {
- const sv = variableContext[extractVarName(action.inVars[0])]
- screenshotPath = sv && typeof sv === 'string' && !sv.includes('{') ? sv : action.inVars[0]
- const rv = variableContext[extractVarName(action.inVars[1])]
- regionPath = rv && typeof rv === 'string' && !rv.includes('{') ? rv : action.inVars[1]
- }
- }
- if (screenshotPath !== null && !screenshotPath) screenshotPath = action.screenshot
- if (!regionPath) regionPath = action.region
- if (!regionPath) return { success: false, error: '缺少区域截图路径' }
- if (screenshotPath === null && !device) return { success: false, error: '缺少完整截图路径,且无法自动获取设备截图(缺少设备ID)' }
- const result = await executeImgBoundingBoxLocation({ device, screenshot: screenshotPath, region: regionPath, folderPath })
- 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)
- if (outputVarName) {
- variableContext[outputVarName] = result.corners && typeof result.corners === 'object' ? JSON.stringify(result.corners) : ''
- await logOutVars(action, variableContext, folderPath)
- }
- return { success: true, result: result.corners }
- }
- case 'img-center-point-location': {
- const { executeImgCenterPointLocation } = get(funcDir, 'img')
- let templatePath = action.template
- if (action.inVars?.length > 0) {
- const templateVar = extractVarName(action.inVars[0])
- const templateValue = variableContext[templateVar]
- templatePath = templateValue && typeof templateValue === 'string' && !templateValue.includes('{') ? templateValue : action.inVars[0]
- }
- if (!templatePath) templatePath = action.template
- if (!templatePath) return { success: false, error: '缺少模板图片路径' }
- if (!device) return { success: false, error: '缺少设备 ID,无法自动获取截图' }
- const result = await executeImgCenterPointLocation({ device, template: templatePath, folderPath })
- 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)
- if (outputVarName) {
- 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 }) : ''
- await logOutVars(action, variableContext, folderPath)
- }
- return { success: true, result: result.center }
- }
- case 'img-cropping': {
- const { executeImgCropping } = get(funcDir, 'img')
- let area = action.area
- let savePath = action.savePath
- if (action.inVars && Array.isArray(action.inVars)) {
- if (action.inVars.length > 0) {
- const areaValue = variableContext[extractVarName(action.inVars[0])]
- area = areaValue !== undefined ? areaValue : resolveValue(action.inVars[0])
- }
- if (action.inVars.length > 1) {
- const savePathValue = variableContext[extractVarName(action.inVars[1])]
- savePath = savePathValue !== undefined ? savePathValue : resolveValue(action.inVars[1])
- }
- }
- if (!area) return { success: false, error: 'img-cropping 缺少 area 参数' }
- if (!savePath) return { success: false, error: 'img-cropping 缺少 savePath 参数' }
- const result = await executeImgCropping({ area, savePath, folderPath, device })
- if (!result.success) return { success: false, error: result.error }
- if (action.outVars?.length > 0) {
- const outputVarName = extractVarName(action.outVars[0])
- if (outputVarName) variableContext[outputVarName] = result.success ? '1' : '0'
- }
- await logOutVars(action, variableContext, folderPath)
- return { success: true }
- }
- case 'read-last-message': {
- const { executeReadLastMessage } = get(funcDir, 'io')
- const inputVars = action.inVars || action.inputVars || []
- 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
- 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 })
- 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': {
- const { executeReadTxt } = get(funcDir, 'io')
- let filePath = action.filePath
- let varName = action.variable
- if (action.inVars?.length > 0) {
- const filePathValue = variableContext[extractVarName(action.inVars[0])]
- filePath = filePathValue !== undefined ? filePathValue : resolveValue(action.inVars[0])
- }
- if (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': {
- const { executeSmartChatAppend } = get(funcDir, 'io')
- let history = action.history
- let current = action.current
- if (action.inVars && Array.isArray(action.inVars)) {
- if (action.inVars.length > 0) {
- const historyValue = variableContext[extractVarName(action.inVars[0])]
- history = historyValue !== undefined ? historyValue : resolveValue(action.inVars[0])
- }
- if (action.inVars.length > 1) {
- const currentValue = variableContext[extractVarName(action.inVars[1])]
- current = currentValue !== undefined ? currentValue : 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 }
- const outputVarName = action.outVars?.length > 0 ? extractVarName(action.outVars[0]) : (action.variable ? extractVarName(action.variable) : null)
- if (outputVarName && result.result) variableContext[outputVarName] = result.result
- return { success: true, result: result.result }
- }
- case 'save-txt':
- case 'save-text': {
- const { executeSaveTxt } = get(funcDir, 'io')
- let filePath = action.filePath
- let content = action.content
- if (action.inVars && Array.isArray(action.inVars)) {
- if (action.inVars.length > 0) {
- const contentValue = variableContext[extractVarName(action.inVars[0])]
- content = contentValue !== undefined ? contentValue : resolveValue(action.inVars[0])
- }
- if (action.inVars.length > 1) {
- const filePathValue = variableContext[extractVarName(action.inVars[1])]
- filePath = filePathValue !== undefined ? filePathValue : 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 }
- if (action.outVars?.length > 0) {
- const outputVarName = extractVarName(action.outVars[0])
- if (outputVarName) variableContext[outputVarName] = result.success ? '1' : '0'
- }
- await logOutVars(action, variableContext, folderPath)
- return { success: true }
- }
- case 'extract-messages':
- case 'ocr-chat':
- case 'ocr-chat-history':
- case 'extract-chat-history': {
- const { executeOcrChat } = get(funcDir, 'chat')
- const folderName = folderPath.split(/[/\\]/).pop()
- let avatar1Path = null
- let avatar2Path = null
- let avatar1Name, avatar2Name, regionArea = null
- let friendRgb = null, myRgb = null
- if (action.inVars && Array.isArray(action.inVars)) {
- if (action.inVars.length >= 3) {
- const param1 = resolveValue(action.inVars[0])
- const param2 = resolveValue(action.inVars[1])
- if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) && typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
- 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 {
- 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
- }
- }
- regionArea = parseRegion(regionArea)
- } else if (action.inVars.length >= 2) {
- const param1 = resolveValue(action.inVars[0])
- const param2 = resolveValue(action.inVars[1])
- if (typeof param1 === 'string' && rgbPattern.test(param1.trim()) && typeof param2 === 'string' && rgbPattern.test(param2.trim())) {
- friendRgb = param1.trim()
- myRgb = param2.trim()
- } else {
- avatar1Name = action.inVars[0]
- avatar2Name = action.inVars[1]
- }
- } else if (action.inVars.length === 1) {
- avatar1Name = action.inVars[0]
- avatar2Name = action.avatar2 || action.myAvatar
- }
- } else {
- avatar1Name = action.avatar1 || action.friendAvatar
- avatar2Name = action.avatar2 || action.myAvatar
- }
- if (avatar1Name) {
- const resolved = resolveValue(avatar1Name)
- if (resolved) avatar1Path = `${folderName}/resources/${resolved}`
- }
- if (avatar2Name) {
- const resolved = resolveValue(avatar2Name)
- if (resolved) avatar2Path = `${folderName}/resources/${resolved}`
- }
- const regionParam = regionArea && typeof regionArea === 'string' ? (() => { try { return JSON.parse(regionArea) } catch (e) { return null } })() : regionArea
- const chatResult = await executeOcrChat({ device, avatar1: avatar1Path, avatar2: avatar2Path, folderPath, region: regionParam, friendRgb, myRgb })
- 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)
- if (outputVarName) {
- variableContext[outputVarName] = chatResult.messagesJson || JSON.stringify(chatResult.messages || [])
- await logOutVars(action, variableContext, folderPath)
- }
- return {
- success: true,
- messages: chatResult.messages || [],
- messagesJson: chatResult.messagesJson || JSON.stringify(chatResult.messages || []),
- lastMessage: chatResult.messages?.length > 0 ? chatResult.messages[chatResult.messages.length - 1] : null,
- }
- }
- case 'save-messages':
- case 'generate-summary':
- case 'generate-history-summary': {
- const { generateHistorySummary } = get(funcDir, 'chat')
- 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 'ai-generate': {
- const { getHistorySummary } = get(funcDir, 'chat')
- 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.includes('{historySummary}') && getHistorySummary) {
- let historySummary = variableContext['historySummary'] || ''
- if (!historySummary) {
- historySummary = await getHistorySummary(folderPath)
- if (historySummary) variableContext['historySummary'] = historySummary
- }
- prompt = prompt.replace(/{historySummary}/g, historySummary)
- }
- prompt = replaceVariablesInString(prompt, variableContext)
- try {
- const response = await fetch('https://ai-anim.com/api/text2textByModel', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ prompt, modelName: action.model || 'gpt-5-nano-ca' }),
- })
- if (!response.ok) return { success: false, error: `AI请求失败: ${response.statusText}` }
- const data = await response.json()
- let rawResult = ''
- if (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 (typeof data.data === 'string') rawResult = data.data
- else rawResult = JSON.stringify(data)
- rawResult = rawResult ? String(rawResult) : ''
- let result = rawResult
- try {
- try {
- const jsonResult = JSON.parse(rawResult.trim())
- if (jsonResult.reply) result = jsonResult.reply
- } catch (e) {
- 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) {}
- } else {
- const jsonMatch = rawResult.match(/\{\s*"reply"\s*:\s*"([^"]+)"\s*\}/)
- if (jsonMatch) result = jsonMatch[1]
- else {
- const lines = rawResult.split('\n').map((l) => l.trim()).filter(Boolean)
- 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) {}
- if (action.outVars?.length > 0) {
- if (action.outVars.length > 0) {
- const outputVarName = extractVarName(action.outVars[0])
- if (outputVarName) variableContext[outputVarName] = result
- }
- 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) {
- const outputVarName = extractVarName(action.variable)
- if (outputVarName) variableContext[outputVarName] = result
- }
- if (!action.outVars || action.outVars.length <= 1) {
- if (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 'string-press': {
- const api = ctx.electronAPI
- const inVars = action.inVars || []
- let targetText = inVars.length > 0 ? (variableContext[extractVarName(inVars[0])] || action.value) : (resolveValue(action.value, variableContext) || action.value)
- if (!targetText) return { success: false, error: 'string-press 操作缺少文字内容' }
- if (!api?.findTextAndGetCoordinate) return { success: false, error: '文字识别 API 不可用' }
- const matchResult = await api.findTextAndGetCoordinate(device, targetText)
- if (!matchResult.success) return { success: false, error: `文字识别失败: ${matchResult.error}` }
- const { x, y } = matchResult.clickPosition
- if (!api?.sendTap) return { success: false, error: '点击 API 不可用' }
- const tapResult = await api.sendTap(device, x, y)
- if (!tapResult.success) return { success: false, error: `点击失败: ${tapResult.error}` }
- return { success: true }
- }
- default:
- return { success: false, error: `fun-parser 不支持的 type: ${actionType}` }
- }
- }
- const FUN_TYPES = new Set([
- 'img-bounding-box-location', 'img-center-point-location', 'img-cropping',
- 'read-last-message', 'read-txt', 'read-text', 'smart-chat-append', 'save-txt', 'save-text',
- 'extract-messages', 'ocr-chat', 'ocr-chat-history', 'extract-chat-history',
- 'save-messages', 'generate-summary', 'generate-history-summary',
- 'ai-generate',
- 'string-press',
- ])
- function supports(type) {
- return FUN_TYPES.has(type)
- }
- module.exports = { types, parse, execute, runAction, get, run, supports }
|