Add update_build_config method that updates any part of the template

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-03-05 16:29:03 -03:00
commit 240b1900b9
11 changed files with 257 additions and 113 deletions

View file

@ -83,15 +83,13 @@ 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_. | | _`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]`_. | | _`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. | | _`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. |
<Admonition type="info" label="Tip"> <Admonition type="info" label="Tip">
Keys _`options`_ and _`value`_ can receive a method or function that returns a list of strings or a string, respectively. This is useful when you want to dynamically generate the options or the default value of a field. A refresh button will appear next to the field in the component, allowing the user to update the options or the default value.
</Admonition>
By using the _`update_build_config`_ method, you can update the _`build_config`_ in whatever way you want based on the value of the field or not.
</Admonition>
- The CustomComponent class also provides helpful methods for specific tasks (e.g., to load and use other flows from the Langflow platform): - The CustomComponent class also provides helpful methods for specific tasks (e.g., to load and use other flows from the Langflow platform):

View file

@ -306,7 +306,10 @@ async def custom_component_update(
component = CustomComponent(code=raw_code.code) component = CustomComponent(code=raw_code.code)
component_node = build_custom_component_template( component_node = build_custom_component_template(
component, user_id=user.id, update_field=raw_code.field component,
user_id=user.id,
update_field=raw_code.field,
update_field_value=raw_code.field_value,
) )
# Update the field # Update the field
return component_node return component_node

View file

@ -166,6 +166,7 @@ class StreamData(BaseModel):
class CustomComponentCode(BaseModel): class CustomComponentCode(BaseModel):
code: str code: str
field: Optional[str] = None field: Optional[str] = None
field_value: Optional[str] = None
frontend_node: Optional[dict] = None frontend_node: Optional[dict] = None

View file

@ -1,4 +1,6 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from loguru import logger
from langflow.api.v1.base import ( from langflow.api.v1.base import (
Code, Code,
CodeValidationResponse, CodeValidationResponse,
@ -6,9 +8,8 @@ from langflow.api.v1.base import (
ValidatePromptRequest, ValidatePromptRequest,
validate_prompt, validate_prompt,
) )
from langflow.template.field.base import TemplateField from langflow.template.field.prompt import DefaultPromptField
from langflow.utils.validate import PROMPT_INPUT_TYPES, validate_code from langflow.utils.validate import validate_code
from loguru import logger
# build router # build router
router = APIRouter(prefix="/validate", tags=["Validate"]) router = APIRouter(prefix="/validate", tags=["Validate"])
@ -40,7 +41,9 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
add_new_variables_to_template(input_variables, prompt_request) add_new_variables_to_template(input_variables, prompt_request)
remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request) remove_old_variables_from_template(
old_custom_fields, input_variables, prompt_request
)
update_input_variables_field(input_variables, prompt_request) update_input_variables_field(input_variables, prompt_request)
@ -55,12 +58,19 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
def get_old_custom_fields(prompt_request): def get_old_custom_fields(prompt_request):
try: try:
if len(prompt_request.frontend_node.custom_fields) == 1 and prompt_request.name == "": if (
len(prompt_request.frontend_node.custom_fields) == 1
and prompt_request.name == ""
):
# If there is only one custom field and the name is empty string # If there is only one custom field and the name is empty string
# then we are dealing with the first prompt request after the node was created # then we are dealing with the first prompt request after the node was created
prompt_request.name = list(prompt_request.frontend_node.custom_fields.keys())[0] prompt_request.name = list(
prompt_request.frontend_node.custom_fields.keys()
)[0]
old_custom_fields = prompt_request.frontend_node.custom_fields[prompt_request.name] old_custom_fields = prompt_request.frontend_node.custom_fields[
prompt_request.name
]
if old_custom_fields is None: if old_custom_fields is None:
old_custom_fields = [] old_custom_fields = []
@ -74,38 +84,43 @@ def get_old_custom_fields(prompt_request):
def add_new_variables_to_template(input_variables, prompt_request): def add_new_variables_to_template(input_variables, prompt_request):
for variable in input_variables: for variable in input_variables:
try: try:
template_field = TemplateField( template_field = DefaultPromptField(name=variable, display_name=variable)
name=variable,
display_name=variable,
field_type="str",
show=True,
advanced=False,
multiline=True,
input_types=PROMPT_INPUT_TYPES,
value="", # Set the value to empty string
)
if variable in prompt_request.frontend_node.template: if variable in prompt_request.frontend_node.template:
# Set the new field with the old value # Set the new field with the old value
template_field.value = prompt_request.frontend_node.template[variable]["value"] template_field.value = prompt_request.frontend_node.template[variable][
"value"
]
prompt_request.frontend_node.template[variable] = template_field.to_dict() prompt_request.frontend_node.template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending # Check if variable is not already in the list before appending
if variable not in prompt_request.frontend_node.custom_fields[prompt_request.name]: if (
prompt_request.frontend_node.custom_fields[prompt_request.name].append(variable) variable
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
):
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
variable
)
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request): def remove_old_variables_from_template(
old_custom_fields, input_variables, prompt_request
):
for variable in old_custom_fields: for variable in old_custom_fields:
if variable not in input_variables: if variable not in input_variables:
try: try:
# Remove the variable from custom_fields associated with the given name # Remove the variable from custom_fields associated with the given name
if variable in prompt_request.frontend_node.custom_fields[prompt_request.name]: if (
prompt_request.frontend_node.custom_fields[prompt_request.name].remove(variable) variable
in prompt_request.frontend_node.custom_fields[prompt_request.name]
):
prompt_request.frontend_node.custom_fields[
prompt_request.name
].remove(variable)
# Remove the variable from the template # Remove the variable from the template
prompt_request.frontend_node.template.pop(variable, None) prompt_request.frontend_node.template.pop(variable, None)
@ -117,4 +132,6 @@ def remove_old_variables_from_template(old_custom_fields, input_variables, promp
def update_input_variables_field(input_variables, prompt_request): def update_input_variables_field(input_variables, prompt_request):
if "input_variables" in prompt_request.frontend_node.template: if "input_variables" in prompt_request.frontend_node.template:
prompt_request.frontend_node.template["input_variables"]["value"] = input_variables prompt_request.frontend_node.template["input_variables"][
"value"
] = input_variables

View file

@ -95,7 +95,9 @@ class CodeParser:
elif isinstance(node, ast.ImportFrom): elif isinstance(node, ast.ImportFrom):
for alias in node.names: for alias in node.names:
if alias.asname: if alias.asname:
self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}")) self.data["imports"].append(
(node.module, f"{alias.name} as {alias.asname}")
)
else: else:
self.data["imports"].append((node.module, alias.name)) self.data["imports"].append((node.module, alias.name))
@ -144,7 +146,9 @@ class CodeParser:
return_type = None return_type = None
if node.returns: if node.returns:
return_type_str = ast.unparse(node.returns) return_type_str = ast.unparse(node.returns)
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"])) eval_env = self.construct_eval_env(
return_type_str, tuple(self.data["imports"])
)
try: try:
return_type = eval(return_type_str, eval_env) return_type = eval(return_type_str, eval_env)
@ -174,7 +178,7 @@ class CodeParser:
args += self.parse_keyword_args(node) args += self.parse_keyword_args(node)
# Commented out because we don't want kwargs # Commented out because we don't want kwargs
# showing up as fields in the frontend # showing up as fields in the frontend
# args += self.parse_kwargs(node) args += self.parse_kwargs(node)
return args return args
@ -186,14 +190,22 @@ class CodeParser:
num_defaults = len(node.args.defaults) num_defaults = len(node.args.defaults)
num_missing_defaults = num_args - num_defaults num_missing_defaults = num_args - num_defaults
missing_defaults = [None] * num_missing_defaults missing_defaults = [None] * num_missing_defaults
default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults] default_values = [
ast.unparse(default).strip("'") if default else None
for default in node.args.defaults
]
# Now check all default values to see if there # Now check all default values to see if there
# are any "None" values in the middle # are any "None" values in the middle
default_values = [None if value == "None" else value for value in default_values] default_values = [
None if value == "None" else value for value in default_values
]
defaults = missing_defaults + default_values defaults = missing_defaults + default_values
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)] args = [
self.parse_arg(arg, default)
for arg, default in zip(node.args.args, defaults)
]
return args return args
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
@ -211,11 +223,17 @@ class CodeParser:
""" """
Parses the keyword-only arguments of a function or method node. Parses the keyword-only arguments of a function or method node.
""" """
kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [ kw_defaults = [None] * (
ast.unparse(default) if default else None for default in node.args.kw_defaults len(node.args.kwonlyargs) - len(node.args.kw_defaults)
) + [
ast.unparse(default) if default else None
for default in node.args.kw_defaults
] ]
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)] args = [
self.parse_arg(arg, default)
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
]
return args return args
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
@ -319,7 +337,9 @@ class CodeParser:
Extracts global variables from the code. Extracts global variables from the code.
""" """
global_var = { global_var = {
"targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets], "targets": [
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
],
"value": ast.unparse(node.value), "value": ast.unparse(node.value),
} }
self.data["global_vars"].append(global_var) self.data["global_vars"].append(global_var)

