Hybrid engineChatbotPluginDataflowControl flow
npm i rete rete-engine
In order to work properly, all node classes must implement execute
method for Control flow and data
method for Dataflow.
The Start
class is designed to pass control and has a default data
method that returns an empty object.
const socket = new ClassicPreset.Socket("socket");
class Start extends ClassicPreset.Node {
constructor() {
super("Start");
this.addOutput("exec", new ClassicPreset.Output(socket, "Exec"));
}
execute(_: never, forward: (output: "exec") => void) {
forward("exec");
}
data() {
return {};
}
}
Along with receiving control, the Log
class can also request data from the incoming nodes through the message
port using the fetchInputs
method of the DataflowEngine
instance.
class Log extends ClassicPreset.Node {
constructor() {
super("Log");
this.addInput("exec", new ClassicPreset.Input(socket, "Exec", true));
this.addInput("message", new ClassicPreset.Input(socket, "Text"));
this.addOutput("exec", new ClassicPreset.Output(socket, "Exec"));
}
async execute(input: "exec", forward: (output: "exec") => void) {
const inputs = (await dataflow.fetchInputs(this.id)) as {
message: string[];
};
console.log((inputs.message && inputs.message[0]) || "");
forward("exec");
}
data() {
return {};
}
}
The TextNode
class is responsible only for providing data and cannot receive or pass control.
class TextNode extends ClassicPreset.Node {
constructor(private text: string) {
super("Text");
this.addOutput("value", new ClassicPreset.Output(socket, "Number"));
}
execute() {}
data(): { value: string } {
return {
value: this.text
};
}
}
class Connection<A extends NodeProps, B extends NodeProps> extends ClassicPreset.Connection<A, B> {}
type NodeProps = Start | Log | TextNode;
type ConnProps = Connection<Start, Log> | Connection<TextNode, Log>;
type Schemes = GetSchemes<NodeProps, ConnProps>;
import { NodeEditor } from "rete";
import { DataflowEngine, ControlFlowEngine } from "rete-engine";
const editor = new NodeEditor<Schemes>();
const dataflow = new DataflowEngine<Schemes>(({ inputs, outputs }) => {
return {
inputs: () => Object.keys(inputs).filter((name) => name !== "exec"),
outputs: () => Object.keys(outputs).filter((name) => name !== "exec")
};
});
const controlflow = new ControlFlowEngine<Schemes>(() => {
return {
inputs: () => ["exec"],
outputs: () => ["exec"]
};
});
editor.use(dataflow);
editor.use(controlflow);
const start = new Start();
const text1 = new TextNode("log");
const log1 = new Log();
const con1 = new Connection(start, "exec", log1, "exec");
const con2 = new Connection(text1, "value", log1, "message");
await editor.addNode(start);
await editor.addNode(text1);
await editor.addNode(log1);
await editor.addConnection(con1);
await editor.addConnection(con2);
The node start
serves as the starting point for the graph execution.
engine.execute(start.id);
As a result, the start
node passes control to the log1
node, which fetches data from the text1
node using the fetchInputs
method.
Check out the complete result on the Hybrid engine example page. Additionally, you can explore another more sophisticated Chatbot example employing this approach.