|  | @@ -5,10 +5,9 @@ from collections.abc import Generator, Mapping, Sequence
 | 
	
		
			
				|  |  |  from typing import Any, Optional, cast
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  from configs import dify_config
 | 
	
		
			
				|  |  | -from core.app.app_config.entities import FileUploadConfig
 | 
	
		
			
				|  |  |  from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError
 | 
	
		
			
				|  |  |  from core.app.entities.app_invoke_entities import InvokeFrom
 | 
	
		
			
				|  |  | -from core.file.models import File, FileTransferMethod, ImageConfig
 | 
	
		
			
				|  |  | +from core.file.models import File
 | 
	
		
			
				|  |  |  from core.workflow.callbacks import WorkflowCallback
 | 
	
		
			
				|  |  |  from core.workflow.entities.variable_pool import VariablePool
 | 
	
		
			
				|  |  |  from core.workflow.errors import WorkflowNodeRunFailedError
 | 
	
	
		
			
				|  | @@ -18,9 +17,8 @@ from core.workflow.graph_engine.entities.graph_init_params import GraphInitParam
 | 
	
		
			
				|  |  |  from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState
 | 
	
		
			
				|  |  |  from core.workflow.graph_engine.graph_engine import GraphEngine
 | 
	
		
			
				|  |  |  from core.workflow.nodes import NodeType
 | 
	
		
			
				|  |  | -from core.workflow.nodes.base import BaseNode, BaseNodeData
 | 
	
		
			
				|  |  | +from core.workflow.nodes.base import BaseNode
 | 
	
		
			
				|  |  |  from core.workflow.nodes.event import NodeEvent
 | 
	
		
			
				|  |  | -from core.workflow.nodes.llm import LLMNodeData
 | 
	
		
			
				|  |  |  from core.workflow.nodes.node_mapping import node_type_classes_mapping
 | 
	
		
			
				|  |  |  from factories import file_factory
 | 
	
		
			
				|  |  |  from models.enums import UserFrom
 | 
	
	
		
			
				|  | @@ -115,7 +113,12 @@ class WorkflowEntry:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @classmethod
 | 
	
		
			
				|  |  |      def single_step_run(
 | 
	
		
			
				|  |  | -        cls, workflow: Workflow, node_id: str, user_id: str, user_inputs: dict
 | 
	
		
			
				|  |  | +        cls,
 | 
	
		
			
				|  |  | +        *,
 | 
	
		
			
				|  |  | +        workflow: Workflow,
 | 
	
		
			
				|  |  | +        node_id: str,
 | 
	
		
			
				|  |  | +        user_id: str,
 | 
	
		
			
				|  |  | +        user_inputs: dict,
 | 
	
		
			
				|  |  |      ) -> tuple[BaseNode, Generator[NodeEvent | InNodeEvent, None, None]]:
 | 
	
		
			
				|  |  |          """
 | 
	
		
			
				|  |  |          Single step run workflow node
 | 
	
	
		
			
				|  | @@ -135,13 +138,9 @@ class WorkflowEntry:
 | 
	
		
			
				|  |  |              raise ValueError("nodes not found in workflow graph")
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          # fetch node config from node id
 | 
	
		
			
				|  |  | -        node_config = None
 | 
	
		
			
				|  |  | -        for node in nodes:
 | 
	
		
			
				|  |  | -            if node.get("id") == node_id:
 | 
	
		
			
				|  |  | -                node_config = node
 | 
	
		
			
				|  |  | -                break
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if not node_config:
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            node_config = next(filter(lambda node: node["id"] == node_id, nodes))
 | 
	
		
			
				|  |  | +        except StopIteration:
 | 
	
		
			
				|  |  |              raise ValueError("node id not found in workflow graph")
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          # Get node class
 | 
	
	
		
			
				|  | @@ -153,11 +152,7 @@ class WorkflowEntry:
 | 
	
		
			
				|  |  |              raise ValueError(f"Node class not found for node type {node_type}")
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          # init variable pool
 | 
	
		
			
				|  |  | -        variable_pool = VariablePool(
 | 
	
		
			
				|  |  | -            system_variables={},
 | 
	
		
			
				|  |  | -            user_inputs={},
 | 
	
		
			
				|  |  | -            environment_variables=workflow.environment_variables,
 | 
	
		
			
				|  |  | -        )
 | 
	
		
			
				|  |  | +        variable_pool = VariablePool(environment_variables=workflow.environment_variables)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          # init graph
 | 
	
		
			
				|  |  |          graph = Graph.init(graph_config=workflow.graph_dict)
 | 
	
	
		
			
				|  | @@ -183,28 +178,24 @@ class WorkflowEntry:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          try:
 | 
	
		
			
				|  |  |              # variable selector to variable mapping
 | 
	
		
			
				|  |  | -            try:
 | 
	
		
			
				|  |  | -                variable_mapping = node_cls.extract_variable_selector_to_variable_mapping(
 | 
	
		
			
				|  |  | -                    graph_config=workflow.graph_dict, config=node_config
 | 
	
		
			
				|  |  | -                )
 | 
	
		
			
				|  |  | -            except NotImplementedError:
 | 
	
		
			
				|  |  | -                variable_mapping = {}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            cls.mapping_user_inputs_to_variable_pool(
 | 
	
		
			
				|  |  | -                variable_mapping=variable_mapping,
 | 
	
		
			
				|  |  | -                user_inputs=user_inputs,
 | 
	
		
			
				|  |  | -                variable_pool=variable_pool,
 | 
	
		
			
				|  |  | -                tenant_id=workflow.tenant_id,
 | 
	
		
			
				|  |  | -                node_type=node_type,
 | 
	
		
			
				|  |  | -                node_data=node_instance.node_data,
 | 
	
		
			
				|  |  | +            variable_mapping = node_cls.extract_variable_selector_to_variable_mapping(
 | 
	
		
			
				|  |  | +                graph_config=workflow.graph_dict, config=node_config
 | 
	
		
			
				|  |  |              )
 | 
	
		
			
				|  |  | +        except NotImplementedError:
 | 
	
		
			
				|  |  | +            variable_mapping = {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        cls.mapping_user_inputs_to_variable_pool(
 | 
	
		
			
				|  |  | +            variable_mapping=variable_mapping,
 | 
	
		
			
				|  |  | +            user_inputs=user_inputs,
 | 
	
		
			
				|  |  | +            variable_pool=variable_pool,
 | 
	
		
			
				|  |  | +            tenant_id=workflow.tenant_id,
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  |              # run node
 | 
	
		
			
				|  |  |              generator = node_instance.run()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            return node_instance, generator
 | 
	
		
			
				|  |  |          except Exception as e:
 | 
	
		
			
				|  |  |              raise WorkflowNodeRunFailedError(node_instance=node_instance, error=str(e))
 | 
	
		
			
				|  |  | +        return node_instance, generator
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @staticmethod
 | 
	
		
			
				|  |  |      def handle_special_values(value: Optional[Mapping[str, Any]]) -> Mapping[str, Any] | None:
 | 
	
	
		
			
				|  | @@ -231,12 +222,11 @@ class WorkflowEntry:
 | 
	
		
			
				|  |  |      @classmethod
 | 
	
		
			
				|  |  |      def mapping_user_inputs_to_variable_pool(
 | 
	
		
			
				|  |  |          cls,
 | 
	
		
			
				|  |  | +        *,
 | 
	
		
			
				|  |  |          variable_mapping: Mapping[str, Sequence[str]],
 | 
	
		
			
				|  |  |          user_inputs: dict,
 | 
	
		
			
				|  |  |          variable_pool: VariablePool,
 | 
	
		
			
				|  |  |          tenant_id: str,
 | 
	
		
			
				|  |  | -        node_type: NodeType,
 | 
	
		
			
				|  |  | -        node_data: BaseNodeData,
 | 
	
		
			
				|  |  |      ) -> None:
 | 
	
		
			
				|  |  |          for node_variable, variable_selector in variable_mapping.items():
 | 
	
		
			
				|  |  |              # fetch node id and variable key from node_variable
 | 
	
	
		
			
				|  | @@ -254,40 +244,21 @@ class WorkflowEntry:
 | 
	
		
			
				|  |  |              # fetch variable node id from variable selector
 | 
	
		
			
				|  |  |              variable_node_id = variable_selector[0]
 | 
	
		
			
				|  |  |              variable_key_list = variable_selector[1:]
 | 
	
		
			
				|  |  | -            variable_key_list = cast(list[str], variable_key_list)
 | 
	
		
			
				|  |  | +            variable_key_list = list(variable_key_list)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              # get input value
 | 
	
		
			
				|  |  |              input_value = user_inputs.get(node_variable)
 | 
	
		
			
				|  |  |              if not input_value:
 | 
	
		
			
				|  |  |                  input_value = user_inputs.get(node_variable_key)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            # FIXME: temp fix for image type
 | 
	
		
			
				|  |  | -            if node_type == NodeType.LLM:
 | 
	
		
			
				|  |  | -                new_value = []
 | 
	
		
			
				|  |  | -                if isinstance(input_value, list):
 | 
	
		
			
				|  |  | -                    node_data = cast(LLMNodeData, node_data)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    detail = node_data.vision.configs.detail if node_data.vision.configs else None
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    for item in input_value:
 | 
	
		
			
				|  |  | -                        if isinstance(item, dict) and "type" in item and item["type"] == "image":
 | 
	
		
			
				|  |  | -                            transfer_method = FileTransferMethod.value_of(item.get("transfer_method"))
 | 
	
		
			
				|  |  | -                            mapping = {
 | 
	
		
			
				|  |  | -                                "id": item.get("id"),
 | 
	
		
			
				|  |  | -                                "transfer_method": transfer_method,
 | 
	
		
			
				|  |  | -                                "upload_file_id": item.get("upload_file_id"),
 | 
	
		
			
				|  |  | -                                "url": item.get("url"),
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                            config = FileUploadConfig(image_config=ImageConfig(detail=detail) if detail else None)
 | 
	
		
			
				|  |  | -                            file = file_factory.build_from_mapping(
 | 
	
		
			
				|  |  | -                                mapping=mapping,
 | 
	
		
			
				|  |  | -                                tenant_id=tenant_id,
 | 
	
		
			
				|  |  | -                                config=config,
 | 
	
		
			
				|  |  | -                            )
 | 
	
		
			
				|  |  | -                            new_value.append(file)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                if new_value:
 | 
	
		
			
				|  |  | -                    input_value = new_value
 | 
	
		
			
				|  |  | +            if isinstance(input_value, dict) and "type" in input_value and "transfer_method" in input_value:
 | 
	
		
			
				|  |  | +                input_value = file_factory.build_from_mapping(mapping=input_value, tenant_id=tenant_id)
 | 
	
		
			
				|  |  | +            if (
 | 
	
		
			
				|  |  | +                isinstance(input_value, list)
 | 
	
		
			
				|  |  | +                and all(isinstance(item, dict) for item in input_value)
 | 
	
		
			
				|  |  | +                and all("type" in item and "transfer_method" in item for item in input_value)
 | 
	
		
			
				|  |  | +            ):
 | 
	
		
			
				|  |  | +                input_value = file_factory.build_from_mappings(mappings=input_value, tenant_id=tenant_id)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              # append variable and value to variable pool
 | 
	
		
			
				|  |  |              variable_pool.add([variable_node_id] + variable_key_list, input_value)
 |