variable-parser.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /**
  2. * 统一解析结点入参、出参:将 action 中的变量引用用 variableContext 解析为实际值。
  3. * 规则:{var}、{{var}} 为变量(替换为变量值);{arr}[{idx}]、{arr}[数字] 为数组下标,解析为对应元素;"hello{var}" 为字符串+变量拼接。
  4. * 所有函数的入参、出参都经本脚本解析后再传给对应结点。
  5. */
  6. const setParser = require('./actions/set-parser.js')
  7. const resolveValue = setParser.resolveValue
  8. const replaceVariablesInString = setParser.replaceVariablesInString
  9. const extractVarName = setParser.extractVarName
  10. /** 视为入参的字段(会被解析);inVars 由本脚本统一解析后传给各结点。 */
  11. const INPUT_KEYS = [
  12. 'value', 'target', 'template', 'area', 'savePath', 'condition', 'delay', 'interval',
  13. 'items', 'screenshot', 'region', 'method', 'clear', 'timeout', 'retry',
  14. 'min', 'max', 'avatar1', 'avatar2', 'friendAvatar', 'avatar', 'path', 'filePath',
  15. 'inputDataString', 'textVariable', 'senderVariable', 'appendMode',
  16. 'summaryPrompt', 'historyPrompt', 'model', 'prompt', 'systemPrompt',
  17. 'regionArea', 'saveDir', 'url', 'filename', 'imageUrl',
  18. ]
  19. /**
  20. * 将数组下标取值结果转为可嵌入字符串的形式
  21. */
  22. function toEmbedString(v) {
  23. if (v === undefined || v === null) return ''
  24. if (typeof v === 'string') return v
  25. if (typeof v === 'number' || typeof v === 'boolean') return String(v)
  26. try {
  27. return JSON.stringify(v)
  28. } catch (e) {
  29. return String(v)
  30. }
  31. }
  32. /**
  33. * 解析字符串中的数组下标:{arr}[{idx}]、{arr}[数字],替换为实际元素值。
  34. * 先于普通变量替换执行,以便 {img-prompt-arr}[{idx}] 能解析为当前项。
  35. */
  36. function replaceArrayIndexInString(str, variableContext) {
  37. if (typeof str !== 'string' || !variableContext) return str
  38. // {var}[{indexVar}]
  39. let out = str.replace(/\{([\w-]+)\}\s*\[\s*\{([\w-]+)\}\s*\]/g, (_, arrName, idxName) => {
  40. const arr = variableContext[arrName]
  41. const idxVal = variableContext[idxName]
  42. if (!Array.isArray(arr)) return toEmbedString(arr)
  43. const i = typeof idxVal === 'number' ? idxVal : parseInt(idxVal, 10)
  44. if (Number.isNaN(i) || i < 0 || i >= arr.length) return ''
  45. return toEmbedString(arr[i])
  46. })
  47. // {var}[数字]
  48. out = out.replace(/\{([\w-]+)\}\s*\[\s*(\d+)\s*\]/g, (_, arrName, numStr) => {
  49. const arr = variableContext[arrName]
  50. if (!Array.isArray(arr)) return toEmbedString(arr)
  51. const i = parseInt(numStr, 10)
  52. if (i < 0 || i >= arr.length) return ''
  53. return toEmbedString(arr[i])
  54. })
  55. return out
  56. }
  57. /**
  58. * 解析单值:先解析数组下标 {arr}[{idx}] / {arr}[n],再做 {{var}}、{var} 替换,最后对整体做引用解析。
  59. */
  60. function resolveInputValue(val, variableContext) {
  61. if (variableContext == null) return val
  62. if (typeof val === 'string') {
  63. const afterIndex = replaceArrayIndexInString(val, variableContext)
  64. const replaced = replaceVariablesInString(afterIndex, variableContext)
  65. let result = resolveValue(replaced, variableContext)
  66. if (result === val && /^[\w-]+$/.test(val) && variableContext[val] !== undefined) result = variableContext[val]
  67. return result
  68. }
  69. if (Array.isArray(val)) return val.map(item => resolveInputValue(item, variableContext))
  70. if (typeof val === 'object' && val !== null) {
  71. const out = {}
  72. for (const k in val) out[k] = resolveInputValue(val[k], variableContext)
  73. return out
  74. }
  75. return val
  76. }
  77. /**
  78. * 解析整条 action 的入参,返回新对象(不修改原 action)
  79. * @param {object} action - 原始或已 parse 的 action
  80. * @param {object} variableContext - 变量表
  81. * @returns {object} 入参解析后的 action 副本
  82. */
  83. function resolveActionInputs(action, variableContext) {
  84. if (!action || typeof action !== 'object') return action
  85. if (!variableContext || typeof variableContext !== 'object') return Object.assign({}, action)
  86. const resolved = Object.assign({}, action)
  87. for (const key of INPUT_KEYS) {
  88. if (key in resolved && resolved[key] !== undefined && resolved[key] !== null) {
  89. resolved[key] = resolveInputValue(resolved[key], variableContext)
  90. }
  91. }
  92. if (resolved.inVars && Array.isArray(resolved.inVars)) {
  93. resolved.inVars = resolved.inVars.map((v) => resolveInputValue(v, variableContext))
  94. }
  95. if (resolved.outVars && Array.isArray(resolved.outVars)) {
  96. resolved.outVars = resolved.outVars.map((v) => (typeof v === 'string' ? extractVarName(v) : v))
  97. }
  98. if (resolved.condition && typeof resolved.condition === 'object' && !Array.isArray(resolved.condition)) {
  99. const c = resolved.condition
  100. if (c.interval != null) resolved.condition = Object.assign({}, c, { interval: resolveInputValue(c.interval, variableContext) })
  101. if (c.repeat != null) resolved.condition = Object.assign({}, resolved.condition, { repeat: resolveInputValue(c.repeat, variableContext) })
  102. }
  103. return resolved
  104. }
  105. module.exports = {
  106. resolveActionInputs,
  107. resolveInputValue,
  108. replaceArrayIndexInString,
  109. extractVarName,
  110. }