fix: flow experimental components (#3093)
* refactor(utils.py): simplify data processing logic in build_data_from_result_data function for better readability and maintainability * feat: set default output type to "chat" in run_flow function * refactor(FlowTool.py): refactor FlowToolComponent class to inherit from LCToolComponent * Fixed Flow as Tool component * Fixed refresh button not appearing at the end * Added way of connecting SecretStrInput to messages * Added real_time_refresh when field has button update too * Refactored SubFlow component * Fixed FlowTool to only output tool and removed async function def * fix: two statements in the same line * [autofix.ci] apply automated fixes * Fixed lint issues * fixed dataobj with wrong name * changed tweaks dict type * Fixed margin appearing in output too * Fixed useless button that made styling worse on handlerendercomponent --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7276f699fc
commit
5076f2014e
7 changed files with 106 additions and 81 deletions
|
|
@ -1,29 +1,25 @@
|
|||
from typing import Any, List, Optional
|
||||
|
||||
from langflow.base.flow_processing.utils import build_data_from_result_data
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.schema import RunOutputs
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.helpers.flow import get_flow_inputs
|
||||
from langflow.schema import Data
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.template.field.base import Input
|
||||
from loguru import logger
|
||||
|
||||
from langflow.base.flow_processing.utils import build_data_from_result_data
|
||||
from langflow.custom import Component
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.helpers.flow import get_flow_inputs
|
||||
from langflow.io import DropdownInput, Output
|
||||
from langflow.schema import Data, dotdict
|
||||
|
||||
class SubFlowComponent(CustomComponent):
|
||||
|
||||
class SubFlowComponent(Component):
|
||||
display_name = "Sub Flow"
|
||||
description = (
|
||||
"Dynamically Generates a Component from a Flow. The output is a list of data with keys 'result' and 'message'."
|
||||
)
|
||||
description = "Generates a Component from a Flow, with all of its inputs, and "
|
||||
name = "SubFlow"
|
||||
beta: bool = True
|
||||
field_order = ["flow_name"]
|
||||
|
||||
def get_flow_names(self) -> List[str]:
|
||||
flow_datas = self.list_flows()
|
||||
return [flow_data.data["name"] for flow_data in flow_datas]
|
||||
flow_data = self.list_flows()
|
||||
return [flow_data.data["name"] for flow_data in flow_data]
|
||||
|
||||
def get_flow(self, flow_name: str) -> Optional[Data]:
|
||||
flow_datas = self.list_flows()
|
||||
|
|
@ -33,12 +29,11 @@ class SubFlowComponent(CustomComponent):
|
|||
return None
|
||||
|
||||
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
|
||||
logger.debug(f"Updating build config with field value {field_value} and field name {field_name}")
|
||||
if field_name == "flow_name":
|
||||
build_config["flow_name"]["options"] = self.get_flow_names()
|
||||
# Clean up the build config
|
||||
|
||||
for key in list(build_config.keys()):
|
||||
if key not in self.field_order + ["code", "_type", "get_final_results_only"]:
|
||||
if key not in [x.name for x in self.inputs] + ["code", "_type", "get_final_results_only"]:
|
||||
del build_config[key]
|
||||
if field_value is not None and field_name == "flow_name":
|
||||
try:
|
||||
|
|
@ -55,62 +50,58 @@ class SubFlowComponent(CustomComponent):
|
|||
|
||||
return build_config
|
||||
|
||||
def add_inputs_to_build_config(self, inputs: List[Vertex], build_config: dotdict):
|
||||
new_fields: list[Input] = []
|
||||
for vertex in inputs:
|
||||
field = Input(
|
||||
display_name=vertex.display_name,
|
||||
name=vertex.id,
|
||||
info=vertex.description,
|
||||
field_type="str",
|
||||
value=None,
|
||||
)
|
||||
new_fields.append(field)
|
||||
logger.debug(new_fields)
|
||||
def add_inputs_to_build_config(self, inputs_vertex: List[Vertex], build_config: dotdict):
|
||||
new_fields: list[dotdict] = []
|
||||
|
||||
for vertex in inputs_vertex:
|
||||
new_vertex_inputs = []
|
||||
field_template = vertex.data["node"]["template"]
|
||||
for inp in field_template.keys():
|
||||
if inp not in ["code", "_type"]:
|
||||
field_template[inp]["display_name"] = (
|
||||
vertex.display_name + " - " + field_template[inp]["display_name"]
|
||||
)
|
||||
field_template[inp]["name"] = vertex.id + "|" + inp
|
||||
new_vertex_inputs.append(field_template[inp])
|
||||
new_fields += new_vertex_inputs
|
||||
for field in new_fields:
|
||||
build_config[field.name] = field.to_dict()
|
||||
build_config[field["name"]] = field
|
||||
return build_config
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
"input_value": {
|
||||
"display_name": "Input Value",
|
||||
"multiline": True,
|
||||
},
|
||||
"flow_name": {
|
||||
"display_name": "Flow Name",
|
||||
"info": "The name of the flow to run.",
|
||||
"options": [],
|
||||
"real_time_refresh": True,
|
||||
"refresh_button": True,
|
||||
},
|
||||
"tweaks": {
|
||||
"display_name": "Tweaks",
|
||||
"info": "Tweaks to apply to the flow.",
|
||||
},
|
||||
"get_final_results_only": {
|
||||
"display_name": "Get Final Results Only",
|
||||
"info": "If False, the output will contain all outputs from the flow.",
|
||||
"advanced": True,
|
||||
},
|
||||
}
|
||||
inputs = [
|
||||
DropdownInput(
|
||||
name="flow_name",
|
||||
display_name="Flow Name",
|
||||
info="The name of the flow to run.",
|
||||
options=[],
|
||||
refresh_button=True,
|
||||
real_time_refresh=True,
|
||||
),
|
||||
]
|
||||
|
||||
async def build(self, flow_name: str, get_final_results_only: bool = True, **kwargs) -> List[Data]:
|
||||
tweaks = {key: {"input_value": value} for key, value in kwargs.items()}
|
||||
run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
|
||||
outputs = [Output(name="flow_outputs", display_name="Flow Outputs", method="generate_results")]
|
||||
|
||||
async def generate_results(self) -> List[Data]:
|
||||
tweaks: dict = {}
|
||||
for field in self._attributes.keys():
|
||||
if field != "flow_name":
|
||||
[node, name] = field.split("|")
|
||||
if node not in tweaks.keys():
|
||||
tweaks[node] = {}
|
||||
tweaks[node][name] = self._attributes[field]
|
||||
|
||||
run_outputs = await self.run_flow(
|
||||
tweaks=tweaks,
|
||||
flow_name=flow_name,
|
||||
flow_name=self.flow_name,
|
||||
output_type="all",
|
||||
)
|
||||
data: list[Data] = []
|
||||
if not run_outputs:
|
||||
return []
|
||||
return data
|
||||
run_output = run_outputs[0]
|
||||
|
||||
data = []
|
||||
if run_output is not None:
|
||||
for output in run_output.outputs:
|
||||
if output:
|
||||
data.extend(build_data_from_result_data(output, get_final_results_only))
|
||||
|
||||
self.status = data
|
||||
logger.debug(data)
|
||||
data.extend(build_data_from_result_data(output))
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -343,7 +343,10 @@ def load_starter_projects() -> list[tuple[Path, dict]]:
|
|||
starter_projects = []
|
||||
folder = Path(__file__).parent / "starter_projects"
|
||||
for file in folder.glob("*.json"):
|
||||
project = orjson.loads(file.read_text(encoding="utf-8"))
|
||||
try:
|
||||
project = orjson.loads(file.read_text(encoding="utf-8"))
|
||||
except orjson.JSONDecodeError as e:
|
||||
raise ValueError(f"Error loading starter project {file}: {e}")
|
||||
starter_projects.append((file, project))
|
||||
logger.info(f"Loaded starter project {file}")
|
||||
return starter_projects
|
||||
|
|
|
|||
|
|
@ -226,9 +226,46 @@ class SecretStrInput(BaseInputMixin, DatabaseLoadMixin):
|
|||
|
||||
field_type: Optional[SerializableFieldTypes] = FieldTypes.PASSWORD
|
||||
password: CoalesceBool = Field(default=True)
|
||||
input_types: list[str] = []
|
||||
input_types: list[str] = ["Message"]
|
||||
load_from_db: CoalesceBool = True
|
||||
|
||||
@field_validator("value")
|
||||
@classmethod
|
||||
def validate_value(cls, v: Any, _info):
|
||||
"""
|
||||
Validates the given value and returns the processed value.
|
||||
|
||||
Args:
|
||||
v (Any): The value to be validated.
|
||||
_info: Additional information about the input.
|
||||
|
||||
Returns:
|
||||
The processed value.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value is not of a valid type or if the input is missing a required key.
|
||||
"""
|
||||
value: str | AsyncIterator | Iterator | None = None
|
||||
if isinstance(v, str):
|
||||
value = v
|
||||
elif isinstance(v, Message):
|
||||
value = v.text
|
||||
elif isinstance(v, Data):
|
||||
if v.text_key in v.data:
|
||||
value = v.data[v.text_key]
|
||||
else:
|
||||
keys = ", ".join(v.data.keys())
|
||||
input_name = _info.data["name"]
|
||||
raise ValueError(
|
||||
f"The input to '{input_name}' must contain the key '{v.text_key}'."
|
||||
f"You can set `text_key` to one of the following keys: {keys} or set the value using another Component."
|
||||
)
|
||||
elif isinstance(v, (AsyncIterator, Iterator)):
|
||||
value = v
|
||||
else:
|
||||
raise ValueError(f"Invalid value type {type(v)}")
|
||||
return value
|
||||
|
||||
|
||||
class IntInput(BaseInputMixin, ListableInputMixin, RangeMixin, MetadataTraceMixin):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { title } from "process";
|
||||
import { Handle, Position } from "reactflow";
|
||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import {
|
||||
isValidConnection,
|
||||
scapedJSONStringfy,
|
||||
|
|
@ -37,10 +35,7 @@ export default function HandleRenderComponent({
|
|||
testIdComplement?: string;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
unstyled
|
||||
className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted"
|
||||
>
|
||||
<div>
|
||||
<ShadTooltip
|
||||
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
|
||||
delayDuration={1000}
|
||||
|
|
@ -100,6 +95,6 @@ export default function HandleRenderComponent({
|
|||
left ? "-left-[4px] -ml-0.5" : "-right-[4px] -mr-0.5",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,8 +349,8 @@ export default function ParameterComponent({
|
|||
testIdComplement={`${data?.type?.toLowerCase()}-shownode`}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-2 w-full">
|
||||
{data.node?.template[name] !== undefined && (
|
||||
{data.node?.template[name] !== undefined && (
|
||||
<div className="mt-2 w-full">
|
||||
<ParameterRenderComponent
|
||||
handleOnNewValue={handleOnNewValue}
|
||||
name={name}
|
||||
|
|
@ -362,8 +362,8 @@ export default function ParameterComponent({
|
|||
nodeClass={data.node!}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{openOutputModal && (
|
||||
<OutputModal
|
||||
open={openOutputModal}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,7 @@ const useHandleOnNewValue = ({
|
|||
if (value !== undefined) parameter[key] = value;
|
||||
});
|
||||
|
||||
const shouldUpdate =
|
||||
parameter.real_time_refresh && !parameter.refresh_button;
|
||||
const shouldUpdate = parameter.real_time_refresh;
|
||||
|
||||
const setNodeClass = (newNodeClass: APIClassType) => {
|
||||
options?.setNodeClass && options.setNodeClass(newNodeClass);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export function RefreshParameterComponent({
|
|||
setErrorData,
|
||||
);
|
||||
return (
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
{children}
|
||||
{templateData.refresh_button && (
|
||||
<div className="shrink-0 flex-col">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue