From e6eb879c611bb4759ddcf9e7e50c22a664a59d35 Mon Sep 17 00:00:00 2001 From: QuantumGhost Date: Sun, 4 Jan 2026 16:50:24 +0800 Subject: [PATCH] fix(api): fix human input form substitution Fix the issues that output fields are not properly replaced for humaninput form. --- .../workflow/nodes/human_input/entities.py | 2 +- .../nodes/human_input/human_input_node.py | 6 +++--- api/services/workflow_service.py | 21 ++++++------------- .../nodes/human_input/test_entities.py | 6 +++--- .../test_human_input_form_filled_event.py | 2 +- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/api/core/workflow/nodes/human_input/entities.py b/api/core/workflow/nodes/human_input/entities.py index f2ebd9c1e5..2950c41637 100644 --- a/api/core/workflow/nodes/human_input/entities.py +++ b/api/core/workflow/nodes/human_input/entities.py @@ -16,7 +16,7 @@ from core.workflow.nodes.base.variable_template_parser import VariableTemplatePa from .enums import ButtonStyle, DeliveryMethodType, EmailRecipientType, FormInputType, PlaceholderType, TimeoutUnit -_OUTPUT_VARIABLE_PATTERN = re.compile(r"\{\{#\$outputs\.(?P[a-zA-Z_][a-zA-Z0-9_]{0,29})#\}\}") +_OUTPUT_VARIABLE_PATTERN = re.compile(r"\{\{#\$output\.(?P[a-zA-Z_][a-zA-Z0-9_]{0,29})#\}\}") class _WebAppDeliveryConfig(BaseModel): diff --git a/api/core/workflow/nodes/human_input/human_input_node.py b/api/core/workflow/nodes/human_input/human_input_node.py index 0120b1a1c7..dff8b75f57 100644 --- a/api/core/workflow/nodes/human_input/human_input_node.py +++ b/api/core/workflow/nodes/human_input/human_input_node.py @@ -264,7 +264,7 @@ class HumanInputNode(Node[HumanInputNodeData]): This method should: 1. Parse the form_content markdown 2. Substitute {{#node_name.var_name#}} with actual values - 3. Keep {{#$outputs.field_name#}} placeholders for form inputs + 3. Keep {{#$output.field_name#}} placeholders for form inputs """ rendered_form_content = self.graph_runtime_state.variable_pool.convert_template( self._node_data.form_content, @@ -278,11 +278,11 @@ class HumanInputNode(Node[HumanInputNodeData]): field_names: Sequence[str], ) -> str: """ - Replace {{#$outputs.xxx#}} placeholders with submitted values. + Replace {{#$output.xxx#}} placeholders with submitted values. """ rendered_content = form_content for field_name in field_names: - placeholder = "{{#$outputs." + field_name + "#}}" + placeholder = "{{#$output." + field_name + "#}}" value = outputs.get(field_name) if value is None: replacement = "" diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 59df1fb7b8..10fafd20c6 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -778,7 +778,7 @@ class WorkflowService: variable_pool=variable_pool, ) - rendered_content = node._render_form_content() + rendered_content = node._render_form_content_before_submission() resolved_placeholder_values = node._resolve_inputs() node_data = cast(HumanInputNodeData, node.get_base_node_data()) human_input_required = HumanInputRequired( @@ -823,7 +823,7 @@ class WorkflowService: node_config=node_config, variable_pool=variable_pool, ) - node_data = cast(HumanInputNodeData, node.get_base_node_data()) + node_data = node.node_data available_actions = {user_action.id for user_action in node_data.user_actions} if action not in available_actions: @@ -835,21 +835,12 @@ class WorkflowService: missing_list = ", ".join(sorted(missing_inputs)) raise ValueError(f"Missing inputs: {missing_list}") - rendered_content = node._render_form_content() + rendered_content = node._render_form_content_before_submission() outputs: dict[str, Any] = dict(form_inputs) outputs["__action_id"] = action - rendered_content_with_outputs = rendered_content - for field_name in node_data.outputs_field_names(): - placeholder = f"{{{{#$outputs.{field_name}#}}}}" - value = outputs.get(field_name) - if value is None: - replacement = "" - elif isinstance(value, (dict, list)): - replacement = json.dumps(value, ensure_ascii=False) - else: - replacement = str(value) - rendered_content_with_outputs = rendered_content_with_outputs.replace(placeholder, replacement) - outputs["__rendered_content"] = rendered_content_with_outputs + outputs["__rendered_content"] = node._render_form_content_with_outputs( + node_data.form_content, outputs, node_data.outputs_field_names() + ) enclosing_node_type_and_id = draft_workflow.get_enclosing_node_type_and_id(node_config) enclosing_node_id = enclosing_node_type_and_id[1] if enclosing_node_type_and_id else None diff --git a/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py b/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py index be54113f63..4c1d2cbf2e 100644 --- a/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py +++ b/api/tests/unit_tests/core/workflow/nodes/human_input/test_entities.py @@ -221,11 +221,11 @@ class TestHumanInputNodeData: A content is required: - {{#$outputs.content#}} + {{#$output.content#}} A ending is required: - {{#$outputs.ending#}} + {{#$output.ending#}} """ node_data = HumanInputNodeData(title="Human Input", form_content=content) @@ -407,7 +407,7 @@ class TestHumanInputNodeRenderedContent: node_data = HumanInputNodeData( title="Human Input", - form_content="Name: {{#$outputs.name#}}", + form_content="Name: {{#$output.name#}}", inputs=[ FormInput( type=FormInputType.TEXT_INPUT, diff --git a/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py b/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py index e5e359ce76..07b920fe39 100644 --- a/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py +++ b/api/tests/unit_tests/core/workflow/nodes/human_input/test_human_input_form_filled_event.py @@ -22,7 +22,7 @@ class _FakeFormRepository: return self._form -def _build_node(form_content: str = "Please enter your name:\n\n{{#$outputs.name#}}") -> HumanInputNode: +def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name#}}") -> HumanInputNode: system_variables = SystemVariable.empty() system_variables.workflow_execution_id = str(uuid.uuid4()) graph_runtime_state = GraphRuntimeState(