diff --git a/src/backend/langflow/interface/custom/custom_component/component.py b/src/backend/langflow/interface/custom/custom_component/component.py index 357a04c3d..2cb99e99a 100644 --- a/src/backend/langflow/interface/custom/custom_component/component.py +++ b/src/backend/langflow/interface/custom/custom_component/component.py @@ -2,6 +2,7 @@ import operator import warnings from typing import Any, ClassVar, Optional +import emoji from cachetools import TTLCache, cachedmethod from fastapi import HTTPException @@ -35,6 +36,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.") @@ -79,6 +84,7 @@ class Component: "description": self.getattr_return_str, "beta": self.getattr_return_str, "documentation": self.getattr_return_str, + "icon": self.validate_icon, } for attribute, func in attributes_func_mapping.items(): @@ -87,5 +93,16 @@ class Component: 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 0021de41d..945d7db2c 100644 --- a/src/backend/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component/custom_component.py @@ -10,26 +10,32 @@ 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, get_storage_service from langflow.services.storage.service import StorageService 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 be3f6787d..650047efc 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 @@ -20,7 +22,6 @@ from langflow.template.field.base import TemplateField 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]): @@ -232,6 +233,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 817fdfeb9..26def31e6 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -173,14 +173,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 3fc20be68..48db814cf 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;