From fb08424cef3d427408b23532068c847cb7f9011e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Fri, 31 Jul 2026 14:45:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=89=81=E5=B9=B3=E8=81=9A=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 校验并恢复循环输出的聚合策略 - 将最终类型并入参数值并优化同排开关交互 --- .../event/WorkflowExecutionAuditConsumer.java | 4 +- .../service/WorkflowCheckService.java | 46 ++++++++ .../WorkflowExecutionAuditConsumerTest.java | 11 +- .../service/WorkflowCheckServiceTest.java | 49 +++++++++ .../tinyflow-ui/src/components/base/index.ts | 2 + .../src/components/base/info-tooltip.svelte | 95 ++++++++++++++++ .../src/components/base/select.svelte | 18 +-- .../components/core/RefParameterItem.svelte | 104 ++++++++++++++++-- .../components/core/RefParameterList.svelte | 11 +- .../src/components/nodes/LoopNode.svelte | 7 +- .../components/utils/useRefOptions.svelte.ts | 64 +++++++---- .../packages/tinyflow-ui/src/types.ts | 1 + .../tinyflow-ui/src/utils/loopScope.test.ts | 64 ++++++++++- .../tinyflow-ui/src/utils/loopScope.ts | 87 +++++++++++++-- 14 files changed, 504 insertions(+), 59 deletions(-) create mode 100644 easyflow-ui-admin/packages/tinyflow-ui/src/components/base/info-tooltip.svelte diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumer.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumer.java index e8d8340d..e56886be 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumer.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumer.java @@ -241,7 +241,9 @@ public class WorkflowExecutionAuditConsumer implements MQConsumerHandler { "iterationCount")) .intValue(), String.valueOf( - map.get("outputName"))); + map.get("outputName")), + Boolean.TRUE.equals( + map.get("flattenAggregation"))); } Map restored = new LinkedHashMap<>(); 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 a73a74ce..700f891c 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 @@ -248,6 +248,7 @@ public class WorkflowCheckService { for (NodeView node : nodes) { checkConfiguredLoopCount(node, issues, issueKeys); checkExplicitLoopInputs(node, issues, issueKeys); + checkLoopOutputAggregations(node, issues, issueKeys); if (StringUtils.hasText(node.parentId)) { NodeView parent = nodeMap.get(node.parentId); if (parent != null && !TYPE_LOOP.equals(parent.type)) { @@ -605,6 +606,51 @@ public class WorkflowCheckService { true, 0, "Array<", 0, "Array<".length())); } + /** + * 校验循环输出的扁平聚合只作用于数组引用。 + * + * @param node 循环节点 + * @param issues 问题列表 + * @param issueKeys 问题去重键 + */ + private void checkLoopOutputAggregations( + NodeView node, + List issues, + Set issueKeys) { + if (!TYPE_LOOP.equals(node.type) || node.data == null) { + return; + } + JSONArray outputDefs = node.data.getJSONArray("outputDefs"); + if (outputDefs == null || outputDefs.isEmpty()) { + return; + } + for (int index = 0; index < outputDefs.size(); index++) { + JSONObject outputDef = outputDefs.getJSONObject(index); + if (outputDef == null + || !outputDef.getBooleanValue("flattenAggregation")) { + continue; + } + String refType = trimToNull(outputDef.getString("refType")); + String ref = trimToNull(outputDef.getString("ref")); + String dataType = trimToNull(outputDef.getString("dataType")); + if ("ref".equals(refType) + && StringUtils.hasText(ref) + && isArrayDataType(dataType)) { + continue; + } + String outputName = trimToNull(outputDef.getString("name")); + addIssue( + issues, + issueKeys, + "LOOP_OUTPUT_FLATTEN_TYPE_INVALID", + "循环输出参数[" + safe(outputName) + + "]启用扁平聚合时必须引用数组变量", + node.id, + null, + node.name); + } + } + /** * 校验 index 和 loopItem 仅在所属循环体内引用。 * diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumerTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumerTest.java index 26db15a2..db08efa9 100644 --- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumerTest.java +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/event/WorkflowExecutionAuditConsumerTest.java @@ -627,11 +627,11 @@ public class WorkflowExecutionAuditConsumerTest { loopRepository.append( resultId, 0, - Map.of("answer", "first")); + Map.of("answer", List.of("first", "second"))); loopRepository.append( resultId, 1, - Map.of("answer", "second")); + Map.of("answer", List.of("third"))); WorkflowExecutionAuditConsumer consumer = new WorkflowExecutionAuditConsumer( resultService, @@ -650,7 +650,8 @@ public class WorkflowExecutionAuditConsumerTest { new LoopResultReference( resultId, 2, - "answer")); + "answer", + true)); WorkflowExecStep incomingStep = new WorkflowExecStep(); incomingStep.setExecKey("step-loop"); @@ -700,10 +701,10 @@ public class WorkflowExecutionAuditConsumerTest { resultCaptor.getValue().getOutput(), Map.class); Assert.assertEquals( - List.of("first", "second"), + List.of("first", "second", "third"), stepOutput.get("answers")); Assert.assertEquals( - List.of("first", "second"), + List.of("first", "second", "third"), resultOutput.get("answers")); } 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 48dbf070..40eefd45 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 @@ -221,6 +221,55 @@ public class WorkflowCheckServiceTest { assertHasCode(result, "EXPLICIT_LOOP_ITEMS_TYPE_INVALID"); } + /** + * 验证循环数组输出允许启用扁平聚合。 + */ + @Test + public void testSaveShouldPassArrayLoopOutputFlattenAggregation() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject loopData = loopData( + fixedParameter("count", "2", "Number"), null); + JSONObject output = refParameter( + "res", + "knowledge.documents.content", + "Array"); + output.put("flattenAggregation", true); + loopData.put("outputDefs", array(output)); + String content = workflowJson( + array(node("loop-1", "loopNode", null, loopData)), + new JSONArray()); + + WorkflowCheckResult result = service.checkContent( + content, WorkflowCheckStage.SAVE, null); + + Assert.assertTrue(result.isPassed()); + } + + /** + * 验证循环标量输出不能启用扁平聚合。 + */ + @Test + public void testSaveShouldBlockScalarLoopOutputFlattenAggregation() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject loopData = loopData( + fixedParameter("count", "2", "Number"), null); + JSONObject output = refParameter( + "res", + "child.output", + "String"); + output.put("flattenAggregation", true); + loopData.put("outputDefs", array(output)); + 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_OUTPUT_FLATTEN_TYPE_INVALID"); + } + /** * 验证新旧循环输入不能同时提交。 */ diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/index.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/index.ts index 7b36fe7d..df632e2a 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/index.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/index.ts @@ -11,6 +11,7 @@ import FloatingTrigger from './floating-trigger.svelte'; import Heading from './heading.svelte'; import MenuButton from './menu-button.svelte'; import MixedInput from './mixed-input.svelte'; +import InfoTooltip from './info-tooltip.svelte'; export { Button, @@ -26,4 +27,5 @@ export { Heading, MenuButton, MixedInput, + InfoTooltip, }; diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/info-tooltip.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/info-tooltip.svelte new file mode 100644 index 00000000..e1149b25 --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/info-tooltip.svelte @@ -0,0 +1,95 @@ + + + + + diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/select.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/select.svelte index c5a30727..aa945bbb 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/select.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/select.svelte @@ -14,6 +14,7 @@ placeholder, variant = 'default', showSelectedType = true, + selectedType, ...rest }: { items: SelectItem[], @@ -24,6 +25,7 @@ placeholder?: string variant?: 'default' | 'reference' | 'model' showSelectedType?: boolean + selectedType?: string [key: string]: any } = $props(); @@ -184,10 +186,10 @@ {/if} - {item.displayLabel || item.label} + {item.displayLabel || item.label} {#if item.dataType} - {item.dataType} + {item.dataType} {/if} {#if item.itemTypeLabel} {item.itemTypeLabel} @@ -230,9 +232,9 @@ {/if} {/if} - {item.displayLabel || item.label} - {#if variant === 'reference' && showSelectedType && item.dataType} - {item.dataType} + {item.displayLabel || item.label} + {#if variant === 'reference' && showSelectedType && (selectedType ?? item.dataType)} + {selectedType ?? item.dataType} {/if} {/if} @@ -251,9 +253,9 @@ {/if} {/if} - {item.displayLabel || item.label} - {#if variant === 'reference' && showSelectedType && item.dataType} - {item.dataType} + {item.displayLabel || item.label} + {#if variant === 'reference' && showSelectedType && (selectedType ?? item.dataType)} + {selectedType ?? item.dataType} {/if} {#if index < activeItemsState.length - 1} diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/RefParameterItem.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/RefParameterItem.svelte index f7d6f126..967063fd 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/RefParameterItem.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/RefParameterItem.svelte @@ -1,5 +1,5 @@ @@ -153,11 +206,25 @@ {:else if (param.refType !== 'input')}