Merge branch 'dynamic_field' into feature/store

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-11-24 12:51:41 -03:00
commit 42bd345162
7 changed files with 118 additions and 18 deletions

View file

@ -14,6 +14,7 @@ from langflow.api.v1.schemas import (
) )
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import DirectoryReader 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.processing.process import process_graph_cached, process_tweaks
from langflow.services.auth.utils import api_key_security, get_current_active_user from langflow.services.auth.utils import api_key_security, get_current_active_user
from langflow.services.cache.utils import save_uploaded_file from langflow.services.cache.utils import save_uploaded_file
@ -208,9 +209,7 @@ async def custom_component(
raw_code: CustomComponentCode, raw_code: CustomComponentCode,
user: User = Depends(get_current_active_user), user: User = Depends(get_current_active_user),
): ):
from langflow.interface.types import ( component = create_and_validate_component(raw_code.code)
build_langchain_template_custom_component,
)
extractor = CustomComponent(code=raw_code.code) extractor = CustomComponent(code=raw_code.code)
extractor.validate() extractor.validate()
@ -235,3 +234,15 @@ async def reload_custom_component(path: str):
return build_langchain_template_custom_component(extractor, user_id=user.id) return build_langchain_template_custom_component(extractor, user_id=user.id)
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=400, detail=str(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

View file

@ -2,13 +2,14 @@ from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from uuid import UUID 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 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): class BuildStatus(Enum):
"""Status of the build.""" """Status of the build."""
@ -156,6 +157,7 @@ class StreamData(BaseModel):
class CustomComponentCode(BaseModel): class CustomComponentCode(BaseModel):
code: str code: str
field: Optional[str] = None
class CustomComponentResponseError(BaseModel): class CustomComponentResponseError(BaseModel):

View file

@ -3,7 +3,7 @@ import contextlib
import re import re
import traceback import traceback
import warnings import warnings
from typing import Any, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from uuid import UUID from uuid import UUID
from cachetools import LRUCache, cached from cachetools import LRUCache, cached
@ -201,7 +201,9 @@ def update_attributes(frontend_node, template_config):
frontend_node[attribute] = template_config[attribute] 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""" """Build the field configuration for a custom component"""
try: try:
@ -222,7 +224,22 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio
) from exc ) from exc
try: 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: 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 HTTPException(
@ -234,6 +251,17 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio
) from exc ) 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): 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:
@ -314,7 +342,9 @@ def add_output_types(frontend_node, return_types: List[str]):
def build_langchain_template_custom_component( 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""" """Build a custom component template for the langchain"""
try: try:
@ -328,7 +358,7 @@ def build_langchain_template_custom_component(
update_attributes(frontend_node, template_config) update_attributes(frontend_node, template_config)
logger.debug("Updated attributes") 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") logger.debug("Built field config")
entrypoint_args = custom_component.get_function_entrypoint_args entrypoint_args = custom_component.get_function_entrypoint_args
@ -514,3 +544,9 @@ def merge_nested_dicts(dict1, dict2):
else: else:
dict1[key] = value dict1[key] = value
return dict1 return dict1
def create_and_validate_component(code: str) -> CustomComponent:
component = CustomComponent(code=code)
component.is_check_valid()
return component

View file

@ -59,6 +59,9 @@ class TemplateFieldCreator(BaseModel, ABC):
info: Optional[str] = "" info: Optional[str] = ""
"""Additional information about the field to be shown in the tooltip. Defaults to an empty string.""" """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): def to_dict(self):
result = self.model_dump() result = self.model_dump()
# Remove key if it is None # Remove key if it is None

View file

@ -26,9 +26,12 @@ import {
LANGFLOW_SUPPORTED_TYPES, LANGFLOW_SUPPORTED_TYPES,
TOOLTIP_EMPTY, TOOLTIP_EMPTY,
} from "../../../../constants/constants"; } from "../../../../constants/constants";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext"; import { FlowsContext } from "../../../../contexts/flowsContext";
import { typesContext } from "../../../../contexts/typesContext"; import { typesContext } from "../../../../contexts/typesContext";
import { postCustomComponentUpdate } from "../../../../controllers/API";
import { ParameterComponentType } from "../../../../types/components"; import { ParameterComponentType } from "../../../../types/components";
import { NodeDataType } from "../../../../types/flow";
import { import {
convertObjToArray, convertObjToArray,
convertValuesToNumbers, convertValuesToNumbers,
@ -63,6 +66,7 @@ export default function ParameterComponent({
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null); const refHtml = useRef<HTMLDivElement & ReactNode>(null);
const infoHtml = useRef<HTMLDivElement & ReactNode>(null); const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
const { setErrorData } = useContext(alertContext);
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0); const [position, setPosition] = useState(0);
const { setTabsState, tabId, flows } = useContext(FlowsContext); const { setTabsState, tabId, flows } = useContext(FlowsContext);
@ -95,6 +99,25 @@ export default function ParameterComponent({
const { data: myData } = useContext(typesContext); 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<string> });
}
};
const handleOnNewValue = ( const handleOnNewValue = (
newValue: string | string[] | boolean | Object[] newValue: string | string[] | boolean | Object[]
): void => { ): void => {
@ -392,12 +415,25 @@ export default function ParameterComponent({
) : left === true && ) : left === true &&
type === "str" && type === "str" &&
data.node?.template[name].options ? ( data.node?.template[name].options ? (
<div className="mt-2 w-full"> // TODO: Improve CSS
<Dropdown <div className="mt-2 flex w-full items-center">
options={data.node.template[name].options} <div className="w-5/6 flex-grow">
onSelect={handleOnNewValue} <Dropdown
value={data.node.template[name].value ?? "Choose an option"} options={data.node.template[name].options}
></Dropdown> onSelect={handleOnNewValue}
value={data.node.template[name].value ?? "Choose an option"}
/>
</div>
{data.node?.template[name].refresh && (
<button
className="extra-side-bar-buttons ml-2 mt-1 w-1/6"
onClick={() => {
handleUpdateValues(name, data);
}}
>
<IconComponent name="RefreshCcw" />
</button>
)}
</div> </div>
) : left === true && type === "code" ? ( ) : left === true && type === "code" ? (
<div className="mt-2 w-full"> <div className="mt-2 w-full">

View file

@ -360,6 +360,16 @@ export async function postCustomComponent(
return await api.post(`${BASE_URL_API}custom_component`, { code }); return await api.post(`${BASE_URL_API}custom_component`, { code });
} }
export async function postCustomComponentUpdate(
code: string,
field: string
): Promise<AxiosResponse<APIClassType>> {
return await api.post(`${BASE_URL_API}custom_component/update`, {
code,
field,
});
}
export async function onLogin(user: LoginType) { export async function onLogin(user: LoginType) {
try { try {
const response = await api.post( const response = await api.post(

View file

@ -67,6 +67,7 @@ import {
Pencil, Pencil,
Plus, Plus,
Redo, Redo,
RefreshCcw,
Rocket, Rocket,
Save, Save,
SaveAll, SaveAll,
@ -354,4 +355,5 @@ export const nodeIconsLucide: iconsType = {
Heart, Heart,
Link, Link,
ToyBrick, ToyBrick,
RefreshCcw,
}; };