8. 节点脚本与高级扩展 (Entity Scripts)

MindLogic 在内置的数学公式引擎之上,提供了更为强大的 节点脚本 (Entity Script) 支持。您可以通过编写原生的 JavaScript 代码,在推演计算时动态拦截和修改节点的状态,从而实现高阶的逻辑交互。

什么是节点脚本?

在属性面板(Inspector)的最底部,您会看到一个名为 “节点脚本 (Script)” 的多行代码输入框。 当图谱进行正向计算时,引擎在汇总完所有上游的输入变量、且执行完当前节点的输出公式之后,会自动执行这段脚本。这意味着你的脚本可以轻松读取到内置数学引擎计算出来的公式结果(例如求和结果)。

node 对象

在节点脚本的上下文中,系统暴露了一个全局的代理对象:node。这个对象代表了“当前正在被计算的节点实体”。 通过对 node 对象属性的读取和赋值,您可以利用上游变量改变当前节点的显示和行为。

支持动态读写的属性列表

  • node.id (数字): 节点的唯一编号(只读)。
  • node.title (字符串): 节点的主标题。
  • node.customClassName (字符串): 节点的自定义类名。
  • node.classID (字符串): 节点的底层分类 ID。例如内置的 goal 或自定义类别的专属 ID(可在右侧域(Domain)面板中查看并在名称后复制)。通过修改此属性,可以动态改变节点所属的分类。
  • node.confidence (数字,0~1): 节点的置信度状态。
  • node.annotation (字符串): 节点的备注/注释。
  • node.symbolName (字符串): 节点图标(SF Symbols名称,如 star.fill)。
  • node.statusIcon (字符串): 节点右下角的状态图标。支持的值包括内置快捷键:uncheck (待办), check (完成), complete (高亮完成), error (错误), warning (警告), loading (加载中), none (隐藏)。此外,也支持直接填入任意的 SF Symbols 名称(如 heart.fill)。
  • node.frameType (字符串): 边框形状。支持的值包括 none, roundedRectangle, capsule, circle, diamond, hexagon 等。
  • node.contentType (字符串): 内容形式。支持的值包括 plainText, markdown, image, chart
  • node.contentPayload / node.imagePath (字符串/对象): 万能数据载体。当 contentType 设为 image 时,指定要显示的图片本地路径或远端 URL;当设为 chart 时,支持直接赋值一个 JavaScript 字典或数组对象,底层会自动序列化渲染为图表。
  • node.outputs (字典对象): 存放任何你想通过脚本传递给下游的新变量!你也可以使用全局的 outputs 对象作为简写来直接读写,例如 node.outputs['score'] = 10 完全等价于 outputs.score = 10

获取输入参数

上游节点传递过来的属性(Outputs)或当前节点的自定义属性,会自动作为全局变量注入到 JS 引擎中。你可以直接通过变量名来读取:

  1. 直接读取全局变量:无论是上游有一个叫做 cost 的参数,还是当前节点自己定义了一个名叫 cost 的自定义属性,你都可以直接写 cost 获取它的值。如果有多个上游传入同名参数,直接写 cost 会得到它们的总和(数字类型)或首次出现的值(字符串类型)。当前节点自身的自定义属性也会一并汇总在内。
  2. 按节点编号读取 (ID Indexing):这些全局变量同时也是一个对象,你可以通过节点的编号(右上角的数字 ID)来精确获取对应节点传入的值。例如:cost[1] 表示获取编号为 1 的节点的值(可以是上游节点,也可以是当前节点自己)。
  3. 获取原生集合:系统还会注入 inputs 数组(包含所有上游传来的字典对象)以及 self 字典(专门包含当前节点的自定义属性),您可以遍历它们进行高级操作(例如使用 self.cost 专门获取当前节点的 cost 属性)。

注:如果要获取当前节点的基础信息(如标题、形状等),请直接读取 node 对象的属性(如 node.title)。

system 对象

在节点脚本和插件脚本的上下文中,系统还提供了一个全局对象 system,用于执行底层的系统级操作(如网络请求和文件读取)。注意:调用异步方法时,请务必使用 await

支持的方法列表

  • system.request(url, options): 异步的 HTTP 网络请求。这是执行网络调用的推荐方式。
    • 示例:let res = await system.request("https://api.example.com", { method: "POST", body: JSON.stringify({...}) })
    • 返回值:字符串形式的响应内容。
  • system.fetch(url): 简易的同步 HTTP GET 请求。返回字符串响应。
  • system.readFile(path): 同步读取本地文件系统中的文件,返回文件的字符串内容。
  • system.openFileDialog(): 弹出一个系统的文件选择器面板,让用户选择一个文件,并返回选中的文件绝对路径(字符串)。
  • system.extractPDF(url): 异步读取并提取位于本地或远程 url 的 PDF 文件内容,返回包含所有文本的字符串。
  • system.sleep(ms): 异步等待指定的时间(毫秒)。例如:await system.sleep(1000) 将等待 1 秒钟。

脚本的使用范例

1. 动态警告状态

当上游传入的成本参数总和超过阈值时,自动将当前节点变成危险警告节点:

// 假设上游流入了一个名叫 'cost' 的变量
if (SUM(cost) > 1000) {
    node.title = "🚨 成本超标警报!"; 
    node.confidence = 0.9;
    node.frameType = "diamond"; // 将形状修改为菱形
    node.outputs["isOverBudget"] = true; // 给下游传递判断结果
} else {
    node.title = "成本安全";
    node.outputs["isOverBudget"] = false; // 等价于 outputs.isOverBudget = ...
}

2. 动态改变显示形式

如果存在某个特定的外部输入,将节点转换为富文本显示:

if (ISBLANK(reportData)) {
    node.title = "无报告数据";
} else {
    node.contentType = "markdown";
    node.outputs["markdownOutput"] = "# 报告概要\n数据非常健康!"; // 等价于 outputs.markdownOutput = ...
}

3. 图表渲染与动态数据注入

通过脚本,您可以直接将上游传来的业务数据动态渲染为原生图表。 引擎支持将原生的 JavaScript 对象或数组直接赋值给 node.contentPayload,底层会自动处理所有的序列化与数据绑定,彻底免去了繁琐的 JSON.stringify

支持的图表类型 (chartType)

  • bar (柱状图)
  • line (折线图)
  • area (面积图)
  • point (散点图)
  • pie (饼状图)

示例脚本:直接赋值数据对象

// 假设这是我们根据上游变量动态计算好的数据对象
let data = {
    chartType: "bar",
    xAxisTitle: "季度",
    yAxisTitle: "营收 (万元)",
    series: [
        { x: "Q1", y: 120, category: "产品A" },
        { x: "Q1", y: 80, category: "产品B" },
        { x: "Q2", y: 150, category: "产品A" },
        { x: "Q2", y: 90, category: "产品B" }
    ]
};

// 1. 将节点的显示模式切换为图表
node.contentType = "chart";

// 2. 极简赋值:直接把 JS 对象塞进去即可
node.contentPayload = data;

(注:如果您只需要最简单的基础图表,甚至可以省略外层结构,直接将包含 {x, y} 元素的数组赋值给 node.contentPayload,引擎的智能回退模式会自动尝试解析。)

注意事项

  • 副作用覆盖机制:如果您的脚本内硬编码了 node.title = "某某",那么每次画布刷新、重新计算时,脚本的赋值会强制覆盖您在画布上手动修改的标题。因此,推荐使用条件语句(if...else)来精确控制何时改变属性。
  • 环境隔离:每个节点的脚本运行在一个完全隔离且仅包含当前计算上下文的作用域中,无法直接操纵画布上的其他节点,只能通过 node.outputs 向下流转数据。这确保了推演引擎绝对的单向数据流与确定性。