diff --git a/docs/docs/components/custom.mdx b/docs/docs/components/custom.mdx index 9fb0fe689..43eb336fc 100644 --- a/docs/docs/components/custom.mdx +++ b/docs/docs/components/custom.mdx @@ -83,7 +83,8 @@ The CustomComponent class serves as the foundation for creating custom component | _`file_types: List[str]`_ | This is a requirement if the _`field_type`_ is _file_. Defines which file types will be accepted. For example, _json_, _yaml_ or _yml_. | | _`range_spec: langflow.field_typing.RangeSpec`_ | This is a requirement if the _`field_type`_ is _`float`_. Defines the range of values accepted and the step size. If none is defined, the default is _`[-1, 1, 0.1]`_. | | _`title_case: bool`_ | Formats the name of the field when _`display_name`_ is not defined. Set it to False to keep the name as you set it in the _`build`_ method. | - | _`refresh: bool`_ | If set to True a button will appear to the right of the field, and when clicked, it will call the _`update_build_config`_ method which takes in the _`build_config`_, the name of the field (_`field_name`_) and the latest value of the field (_`field_value`_). This is useful when you want to update the _`build_config`_ based on the value of the field. | + | _`refresh_button: bool`_ | If set to True a button will appear to the right of the field, and when clicked, it will call the _`update_build_config`_ method which takes in the _`build_config`_, the name of the field (_`field_name`_) and the latest value of the field (_`field_value`_). This is useful when you want to update the _`build_config`_ based on the value of the field. | + | _`real_time_refresh: bool`_ | If set to True, the _`update_build_config`_ method will be called every time the field value changes. | diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 68951f3f8..bfc8c18fe 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -468,7 +468,9 @@ def update_field_dict( call: bool = False, ): """Update the field dictionary by calling options() or value() if they are callable""" - if "refresh" in field_dict: + if ("real_time_refresh" in field_dict or "refresh_button" in field_dict) and any( + (field_dict["real_time_refresh"], field_dict["refresh_button"]) + ): if call: try: dd_build_config = dotdict(build_config) @@ -481,7 +483,6 @@ def update_field_dict( raise UpdateBuildConfigError( f"Error while running update_build_config: {str(exc)}" ) from exc - field_dict["refresh"] = True # Let's check if "range_spec" is a RangeSpec object if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec): diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index bf6d461f7..76e5a13d0 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -65,10 +65,17 @@ class TemplateField(BaseModel): info: Optional[str] = "" """Additional information about the field to be shown in the tooltip. Defaults to an empty string.""" - refresh: Optional[bool] = None - """Specifies if the field should be refreshed. Defaults to False.""" + real_time_refresh: Optional[bool] = None + """Specifies if the field should have real time refresh. `refresh_button` must be False. Defaults to None.""" - range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec") + refresh_button: Optional[bool] = None + """Specifies if the field should have a refresh button. Defaults to False.""" + refresh_button_text: Optional[str] = None + """Specifies the text for the refresh button. Defaults to None.""" + + range_spec: Optional[RangeSpec] = Field( + default=None, serialization_alias="rangeSpec" + ) """Range specification for the field. Defaults to None.""" title_case: bool = False @@ -117,6 +124,10 @@ class TemplateField(BaseModel): if not isinstance(value, list): raise ValueError("file_types must be a list") return [ - (f".{file_type}" if isinstance(file_type, str) and not file_type.startswith(".") else file_type) + ( + f".{file_type}" + if isinstance(file_type, str) and not file_type.startswith(".") + else file_type + ) for file_type in value ] diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 075ded804..b0fd6804d 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -88,10 +88,36 @@ export default function ParameterComponent({ const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); + const handleRefreshButtonPress = async (name, data) => { + setIsLoading(true); + try { + let newTemplate = await handleUpdateValues(name, data); + if (newTemplate) { + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + }; + newNode.data.node.template = newTemplate; + return newNode; + }); + } + } catch (error) { + let responseError = error as ResponseErrorTypeAPI; + setErrorData({ + title: "Error while updating the Component", + list: [responseError.response.data.detail.error ?? "Unknown error"], + }); + } + setIsLoading(false); + renderTooltips(); + }; + useEffect(() => { async function fetchData() { if ( - data.node?.template[name]?.refresh && + (data.node?.template[name]?.real_time_refresh || + data.node?.template[name]?.refresh_button) && // options can be undefined but not an empty array (data.node?.template[name]?.options?.length ?? 0) === 0 ) { @@ -128,7 +154,8 @@ export default function ParameterComponent({ takeSnapshot(); } const shouldUpdate = - data.node?.template[name].refresh && + data.node?.template[name].real_time_refresh && + !data.node?.template[name].refresh_button && data.node!.template[name].value !== newValue; data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input @@ -154,7 +181,7 @@ export default function ParameterComponent({ ...newNode.data, }; - if (data.node?.template[name].refresh && newTemplate) { + if (data.node?.template[name].real_time_refresh && newTemplate) { newNode.data.node.template = newTemplate; } else newNode.data.node.template[name].value = newValue; @@ -458,7 +485,7 @@ export default function ParameterComponent({ } onChange={handleOnNewValue} /> - {/* {data.node?.template[name].refresh && ( + {/* {data.node?.template[name].refresh_button && (
)} */} ) : data.node?.template[name].multiline ? ( - +
+
+ +
+ {data.node?.template[name].refresh_button && ( +
+ +
+ )} +
) : (
- {data.node?.template[name].refresh && ( + {data.node?.template[name].refresh_button && (
@@ -535,7 +587,7 @@ export default function ParameterComponent({ ) : left === true && type === "str" && (data.node?.template[name].options || - data.node?.template[name]?.refresh) ? ( + data.node?.template[name]?.real_time_refresh) ? ( // TODO: Improve CSS
@@ -548,15 +600,18 @@ export default function ParameterComponent({ id={"dropdown-" + name} />
- {data.node?.template[name].refresh && ( + {data.node?.template[name].refresh_button && (
diff --git a/src/frontend/src/components/ui/refreshButton.tsx b/src/frontend/src/components/ui/refreshButton.tsx index bf41ba114..791b0ca05 100644 --- a/src/frontend/src/components/ui/refreshButton.tsx +++ b/src/frontend/src/components/ui/refreshButton.tsx @@ -7,6 +7,7 @@ function RefreshButton({ isLoading, disabled, name, + button_text, data, handleUpdateValues, className, @@ -15,6 +16,7 @@ function RefreshButton({ isLoading: boolean; disabled: boolean; name: string; + button_text: string; data: NodeDataType; className?: string; handleUpdateValues: (name: string, data: NodeDataType) => void; @@ -43,6 +45,7 @@ function RefreshButton({ onClick={handleClick} id={id} > + {button_text} ; display_name?: string; name?: string; - refresh?: boolean; + real_time_refresh?: boolean; + refresh_button?: boolean; + refresh_button_text?: string; [key: string]: any; }; export type sendAllProps = {