diff --git a/service-ai/service-ai-web/pom.xml b/service-ai/service-ai-web/pom.xml
index d216b41..ce3546c 100644
--- a/service-ai/service-ai-web/pom.xml
+++ b/service-ai/service-ai-web/pom.xml
@@ -86,6 +86,11 @@
org.noear
solon-ai-dialect-openai
+
+ com.yomahub
+ liteflow-script-graaljs
+ ${liteflow.version}
+
org.hibernate.orm
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java
index 3e4c180..1d7bdd1 100644
--- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/WebApplication.java
@@ -27,6 +27,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
public class WebApplication implements ApplicationRunner {
public static void main(String[] args) {
+ System.setProperty("polyglot.engine.WarnInterpreterOnly", "false");
+
SpringApplication.run(WebApplication.class, args);
}
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowGraphRunner.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowGraphRunner.java
index 81bfe16..65e16ae 100644
--- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowGraphRunner.java
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowGraphRunner.java
@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.service.ai.web.engine;
+import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
@@ -44,7 +45,9 @@ public final class FlowGraphRunner {
flowStore.init(flowGraph);
for (FlowNode node : flowGraph.nodes()) {
- executionQueue.offer(node);
+ if (ObjectUtil.isNull(node.parentId())) {
+ executionQueue.offer(node);
+ }
}
while (!executionQueue.isEmpty()) {
var node = executionQueue.poll();
@@ -77,8 +80,17 @@ public final class FlowGraphRunner {
var runner = runnerClazz.getDeclaredConstructor().newInstance();
runner.setNodeId(node.id());
runner.setContext(context);
+
+ // 处理子流程节点的逻辑
+ if (runner instanceof FlowNodeSubflowRunner subflowRunner) {
+ var subflowNodes = flowGraph.nodes().select(n -> StrUtil.equals(n.parentId(), node.id()));
+ var subGraph = new FlowGraph(IdUtil.fastUUID(), subflowNodes, flowGraph.edges());
+ subflowRunner.setSubGraph(subGraph);
+ }
+
runner.run();
+ // 处理选择节点的逻辑
if (runner instanceof FlowNodeOptionalRunner) {
var targetPoint = ((FlowNodeOptionalRunner) runner).getTargetPoint();
for (FlowEdge edge : nodeOutputMap.get(node.id())) {
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowNodeSubflowRunner.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowNodeSubflowRunner.java
new file mode 100644
index 0000000..650c6cb
--- /dev/null
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/FlowNodeSubflowRunner.java
@@ -0,0 +1,17 @@
+package com.lanyuanxiaoyao.service.ai.web.engine;
+
+import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 包含子流程的流程
+ *
+ * @author lanyuanxiaoyao
+ * @version 20250717
+ */
+public abstract class FlowNodeSubflowRunner extends FlowNodeRunner {
+ @Getter
+ @Setter
+ private FlowGraph subGraph;
+}
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowEdge.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowEdge.java
index 2e5b0dc..7d8f5b0 100644
--- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowEdge.java
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowEdge.java
@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
+import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
/**
@@ -12,7 +13,9 @@ public record FlowEdge(
String id,
String source,
String target,
+ @JsonProperty("sourceHandle")
String sourcePoint,
+ @JsonProperty("targetHandle")
String targetPoint
) {
public enum Status {
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowNode.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowNode.java
index 50a65f1..52e5898 100644
--- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowNode.java
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/entity/FlowNode.java
@@ -10,7 +10,8 @@ import java.time.LocalDateTime;
*/
public record FlowNode(
String id,
- String type
+ String type,
+ String parentId
) {
public enum Status {
INITIAL, RUNNING, FINISHED, SKIPPED
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/CodeNode.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/CodeNode.java
new file mode 100644
index 0000000..2bbe750
--- /dev/null
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/CodeNode.java
@@ -0,0 +1,33 @@
+package com.lanyuanxiaoyao.service.ai.web.engine.node;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
+import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeRunner;
+import com.lanyuanxiaoyao.service.ai.web.engine.node.code.CodeExecutor;
+import com.lanyuanxiaoyao.service.ai.web.engine.node.code.JavaScriptCodeExecutor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author lanyuanxiaoyao
+ * @version 20250717
+ */
+@Slf4j
+public class CodeNode extends FlowNodeRunner {
+ @Override
+ public void run() {
+ var inputVariablesMap = FlowHelper.generateInputVariablesMap(getNodeId(), getContext());
+ var type = this.getData("type");
+ var script = this.getData("content");
+ CodeExecutor executor = null;
+ switch (type) {
+ case "javascript":
+ executor = new JavaScriptCodeExecutor();
+ }
+ if (ObjectUtil.isNull(executor)) {
+ throw new RuntimeException(StrUtil.format("Unsupported type: {}", type));
+ }
+ var result = executor.execute(script, inputVariablesMap);
+ result.forEachKeyValue(this::setData);
+ }
+}
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/LoopNode.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/LoopNode.java
new file mode 100644
index 0000000..bd476ab
--- /dev/null
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/LoopNode.java
@@ -0,0 +1,18 @@
+package com.lanyuanxiaoyao.service.ai.web.engine.node;
+
+import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeSubflowRunner;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 循环节点
+ *
+ * @author lanyuanxiaoyao
+ * @version 20250717
+ */
+@Slf4j
+public class LoopNode extends FlowNodeSubflowRunner {
+ @Override
+ public void run() {
+ log.info("{}", getSubGraph());
+ }
+}
diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/SwitchNode.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/SwitchNode.java
new file mode 100644
index 0000000..e264f8a
--- /dev/null
+++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/engine/node/SwitchNode.java
@@ -0,0 +1,65 @@
+package com.lanyuanxiaoyao.service.ai.web.engine.node;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
+import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeOptionalRunner;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author lanyuanxiaoyao
+ * @version 20250717
+ */
+@SuppressWarnings("unchecked")
+@Slf4j
+public class SwitchNode extends FlowNodeOptionalRunner {
+ @Override
+ public String runOptional() {
+ var conditions = this.>>getData("conditions");
+ for (Map item : conditions) {
+ var condition = (Map) item.getOrDefault("condition", Map.of());
+ var id = (String) condition.getOrDefault("id", "");
+ var conjunction = (String) condition.getOrDefault("conjunction", "and");
+ var conditionChildren = ((List