diff --git a/docs/docs/components/custom.mdx b/docs/docs/components/custom.mdx index 84823e341..112e40ed8 100644 --- a/docs/docs/components/custom.mdx +++ b/docs/docs/components/custom.mdx @@ -94,7 +94,8 @@ The CustomComponent class serves as the foundation for creating custom component | Attribute Name | Description | | -------------- | ----------------------------------------------------------------------------- | - | _`repr_value`_ | Displays the value it receives in the _`build`_ method. Useful for debugging. | + | _`status`_ | Displays the value it receives in the _`build`_ method. Useful for debugging. | + | _`field_order`_ | Defines the order the fields will be displayed in the canvas. | diff --git a/src/backend/langflow/interface/custom/custom_component/custom_component.py b/src/backend/langflow/interface/custom/custom_component/custom_component.py index f4cbf5ea5..3cc52438e 100644 --- a/src/backend/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component/custom_component.py @@ -5,7 +5,6 @@ from uuid import UUID import yaml from cachetools import TTLCache, cachedmethod from fastapi import HTTPException - from langflow.interface.custom.code_parser.utils import ( extract_inner_type_from_generic_alias, extract_union_types_from_generic_alias, @@ -28,6 +27,8 @@ class CustomComponent(Component): """The code of the component. Defaults to None.""" field_config: dict = {} """The field configuration of the component. Defaults to an empty dictionary.""" + field_order: Optional[List[str]] = None + """The field order of the component. Defaults to an empty list.""" code_class_base_inheritance: ClassVar[str] = "CustomComponent" function_entrypoint_name: ClassVar[str] = "build" function: Optional[Callable] = None @@ -41,6 +42,9 @@ class CustomComponent(Component): self.cache = TTLCache(maxsize=1024, ttl=60) super().__init__(**data) + def _get_field_order(self): + return self.field_order or list(self.field_config.keys()) + def custom_repr(self): if self.repr_value == "": self.repr_value = self.status diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 277752b03..af21e72c3 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -43,6 +43,21 @@ def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: L frontend_node.add_output_type(return_type) +def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List[str]): + """Reorder fields in the frontend node based on the specified field_order.""" + if not field_order: + return + + # Create a dictionary for O(1) lookup time. + field_dict = {field.name: field for field in frontend_node.template.fields} + reordered_fields = [field_dict[name] for name in field_order if name in field_dict] + # Add any fields that are not in the field_order list + for field in frontend_node.template.fields: + if field.name not in field_order: + reordered_fields.append(field) + frontend_node.template.fields = reordered_fields + + def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]): """Add base classes to the frontend node""" for return_type_instance in return_types: @@ -193,7 +208,8 @@ def run_build_config(custom_component: CustomComponent, user_id: Optional[Union[ ) from exc try: - build_config: Dict = custom_class(user_id=user_id).build_config() + custom_instance = custom_class(user_id=user_id) + build_config: Dict = custom_instance.build_config() for field_name, field in build_config.items(): # Allow user to build TemplateField as well @@ -207,7 +223,7 @@ def run_build_config(custom_component: CustomComponent, user_id: Optional[Union[ except Exception as exc: logger.error(f"Error while getting build_config: {str(exc)}") - return build_config + return build_config, custom_instance except Exception as exc: logger.error(f"Error while building field config: {str(exc)}") @@ -278,7 +294,7 @@ def build_custom_component_template( logger.debug("Built base frontend node") logger.debug("Updated attributes") - field_config = run_build_config(custom_component, user_id=user_id, update_field=update_field) + field_config, custom_instance = run_build_config(custom_component, user_id=user_id, update_field=update_field) logger.debug("Built field config") entrypoint_args = custom_component.get_function_entrypoint_args @@ -289,6 +305,9 @@ def build_custom_component_template( add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type) add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type) logger.debug("Added base classes") + + reorder_fields(frontend_node, custom_instance._get_field_order()) + return frontend_node.to_dict(add_name=False) except Exception as exc: if isinstance(exc, HTTPException): diff --git a/src/backend/langflow/template/template/base.py b/src/backend/langflow/template/template/base.py index d7632e239..9bc375b0f 100644 --- a/src/backend/langflow/template/template/base.py +++ b/src/backend/langflow/template/template/base.py @@ -1,14 +1,14 @@ from typing import Callable, Union -from pydantic import BaseModel, model_serializer - from langflow.template.field.base import TemplateField from langflow.utils.constants import DIRECT_TYPES +from pydantic import BaseModel, model_serializer class Template(BaseModel): type_name: str fields: list[TemplateField] + field_order: list[str] = [] def process_fields( self, @@ -30,6 +30,7 @@ class Template(BaseModel): for field in self.fields: result[field.name] = field.model_dump(by_alias=True, exclude_none=True) result["_type"] = result.pop("type_name") + result.pop("field_order", None) return result # For backwards compatibility