View file

@ -1,15 +1,7 @@
import operator import operator
from pathlib import Path from pathlib import Path
from typing import ( from typing import (TYPE_CHECKING, Any, Callable, ClassVar, List, Optional,
TYPE_CHECKING, Sequence, Union)
Any,
Callable,
ClassVar,
List,
Optional,
Sequence,
Union,
)
from uuid import UUID from uuid import UUID
import yaml import yaml
@ -20,17 +12,13 @@ from sqlmodel import select
from langflow.interface.custom.code_parser.utils import ( from langflow.interface.custom.code_parser.utils import (
extract_inner_type_from_generic_alias, extract_inner_type_from_generic_alias,
extract_union_types_from_generic_alias, extract_union_types_from_generic_alias)
)
from langflow.interface.custom.custom_component.component import Component from langflow.interface.custom.custom_component.component import Component
from langflow.schema import Record from langflow.schema import Record
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.services.database.utils import session_getter from langflow.services.database.utils import session_getter
from langflow.services.deps import ( from langflow.services.deps import (get_credential_service, get_db_service,
get_credential_service, get_storage_service)
get_db_service,
get_storage_service,
)
from langflow.services.storage.service import StorageService from langflow.services.storage.service import StorageService
from langflow.utils import validate from langflow.utils import validate
@ -138,6 +126,10 @@ class CustomComponent(Component):
def build_config(self): def build_config(self):
return self.field_config return self.field_config
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
build_config[field_name] = field_value
return build_config
@property @property
def tree(self): def tree(self):
return self.get_code_tree(self.code or "") return self.get_code_tree(self.code or "")

