Update refresh functionality in API and UI components

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-03-07 14:36:34 -03:00
commit 24a2e640ff
6 changed files with 99 additions and 26 deletions

View file

@ -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. |
<Admonition type="info" label="Tip">

View file

@ -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):

View file

@ -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
]

View file

@ -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 && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
@ -466,26 +493,47 @@ export default function ParameterComponent({
name={name}
data={data}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
)} */}
</div>
) : data.node?.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + data.node.template[name].name}
data-testid={"textarea-" + data.node.template[name].name}
/>
<div className="mt-2 flex w-full flex-col ">
<div className="flex-grow">
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + data.node.template[name].name}
data-testid={"textarea-" + data.node.template[name].name}
/>
</div>
{data.node?.template[name].refresh_button && (
<div className="flex-grow">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
button_text={
data.node?.template[name].refresh_button_text ??
"Refresh"
}
className="extra-side-bar-buttons mt-1"
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
)}
</div>
) : (
<div className="mt-2 flex w-full items-center">
<div
className={
"flex-grow " +
(data.node?.template[name].refresh ? "w-5/6" : "")
(data.node?.template[name].refresh_button ? "w-5/6" : "")
}
>
<InputComponent
@ -496,15 +544,19 @@ export default function ParameterComponent({
onChange={handleOnNewValue}
/>
</div>
{data.node?.template[name].refresh && (
{data.node?.template[name].refresh_button && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
button_text={
data.node?.template[name].refresh_button_text ??
"Refresh"
}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
@ -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
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
@ -548,15 +600,18 @@ export default function ParameterComponent({
id={"dropdown-" + name}
/>
</div>
{data.node?.template[name].refresh && (
{data.node?.template[name].refresh_button && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
button_text={
data.node?.template[name].refresh_button_text ?? "Refresh"
}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>

View file

@ -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}
>
<span className="mr-1">{button_text}</span>
<IconComponent
name={isLoading ? "Loader2" : "RefreshCcw"}
className={iconClassName}

View file

@ -55,7 +55,9 @@ export type TemplateVariableType = {
input_types?: Array<string>;
display_name?: string;
name?: string;
refresh?: boolean;
real_time_refresh?: boolean;
refresh_button?: boolean;
refresh_button_text?: string;
[key: string]: any;
};
export type sendAllProps = {