diff --git a/src/backend/langflow/interface/custom/custom_component/component.py b/src/backend/langflow/interface/custom/custom_component/component.py index 594ec982f..e2d84ed70 100644 --- a/src/backend/langflow/interface/custom/custom_component/component.py +++ b/src/backend/langflow/interface/custom/custom_component/component.py @@ -3,9 +3,9 @@ import operator import warnings from typing import Any, ClassVar, Optional +import emoji from cachetools import TTLCache, cachedmethod from fastapi import HTTPException - from langflow.interface.custom.code_parser import CodeParser from langflow.utils import validate @@ -35,6 +35,10 @@ class Component: else: setattr(self, key, value) + # Validate the emoji at the icon field + if self.icon: + self.icon = self.validate_icon(self.icon) + def __setattr__(self, key, value): if key == "_user_id" and hasattr(self, "_user_id"): warnings.warn("user_id is immutable and cannot be changed.") @@ -82,7 +86,22 @@ class Component: elif "documentation" in item_name: template_config["documentation"] = ast.literal_eval(item_value) + elif "icon" in item_name: + icon_str = ast.literal_eval(item_value) + template_config["icon"] = self.validate_icon(icon_str) + return template_config + def validate_icon(self, value: str): + # we are going to use the emoji library to validate the emoji + # emojis can be defined using the :emoji_name: syntax + if not value.startswith(":") or not value.endswith(":"): + raise ValueError("Invalid emoji. Please use the :emoji_name: syntax.") + + emoji_value = emoji.emojize(value, variant="emoji_type") + if value == emoji_value: + raise ValueError(f"Invalid emoji. {value} is not a valid emoji.") + return emoji_value + def build(self, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError 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 15e7cd845..f4cbf5ea5 100644 --- a/src/backend/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component/custom_component.py @@ -10,25 +10,31 @@ from langflow.interface.custom.code_parser.utils import ( extract_inner_type_from_generic_alias, extract_union_types_from_generic_alias, ) +from langflow.interface.custom.custom_component.component import Component from langflow.services.database.models.flow import Flow from langflow.services.database.utils import session_getter from langflow.services.deps import get_credential_service, get_db_service from langflow.utils import validate -from .component import Component - class CustomComponent(Component): display_name: Optional[str] = None + """The display name of the component. Defaults to None.""" description: Optional[str] = None + """The description of the component. Defaults to None.""" + icon: Optional[str] = None + """The icon of the component. It should be an emoji. Defaults to None.""" code: Optional[str] = None + """The code of the component. Defaults to None.""" field_config: dict = {} + """The field configuration of the component. Defaults to an empty dictionary.""" code_class_base_inheritance: ClassVar[str] = "CustomComponent" function_entrypoint_name: ClassVar[str] = "build" function: Optional[Callable] = None repr_value: Optional[Any] = "" user_id: Optional[Union[UUID, str]] = None status: Optional[Any] = None + """The status of the component. This is displayed on the frontend. Defaults to None.""" _tree: Optional[dict] = None def __init__(self, **data): diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index e6125b515..1a8bdc91f 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -7,6 +7,8 @@ from typing import Any, Dict, List, Optional, Union from uuid import UUID from fastapi import HTTPException +from loguru import logger + from langflow.field_typing.range_spec import RangeSpec from langflow.interface.custom.code_parser.utils import extract_inner_type from langflow.interface.custom.custom_component import CustomComponent @@ -19,7 +21,6 @@ from langflow.interface.importing.utils import eval_custom_component_code from langflow.template.field.base import TemplateField from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.utils.util import get_base_classes -from loguru import logger def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]): @@ -231,6 +232,7 @@ def sanitize_template_config(template_config): "beta", "documentation", "output_types", + "icon", } for key in template_config.copy(): if key not in attributes: diff --git a/src/backend/langflow/template/frontend_node/base.py b/src/backend/langflow/template/frontend_node/base.py index 6379dd358..daef4f9a8 100644 --- a/src/backend/langflow/template/frontend_node/base.py +++ b/src/backend/langflow/template/frontend_node/base.py @@ -2,13 +2,12 @@ import re from collections import defaultdict from typing import ClassVar, Dict, List, Optional, Union -from pydantic import BaseModel, Field, field_serializer, model_serializer - from langflow.template.field.base import TemplateField from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE, FORCE_SHOW_FIELDS from langflow.template.frontend_node.formatter import field_formatters from langflow.template.template.base import Template from langflow.utils import constants +from pydantic import BaseModel, Field, field_serializer, model_serializer class FieldFormatters(BaseModel): @@ -43,6 +42,7 @@ class FrontendNode(BaseModel): _format_template: bool = True template: Template description: Optional[str] = None + icon: Optional[str] = None base_classes: List[str] name: str = "" display_name: Optional[str] = "" diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index e8a95fb1b..b8d8f5a9d 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -156,14 +156,19 @@ export default function GenericNode({ (!showNode && "justify-center") } > - + {data?.node?.icon ? ( + {data?.node?.icon} + ) : ( + + )} + {showNode && (
{nameEditable && inputName ? ( diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 16c840e37..46e62376c 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -17,6 +17,7 @@ export type APIClassType = { description: string; template: APITemplateType; display_name: string; + icon?: string; input_types?: Array; output_types?: Array; custom_fields?: CustomFieldsType;