View file

@ -8,6 +8,7 @@ from uuid import UUID
from fastapi import HTTPException from fastapi import HTTPException
from loguru import logger from loguru import logger
from pydantic import BaseModel
from langflow.field_typing.range_spec import RangeSpec from langflow.field_typing.range_spec import RangeSpec
from langflow.interface.custom.attributes import ATTR_FUNC_MAPPING from langflow.interface.custom.attributes import ATTR_FUNC_MAPPING
@ -27,6 +28,10 @@ from langflow.utils import validate
from langflow.utils.util import get_base_classes from langflow.utils.util import get_base_classes
class UpdateBuildConfigError(Exception):
pass
def add_output_types( def add_output_types(
frontend_node: CustomComponentFrontendNode, return_types: List[str] frontend_node: CustomComponentFrontendNode, return_types: List[str]
): ):
@ -190,15 +195,23 @@ def add_extra_fields(frontend_node, field_config, function_args):
"""Add extra fields to the frontend node""" """Add extra fields to the frontend node"""
if not function_args: if not function_args:
return return
_field_config = field_config.copy()
function_args_names = [arg["name"] for arg in function_args]
# If kwargs is in the function_args and not all field_config keys are in function_args
# then we need to add the extra fields
for extra_field in function_args: for extra_field in function_args:
if "name" not in extra_field or extra_field["name"] == "self": if "name" not in extra_field or extra_field["name"] in [
"self",
"kwargs",
"args",
]:
continue continue
field_name, field_type, field_value, field_required = get_field_properties( field_name, field_type, field_value, field_required = get_field_properties(
extra_field extra_field
) )
config = field_config.get(field_name, {}) config = _field_config.pop(field_name, {})
frontend_node = add_new_custom_field( frontend_node = add_new_custom_field(
frontend_node, frontend_node,
field_name, field_name,
@ -207,6 +220,23 @@ def add_extra_fields(frontend_node, field_config, function_args):
field_required, field_required,
config, config,
) )
if "kwargs" in function_args_names and not all(
key in function_args_names for key in field_config.keys()
):
for field_name, field_config in _field_config.copy().items():
config = _field_config.get(field_name, {})
config = config.model_dump() if isinstance(config, BaseModel) else config
field_name, field_type, field_value, field_required = get_field_properties(
extra_field=config
)
frontend_node = add_new_custom_field(
frontend_node,
field_name,
field_type,
field_value,
field_required,
config,
)
def get_field_dict(field: Union[TemplateField, dict]): def get_field_dict(field: Union[TemplateField, dict]):
@ -220,6 +250,7 @@ def run_build_config(
custom_component: CustomComponent, custom_component: CustomComponent,
user_id: Optional[Union[str, UUID]] = None, user_id: Optional[Union[str, UUID]] = None,
update_field=None, update_field=None,
update_field_value=None,
): ):
"""Build the field configuration for a custom component""" """Build the field configuration for a custom component"""
@ -246,33 +277,48 @@ def run_build_config(
custom_instance = custom_class(user_id=user_id) custom_instance = custom_class(user_id=user_id)
build_config: Dict = custom_instance.build_config() build_config: Dict = custom_instance.build_config()
for field_name, field in build_config.items(): for field_name, field in build_config.copy().items():
# Allow user to build TemplateField as well # Allow user to build TemplateField as well
# as a dict with the same keys as TemplateField # as a dict with the same keys as TemplateField
field_dict = get_field_dict(field) field_dict = get_field_dict(field)
# This has to be done to set refresh if options or value are callable # This has to be done to set refresh if options or value are callable
update_field_dict(field_dict)
if update_field is not None and field_name != update_field: if update_field is not None and field_name != update_field:
build_config = update_field_dict(
custom_component_instance=custom_instance,
field_dict=field_dict,
build_config=build_config,
call=False,
)
continue continue
try: try:
update_field_dict(field_dict, call=True) build_config = update_field_dict(
custom_component_instance=custom_instance,
field_dict=field_dict,
build_config=build_config,
update_field=update_field,
update_field_value=update_field_value,
call=True,
)
build_config[field_name] = field_dict build_config[field_name] = field_dict
except Exception as exc: except Exception as exc:
logger.error(f"Error while getting build_config: {str(exc)}") logger.error(f"Error while getting build_config: {str(exc)}")
if isinstance(exc, UpdateBuildConfigError):
message = str(exc)
else:
message = f"Error while getting build_config: {str(exc)}"
raise HTTPException(
status_code=400,
detail={
"error": message,
"traceback": traceback.format_exc(),
},
) from exc
return build_config, custom_instance return build_config, custom_instance
except Exception as exc: except Exception as exc:
logger.error(f"Error while building field config: {str(exc)}") logger.error(f"Error while building field config: {str(exc)}")
raise HTTPException( raise exc
status_code=400,
detail={
"error": (
"Invalid type convertion. Please check your code and try again."
),
"traceback": traceback.format_exc(),
},
) from exc
def sanitize_template_config(template_config): def sanitize_template_config(template_config):
@ -317,13 +363,17 @@ def build_custom_component_template(
custom_component: CustomComponent, custom_component: CustomComponent,
user_id: Optional[Union[str, UUID]] = None, user_id: Optional[Union[str, UUID]] = None,
update_field: Optional[str] = None, update_field: Optional[str] = None,
update_field_value: Optional[str] = None,
) -> Optional[Dict[str, Any]]: ) -> Optional[Dict[str, Any]]:
"""Build a custom component template for the langchain""" """Build a custom component template for the langchain"""
try: try:
frontend_node = build_frontend_node(custom_component.template_config) frontend_node = build_frontend_node(custom_component.template_config)
field_config, custom_instance = run_build_config( field_config, custom_instance = run_build_config(
custom_component, user_id=user_id, update_field=update_field custom_component,
user_id=user_id,
update_field=update_field,
update_field_value=update_field_value,
) )
entrypoint_args = custom_component.get_function_entrypoint_args entrypoint_args = custom_component.get_function_entrypoint_args
@ -402,22 +452,32 @@ def build_custom_components(settings_service):
return custom_components_from_file return custom_components_from_file
def update_field_dict(field_dict, call=False): def update_field_dict(
custom_component_instance: "CustomComponent",
field_dict: Dict,
build_config: Dict,
update_field: Optional[str] = None,
update_field_value: Optional[Any] = None,
call: bool = False,
):
"""Update the field dictionary by calling options() or value() if they are callable""" """Update the field dictionary by calling options() or value() if they are callable"""
if "options" in field_dict and callable(field_dict["options"]): if "refresh" in field_dict:
if call: if call:
field_dict["options"] = field_dict["options"]() try:
# Also update the "refresh" key custom_component_instance.update_build_config(
field_dict["refresh"] = True build_config, update_field, update_field_value
)
if "value" in field_dict and callable(field_dict["value"]): except Exception as exc:
if call: logger.error(f"Error while running update_build_config: {str(exc)}")
field_dict["value"] = field_dict["value"]() raise UpdateBuildConfigError(
f"Error while running update_build_config: {str(exc)}"
) from exc
field_dict["refresh"] = True field_dict["refresh"] = True
# Let's check if "range_spec" is a RangeSpec object # Let's check if "range_spec" is a RangeSpec object
if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec): if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec):
field_dict["rangeSpec"] = field_dict["rangeSpec"].model_dump() field_dict["rangeSpec"] = field_dict["rangeSpec"].model_dump()
return build_config
def sanitize_field_config(field_config: Dict): def sanitize_field_config(field_config: Dict):

