diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 58224d9ef..18e241133 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -14,6 +14,7 @@ from langflow.api.v1.schemas import ( ) from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.directory_reader import DirectoryReader +from langflow.interface.types import build_langchain_template_custom_component, create_and_validate_component from langflow.processing.process import process_graph_cached, process_tweaks from langflow.services.auth.utils import api_key_security, get_current_active_user from langflow.services.cache.utils import save_uploaded_file @@ -208,9 +209,7 @@ async def custom_component( raw_code: CustomComponentCode, user: User = Depends(get_current_active_user), ): - from langflow.interface.types import ( - build_langchain_template_custom_component, - ) + component = create_and_validate_component(raw_code.code) extractor = CustomComponent(code=raw_code.code) extractor.validate() @@ -235,3 +234,15 @@ async def reload_custom_component(path: str): return build_langchain_template_custom_component(extractor, user_id=user.id) except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) + + +@router.post("/custom_component/update", status_code=HTTPStatus.OK) +async def custom_component_update( + raw_code: CustomComponentCode, + user: User = Depends(get_current_active_user), +): + component = create_and_validate_component(raw_code.code) + + component_node = build_langchain_template_custom_component(component, user_id=user.id, update_field=raw_code.field) + # Update the field + return component_node diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index f1f002184..cb22bcd92 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -2,13 +2,14 @@ from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Union from uuid import UUID -from langflow.services.database.models.api_key.model import ApiKeyRead -from langflow.services.database.models.flow import FlowCreate, FlowRead -from langflow.services.database.models.user import UserRead -from langflow.services.database.models.base import orjson_dumps from pydantic import BaseModel, Field, field_validator +from langflow.services.database.models.api_key.model import ApiKeyRead +from langflow.services.database.models.base import orjson_dumps +from langflow.services.database.models.flow import FlowCreate, FlowRead +from langflow.services.database.models.user import UserRead + class BuildStatus(Enum): """Status of the build.""" @@ -156,6 +157,7 @@ class StreamData(BaseModel): class CustomComponentCode(BaseModel): code: str + field: Optional[str] = None class CustomComponentResponseError(BaseModel): diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 94c924033..a82f7f213 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -3,7 +3,7 @@ import contextlib import re import traceback import warnings -from typing import Any, List, Optional, Union +from typing import Any, Dict, List, Optional, Union from uuid import UUID from cachetools import LRUCache, cached @@ -201,7 +201,9 @@ def update_attributes(frontend_node, template_config): frontend_node[attribute] = template_config[attribute] -def build_field_config(custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None): +def build_field_config( + custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None, update_field=None +): """Build the field configuration for a custom component""" try: @@ -222,7 +224,22 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio ) from exc try: - return custom_class(user_id=user_id).build_config() + build_config: Dict = custom_class(user_id=user_id).build_config() + + if update_field is not None: + try: + field_dict = build_config.get(update_field, {}) + update_field_dict(field_dict) + build_config[update_field] = field_dict + except Exception as exc: + logger.error(f"Error while getting build_config: {str(exc)}") + else: + for field_name, field_dict in build_config.items(): + update_field_dict(field_dict) + build_config[field_name] = field_dict + + return build_config + except Exception as exc: logger.error(f"Error while building field config: {str(exc)}") raise HTTPException( @@ -234,6 +251,17 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio ) from exc +def update_field_dict(field_dict): + """Update the field dictionary by calling options() or value() if they are callable""" + if "options" in field_dict and callable(field_dict["options"]): + field_dict["options"] = field_dict["options"]() + # Also update the "refresh" key + field_dict["refresh"] = True + elif "value" in field_dict and callable(field_dict["value"]): + field_dict["value"] = field_dict["value"]() + field_dict["refresh"] = True + + def add_extra_fields(frontend_node, field_config, function_args): """Add extra fields to the frontend node""" if not function_args: @@ -314,7 +342,9 @@ def add_output_types(frontend_node, return_types: List[str]): def build_langchain_template_custom_component( - custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None + custom_component: CustomComponent, + user_id: Optional[Union[str, UUID]] = None, + update_field: Optional[str] = None, ): """Build a custom component template for the langchain""" try: @@ -328,7 +358,7 @@ def build_langchain_template_custom_component( update_attributes(frontend_node, template_config) logger.debug("Updated attributes") - field_config = build_field_config(custom_component, user_id=user_id) + field_config = build_field_config(custom_component, user_id=user_id, update_field=update_field) logger.debug("Built field config") entrypoint_args = custom_component.get_function_entrypoint_args @@ -514,3 +544,9 @@ def merge_nested_dicts(dict1, dict2): else: dict1[key] = value return dict1 + + +def create_and_validate_component(code: str) -> CustomComponent: + component = CustomComponent(code=code) + component.is_check_valid() + return component diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index e596f21be..120e449b7 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -59,6 +59,9 @@ class TemplateFieldCreator(BaseModel, ABC): info: Optional[str] = "" """Additional information about the field to be shown in the tooltip. Defaults to an empty string.""" + refresh: bool = False + """Specifies if the field should be refreshed. Defaults to False.""" + def to_dict(self): result = self.model_dump() # Remove key if it is None diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 4a98a7737..adbdc15fe 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -26,9 +26,12 @@ import { LANGFLOW_SUPPORTED_TYPES, TOOLTIP_EMPTY, } from "../../../../constants/constants"; +import { alertContext } from "../../../../contexts/alertContext"; import { FlowsContext } from "../../../../contexts/flowsContext"; import { typesContext } from "../../../../contexts/typesContext"; +import { postCustomComponentUpdate } from "../../../../controllers/API"; import { ParameterComponentType } from "../../../../types/components"; +import { NodeDataType } from "../../../../types/flow"; import { convertObjToArray, convertValuesToNumbers, @@ -63,6 +66,7 @@ export default function ParameterComponent({ const ref = useRef(null); const refHtml = useRef(null); const infoHtml = useRef(null); + const { setErrorData } = useContext(alertContext); const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); const { setTabsState, tabId, flows } = useContext(FlowsContext); @@ -95,6 +99,25 @@ export default function ParameterComponent({ const { data: myData } = useContext(typesContext); + const handleUpdateValues = async (name: string, data: NodeDataType) => { + const code = data.node?.template["code"]?.value; + if (!code) { + console.error("Code not found in the template"); + return; + } + + try { + const res = await postCustomComponentUpdate(code, name); + if (res.status === 200 && data.node?.template) { + let clone = cloneDeep(data); + clone.node!.template[name] = res.data.template[name]; + setData(clone); + } + } catch (err) { + setErrorData(err as { title: string; list?: Array }); + } + }; + const handleOnNewValue = ( newValue: string | string[] | boolean | Object[] ): void => { @@ -392,12 +415,25 @@ export default function ParameterComponent({ ) : left === true && type === "str" && data.node?.template[name].options ? ( -
- + // TODO: Improve CSS +
+
+ +
+ {data.node?.template[name].refresh && ( + + )}
) : left === true && type === "code" ? (
diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index dc5fc014e..37e579428 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -360,6 +360,16 @@ export async function postCustomComponent( return await api.post(`${BASE_URL_API}custom_component`, { code }); } +export async function postCustomComponentUpdate( + code: string, + field: string +): Promise> { + return await api.post(`${BASE_URL_API}custom_component/update`, { + code, + field, + }); +} + export async function onLogin(user: LoginType) { try { const response = await api.post( diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index da37cc46c..70498351c 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -67,6 +67,7 @@ import { Pencil, Plus, Redo, + RefreshCcw, Rocket, Save, SaveAll, @@ -354,4 +355,5 @@ export const nodeIconsLucide: iconsType = { Heart, Link, ToyBrick, + RefreshCcw, };