diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java index 840ae6c..f1874e9 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java @@ -75,6 +75,8 @@ public class TinyFlowConfigService { SearchDatacenterNodeParser searchDatacenterNodeParser = new SearchDatacenterNodeParser(); // 工作流节点 WorkflowNodeParser workflowNodeParser = new WorkflowNodeParser(); + // 条件判断节点 + ConditionNodeParser conditionNodeParser = new ConditionNodeParser(); chainParser.addNodeParser(docNodeParser.getNodeName(), docNodeParser); chainParser.addNodeParser(makeFileNodeParser.getNodeName(), makeFileNodeParser); @@ -84,6 +86,7 @@ public class TinyFlowConfigService { chainParser.addNodeParser(saveDaveParser.getNodeName(), saveDaveParser); chainParser.addNodeParser(searchDatacenterNodeParser.getNodeName(), searchDatacenterNodeParser); chainParser.addNodeParser(workflowNodeParser.getNodeName(), workflowNodeParser); + chainParser.addNodeParser(conditionNodeParser.getNodeName(), conditionNodeParser); } public void setSearchEngineProvider() { diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java new file mode 100644 index 0000000..b4d6d24 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java @@ -0,0 +1,496 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSON; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.ChainException; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.util.JsConditionUtil; +import com.easyagents.flow.core.util.StringUtil; + +import java.math.BigDecimal; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 条件判断节点:首个命中(if / else-if)语义。 + */ +public class ConditionNode extends BaseNode { + private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\{\\{\\s*([^{}]+?)\\s*}}"); + + private String branchMode = "first_match"; + private String defaultBranchId; + private String defaultBranchLabel; + private List branches = new ArrayList<>(); + + @Override + public Map execute(Chain chain) { + if (branches == null || branches.isEmpty()) { + throw new ChainException("条件判断节点未配置任何分支"); + } + + ConditionBranch defaultBranch = resolveDefaultBranch(); + + for (ConditionBranch branch : branches) { + if (branch == null) { + continue; + } + if (defaultBranch != null && Objects.equals(defaultBranch.getId(), branch.getId())) { + continue; + } + if (isBranchMatched(chain, branch)) { + return buildMatchedResult(branch, false); + } + } + + if (defaultBranch == null) { + throw new ChainException("条件判断节点缺少默认分支,请检查配置"); + } + + return buildMatchedResult(defaultBranch, true); + } + + private ConditionBranch resolveDefaultBranch() { + if (branches == null || branches.isEmpty()) { + return null; + } + + if (StringUtil.hasText(defaultBranchId)) { + for (ConditionBranch branch : branches) { + if (branch != null && Objects.equals(defaultBranchId, branch.getId())) { + return branch; + } + } + } + + if (StringUtil.hasText(defaultBranchLabel)) { + for (ConditionBranch branch : branches) { + if (branch != null && Objects.equals(defaultBranchLabel, branch.getLabel())) { + return branch; + } + } + } + + return branches.get(branches.size() - 1); + } + + private Map buildMatchedResult(ConditionBranch branch, boolean matchedByDefault) { + Map result = new HashMap<>(); + result.put("matchedBranchId", branch.getId()); + result.put("matchedBranchLabel", branch.getLabel()); + result.put("matchedByDefault", matchedByDefault); + return result; + } + + private boolean isBranchMatched(Chain chain, ConditionBranch branch) { + if (branch == null) { + return false; + } + + String mode = StringUtil.getFirstWithText(branch.getMode(), "visual"); + if ("expression".equalsIgnoreCase(mode)) { + return checkByExpression(chain, branch); + } + + return checkByVisualRules(chain, branch); + } + + private boolean checkByExpression(Chain chain, ConditionBranch branch) { + String expression = branch.getExpression(); + if (StringUtil.noText(expression)) { + return false; + } + + try { + String resolvedExpression = resolveExpressionTemplate(expression, chain); + return JsConditionUtil.eval(resolvedExpression, chain, new HashMap<>()); + } catch (Exception e) { + throw new ChainException(String.format("条件分支表达式执行失败,分支[%s/%s]: %s", + branch.getId(), branch.getLabel(), e.getMessage()), e); + } + } + + private String resolveExpressionTemplate(String expression, Chain chain) { + Matcher matcher = TEMPLATE_PARAM_PATTERN.matcher(expression); + StringBuffer output = new StringBuffer(); + + while (matcher.find()) { + String path = matcher.group(1) == null ? "" : matcher.group(1).trim(); + Object value = StringUtil.noText(path) ? null : chain.getState().resolveValue(path); + matcher.appendReplacement(output, Matcher.quoteReplacement(toJsLiteral(value))); + } + matcher.appendTail(output); + return output.toString(); + } + + private String toJsLiteral(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof Number || value instanceof Boolean) { + return String.valueOf(value); + } + if (value instanceof Character || value.getClass().isEnum()) { + return JSON.toJSONString(String.valueOf(value)); + } + return JSON.toJSONString(value); + } + + private boolean checkByVisualRules(Chain chain, ConditionBranch branch) { + List rules = branch.getRules(); + if (rules == null || rules.isEmpty()) { + return false; + } + + Boolean matched = null; + for (ConditionRule rule : rules) { + boolean ruleMatched = checkRule(chain, rule); + if (matched == null) { + matched = ruleMatched; + continue; + } + + String joiner = StringUtil.getFirstWithText(rule.getJoiner(), "AND"); + if ("OR".equalsIgnoreCase(joiner)) { + matched = matched || ruleMatched; + } else { + matched = matched && ruleMatched; + } + } + + return Boolean.TRUE.equals(matched); + } + + private boolean checkRule(Chain chain, ConditionRule rule) { + if (rule == null || StringUtil.noText(rule.getOperator())) { + return false; + } + + Object leftValue = resolveValue(chain, rule.getLeftRef()); + String operator = rule.getOperator(); + + if ("isEmpty".equals(operator)) { + return isEmpty(leftValue); + } + + if ("isNotEmpty".equals(operator)) { + return !isEmpty(leftValue); + } + + Object rightValue = resolveRightValue(chain, rule); + + switch (operator) { + case "eq": + return equalsSmart(leftValue, rightValue); + case "ne": + return !equalsSmart(leftValue, rightValue); + case "gt": + return compareNumber(leftValue, rightValue, it -> it > 0); + case "gte": + return compareNumber(leftValue, rightValue, it -> it >= 0); + case "lt": + return compareNumber(leftValue, rightValue, it -> it < 0); + case "lte": + return compareNumber(leftValue, rightValue, it -> it <= 0); + case "contains": + return contains(leftValue, rightValue); + case "notContains": + return !contains(leftValue, rightValue); + default: + return false; + } + } + + private Object resolveRightValue(Chain chain, ConditionRule rule) { + if (rule == null) { + return null; + } + + if ("ref".equals(rule.getRightType())) { + return resolveValue(chain, rule.getRightRef()); + } + + return rule.getRightValue(); + } + + private Object resolveValue(Chain chain, String path) { + if (chain == null || StringUtil.noText(path)) { + return null; + } + + return chain.getState().resolveValue(path); + } + + private boolean isEmpty(Object value) { + if (value == null) { + return true; + } + + if (value instanceof CharSequence) { + return StringUtil.noText(value.toString()); + } + + if (value instanceof Collection) { + return ((Collection) value).isEmpty(); + } + + if (value instanceof Map) { + return ((Map) value).isEmpty(); + } + + if (value.getClass().isArray()) { + return java.lang.reflect.Array.getLength(value) == 0; + } + + return false; + } + + private boolean contains(Object leftValue, Object rightValue) { + if (leftValue == null) { + return false; + } + + if (leftValue instanceof String) { + return rightValue != null && ((String) leftValue).contains(String.valueOf(rightValue)); + } + + if (leftValue instanceof Collection) { + Collection collection = (Collection) leftValue; + for (Object item : collection) { + if (equalsSmart(item, rightValue)) { + return true; + } + } + return false; + } + + if (leftValue.getClass().isArray()) { + int length = java.lang.reflect.Array.getLength(leftValue); + for (int i = 0; i < length; i++) { + Object item = java.lang.reflect.Array.get(leftValue, i); + if (equalsSmart(item, rightValue)) { + return true; + } + } + return false; + } + + return false; + } + + private boolean compareNumber(Object leftValue, Object rightValue, java.util.function.IntPredicate compareFunc) { + BigDecimal leftNumber = toBigDecimal(leftValue); + BigDecimal rightNumber = toBigDecimal(rightValue); + + if (leftNumber == null || rightNumber == null) { + return false; + } + + return compareFunc.test(leftNumber.compareTo(rightNumber)); + } + + private boolean equalsSmart(Object leftValue, Object rightValue) { + if (leftValue == null || rightValue == null) { + return leftValue == rightValue; + } + + BigDecimal leftNumber = toBigDecimal(leftValue); + BigDecimal rightNumber = toBigDecimal(rightValue); + if (leftNumber != null && rightNumber != null) { + return leftNumber.compareTo(rightNumber) == 0; + } + + if (leftValue instanceof Boolean || rightValue instanceof Boolean) { + return toBoolean(leftValue) == toBoolean(rightValue); + } + + return Objects.equals(String.valueOf(leftValue), String.valueOf(rightValue)); + } + + private boolean toBoolean(Object value) { + if (value == null) { + return false; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + + String stringValue = String.valueOf(value).trim(); + if (StringUtil.noText(stringValue)) { + return false; + } + + return "true".equalsIgnoreCase(stringValue) + || "1".equals(stringValue) + || "yes".equalsIgnoreCase(stringValue) + || "y".equalsIgnoreCase(stringValue); + } + + private BigDecimal toBigDecimal(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return new BigDecimal(String.valueOf(value)); + } + + String text = String.valueOf(value).trim(); + if (StringUtil.noText(text)) { + return null; + } + + try { + return new BigDecimal(text); + } catch (Exception e) { + return null; + } + } + + public String getBranchMode() { + return branchMode; + } + + public void setBranchMode(String branchMode) { + this.branchMode = branchMode; + } + + public String getDefaultBranchId() { + return defaultBranchId; + } + + public void setDefaultBranchId(String defaultBranchId) { + this.defaultBranchId = defaultBranchId; + } + + public String getDefaultBranchLabel() { + return defaultBranchLabel; + } + + public void setDefaultBranchLabel(String defaultBranchLabel) { + this.defaultBranchLabel = defaultBranchLabel; + } + + public List getBranches() { + return branches; + } + + public void setBranches(List branches) { + this.branches = branches; + } + + public static class ConditionBranch { + private String id; + private String label; + private String mode; + private String expression; + private List rules = new ArrayList<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + } + + public static class ConditionRule { + private String id; + private String joiner; + private String leftRef; + private String operator; + private String rightType; + private String rightValue; + private String rightRef; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getJoiner() { + return joiner; + } + + public void setJoiner(String joiner) { + this.joiner = joiner; + } + + public String getLeftRef() { + return leftRef; + } + + public void setLeftRef(String leftRef) { + this.leftRef = leftRef; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getRightType() { + return rightType; + } + + public void setRightType(String rightType) { + this.rightType = rightType; + } + + public String getRightValue() { + return rightValue; + } + + public void setRightValue(String rightValue) { + this.rightValue = rightValue; + } + + public String getRightRef() { + return rightRef; + } + + public void setRightRef(String rightRef) { + this.rightRef = rightRef; + } + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java new file mode 100644 index 0000000..46b673c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java @@ -0,0 +1,42 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.parser.BaseNodeParser; +import com.easyagents.flow.core.util.StringUtil; + +import java.util.Collections; +import java.util.List; + +/** + * 条件判断节点解析器。 + */ +public class ConditionNodeParser extends BaseNodeParser { + + @Override + protected ConditionNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + ConditionNode node = new ConditionNode(); + node.setBranchMode(StringUtil.getFirstWithText(data.getString("branchMode"), "first_match")); + node.setDefaultBranchId(data.getString("defaultBranchId")); + node.setDefaultBranchLabel(data.getString("defaultBranchLabel")); + + JSONArray branchesJson = data.getJSONArray("branches"); + List branches = branchesJson == null + ? Collections.emptyList() + : branchesJson.toJavaList(ConditionNode.ConditionBranch.class); + node.setBranches(branches); + + if (branches.isEmpty()) { + throw new RuntimeException("条件判断节点至少需要一个分支"); + } + if (StringUtil.noText(node.getDefaultBranchId())) { + throw new RuntimeException("条件判断节点必须配置默认分支"); + } + + return node; + } + + public String getNodeName() { + return "conditionNode"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java new file mode 100644 index 0000000..6ea90aa --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java @@ -0,0 +1,231 @@ +package tech.easyflow.ai.node; + +import com.easyagents.flow.core.chain.*; +import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; + +public class ConditionNodeTest { + + @Test + public void testFirstMatch() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = visualBranch("branch_a", "分支A", + visualRule("ctx.score", "gte", "fixed", "80", null)); + ConditionNode.ConditionBranch b2 = visualBranch("branch_b", "分支B", + visualRule("ctx.score", "gte", "fixed", "60", null)); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + + node.setBranches(Arrays.asList(b1, b2, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Chain chain = createChain(Map.of("ctx", Map.of("score", 90))); + Map result = node.execute(chain); + + Assert.assertEquals("branch_a", result.get("matchedBranchId")); + Assert.assertEquals("分支A", result.get("matchedBranchLabel")); + Assert.assertEquals(false, result.get("matchedByDefault")); + } + + @Test + public void testDefaultBranchWhenNoMatch() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = visualBranch("branch_a", "分支A", + visualRule("ctx.level", "eq", "fixed", "vip", null)); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + + node.setBranches(Arrays.asList(b1, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Chain chain = createChain(Map.of("ctx", Map.of("level", "normal"))); + Map result = node.execute(chain); + + Assert.assertEquals("branch_default", result.get("matchedBranchId")); + Assert.assertEquals("默认分支", result.get("matchedBranchLabel")); + Assert.assertEquals(true, result.get("matchedByDefault")); + } + + @Test + public void testVisualOperators() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = visualBranch("branch_hit", "命中分支", + visualRule("ctx.amount", "gt", "fixed", "100", null), + visualRule("ctx.tags", "contains", "fixed", "vip", null), + visualRule("ctx.title", "notContains", "fixed", "blacklist", null), + visualRule("ctx.emptyText", "isEmpty", "fixed", "", null), + visualRule("ctx.note", "isNotEmpty", "fixed", "", null)); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + + node.setBranches(Arrays.asList(b1, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Map ctx = new HashMap<>(); + ctx.put("amount", 120); + ctx.put("tags", Arrays.asList("vip", "new")); + ctx.put("title", "white-user"); + ctx.put("emptyText", ""); + ctx.put("note", "ok"); + + Chain chain = createChain(Map.of("ctx", ctx)); + Map result = node.execute(chain); + Assert.assertEquals("branch_hit", result.get("matchedBranchId")); + Assert.assertEquals(false, result.get("matchedByDefault")); + } + + @Test + public void testVisualRuleJoinerOr() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = visualBranch("branch_or", "OR 分支", + visualRule("ctx.score", "gt", "fixed", "100", null, "AND"), + visualRule("ctx.level", "eq", "fixed", "vip", null, "OR")); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + + node.setBranches(Arrays.asList(b1, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Chain chain = createChain(Map.of("ctx", Map.of("score", 80, "level", "vip"))); + Map result = node.execute(chain); + + Assert.assertEquals("branch_or", result.get("matchedBranchId")); + Assert.assertEquals(false, result.get("matchedByDefault")); + } + + @Test + public void testExpressionModeAndInvalidExpression() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = expressionBranch("branch_expr", "表达式分支", + "{{amount}} >= 100 && {{level}} === 'vip'"); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + node.setBranches(Arrays.asList(b1, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Chain chain = createChain(Map.of("amount", 120, "level", "vip")); + Map result = node.execute(chain); + Assert.assertEquals("branch_expr", result.get("matchedBranchId")); + + ConditionNode invalidNode = new ConditionNode(); + ConditionNode.ConditionBranch bad = expressionBranch("branch_bad", "异常分支", "amount >"); + invalidNode.setBranches(Arrays.asList(bad, def)); + invalidNode.setDefaultBranchId(def.getId()); + invalidNode.setDefaultBranchLabel(def.getLabel()); + + ChainException ex = Assert.assertThrows(ChainException.class, () -> invalidNode.execute(chain)); + Assert.assertTrue(ex.getMessage().contains("branch_bad")); + Assert.assertTrue(ex.getMessage().contains("异常分支")); + } + + @Test + public void testExpressionModeWithTemplatePath() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = expressionBranch( + "branch_expr_tpl", + "模板分支", + "{{ctx.order.amount}} >= 10 && {{ctx.user.level}} === 'vip'" + ); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + node.setBranches(Arrays.asList(b1, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Map ctx = new HashMap<>(); + ctx.put("order", Map.of("amount", 18)); + ctx.put("user", Map.of("level", "vip")); + Chain chain = createChain(Map.of("ctx", ctx)); + + Map result = node.execute(chain); + Assert.assertEquals("branch_expr_tpl", result.get("matchedBranchId")); + Assert.assertEquals(false, result.get("matchedByDefault")); + } + + @Test + public void testManagedEdgeConditionRouting() { + ConditionNode node = new ConditionNode(); + + ConditionNode.ConditionBranch b1 = visualBranch("branch_yes", "通过", + visualRule("ctx.allow", "eq", "fixed", "true", null)); + ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支"); + node.setBranches(Arrays.asList(b1, def)); + node.setDefaultBranchId(def.getId()); + node.setDefaultBranchLabel(def.getLabel()); + + Chain chain = createChain(Map.of("ctx", Map.of("allow", true))); + Map result = node.execute(chain); + + Edge yesEdge = new Edge("e_yes"); + yesEdge.setCondition(new JsCodeCondition("matchedBranchId === 'branch_yes'")); + Assert.assertTrue(yesEdge.getCondition().check(chain, yesEdge, result)); + + Edge noEdge = new Edge("e_no"); + noEdge.setCondition(new JsCodeCondition("matchedBranchId === 'branch_other'")); + Assert.assertFalse(noEdge.getCondition().check(chain, noEdge, result)); + } + + private static Chain createChain(Map memory) { + Chain chain = new Chain(new ChainDefinition(), UUID.randomUUID().toString()); + chain.setChainStateRepository(new InMemoryChainStateRepository()); + if (memory != null && !memory.isEmpty()) { + chain.getState().getMemory().putAll(memory); + } + return chain; + } + + private static ConditionNode.ConditionBranch defaultBranch(String id, String label) { + ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch(); + branch.setId(id); + branch.setLabel(label); + branch.setMode("visual"); + branch.setRules(Collections.emptyList()); + return branch; + } + + private static ConditionNode.ConditionBranch expressionBranch(String id, String label, String expression) { + ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch(); + branch.setId(id); + branch.setLabel(label); + branch.setMode("expression"); + branch.setExpression(expression); + branch.setRules(Collections.emptyList()); + return branch; + } + + private static ConditionNode.ConditionBranch visualBranch( + String id, String label, ConditionNode.ConditionRule... rules) { + ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch(); + branch.setId(id); + branch.setLabel(label); + branch.setMode("visual"); + branch.setRules(Arrays.asList(rules)); + return branch; + } + + private static ConditionNode.ConditionRule visualRule( + String leftRef, String operator, String rightType, String rightValue, String rightRef) { + return visualRule(leftRef, operator, rightType, rightValue, rightRef, "AND"); + } + + private static ConditionNode.ConditionRule visualRule( + String leftRef, String operator, String rightType, String rightValue, String rightRef, String joiner) { + ConditionNode.ConditionRule rule = new ConditionNode.ConditionRule(); + rule.setId(UUID.randomUUID().toString()); + rule.setJoiner(joiner); + rule.setLeftRef(leftRef); + rule.setOperator(operator); + rule.setRightType(rightType); + rule.setRightValue(rightValue); + rule.setRightRef(rightRef); + return rule; + } +} diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowCore.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowCore.svelte index 5881b96..3f7c42d 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowCore.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowCore.svelte @@ -3,7 +3,6 @@ Background, Controls, type Edge, - type Handle, MarkerType, MiniMap, type Node, @@ -33,14 +32,14 @@ import {onDestroy, onMount} from 'svelte'; import {isInEditableElement} from '#components/utils/isInEditableElement'; - const { onInit, ...rest } = $props(); + const { onInit }: { onInit: any; [key: string]: any } = $props(); const svelteFlow = useSvelteFlow(); - console.log('props', rest); onInit(svelteFlow); let showEdgePanel = $state(false); let currentEdge = $state(null); + const asString = (value: unknown) => (value == null ? '' : String(value)); const { updateEdgeData } = useUpdateEdgeData(); @@ -122,7 +121,7 @@ } const fromNode = state.fromNode as Node; - const fromHande = state.fromHandle as Handle; + const fromHande = state.fromHandle as any; const newNode = { position: { ...toNode.position } @@ -338,7 +337,31 @@ showEdgePanel = true; currentEdge = e.edge; }} - onbeforeconnect={(edge) => { + onbeforeconnect={(edge: any) => { + const sourceNode = edge.source ? getNode(edge.source) : null; + const sourceHandle = edge.sourceHandle || ''; + const isConditionBranchEdge = sourceNode?.type === 'conditionNode' + && typeof sourceHandle === 'string' + && sourceHandle.startsWith('branch_'); + + if (isConditionBranchEdge) { + const branchId = sourceHandle.slice(7); + const branches = (sourceNode?.data?.branches || []) as Array; + const branch = branches.find((item) => item?.id === branchId); + const branchLabel = branch?.label || '条件分支'; + return { + ...edge, + id: genShortId(), + data: { + ...((edge as any).data || {}), + managedByConditionNode: true, + branchId, + branchLabel, + condition: `matchedBranchId === '${branchId}'` + } + }; + } + return { ...edge, id:genShortId(), @@ -376,24 +399,43 @@
边属性设置
边条件设置
-