View file

@ -0,0 +1,14 @@
from typing import Optional
from langflow.template.field.base import TemplateField
class DefaultPromptField(TemplateField):
name: str
display_name: Optional[str] = None
field_type: str = "str"
advanced: bool = False
multiline: bool = True
input_types: list[str] = ["Document", "BaseOutputParser", "Text", "Record"]
value: str = "" # Set the value to empty string

View file

@ -6,8 +6,6 @@ from typing import Dict, List, Optional, Union
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
PROMPT_INPUT_TYPES = ["Document", "BaseOutputParser", "Text", "Record"]
def add_type_ignores(): def add_type_ignores():
if not hasattr(ast, "TypeIgnore"): if not hasattr(ast, "TypeIgnore"):
@ -45,7 +43,9 @@ def validate_code(code):
# Evaluate the function definition # Evaluate the function definition
for node in tree.body: for node in tree.body:
if isinstance(node, ast.FunctionDef): if isinstance(node, ast.FunctionDef):
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec") code_obj = compile(
ast.Module(body=[node], type_ignores=[]), "<string>", "exec"
)
try: try:
exec(code_obj) exec(code_obj)
except Exception as e: except Exception as e:
@ -89,15 +89,23 @@ def execute_function(code, function_name, *args, **kwargs):
exec_globals, exec_globals,
locals(), locals(),
) )
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) exec_globals[alias.asname or alias.name] = importlib.import_module(
alias.name
)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e raise ModuleNotFoundError(
f"Module {alias.name} not found. Please install it and try again."
) from e
function_code = next( function_code = next(
node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name node
for node in module.body
if isinstance(node, ast.FunctionDef) and node.name == function_name
) )
function_code.parent = None function_code.parent = None
code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec") code_obj = compile(
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
)
try: try:
exec(code_obj, exec_globals, locals()) exec(code_obj, exec_globals, locals())
except Exception as exc: except Exception as exc:
@ -124,15 +132,23 @@ def create_function(code, function_name):
if isinstance(node, ast.Import): if isinstance(node, ast.Import):
for alias in node.names: for alias in node.names:
try: try:
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) exec_globals[alias.asname or alias.name] = importlib.import_module(
alias.name
)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e raise ModuleNotFoundError(
f"Module {alias.name} not found. Please install it and try again."
) from e
function_code = next( function_code = next(
node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name node
for node in module.body
if isinstance(node, ast.FunctionDef) and node.name == function_name
) )
function_code.parent = None function_code.parent = None
code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec") code_obj = compile(
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
)
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
exec(code_obj, exec_globals, locals()) exec(code_obj, exec_globals, locals())
exec_globals[function_name] = locals()[function_name] exec_globals[function_name] = locals()[function_name]
@ -194,9 +210,13 @@ def prepare_global_scope(code, module):
if isinstance(node, ast.Import): if isinstance(node, ast.Import):
for alias in node.names: for alias in node.names:
try: try:
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) exec_globals[alias.asname or alias.name] = importlib.import_module(
alias.name
)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e raise ModuleNotFoundError(
f"Module {alias.name} not found. Please install it and try again."
) from e
elif isinstance(node, ast.ImportFrom) and node.module is not None: elif isinstance(node, ast.ImportFrom) and node.module is not None:
try: try:
imported_module = importlib.import_module(node.module) imported_module = importlib.import_module(node.module)
@ -217,7 +237,11 @@ def extract_class_code(module, class_name):
:param class_name: Name of the class to extract :param class_name: Name of the class to extract
:return: AST node of the specified class :return: AST node of the specified class
""" """
class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name) class_code = next(
node
for node in module.body
if isinstance(node, ast.ClassDef) and node.name == class_name
)
class_code.parent = None class_code.parent = None
return class_code return class_code
@ -230,7 +254,9 @@ def compile_class_code(class_code):
:param class_code: AST node of the class :param class_code: AST node of the class
:return: Compiled code object of the class :return: Compiled code object of the class
""" """
code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec") code_obj = compile(
ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec"
)
return code_obj return code_obj
@ -274,7 +300,9 @@ def get_default_imports(code_string):
langflow_imports = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys()) langflow_imports = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
necessary_imports = find_names_in_code(code_string, langflow_imports) necessary_imports = find_names_in_code(code_string, langflow_imports)
langflow_module = importlib.import_module("langflow.field_typing") langflow_module = importlib.import_module("langflow.field_typing")
default_imports.update({name: getattr(langflow_module, name) for name in necessary_imports}) default_imports.update(
{name: getattr(langflow_module, name) for name in necessary_imports}
)
return default_imports return default_imports

View file

@ -98,24 +98,33 @@ export default function ParameterComponent({
return; return;
} }
try { await postCustomComponentUpdate(
const res = await postCustomComponentUpdate(code, name); code,
if (res.status === 200 && data.node?.template) { name,
setNode(data.id, (oldNode) => { data.node?.template[name]?.value
let newNode = cloneDeep(oldNode); )
.then((res) => {
if (res.status === 200 && data.node?.template) {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = { newNode.data = {
...newNode.data, ...newNode.data,
}; };
newNode.data.node.template[name] = res.data.template[name]; newNode.data.node.template = res.data.template;
return newNode; return newNode;
});
}
})
.catch((error) => {
console.error("Error occurred while updating the node:", error);
setErrorData({
title: "Error while updating the Component",
list: [error.response.data.detail.error ?? "Unknown error"],
}); });
} });
} catch (err) {
setErrorData(err as { title: string; list?: Array<string> });
}
renderTooltips(); renderTooltips();
if (delayAnimation) { if (delayAnimation) {

View file

@ -369,11 +369,13 @@ export async function postCustomComponent(
export async function postCustomComponentUpdate( export async function postCustomComponentUpdate(
code: string, code: string,
field: string field: string,
field_value: string
): Promise<AxiosResponse<APIClassType>> { ): Promise<AxiosResponse<APIClassType>> {
return await api.post(`${BASE_URL_API}custom_component/update`, { return await api.post(`${BASE_URL_API}custom_component/update`, {
code, code,
field, field,
field_value,
}); });
} }