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.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

View file

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

View file

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

View file

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

View file

@ -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<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
const infoHtml = useRef<HTMLDivElement & ReactNode>(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<string> });
}
};
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 ? (
<div className="mt-2 w-full">
<Dropdown
options={data.node.template[name].options}
onSelect={handleOnNewValue}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
// TODO: Improve CSS
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
<Dropdown
options={data.node.template[name].options}
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>
) : left === true && type === "code" ? (
<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 });
}
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) {
try {
const response = await api.post(

View file

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