From 5e641a42b8255f622d6cf4d1633dbef424c0e8c2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 11 Feb 2024 12:31:39 -0300 Subject: [PATCH] Add field_order property to CustomComponent --- docs/docs/components/custom.mdx | 3 +- .../custom_component/custom_component.py | 2 + .../langflow/interface/custom/utils.py | 100 ++++++++++++++---- .../langflow/template/template/base.py | 1 + 4 files changed, 86 insertions(+), 20 deletions(-) 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 999782ffc..a46c980c2 100644 --- a/src/backend/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component/custom_component.py @@ -33,6 +33,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: List[str] = [] + """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 diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 39bbcf1e0..4ddf5338d 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -17,20 +17,26 @@ from langflow.interface.custom.directory_reader.utils import ( ) from langflow.interface.custom.eval import eval_custom_component_code from langflow.template.field.base import TemplateField -from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode +from langflow.template.frontend_node.custom_components import ( + CustomComponentFrontendNode, +) from langflow.utils import validate from langflow.utils.util import get_base_classes from loguru import logger -def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]): +def add_output_types( + frontend_node: CustomComponentFrontendNode, return_types: List[str] +): """Add output types to the frontend node""" for return_type in return_types: if return_type is None: raise HTTPException( status_code=400, detail={ - "error": ("Invalid return type. Please check your code and try again."), + "error": ( + "Invalid return type. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) @@ -44,14 +50,30 @@ def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: L frontend_node.add_output_type(return_type) -def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]): +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] + + 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: if return_type_instance is None: raise HTTPException( status_code=400, detail={ - "error": ("Invalid return type. Please check your code and try again."), + "error": ( + "Invalid return type. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) @@ -120,10 +142,14 @@ def add_new_custom_field( # If options is a list, then it's a dropdown # If options is None, then it's a list of strings is_list = isinstance(field_config.get("options"), list) - field_config["is_list"] = is_list or field_config.get("is_list", False) or field_contains_list + field_config["is_list"] = ( + is_list or field_config.get("is_list", False) or field_contains_list + ) if "name" in field_config: - warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.") + warnings.warn( + "The 'name' key in field_config is used to build the object and can't be changed." + ) required = field_config.pop("required", field_required) placeholder = field_config.pop("placeholder", "") @@ -154,7 +180,9 @@ def add_extra_fields(frontend_node, field_config, function_args): if "name" not in extra_field or extra_field["name"] == "self": continue - field_name, field_type, field_value, field_required = get_field_properties(extra_field) + field_name, field_type, field_value, field_required = get_field_properties( + extra_field + ) config = field_config.get(field_name, {}) frontend_node = add_new_custom_field( frontend_node, @@ -173,7 +201,11 @@ def get_field_dict(field: Union[TemplateField, dict]): return field -def run_build_config(custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None, update_field=None): +def run_build_config( + custom_component: CustomComponent, + user_id: Optional[Union[str, UUID]] = None, + update_field=None, +): """Build the field configuration for a custom component""" try: @@ -188,7 +220,9 @@ def run_build_config(custom_component: CustomComponent, user_id: Optional[Union[ raise HTTPException( status_code=400, detail={ - "error": ("Invalid type convertion. Please check your code and try again."), + "error": ( + "Invalid type convertion. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) from exc @@ -215,7 +249,9 @@ def run_build_config(custom_component: CustomComponent, user_id: Optional[Union[ raise HTTPException( status_code=400, detail={ - "error": ("Invalid type convertion. Please check your code and try again."), + "error": ( + "Invalid type convertion. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) from exc @@ -276,16 +312,27 @@ def build_custom_component_template( logger.debug("Building custom component template") frontend_node = build_frontend_node(custom_component.template_config) - field_config = run_build_config(custom_component, user_id=user_id, update_field=update_field) + field_config = run_build_config( + custom_component, user_id=user_id, update_field=update_field + ) entrypoint_args = custom_component.get_function_entrypoint_args add_extra_fields(frontend_node, field_config, entrypoint_args) - frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {})) + frontend_node = add_code_field( + frontend_node, custom_component.code, field_config.get("code", {}) + ) - add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type) - add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type) + 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_component.field_order) return frontend_node.to_dict(add_name=False) except Exception as exc: @@ -294,7 +341,9 @@ def build_custom_component_template( raise HTTPException( status_code=400, detail={ - "error": ("Invalid type convertion. Please check your code and try again."), + "error": ( + "Invalid type convertion. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) from exc @@ -317,7 +366,9 @@ def build_custom_components(settings_service): if not settings_service.settings.COMPONENTS_PATH: return {} - logger.info(f"Building custom components from {settings_service.settings.COMPONENTS_PATH}") + logger.info( + f"Building custom components from {settings_service.settings.COMPONENTS_PATH}" + ) custom_components_from_file = {} processed_paths = set() for path in settings_service.settings.COMPONENTS_PATH: @@ -328,7 +379,9 @@ def build_custom_components(settings_service): custom_component_dict = build_custom_component_list_from_path(path_str) if custom_component_dict: category = next(iter(custom_component_dict)) - logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}") + logger.info( + f"Loading {len(custom_component_dict[category])} component(s) from category {category}" + ) custom_components_from_file = merge_nested_dicts_with_renaming( custom_components_from_file, custom_component_dict ) @@ -355,7 +408,16 @@ def update_field_dict(field_dict): def sanitize_field_config(field_config: Dict): # If any of the already existing keys are in field_config, remove them - for key in ["name", "field_type", "value", "required", "placeholder", "display_name", "advanced", "show"]: + for key in [ + "name", + "field_type", + "value", + "required", + "placeholder", + "display_name", + "advanced", + "show", + ]: field_config.pop(key, None) return field_config diff --git a/src/backend/langflow/template/template/base.py b/src/backend/langflow/template/template/base.py index d7632e239..ccf76415a 100644 --- a/src/backend/langflow/template/template/base.py +++ b/src/backend/langflow/template/template/base.py @@ -9,6 +9,7 @@ from langflow.utils.constants import DIRECT_TYPES class Template(BaseModel): type_name: str fields: list[TemplateField] + field_order: list[str] = [] def process_fields( self,