Merge branch 'dynamic_field' into feature/store
This commit is contained in:
commit
42bd345162
7 changed files with 118 additions and 18 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue