mirror of
https://github.com/langgenius/dify.git
synced 2026-04-25 13:16:16 +08:00
Read the current implementation and consider the following problem (vibe-kanban 25d8b973)
Currently, the HumanInput node yields a `HumanInputFormFilledEvent` event while form is submmited. However, for form level timeout, current no event about timeout is emitted. This makes the frontend UI not updated while the events of time out are sent to the frontend. Analysis this problem, propose a way to resolve this issue.
This commit is contained in:
@ -1,8 +1,9 @@
|
||||
from datetime import UTC, datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.app.entities.queue_entities import QueueHumanInputFormFilledEvent
|
||||
from core.app.entities.queue_entities import QueueHumanInputFormFilledEvent, QueueHumanInputFormTimeoutEvent
|
||||
from core.workflow.entities.workflow_start_reason import WorkflowStartReason
|
||||
from core.workflow.runtime import GraphRuntimeState, VariablePool
|
||||
from core.workflow.system_variable import SystemVariable
|
||||
@ -60,3 +61,27 @@ def test_human_input_form_filled_stream_response_contains_rendered_content():
|
||||
assert resp.data.node_title == "Human Input"
|
||||
assert resp.data.rendered_content.startswith("# Title")
|
||||
assert resp.data.action_id == "Approve"
|
||||
|
||||
|
||||
def test_human_input_form_timeout_stream_response_contains_timeout_metadata():
|
||||
converter = _build_converter()
|
||||
converter.workflow_start_to_stream_response(
|
||||
task_id="task-1",
|
||||
workflow_run_id="run-1",
|
||||
workflow_id="wf-1",
|
||||
reason=WorkflowStartReason.INITIAL,
|
||||
)
|
||||
|
||||
queue_event = QueueHumanInputFormTimeoutEvent(
|
||||
node_id="node-1",
|
||||
node_type="human-input",
|
||||
node_title="Human Input",
|
||||
expiration_time=datetime(2025, 1, 1, tzinfo=UTC),
|
||||
)
|
||||
|
||||
resp = converter.human_input_form_timeout_to_stream_response(event=queue_event, task_id="task-1")
|
||||
|
||||
assert resp.workflow_run_id == "run-1"
|
||||
assert resp.data.node_id == "node-1"
|
||||
assert resp.data.node_title == "Human Input"
|
||||
assert resp.data.expiration_time == 1735689600
|
||||
|
||||
@ -80,9 +80,7 @@ class TestFormInput:
|
||||
"""Test text input with constant default value."""
|
||||
default = FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter your response here...")
|
||||
|
||||
form_input = FormInput(
|
||||
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default
|
||||
)
|
||||
form_input = FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default)
|
||||
|
||||
assert form_input.type == FormInputType.TEXT_INPUT
|
||||
assert form_input.output_variable_name == "user_input"
|
||||
@ -93,9 +91,7 @@ class TestFormInput:
|
||||
"""Test text input with variable default value."""
|
||||
default = FormInputDefault(type=PlaceholderType.VARIABLE, selector=["node_123", "output_var"])
|
||||
|
||||
form_input = FormInput(
|
||||
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default
|
||||
)
|
||||
form_input = FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default)
|
||||
|
||||
assert form_input.default.type == PlaceholderType.VARIABLE
|
||||
assert form_input.default.selector == ["node_123", "output_var"]
|
||||
|
||||
@ -7,6 +7,7 @@ from core.workflow.entities.graph_init_params import GraphInitParams
|
||||
from core.workflow.enums import NodeType
|
||||
from core.workflow.graph_events import (
|
||||
NodeRunHumanInputFormFilledEvent,
|
||||
NodeRunHumanInputFormTimeoutEvent,
|
||||
NodeRunStartedEvent,
|
||||
)
|
||||
from core.workflow.nodes.human_input.enums import HumanInputFormStatus
|
||||
@ -86,6 +87,67 @@ def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name#
|
||||
)
|
||||
|
||||
|
||||
def _build_timeout_node() -> HumanInputNode:
|
||||
system_variables = SystemVariable.empty()
|
||||
system_variables.workflow_execution_id = str(uuid.uuid4())
|
||||
graph_runtime_state = GraphRuntimeState(
|
||||
variable_pool=VariablePool(system_variables=system_variables, user_inputs={}, environment_variables=[]),
|
||||
start_at=0.0,
|
||||
)
|
||||
graph_init_params = GraphInitParams(
|
||||
tenant_id="tenant",
|
||||
app_id="app",
|
||||
workflow_id="workflow",
|
||||
graph_config={"nodes": [], "edges": []},
|
||||
user_id="user",
|
||||
user_from=UserFrom.ACCOUNT,
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
call_depth=0,
|
||||
)
|
||||
|
||||
config = {
|
||||
"id": "node-1",
|
||||
"type": NodeType.HUMAN_INPUT.value,
|
||||
"data": {
|
||||
"title": "Human Input",
|
||||
"form_content": "Please enter your name:\n\n{{#$output.name#}}",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "text_input",
|
||||
"output_variable_name": "name",
|
||||
"default": {"type": "constant", "value": ""},
|
||||
}
|
||||
],
|
||||
"user_actions": [
|
||||
{
|
||||
"id": "Accept",
|
||||
"title": "Approve",
|
||||
"button_style": "default",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
fake_form = SimpleNamespace(
|
||||
id="form-1",
|
||||
rendered_content="content",
|
||||
submitted=False,
|
||||
selected_action_id=None,
|
||||
submitted_data=None,
|
||||
status=HumanInputFormStatus.TIMEOUT,
|
||||
expiration_time=naive_utc_now() - datetime.timedelta(minutes=1),
|
||||
)
|
||||
|
||||
repo = _FakeFormRepository(fake_form)
|
||||
return HumanInputNode(
|
||||
id="node-1",
|
||||
config=config,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
form_repository=repo,
|
||||
)
|
||||
|
||||
|
||||
def test_human_input_node_emits_form_filled_event_before_succeeded():
|
||||
node = _build_node()
|
||||
|
||||
@ -99,3 +161,15 @@ def test_human_input_node_emits_form_filled_event_before_succeeded():
|
||||
assert filled_event.rendered_content.endswith("Alice")
|
||||
assert filled_event.action_id == "Accept"
|
||||
assert filled_event.action_text == "Approve"
|
||||
|
||||
|
||||
def test_human_input_node_emits_timeout_event_before_succeeded():
|
||||
node = _build_timeout_node()
|
||||
|
||||
events = list(node.run())
|
||||
|
||||
assert isinstance(events[0], NodeRunStartedEvent)
|
||||
assert isinstance(events[1], NodeRunHumanInputFormTimeoutEvent)
|
||||
|
||||
timeout_event = events[1]
|
||||
assert timeout_event.node_title == "Human Input"
|
||||
|
||||
@ -304,9 +304,7 @@ class TestFormValidation:
|
||||
"tenant_id": "tenant-abc",
|
||||
"app_id": "app-def",
|
||||
"form_content": "Test form",
|
||||
"inputs": [
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", default=None)
|
||||
],
|
||||
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", default=None)],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"timeout": 1,
|
||||
"timeout_unit": TimeoutUnit.HOUR,
|
||||
|
||||
Reference in New Issue
Block a user