diff --git a/src/backend/langflow/api/utils.py b/src/backend/langflow/api/utils.py index 2384a4089..91fa93ea4 100644 --- a/src/backend/langflow/api/utils.py +++ b/src/backend/langflow/api/utils.py @@ -57,3 +57,12 @@ def build_input_keys_response(langchain_object, artifacts): input_keys_response["template"] = langchain_object.prompt.template return input_keys_response + + +def merge_nested_dicts(dict1, dict2): + for key, value in dict2.items(): + if isinstance(value, dict) and isinstance(dict1.get(key), dict): + dict1[key] = merge_nested_dicts(dict1[key], value) + else: + dict1[key] = value + return dict1 diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 8e3f66805..f60640d4e 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -21,6 +21,8 @@ from langflow.api.v1.schemas import ( CustomComponentCode, ) +from langflow.api.utils import merge_nested_dicts + from langflow.interface.types import ( build_langchain_types_dict, build_langchain_template_custom_component, @@ -34,16 +36,6 @@ from sqlmodel import Session router = APIRouter(tags=["Base"]) -# TODO: Move to correct local -def merge_nested_dicts(dict1, dict2): - for key, value in dict2.items(): - if isinstance(value, dict) and isinstance(dict1.get(key), dict): - dict1[key] = merge_nested_dicts(dict1[key], value) - else: - dict1[key] = value - return dict1 - - @router.get("/all") def get_all(): native_components = build_langchain_types_dict() diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index eaaa47f2b..ea85013fd 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -18,16 +18,21 @@ from langflow.interface.custom.base import custom_component_creator from langflow.interface.custom.custom_component import CustomComponent from langflow.template.field.base import TemplateField -from langflow.template.frontend_node.tools import CustomComponentNode +from langflow.template.frontend_node.tools import ( + CustomComponentNode, + CustomComponentEmptyNode, +) from langflow.interface.retrievers.base import retriever_creator from langflow.interface.custom.directory_reader import DirectoryReader from langflow.utils.logger import logger +from langflow.utils.util import get_base_classes +from langflow.api.utils import merge_nested_dicts + import re import warnings import traceback from fastapi import HTTPException -from langflow.utils.util import get_base_classes # Used to get the base_classes list @@ -250,15 +255,14 @@ def build_langchain_custom_component_list_from_path(path: str): # Build and validate all files data = reader.build_component_menu_list(file_list) - valid_components = reader.filter_loaded_components(data, False) - # TODO: Handle those invalid components - reader.filter_loaded_components(data, True) + valid_components = reader.filter_loaded_components(data=data, with_errors=False) + invalid_components = reader.filter_loaded_components(data=data, with_errors=True) - menu = {} + valid_menu = {} for menu_item in valid_components["menu"]: menu_name = menu_item["name"] - menu[menu_name] = {} + valid_menu[menu_name] = {} for component in menu_item["components"]: try: @@ -271,8 +275,33 @@ def build_langchain_custom_component_list_from_path(path: str): component_extractor ) - menu[menu_name][component_name] = component_template + valid_menu[menu_name][component_name] = component_template except Exception as exc: logger.error(f"Error while building custom component: {exc}") - return menu + invalid_menu = {} + for menu_item in invalid_components["menu"]: + menu_name = menu_item["name"] + invalid_menu[menu_name] = {} + + for component in menu_item["components"]: + try: + component_name = component["name"] + component_code = component["code"] + + component_template = ( + CustomComponentNode( + description="ERROR - Check your Python Code", + display_name=f"ERROR - {component_name}", + ) + .to_dict() + .get(type(CustomComponent()).__name__) + ) + + component_template.get("template").get("code")["value"] = component_code + + invalid_menu[menu_name][component_name] = component_template + except Exception as exc: + logger.error(f"Error while creating custom component: {exc}") + + return merge_nested_dicts(valid_menu, invalid_menu) diff --git a/src/backend/langflow/template/frontend_node/tools.py b/src/backend/langflow/template/frontend_node/tools.py index d23033b35..c7ed716c1 100644 --- a/src/backend/langflow/template/frontend_node/tools.py +++ b/src/backend/langflow/template/frontend_node/tools.py @@ -5,6 +5,7 @@ from langflow.template.template.base import Template from langflow.utils.constants import ( DEFAULT_PYTHON_FUNCTION, ) +from typing import Optional class ToolNode(FrontendNode): @@ -160,7 +161,32 @@ class CustomComponentNode(FrontendNode): ) ], ) - description: str = "Python Class to be executed." + description: str = "Dynamic Python code to be executed." + base_classes: list[str] = [] + + def to_dict(self): + return super().to_dict() + + +class CustomComponentEmptyNode(FrontendNode): + name: str = "CustomComponent" + template: Template = Template( + type_name="CustomComponent", + fields=[ + TemplateField( + field_type="code", + required=True, + placeholder="", + is_list=False, + show=True, + value="", + name="code", + advanced=False, + dynamic=True, + ) + ], + ) + description: str = "Dynamic Python code to be executed." base_classes: list[str] = [] def to_dict(self):