From 766554bf638f8a18af38df4b1cf9bb5b64390442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Wed, 29 Jul 2026 18:10:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E9=85=8D=E7=BD=AE=E4=B8=8E=E4=BD=9C=E7=94=A8?= =?UTF-8?q?=E5=9F=9F=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持次数与数组独立或组合配置并补齐检查规则 - 统一循环体临时变量与下游正式输出候选 - 稳定知识库对象数组字段并补充前后端测试 --- .../knowledge/KnowledgeProviderImpl.java | 39 +- .../service/WorkflowCheckService.java | 273 +++++++++++- .../knowledge/KnowledgeProviderImplTest.java | 83 ++++ .../service/WorkflowCheckServiceTest.java | 184 ++++++++ .../src/components/nodes/LoopNode.svelte | 31 +- .../nodes/loop/LoopInputEditor.svelte | 414 ++++++++++++++++++ .../components/utils/refOptionFilter.test.ts | 54 +++ .../src/components/utils/refOptionFilter.ts | 48 ++ .../components/utils/useRefOptions.svelte.ts | 62 ++- .../tinyflow-ui/src/utils/loopScope.test.ts | 238 ++++++++++ .../tinyflow-ui/src/utils/loopScope.ts | 237 ++++++++++ .../src/utils/workflowNodeFields.test.ts | 101 +++++ .../src/utils/workflowNodeFields.ts | 35 +- 13 files changed, 1737 insertions(+), 62 deletions(-) create mode 100644 easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImplTest.java create mode 100644 easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/loop/LoopInputEditor.svelte create mode 100644 easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/refOptionFilter.test.ts create mode 100644 easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/refOptionFilter.ts create mode 100644 easyflow-ui-admin/packages/tinyflow-ui/src/utils/loopScope.test.ts create mode 100644 easyflow-ui-admin/packages/tinyflow-ui/src/utils/loopScope.ts diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java index 57a243ec..f2dbbd1e 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java @@ -17,6 +17,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +/** + * 为工作流知识库节点提供检索能力。 + */ @Component public class KnowledgeProviderImpl implements KnowledgeProvider { @@ -24,14 +27,23 @@ public class KnowledgeProviderImpl implements KnowledgeProvider { private DocumentCollectionService documentCollectionService; /** - * 获取知识库 - * @param id 知识库id + * 获取知识库检索器。 + * + * @param id 知识库 ID + * @return 知识库检索器 */ @Override public Knowledge getKnowledge(Object id) { return new Knowledge() { + /** + * {@inheritDoc} + */ @Override - public List> search(String keyword, int limit, KnowledgeNode knowledgeNode, Chain chain) { + public List> search( + String keyword, + int limit, + KnowledgeNode knowledgeNode, + Chain chain) { KnowledgeRetrievalRequest request = new KnowledgeRetrievalRequest(); request.setKnowledgeId(new BigInteger(id.toString())); request.setQuery(keyword); @@ -45,10 +57,29 @@ public class KnowledgeProviderImpl implements KnowledgeProvider { } List> res = new ArrayList<>(); for (Document document : documents) { - res.add(JSONObject.from(document)); + res.add(toWorkflowDocument(document, id)); } return res; } }; } + + /** + * 将检索文档转换为工作流稳定对象,并保留旧序列化字段。 + * + * @param document 检索文档 + * @param knowledgeId 知识库 ID + * @return 工作流文档对象 + */ + private Map toWorkflowDocument( + Document document, Object knowledgeId) { + JSONObject result = JSONObject.from(document); + result.put("title", document.getTitle()); + result.put("content", document.getContent()); + result.put( + "documentId", + document.getMetadata("documentId", document.getId())); + result.put("knowledgeId", knowledgeId); + return result; + } } diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java index 13464c3c..e085e218 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java @@ -40,6 +40,9 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +/** + * 校验工作流结构、节点配置和预执行约束。 + */ @Service public class WorkflowCheckService { private static final String LEVEL_ERROR = "ERROR"; @@ -240,7 +243,7 @@ public class WorkflowCheckService { Set issueKeys) { for (NodeView node : nodes) { checkConfiguredLoopCount(node, issues, issueKeys); - checkFixedExplicitLoopCount(node, issues, issueKeys); + checkExplicitLoopInputs(node, issues, issueKeys); if (StringUtils.hasText(node.parentId)) { NodeView parent = nodeMap.get(node.parentId); if (parent != null && !TYPE_LOOP.equals(parent.type)) { @@ -255,6 +258,7 @@ public class WorkflowCheckService { } } checkLoopParentCycle(node, nodeMap, issues, issueKeys); + checkLoopVariableScope(node, nodeMap, issues, issueKeys); } } @@ -281,36 +285,293 @@ public class WorkflowCheckService { } /** - * 校验显式循环节点使用固定数值时的次数范围。 + * 校验显式循环节点的新旧输入结构。 * * @param node 节点 * @param issues 问题列表 * @param issueKeys 问题去重键 */ - private void checkFixedExplicitLoopCount( + private void checkExplicitLoopInputs( NodeView node, List issues, Set issueKeys) { if (!TYPE_LOOP.equals(node.type) || node.data == null) { return; } + + JSONObject loopInputs = node.data.getJSONObject("loopInputs"); JSONArray loopVars = node.data.getJSONArray("loopVars"); - if (loopVars == null || loopVars.isEmpty()) { + if (loopInputs != null) { + if (loopVars != null && !loopVars.isEmpty()) { + addIssue( + issues, + issueKeys, + "LOOP_INPUT_SCHEMA_CONFLICT", + "循环输入配置存在冲突,请重新保存循环节点", + node.id, + null, + node.name); + } + JSONObject count = loopInputs.getJSONObject("count"); + JSONObject items = loopInputs.getJSONObject("items"); + if (count == null && items == null) { + addIssue( + issues, + issueKeys, + "LOOP_INPUT_REQUIRED", + "请至少配置循环次数或输入数组", + node.id, + null, + node.name); + return; + } + checkExplicitCountParameter(count, node, issues, issueKeys); + checkExplicitItemsParameter(items, node, issues, issueKeys); return; } + + if (loopVars == null || loopVars.isEmpty()) { + addIssue( + issues, + issueKeys, + "LOOP_INPUT_REQUIRED", + "请至少配置循环次数或输入数组", + node.id, + null, + node.name); + return; + } + JSONObject loopVar = loopVars.getJSONObject(0); - if (loopVar == null || !"fixed".equals(loopVar.getString("refType"))) { + if (loopVar == null) { + addIssue( + issues, + issueKeys, + "LOOP_INPUT_REQUIRED", + "请至少配置循环次数或输入数组", + node.id, + null, + node.name); + return; + } + if (!"fixed".equals(loopVar.getString("refType"))) { return; } Object value = loopVar.get("value"); - if (value != null && StringUtils.hasText(String.valueOf(value))) { + if (value == null || !StringUtils.hasText(String.valueOf(value))) { + addIssue( + issues, + issueKeys, + "EXPLICIT_LOOP_COUNT_INVALID", + "循环次数必须是 1~300 的整数", + node.id, + null, + node.name); + return; + } + addLoopCountIssueIfInvalid( + value, + "EXPLICIT_LOOP_COUNT_INVALID", + node, + issues, + issueKeys); + } + + /** + * 校验显式循环次数参数。 + * + * @param count 次数参数 + * @param node 节点 + * @param issues 问题列表 + * @param issueKeys 问题去重键 + */ + private void checkExplicitCountParameter( + JSONObject count, + NodeView node, + List issues, + Set issueKeys) { + if (count == null) { + return; + } + String refType = trimToNull(count.getString("refType")); + if ("fixed".equals(refType)) { + Object value = count.get("value"); + if (value == null || !StringUtils.hasText(String.valueOf(value))) { + addIssue( + issues, + issueKeys, + "EXPLICIT_LOOP_COUNT_INVALID", + "循环次数必须是 1~300 的整数", + node.id, + null, + node.name); + return; + } addLoopCountIssueIfInvalid( value, "EXPLICIT_LOOP_COUNT_INVALID", node, issues, issueKeys); + return; } + if (!"ref".equals(refType) + || !StringUtils.hasText(trimToNull(count.getString("ref"))) + || !"Number".equalsIgnoreCase( + safe(count.getString("dataType")))) { + addIssue( + issues, + issueKeys, + "EXPLICIT_LOOP_COUNT_INVALID", + "循环次数必须引用数值变量", + node.id, + null, + node.name); + } + } + + /** + * 校验显式循环数组参数。 + * + * @param items 数组参数 + * @param node 节点 + * @param issues 问题列表 + * @param issueKeys 问题去重键 + */ + private void checkExplicitItemsParameter( + JSONObject items, + NodeView node, + List issues, + Set issueKeys) { + if (items == null) { + return; + } + String refType = trimToNull(items.getString("refType")); + String ref = trimToNull(items.getString("ref")); + String dataType = trimToNull(items.getString("dataType")); + if (!"ref".equals(refType) + || !StringUtils.hasText(ref) + || !isArrayDataType(dataType)) { + addIssue( + issues, + issueKeys, + "EXPLICIT_LOOP_ITEMS_TYPE_INVALID", + "输入数组必须引用数组变量", + node.id, + null, + node.name); + } + } + + /** + * 判断参数类型是否为数组。 + * + * @param dataType 参数类型 + * @return 数组类型返回 {@code true} + */ + private boolean isArrayDataType(String dataType) { + return StringUtils.hasText(dataType) + && ("Array".equalsIgnoreCase(dataType) + || dataType.regionMatches( + true, 0, "Array<", 0, "Array<".length())); + } + + /** + * 校验 index 和 loopItem 仅在所属循环体内引用。 + * + * @param node 当前节点 + * @param nodeMap 节点索引 + * @param issues 问题列表 + * @param issueKeys 问题去重键 + */ + private void checkLoopVariableScope( + NodeView node, + Map nodeMap, + List issues, + Set issueKeys) { + if (node.data == null) { + return; + } + Set references = new LinkedHashSet<>(); + collectParameterReferences(node.data, references); + for (String reference : references) { + int separator = reference.indexOf('.'); + if (separator <= 0) { + continue; + } + String loopNodeId = reference.substring(0, separator); + String variablePath = reference.substring(separator + 1); + if (!("index".equals(variablePath) + || variablePath.startsWith("index.") + || "loopItem".equals(variablePath) + || variablePath.startsWith("loopItem."))) { + continue; + } + NodeView loopNode = nodeMap.get(loopNodeId); + if (loopNode == null || !TYPE_LOOP.equals(loopNode.type)) { + continue; + } + if (!isDescendantOfLoop(node, loopNodeId, nodeMap)) { + addIssue( + issues, + issueKeys, + "LOOP_SCOPE_REFERENCE_INVALID", + "循环变量只能在对应循环体内使用", + node.id, + null, + node.name); + } + } + } + + /** + * 递归收集节点数据中的参数引用。 + * + * @param value 待遍历值 + * @param references 引用结果 + */ + private void collectParameterReferences( + Object value, Set references) { + if (value instanceof JSONObject object) { + String reference = trimToNull(object.getString("ref")); + if (StringUtils.hasText(reference)) { + references.add(reference); + } + for (Object child : object.values()) { + collectParameterReferences(child, references); + } + return; + } + if (value instanceof JSONArray array) { + for (Object child : array) { + collectParameterReferences(child, references); + } + } + } + + /** + * 判断节点是否位于指定循环节点内部。 + * + * @param node 当前节点 + * @param loopNodeId 循环节点 ID + * @param nodeMap 节点索引 + * @return 位于循环体内返回 {@code true} + */ + private boolean isDescendantOfLoop( + NodeView node, + String loopNodeId, + Map nodeMap) { + Set visited = new HashSet<>(); + NodeView current = node; + while (current != null + && StringUtils.hasText(current.parentId) + && visited.add(current.id)) { + if (loopNodeId.equals(current.parentId)) { + return true; + } + current = nodeMap.get(current.parentId); + } + return false; } /** diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImplTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImplTest.java new file mode 100644 index 00000000..3311bf76 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImplTest.java @@ -0,0 +1,83 @@ +package tech.easyflow.ai.easyagentsflow.knowledge; + +import com.easyagents.core.document.Document; +import com.easyagents.flow.core.knowledge.Knowledge; +import com.easyagents.flow.core.node.KnowledgeNode; +import org.junit.Assert; +import org.junit.Test; +import tech.easyflow.ai.rag.KnowledgeRetrievalRequest; +import tech.easyflow.ai.service.DocumentCollectionService; + +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * {@link KnowledgeProviderImpl} 的工作流文档契约测试。 + */ +public class KnowledgeProviderImplTest { + + /** + * 验证对象数组同时提供稳定顶层字段和旧版字段。 + * + * @throws Exception 注入测试依赖失败时抛出 + */ + @Test + public void shouldExposeStableDocumentFieldsAndKeepLegacyFields() + throws Exception { + Document document = new Document(); + document.setId(BigInteger.valueOf(42)); + document.setTitle("文档标题"); + document.setContent("文档内容"); + document.addMetadata("documentId", BigInteger.valueOf(420)); + document.addMetadata("legacyKey", "legacy-value"); + + DocumentCollectionService service = + mock(DocumentCollectionService.class); + when(service.search(any(KnowledgeRetrievalRequest.class))) + .thenReturn(Collections.singletonList(document)); + KnowledgeProviderImpl provider = new KnowledgeProviderImpl(); + setField(provider, "documentCollectionService", service); + + KnowledgeNode knowledgeNode = new KnowledgeNode(); + knowledgeNode.setId("knowledge-node"); + Knowledge knowledge = + provider.getKnowledge(BigInteger.valueOf(88)); + List> result = + knowledge.search("问题", 10, knowledgeNode, null); + + Assert.assertEquals(1, result.size()); + Map item = result.get(0); + Assert.assertEquals("文档标题", item.get("title")); + Assert.assertEquals("文档内容", item.get("content")); + Assert.assertEquals(BigInteger.valueOf(420), item.get("documentId")); + Assert.assertEquals(BigInteger.valueOf(88), item.get("knowledgeId")); + Assert.assertEquals(42L, ((Number) item.get("id")).longValue()); + Assert.assertTrue(item.containsKey("metadataMap")); + Assert.assertEquals( + "legacy-value", + ((Map) item.get("metadataMap")).get("legacyKey")); + } + + /** + * 注入测试依赖。 + * + * @param target 目标对象 + * @param fieldName 字段名称 + * @param value 字段值 + * @throws Exception 字段不存在或不可访问时抛出 + */ + private static void setField( + Object target, String fieldName, Object value) throws Exception { + Field field = KnowledgeProviderImpl.class + .getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java index 3533663e..d9190cff 100644 --- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java @@ -64,6 +64,134 @@ public class WorkflowCheckServiceTest { assertHasCode(result, "EXPLICIT_LOOP_COUNT_INVALID"); } + /** + * 验证循环次数和输入数组可分别存在,也可同时存在。 + */ + @Test + public void testSaveShouldPassForSupportedExplicitLoopInputs() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject countOnly = loopData( + fixedParameter("count", "3", "Number"), null); + JSONObject itemsOnly = loopData( + null, refParameter("items", "start.items", "Array")); + JSONObject both = loopData( + refParameter("count", "start.count", "Number"), + refParameter("items", "knowledge.documents", "Array")); + String content = workflowJson( + array( + node("loop-count", "loopNode", null, countOnly), + node("loop-items", "loopNode", null, itemsOnly), + node("loop-both", "loopNode", null, both) + ), + new JSONArray()); + + WorkflowCheckResult result = service.checkContent( + content, WorkflowCheckStage.SAVE, null); + + Assert.assertTrue(result.isPassed()); + } + + /** + * 验证显式循环至少需要一个输入。 + */ + @Test + public void testSaveShouldBlockEmptyExplicitLoopInputs() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject loopData = data("循环"); + loopData.put("loopInputs", new JSONObject()); + String content = workflowJson( + array(node("loop-1", "loopNode", null, loopData)), + new JSONArray()); + + WorkflowCheckResult result = service.checkContent( + content, WorkflowCheckStage.SAVE, null); + + Assert.assertFalse(result.isPassed()); + assertHasCode(result, "LOOP_INPUT_REQUIRED"); + } + + /** + * 验证输入数组只接受数组变量引用。 + */ + @Test + public void testSaveShouldBlockFixedOrNonArrayLoopItems() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject fixedItems = loopData( + null, fixedParameter("items", "[]", "Array")); + JSONObject stringItems = loopData( + null, refParameter("items", "start.value", "String")); + String content = workflowJson( + array( + node("loop-fixed", "loopNode", null, fixedItems), + node("loop-string", "loopNode", null, stringItems) + ), + new JSONArray()); + + WorkflowCheckResult result = service.checkContent( + content, WorkflowCheckStage.SAVE, null); + + Assert.assertFalse(result.isPassed()); + assertHasCode(result, "EXPLICIT_LOOP_ITEMS_TYPE_INVALID"); + } + + /** + * 验证新旧循环输入不能同时提交。 + */ + @Test + public void testSaveShouldBlockConflictingLoopInputSchemas() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject loopData = loopData( + fixedParameter("count", "2", "Number"), null); + loopData.put("loopVars", array( + fixedParameter("loopVar", "2", "Number"))); + String content = workflowJson( + array(node("loop-1", "loopNode", null, loopData)), + new JSONArray()); + + WorkflowCheckResult result = service.checkContent( + content, WorkflowCheckStage.SAVE, null); + + Assert.assertFalse(result.isPassed()); + assertHasCode(result, "LOOP_INPUT_SCHEMA_CONFLICT"); + } + + /** + * 验证循环变量只能由对应循环体中的节点引用。 + */ + @Test + public void testSaveShouldBlockLoopVariableReferenceOutsideScope() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject childData = data("循环内节点"); + childData.put("inputDefs", array( + refParameter("item", "loop-1.loopItem.content", "String"))); + JSONObject outsideData = data("循环外节点"); + outsideData.put("inputDefs", array( + refParameter("index", "loop-1.index", "Number"))); + String content = workflowJson( + array( + node( + "loop-1", + "loopNode", + null, + loopData(fixedParameter( + "count", "2", "Number"), null)), + node("inside", "codeNode", "loop-1", childData), + node("outside", "codeNode", null, outsideData) + ), + new JSONArray()); + + WorkflowCheckResult result = service.checkContent( + content, WorkflowCheckStage.SAVE, null); + + Assert.assertFalse(result.isPassed()); + Assert.assertEquals( + 1, + result.getIssues().stream() + .filter(issue -> "LOOP_SCOPE_REFERENCE_INVALID" + .equals(issue.getCode())) + .count()); + } + /** * 验证嵌套节点只能挂在显式循环节点下。 */ @@ -552,6 +680,62 @@ public class WorkflowCheckServiceTest { return node(id, "search-dataset-node", parentId, data); } + /** + * 创建显式循环节点数据。 + * + * @param count 次数参数 + * @param items 数组参数 + * @return 循环节点数据 + */ + private static JSONObject loopData(JSONObject count, JSONObject items) { + JSONObject data = data("循环"); + JSONObject inputs = new JSONObject(); + if (count != null) { + inputs.put("count", count); + } + if (items != null) { + inputs.put("items", items); + } + data.put("loopInputs", inputs); + return data; + } + + /** + * 创建固定值参数。 + * + * @param name 参数名称 + * @param value 参数值 + * @param dataType 参数类型 + * @return 参数对象 + */ + private static JSONObject fixedParameter( + String name, String value, String dataType) { + JSONObject parameter = new JSONObject(); + parameter.put("name", name); + parameter.put("refType", "fixed"); + parameter.put("value", value); + parameter.put("dataType", dataType); + return parameter; + } + + /** + * 创建变量引用参数。 + * + * @param name 参数名称 + * @param ref 引用路径 + * @param dataType 参数类型 + * @return 参数对象 + */ + private static JSONObject refParameter( + String name, String ref, String dataType) { + JSONObject parameter = new JSONObject(); + parameter.put("name", name); + parameter.put("refType", "ref"); + parameter.put("ref", ref); + parameter.put("dataType", dataType); + return parameter; + } + private static JSONObject data(String title) { JSONObject data = new JSONObject(); data.put("title", title); diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/LoopNode.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/LoopNode.svelte index c30d16a8..d0854338 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/LoopNode.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/LoopNode.svelte @@ -8,6 +8,7 @@ import {getCurrentNodeId} from '#components/utils/NodeUtils'; import {useAddParameter} from '../utils/useAddParameter.svelte'; import type {TinyflowNodeData} from '#types'; + import LoopInputEditor from './loop/LoopInputEditor.svelte'; const { data, ...rest }: { data: TinyflowNodeData, @@ -17,17 +18,6 @@ const currentNodeId = getCurrentNodeId(); const { addParameter } = useAddParameter(); - - $effect(() => { - if (!data.loopVars || data.loopVars.length === 0) { - addParameter(currentNodeId, 'loopVars', { - name: 'loopVar', - nameDisabled: true, - deleteDisabled: true - }); - } - }); - @@ -46,17 +36,9 @@ {/snippet}
- 循环变量 - - - - - - - + 循环参数
- -
单次最多循环 300 次
+
输出参数 @@ -79,13 +61,6 @@ align-items: center; } - .loop-limit-hint { - margin-top: 6px; - color: var(--tf-text-secondary); - font-size: 12px; - line-height: 1.4; - } - :global(.loop-handle-wrapper) { &::after { //display: none; diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/loop/LoopInputEditor.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/loop/LoopInputEditor.svelte new file mode 100644 index 00000000..6e8fe479 --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/loop/LoopInputEditor.svelte @@ -0,0 +1,414 @@ + + +
+
+
+ 循环次数 + 可选 +
+ + {#if countHint} +
{countHint}
+ {/if} +
+ +
+
+ 输入数组 + 可选 +
+
+