diff --git a/dbgpt/core/awel/flow/base.py b/dbgpt/core/awel/flow/base.py index fb60538ba..da0b2c378 100644 --- a/dbgpt/core/awel/flow/base.py +++ b/dbgpt/core/awel/flow/base.py @@ -19,6 +19,7 @@ from dbgpt.core.awel.util.parameter_util import BaseDynamicOptions, OptionValue from dbgpt.core.interface.serialization import Serializable from .exceptions import FlowMetadataException, FlowParameterMetadataException +from .ui import UIComponent _TYPE_REGISTRY: Dict[str, Type] = {} @@ -136,6 +137,7 @@ _OPERATOR_CATEGORY_DETAIL = { "agent": _CategoryDetail("Agent", "The agent operator"), "rag": _CategoryDetail("RAG", "The RAG operator"), "experimental": _CategoryDetail("EXPERIMENTAL", "EXPERIMENTAL operator"), + "example": _CategoryDetail("Example", "Example operator"), } @@ -151,6 +153,7 @@ class OperatorCategory(str, Enum): AGENT = "agent" RAG = "rag" EXPERIMENTAL = "experimental" + EXAMPLE = "example" def label(self) -> str: """Get the label of the category.""" @@ -193,6 +196,7 @@ _RESOURCE_CATEGORY_DETAIL = { "embeddings": _CategoryDetail("Embeddings", "The embeddings resource"), "rag": _CategoryDetail("RAG", "The resource"), "vector_store": _CategoryDetail("Vector Store", "The vector store resource"), + "example": _CategoryDetail("Example", "The example resource"), } @@ -209,6 +213,7 @@ class ResourceCategory(str, Enum): EMBEDDINGS = "embeddings" RAG = "rag" VECTOR_STORE = "vector_store" + EXAMPLE = "example" def label(self) -> str: """Get the label of the category.""" @@ -343,6 +348,9 @@ class Parameter(TypeMetadata, Serializable): alias: Optional[List[str]] = Field( None, description="The alias of the parameter(Compatible with old version)" ) + ui: Optional[UIComponent] = Field( + None, description="The UI component of the parameter" + ) @model_validator(mode="before") @classmethod @@ -398,6 +406,7 @@ class Parameter(TypeMetadata, Serializable): label: str, name: str, type: Type, + is_list: bool = False, optional: bool = False, default: Optional[Union[DefaultParameterType, _MISSING_TYPE]] = _MISSING_VALUE, placeholder: Optional[DefaultParameterType] = None, @@ -405,6 +414,7 @@ class Parameter(TypeMetadata, Serializable): options: Optional[Union[BaseDynamicOptions, List[OptionValue]]] = None, resource_type: ResourceType = ResourceType.INSTANCE, alias: Optional[List[str]] = None, + ui: Optional[UIComponent] = None, ): """Build the parameter from the type.""" type_name = type.__qualname__ @@ -419,6 +429,7 @@ class Parameter(TypeMetadata, Serializable): name=name, type_name=type_name, type_cls=type_cls, + is_list=is_list, category=category.value, resource_type=resource_type, optional=optional, @@ -427,6 +438,7 @@ class Parameter(TypeMetadata, Serializable): description=description or label, options=options, alias=alias, + ui=ui, ) @classmethod @@ -456,11 +468,12 @@ class Parameter(TypeMetadata, Serializable): description=data["description"], options=data["options"], value=data["value"], + ui=data.get("ui"), ) def to_dict(self) -> Dict: """Convert current metadata to json dict.""" - dict_value = model_to_dict(self, exclude={"options", "alias"}) + dict_value = model_to_dict(self, exclude={"options", "alias", "ui"}) if not self.options: dict_value["options"] = None elif isinstance(self.options, BaseDynamicOptions): @@ -468,6 +481,9 @@ class Parameter(TypeMetadata, Serializable): dict_value["options"] = [value.to_dict() for value in values] else: dict_value["options"] = [value.to_dict() for value in self.options] + + if self.ui: + dict_value["ui"] = self.ui.to_dict() return dict_value def get_dict_options(self) -> Optional[List[Dict]]: diff --git a/dbgpt/core/awel/flow/ui.py b/dbgpt/core/awel/flow/ui.py index a9f220961..ca4361276 100644 --- a/dbgpt/core/awel/flow/ui.py +++ b/dbgpt/core/awel/flow/ui.py @@ -1,8 +1,9 @@ """UI components for AWEL flow.""" -from typing import Any, Dict, List, Literal, Optional +from typing import Any, Dict, List, Literal, Optional, Union -from dbgpt._private.pydantic import BaseModel, Field +from dbgpt._private.pydantic import BaseModel, Field, model_to_dict +from dbgpt.core.interface.serialization import Serializable from .exceptions import FlowUIComponentException @@ -36,37 +37,6 @@ class RefreshableMixin(BaseModel): ) -class UIComponent(RefreshableMixin, BaseModel): - """UI component.""" - - class UIRange(BaseModel): - """UI range.""" - - min: int | float | str | None = Field(None, description="Minimum value") - max: int | float | str | None = Field(None, description="Maximum value") - step: int | float | str | None = Field(None, description="Step value") - format: str | None = Field(None, description="Format") - - ui_type: _UI_TYPE = Field(..., description="UI component type") - - disabled: bool = Field( - False, - description="Whether the component is disabled", - ) - - def check_parameter(self, parameter_dict: Dict[str, Any]): - """Check parameter. - - Raises: - FlowUIParameterException: If the parameter is invalid. - """ - - def _check_options(self, options: Dict[str, Any]): - """Check options.""" - if not options: - raise FlowUIComponentException("options is required", self.ui_type) - - class StatusMixin(BaseModel): """Status mixin.""" @@ -76,40 +46,6 @@ class StatusMixin(BaseModel): ) -class RangeMixin(BaseModel): - """Range mixin.""" - - ui_range: Optional[UIComponent.UIRange] = Field( - None, - description="Range for the component", - ) - - -class InputMixin(BaseModel): - """Input mixin.""" - - class Count(BaseModel): - """Count.""" - - show: Optional[bool] = Field( - None, - description="Whether to show count", - ) - max: Optional[int] = Field( - None, - description="The maximum count", - ) - exceed_strategy: Optional[Literal["cut", "warning"]] = Field( - None, - description="The strategy when the count exceeds", - ) - - count: Optional[Count] = Field( - None, - description="Count configuration", - ) - - class PanelEditorMixin(BaseModel): """Edit the content in the panel.""" @@ -126,19 +62,62 @@ class PanelEditorMixin(BaseModel): ) editor: Optional[Editor] = Field( - None, + default_factory=lambda: PanelEditorMixin.Editor(width=800, height=400), description="The editor configuration", ) -class UICascader(StatusMixin, UIComponent): +class UIComponent(RefreshableMixin, Serializable, BaseModel): + """UI component.""" + + class UIAttribute(StatusMixin, BaseModel): + """Base UI attribute.""" + + disabled: bool = Field( + False, + description="Whether the component is disabled", + ) + + ui_type: _UI_TYPE = Field(..., description="UI component type") + + attr: Optional[UIAttribute] = Field( + None, + description="The attributes of the component", + ) + + def check_parameter(self, parameter_dict: Dict[str, Any]): + """Check parameter. + + Raises: + FlowUIParameterException: If the parameter is invalid. + """ + + def _check_options(self, options: Dict[str, Any]): + """Check options.""" + if not options: + raise FlowUIComponentException("options is required", self.ui_type) + + def to_dict(self) -> Dict: + """Convert current metadata to json dict.""" + return model_to_dict(self) + + +class UICascader(UIComponent): """Cascader component.""" + class UIAttribute(UIComponent.UIAttribute): + """Cascader attribute.""" + + show_search: bool = Field( + False, + description="Whether to show search input", + ) + ui_type: Literal["cascader"] = Field("cascader", frozen=True) - show_search: bool = Field( - False, - description="Whether to show search input", + attr: Optional[UIAttribute] = Field( + None, + description="The attributes of the component", ) def check_parameter(self, parameter_dict: Dict[str, Any]): @@ -163,53 +142,81 @@ class UICheckbox(UIComponent): self._check_options(parameter_dict.get("options", {})) -class UIDatePicker(StatusMixin, RangeMixin, UIComponent): +class UIDatePicker(UIComponent): """Date picker component.""" + class UIAttribute(UIComponent.UIAttribute): + """Date picker attribute.""" + + placement: Optional[ + Literal["topLeft", "topRight", "bottomLeft", "bottomRight"] + ] = Field( + None, + description="The position of the picker panel, None means bottomLeft", + ) + ui_type: Literal["date_picker"] = Field("date_picker", frozen=True) - placement: Optional[ - Literal["topLeft", "topRight", "bottomLeft", "bottomRight"] - ] = Field( + attr: Optional[UIAttribute] = Field( None, - description="The position of the picker panel, None means bottomLeft", + description="The attributes of the component", ) -class UIInput(StatusMixin, InputMixin, UIComponent): +class UIInput(UIComponent): """Input component.""" + class UIAttribute(UIComponent.UIAttribute): + """Input attribute.""" + + prefix: Optional[str] = Field( + None, + description="The prefix, icon or text", + examples=["$", "icon:UserOutlined"], + ) + suffix: Optional[str] = Field( + None, + description="The suffix, icon or text", + examples=["$", "icon:SearchOutlined"], + ) + show_count: Optional[bool] = Field( + None, + description="Whether to show count", + ) + maxlength: Optional[int] = Field( + None, + description="The maximum length of the input", + ) + ui_type: Literal["input"] = Field("input", frozen=True) - prefix: Optional[str] = Field( + attr: Optional[UIAttribute] = Field( None, - description="The prefix, icon or text", - examples=["$", "icon:UserOutlined"], - ) - suffix: Optional[str] = Field( - None, - description="The suffix, icon or text", - examples=["$", "icon:SearchOutlined"], + description="The attributes of the component", ) class UITextArea(PanelEditorMixin, UIInput): """Text area component.""" + class AutoSize(BaseModel): + """Auto size configuration.""" + + min_rows: Optional[int] = Field( + None, + description="The minimum number of rows", + ) + max_rows: Optional[int] = Field( + None, + description="The maximum number of rows", + ) + ui_type: Literal["text_area"] = Field("text_area", frozen=True) # type: ignore - auto_size: Optional[bool] = Field( + autosize: Optional[Union[bool, AutoSize]] = Field( None, description="Whether the height of the textarea automatically adjusts based " "on the content", ) - min_rows: Optional[int] = Field( - None, - description="The minimum number of rows", - ) - max_rows: Optional[int] = Field( - None, - description="The maximum number of rows", - ) class UIAutoComplete(UIInput): @@ -220,44 +227,73 @@ class UIAutoComplete(UIInput): ) -class UISlider(RangeMixin, UIComponent): +class UISlider(UIComponent): """Slider component.""" + class UIAttribute(UIComponent.UIAttribute): + """Slider attribute.""" + + min: Optional[int | float] = Field( + None, + description="The minimum value", + ) + max: Optional[int | float] = Field( + None, + description="The maximum value", + ) + step: Optional[int | float] = Field( + None, + description="The step of the slider", + ) + ui_type: Literal["slider"] = Field("slider", frozen=True) show_input: bool = Field( False, description="Whether to display the value in a input component" ) + attr: Optional[UIAttribute] = Field( + None, + description="The attributes of the component", + ) -class UITimePicker(StatusMixin, UIComponent): + +class UITimePicker(UIComponent): """Time picker component.""" + class UIAttribute(UIComponent.UIAttribute): + """Time picker attribute.""" + + format: Optional[str] = Field( + None, + description="The format of the time", + examples=["HH:mm:ss", "HH:mm"], + ) + hour_step: Optional[int] = Field( + None, + description="The step of the hour input", + ) + minute_step: Optional[int] = Field( + None, + description="The step of the minute input", + ) + second_step: Optional[int] = Field( + None, + description="The step of the second input", + ) + ui_type: Literal["time_picker"] = Field("time_picker", frozen=True) - format: Optional[str] = Field( + attr: Optional[UIAttribute] = Field( None, - description="The format of the time", - examples=["HH:mm:ss", "HH:mm"], - ) - hour_step: Optional[int] = Field( - None, - description="The step of the hour input", - ) - minute_step: Optional[int] = Field( - None, - description="The step of the minute input", - ) - second_step: Optional[int] = Field( - None, - description="The step of the second input", + description="The attributes of the component", ) -class UITreeSelect(StatusMixin, UIComponent): +class UITreeSelect(UICascader): """Tree select component.""" - ui_type: Literal["tree_select"] = Field("tree_select", frozen=True) + ui_type: Literal["tree_select"] = Field("tree_select", frozen=True) # type: ignore def check_parameter(self, parameter_dict: Dict[str, Any]): """Check parameter.""" @@ -271,19 +307,24 @@ class UITreeSelect(StatusMixin, UIComponent): ) -class UIUpload(StatusMixin, UIComponent): +class UIUpload(UIComponent): """Upload component.""" + class UIAttribute(UIComponent.UIAttribute): + """Upload attribute.""" + + max_count: Optional[int] = Field( + None, + description="The maximum number of files that can be uploaded", + ) + ui_type: Literal["upload"] = Field("upload", frozen=True) max_file_size: Optional[int] = Field( None, description="The maximum size of the file, in bytes", ) - max_count: Optional[int] = Field( - None, - description="The maximum number of files that can be uploaded", - ) + file_types: Optional[List[str]] = Field( None, description="The file types that can be accepted", @@ -346,3 +387,13 @@ class UICodeEditor(UITextArea): "python", description="The language of the code", ) + + +class DefaultUITextArea(UITextArea): + """Default text area component.""" + + autosize: Union[bool, UITextArea.AutoSize] = Field( + default_factory=lambda: UITextArea.AutoSize(min_rows=2, max_rows=40), + description="Whether the height of the textarea automatically adjusts based " + "on the content", + ) diff --git a/dbgpt/core/awel/util/parameter_util.py b/dbgpt/core/awel/util/parameter_util.py index defd99a3b..70015c9ba 100644 --- a/dbgpt/core/awel/util/parameter_util.py +++ b/dbgpt/core/awel/util/parameter_util.py @@ -2,7 +2,7 @@ import inspect from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, List +from typing import Any, Callable, Dict, List, Optional from dbgpt._private.pydantic import BaseModel, Field, model_validator from dbgpt.core.interface.serialization import Serializable @@ -16,6 +16,9 @@ class OptionValue(Serializable, BaseModel): label: str = Field(..., description="The label of the option") name: str = Field(..., description="The name of the option") value: Any = Field(..., description="The value of the option") + children: Optional[List["OptionValue"]] = Field( + None, description="The children of the option" + ) def to_dict(self) -> Dict: """Convert current metadata to json dict.""" diff --git a/dbgpt/core/interface/operators/llm_operator.py b/dbgpt/core/interface/operators/llm_operator.py index 53e34ffe5..45863d0a9 100644 --- a/dbgpt/core/interface/operators/llm_operator.py +++ b/dbgpt/core/interface/operators/llm_operator.py @@ -24,6 +24,7 @@ from dbgpt.core.awel.flow import ( OperatorType, Parameter, ViewMetadata, + ui, ) from dbgpt.core.interface.llm import ( LLMClient, @@ -69,6 +70,10 @@ class RequestBuilderOperator(MapOperator[RequestInput, ModelRequest]): optional=True, default=None, description=_("The temperature of the model request."), + ui=ui.UISlider( + show_input=True, + attr=ui.UISlider.UIAttribute(min=0.0, max=2.0, step=0.1), + ), ), Parameter.build_from( _("Max New Tokens"), diff --git a/dbgpt/core/interface/operators/prompt_operator.py b/dbgpt/core/interface/operators/prompt_operator.py index c3765aa67..7d97230ac 100644 --- a/dbgpt/core/interface/operators/prompt_operator.py +++ b/dbgpt/core/interface/operators/prompt_operator.py @@ -1,4 +1,5 @@ """The prompt operator.""" + from abc import ABC from typing import Any, Dict, List, Optional, Union @@ -18,6 +19,7 @@ from dbgpt.core.awel.flow import ( ResourceCategory, ViewMetadata, register_resource, + ui, ) from dbgpt.core.interface.message import BaseMessage from dbgpt.core.interface.operators.llm_operator import BaseLLM @@ -48,6 +50,7 @@ from dbgpt.util.i18n_utils import _ optional=True, default="You are a helpful AI Assistant.", description=_("The system message."), + ui=ui.DefaultUITextArea(), ), Parameter.build_from( label=_("Message placeholder"), @@ -65,6 +68,7 @@ from dbgpt.util.i18n_utils import _ default="{user_input}", placeholder="{user_input}", description=_("The human message."), + ui=ui.DefaultUITextArea(), ), ], ) diff --git a/dbgpt/serve/flow/api/endpoints.py b/dbgpt/serve/flow/api/endpoints.py index 6cb5ef879..98ff81d2f 100644 --- a/dbgpt/serve/flow/api/endpoints.py +++ b/dbgpt/serve/flow/api/endpoints.py @@ -209,7 +209,7 @@ async def query_page( @router.get("/nodes", dependencies=[Depends(check_api_key)]) -async def get_nodes() -> Result[List[Union[ViewMetadata, ResourceMetadata]]]: +async def get_nodes(): """Get the operator or resource nodes Returns: @@ -218,7 +218,8 @@ async def get_nodes() -> Result[List[Union[ViewMetadata, ResourceMetadata]]]: """ from dbgpt.core.awel.flow.base import _OPERATOR_REGISTRY - return Result.succ(_OPERATOR_REGISTRY.metadata_list()) + metadata_list = _OPERATOR_REGISTRY.metadata_list() + return Result.succ(metadata_list) def init_endpoints(system_app: SystemApp) -> None: diff --git a/examples/awel/awel_flow_ui_components.py b/examples/awel/awel_flow_ui_components.py new file mode 100644 index 000000000..2af3e2bf3 --- /dev/null +++ b/examples/awel/awel_flow_ui_components.py @@ -0,0 +1,583 @@ +"""Some UI components for the AWEL flow.""" + +import logging +from typing import List, Optional + +from dbgpt.core.awel import MapOperator +from dbgpt.core.awel.flow import ( + IOField, + OperatorCategory, + OptionValue, + Parameter, + ViewMetadata, + ui, +) + +logger = logging.getLogger(__name__) + + +class ExampleFlowCascaderOperator(MapOperator[str, str]): + """An example flow operator that includes a cascader as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Cascader", + name="example_flow_cascader", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a cascader as parameter.", + parameters=[ + Parameter.build_from( + "Address Selector", + "address", + type=str, + is_list=True, + optional=True, + default=None, + placeholder="Select the address", + description="The address of the location.", + options=[ + OptionValue( + label="Zhejiang", + name="zhejiang", + value="zhejiang", + children=[ + OptionValue( + label="Hangzhou", + name="hangzhou", + value="hangzhou", + children=[ + OptionValue( + label="Xihu", + name="xihu", + value="xihu", + ), + OptionValue( + label="Feilaifeng", + name="feilaifeng", + value="feilaifeng", + ), + ], + ), + ], + ), + OptionValue( + label="Jiangsu", + name="jiangsu", + value="jiangsu", + children=[ + OptionValue( + label="Nanjing", + name="nanjing", + value="nanjing", + children=[ + OptionValue( + label="Zhonghua Gate", + name="zhonghuamen", + value="zhonghuamen", + ), + OptionValue( + label="Zhongshanling", + name="zhongshanling", + value="zhongshanling", + ), + ], + ), + ], + ), + ], + ui=ui.UICascader(attr=ui.UICascader.UIAttribute(show_search=True)), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "Address", + "address", + str, + description="User's address.", + ) + ], + ) + + def __int__(self, address: Optional[List[str]] = None, **kwargs): + super().__init__(**kwargs) + self.address = address or [] + + async def map(self, user_name: str) -> str: + """Map the user name to the address.""" + full_address_str = " ".join(self.address) + return "Your name is %s, and your address is %s." % ( + user_name, + full_address_str, + ) + + +class ExampleFlowCheckboxOperator(MapOperator[str, str]): + """An example flow operator that includes a checkbox as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Checkbox", + name="example_flow_checkbox", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a checkbox as parameter.", + parameters=[ + Parameter.build_from( + "Fruits Selector", + "fruits", + type=str, + is_list=True, + optional=True, + default=None, + placeholder="Select the fruits", + description="The fruits you like.", + options=[ + OptionValue(label="Apple", name="apple", value="apple"), + OptionValue(label="Banana", name="banana", value="banana"), + OptionValue(label="Orange", name="orange", value="orange"), + OptionValue(label="Pear", name="pear", value="pear"), + ], + ui=ui.UICheckbox(attr=ui.UICheckbox.UIAttribute(show_search=True)), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "Fruits", + "fruits", + str, + description="User's favorite fruits.", + ) + ], + ) + + def __init__(self, fruits: Optional[List[str]] = None, **kwargs): + super().__init__(**kwargs) + self.fruits = fruits or [] + + async def map(self, user_name: str) -> str: + """Map the user name to the fruits.""" + return "Your name is %s, and you like %s." % (user_name, ", ".join(self.fruits)) + + +class ExampleFlowDatePickerOperator(MapOperator[str, str]): + """An example flow operator that includes a date picker as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Date Picker", + name="example_flow_date_picker", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a date picker as parameter.", + parameters=[ + Parameter.build_from( + "Date Selector", + "date", + type=str, + placeholder="Select the date", + description="The date you choose.", + ui=ui.UIDatePicker( + attr=ui.UIDatePicker.UIAttribute(placement="bottomLeft") + ), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "Date", + "date", + str, + description="User's selected date.", + ) + ], + ) + + def __init__(self, date: str, **kwargs): + super().__init__(**kwargs) + self.date = date + + async def map(self, user_name: str) -> str: + """Map the user name to the date.""" + return "Your name is %s, and you choose the date %s." % (user_name, self.date) + + +class ExampleFlowInputOperator(MapOperator[str, str]): + """An example flow operator that includes an input as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Input", + name="example_flow_input", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a input as parameter.", + parameters=[ + Parameter.build_from( + "Your hobby", + "hobby", + type=str, + placeholder="Please input your hobby", + description="The hobby you like.", + ui=ui.UIInput( + attr=ui.UIInput.UIAttribute( + prefix="icon:UserOutlined", show_count=True, maxlength=200 + ) + ), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "User Hobby", + "hobby", + str, + description="User's hobby.", + ) + ], + ) + + def __init__(self, hobby: str, **kwargs): + super().__init__(**kwargs) + self.hobby = hobby + + async def map(self, user_name: str) -> str: + """Map the user name to the input.""" + return "Your name is %s, and your hobby is %s." % (user_name, self.hobby) + + +class ExampleFlowTextAreaOperator(MapOperator[str, str]): + """An example flow operator that includes a text area as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Text Area", + name="example_flow_text_area", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a text area as parameter.", + parameters=[ + Parameter.build_from( + "Your comment", + "comment", + type=str, + placeholder="Please input your comment", + description="The comment you want to say.", + ui=ui.UITextArea( + attr=ui.UITextArea.UIAttribute(show_count=True, maxlength=1000), + autosize=ui.UITextArea.AutoSize(min_rows=2, max_rows=6), + ), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "User Comment", + "comment", + str, + description="User's comment.", + ) + ], + ) + + def __init__(self, comment: str, **kwargs): + super().__init__(**kwargs) + self.comment = comment + + async def map(self, user_name: str) -> str: + """Map the user name to the text area.""" + return "Your name is %s, and your comment is %s." % (user_name, self.comment) + + +class ExampleFlowSliderOperator(MapOperator[float, float]): + + metadata = ViewMetadata( + label="Example Flow Slider", + name="example_flow_slider", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a slider as parameter.", + parameters=[ + Parameter.build_from( + "Default Temperature", + "default_temperature", + type=float, + optional=True, + default=0.7, + placeholder="Set the default temperature, e.g., 0.7", + description="The default temperature to pass to the LLM.", + ui=ui.UISlider( + show_input=True, + attr=ui.UISlider.UIAttribute(min=0.0, max=2.0, step=0.1), + ), + ) + ], + inputs=[ + IOField.build_from( + "Temperature", + "temperature", + float, + description="The temperature.", + ) + ], + outputs=[ + IOField.build_from( + "Temperature", + "temperature", + float, + description="The temperature to pass to the LLM.", + ) + ], + ) + + def __init__(self, default_temperature: float = 0.7, **kwargs): + super().__init__(**kwargs) + self.default_temperature = default_temperature + + async def map(self, temperature: float) -> float: + """Map the temperature to the result.""" + if temperature < 0.0 or temperature > 2.0: + logger.warning("Temperature out of range: %s", temperature) + return self.default_temperature + else: + return temperature + + +class ExampleFlowSliderListOperator(MapOperator[float, float]): + """An example flow operator that includes a slider list as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Slider List", + name="example_flow_slider_list", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a slider list as parameter.", + parameters=[ + Parameter.build_from( + "Temperature Selector", + "temperature_range", + type=float, + is_list=True, + optional=True, + default=None, + placeholder="Set the temperature, e.g., [0.1, 0.9]", + description="The temperature range to pass to the LLM.", + ui=ui.UISlider( + show_input=True, + attr=ui.UISlider.UIAttribute(min=0.0, max=2.0, step=0.1), + ), + ) + ], + inputs=[ + IOField.build_from( + "Temperature", + "temperature", + float, + description="The temperature.", + ) + ], + outputs=[ + IOField.build_from( + "Temperature", + "temperature", + float, + description="The temperature to pass to the LLM.", + ) + ], + ) + + def __init__(self, temperature_range: Optional[List[float]] = None, **kwargs): + super().__init__(**kwargs) + temperature_range = temperature_range or [0.1, 0.9] + if temperature_range and len(temperature_range) != 2: + raise ValueError("The length of temperature range must be 2.") + self.temperature_range = temperature_range + + async def map(self, temperature: float) -> float: + """Map the temperature to the result.""" + min_temperature, max_temperature = self.temperature_range + if temperature < min_temperature or temperature > max_temperature: + logger.warning( + "Temperature out of range: %s, min: %s, max: %s", + temperature, + min_temperature, + max_temperature, + ) + return min_temperature + return temperature + + +class ExampleFlowTimePickerOperator(MapOperator[str, str]): + """An example flow operator that includes a time picker as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Time Picker", + name="example_flow_time_picker", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a time picker as parameter.", + parameters=[ + Parameter.build_from( + "Time Selector", + "time", + type=str, + placeholder="Select the time", + description="The time you choose.", + ui=ui.UITimePicker( + attr=ui.UITimePicker.UIAttribute( + format="HH:mm:ss", hour_step=2, minute_step=10, second_step=10 + ), + ), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "Time", + "time", + str, + description="User's selected time.", + ) + ], + ) + + def __init__(self, time: str, **kwargs): + super().__init__(**kwargs) + self.time = time + + async def map(self, user_name: str) -> str: + """Map the user name to the time.""" + return "Your name is %s, and you choose the time %s." % (user_name, self.time) + + +class ExampleFlowTreeSelectOperator(MapOperator[str, str]): + """An example flow operator that includes a tree select as parameter.""" + + metadata = ViewMetadata( + label="Example Flow Tree Select", + name="example_flow_tree_select", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes a tree select as parameter.", + parameters=[ + Parameter.build_from( + "Address Selector", + "address", + type=str, + is_list=True, + optional=True, + default=None, + placeholder="Select the address", + description="The address of the location.", + options=[ + OptionValue( + label="Zhejiang", + name="zhejiang", + value="zhejiang", + children=[ + OptionValue( + label="Hangzhou", + name="hangzhou", + value="hangzhou", + children=[ + OptionValue( + label="Xihu", + name="xihu", + value="xihu", + ), + OptionValue( + label="Feilaifeng", + name="feilaifeng", + value="feilaifeng", + ), + ], + ), + ], + ), + OptionValue( + label="Jiangsu", + name="jiangsu", + value="jiangsu", + children=[ + OptionValue( + label="Nanjing", + name="nanjing", + value="nanjing", + children=[ + OptionValue( + label="Zhonghua Gate", + name="zhonghuamen", + value="zhonghuamen", + ), + OptionValue( + label="Zhongshanling", + name="zhongshanling", + value="zhongshanling", + ), + ], + ), + ], + ), + ], + ui=ui.UITreeSelect(attr=ui.UITreeSelect.UIAttribute(show_search=True)), + ) + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ) + ], + outputs=[ + IOField.build_from( + "Address", + "address", + str, + description="User's address.", + ) + ], + ) + + def __int__(self, address: Optional[List[str]] = None, **kwargs): + super().__init__(**kwargs) + self.address = address or [] + + async def map(self, user_name: str) -> str: + """Map the user name to the address.""" + full_address_str = " ".join(self.address) + return "Your name is %s, and your address is %s." % ( + user_name, + full_address_str, + )