Merge branch 'two_edges' into two_edges_dev
This commit is contained in:
commit
902e616286
22 changed files with 235 additions and 186 deletions
|
|
@ -5,6 +5,8 @@ from langflow.inputs import StrInput
|
||||||
from langflow.schema import Data
|
from langflow.schema import Data
|
||||||
from langflow.template import Output
|
from langflow.template import Output
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class URLComponent(Component):
|
class URLComponent(Component):
|
||||||
display_name = "URL"
|
display_name = "URL"
|
||||||
|
|
@ -25,8 +27,39 @@ class URLComponent(Component):
|
||||||
Output(display_name="Data", name="data", method="fetch_content"),
|
Output(display_name="Data", name="data", method="fetch_content"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def ensure_url(self, string: str) -> str:
|
||||||
|
"""
|
||||||
|
Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.
|
||||||
|
Raises an error if the string is not a valid URL.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
string (str): The string to be checked and possibly modified.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The modified string that is ensured to be a URL.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the string is not a valid URL.
|
||||||
|
"""
|
||||||
|
if not string.startswith(("http://", "https://")):
|
||||||
|
string = "http://" + string
|
||||||
|
|
||||||
|
# Basic URL validation regex
|
||||||
|
url_regex = re.compile(
|
||||||
|
r"^(http://|https://)?" # http:// or https://
|
||||||
|
r"(([a-zA-Z0-9\.-]+)" # domain
|
||||||
|
r"(\.[a-zA-Z]{2,}))" # top-level domain
|
||||||
|
r"(:[0-9]{1,5})?" # optional port
|
||||||
|
r"(\/.*)?$" # optional path
|
||||||
|
)
|
||||||
|
|
||||||
|
if not re.match(url_regex, string):
|
||||||
|
raise ValueError(f"Invalid URL: {string}")
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
def fetch_content(self) -> Data:
|
def fetch_content(self) -> Data:
|
||||||
urls = [url.strip() for url in self.urls if url.strip()]
|
urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]
|
||||||
loader = WebBaseLoader(web_paths=urls)
|
loader = WebBaseLoader(web_paths=urls)
|
||||||
docs = loader.load()
|
docs = loader.load()
|
||||||
data = [Data(content=doc.page_content, **doc.metadata) for doc in docs]
|
data = [Data(content=doc.page_content, **doc.metadata) for doc in docs]
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,92 @@
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from langflow.custom import Component
|
from langflow.custom import Component
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
from langflow.schema import Data
|
from langflow.inputs import BoolInput, DropdownInput, StrInput
|
||||||
from langflow.template import Input, Output
|
from langflow.template import Output
|
||||||
|
|
||||||
|
|
||||||
class TextOperatorComponent(Component):
|
class TextOperatorComponent(Component):
|
||||||
display_name = "Text Operator"
|
display_name = "Text Operator"
|
||||||
description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity."
|
description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity."
|
||||||
|
icon = "equal"
|
||||||
|
|
||||||
inputs = [
|
inputs = [
|
||||||
Input(name="input_text", type=str, display_name="Input Text", info="The primary text input for the operation."),
|
StrInput(
|
||||||
Input(name="match_text", type=str, display_name="Match Text", info="The text input to compare against."),
|
name="input_text",
|
||||||
Input(
|
display_name="Input Text",
|
||||||
name="operator",
|
info="The primary text input for the operation.",
|
||||||
type=str,
|
|
||||||
display_name="Operator",
|
|
||||||
info="The operator to apply for comparing the texts.",
|
|
||||||
options=["equals", "not equals", "contains", "starts with", "ends with", "exists"],
|
|
||||||
),
|
),
|
||||||
Input(
|
StrInput(
|
||||||
|
name="match_text",
|
||||||
|
display_name="Match Text",
|
||||||
|
info="The text input to compare against.",
|
||||||
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="operator",
|
||||||
|
display_name="Operator",
|
||||||
|
options=["equals", "not equals", "contains", "starts with", "ends with"],
|
||||||
|
info="The operator to apply for comparing the texts.",
|
||||||
|
),
|
||||||
|
BoolInput(
|
||||||
name="case_sensitive",
|
name="case_sensitive",
|
||||||
type=bool,
|
|
||||||
display_name="Case Sensitive",
|
display_name="Case Sensitive",
|
||||||
info="If true, the comparison will be case sensitive.",
|
info="If true, the comparison will be case sensitive.",
|
||||||
default=False,
|
value=False,
|
||||||
|
advanced=True,
|
||||||
),
|
),
|
||||||
Input(
|
StrInput(
|
||||||
name="true_output",
|
name="true_output",
|
||||||
type=Union[str, Data],
|
|
||||||
display_name="True Output",
|
display_name="True Output",
|
||||||
info="The output to return or display when the comparison is true.",
|
info="The output to return or display when the comparison is true.",
|
||||||
input_types=["Text", "Data"],
|
advanced=True,
|
||||||
),
|
),
|
||||||
Input(
|
StrInput(
|
||||||
name="false_output",
|
name="false_output",
|
||||||
type=Union[str, Data],
|
|
||||||
display_name="False Output",
|
display_name="False Output",
|
||||||
info="The output to return or display when the comparison is false.",
|
info="The output to return or display when the comparison is false.",
|
||||||
input_types=["Text", "Data"],
|
advanced=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
outputs = [
|
outputs = [
|
||||||
Output(display_name="True Result", name="true_result", method="result_response"),
|
Output(display_name="True Result", name="true_result", method="true_response"),
|
||||||
Output(display_name="False Result", name="false_result", method="result_response"),
|
Output(display_name="False Result", name="false_result", method="false_response"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def true_response(self) -> Union[Text, Data]:
|
def evaluate_condition(self, input_text: str, match_text: str, operator: str, case_sensitive: bool) -> bool:
|
||||||
self.stop("False Result")
|
|
||||||
return self.true_output if self.true_output else self.input_text
|
|
||||||
|
|
||||||
def false_response(self) -> Union[Text, Data]:
|
|
||||||
self.stop("True Result")
|
|
||||||
return self.false_output if self.false_output else self.input_text
|
|
||||||
|
|
||||||
def result_response(self) -> Union[Text, Data]:
|
|
||||||
input_text = self.input_text
|
|
||||||
match_text = self.match_text
|
|
||||||
operator = self.operator
|
|
||||||
case_sensitive = self.case_sensitive
|
|
||||||
|
|
||||||
if not input_text or not match_text:
|
|
||||||
raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.")
|
|
||||||
|
|
||||||
if not case_sensitive:
|
if not case_sensitive:
|
||||||
input_text = input_text.lower()
|
input_text = input_text.lower()
|
||||||
match_text = match_text.lower()
|
match_text = match_text.lower()
|
||||||
|
|
||||||
result = False
|
|
||||||
if operator == "equals":
|
if operator == "equals":
|
||||||
result = input_text == match_text
|
return input_text == match_text
|
||||||
elif operator == "not equals":
|
elif operator == "not equals":
|
||||||
result = input_text != match_text
|
return input_text != match_text
|
||||||
elif operator == "contains":
|
elif operator == "contains":
|
||||||
result = match_text in input_text
|
return match_text in input_text
|
||||||
elif operator == "starts with":
|
elif operator == "starts with":
|
||||||
result = input_text.startswith(match_text)
|
return input_text.startswith(match_text)
|
||||||
elif operator == "ends with":
|
elif operator == "ends with":
|
||||||
result = input_text.endswith(match_text)
|
return input_text.endswith(match_text)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def true_response(self) -> Text:
|
||||||
|
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
|
||||||
if result:
|
if result:
|
||||||
response = self.true_response()
|
self.stop("false_result")
|
||||||
|
response = self.true_output if self.true_output else self.input_text
|
||||||
self.status = response
|
self.status = response
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
response = self.false_response()
|
self.stop("true_result")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def false_response(self) -> Text:
|
||||||
|
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
|
||||||
|
if not result:
|
||||||
|
self.stop("true_result")
|
||||||
|
response = self.false_output if self.false_output else self.input_text
|
||||||
self.status = response
|
self.status = response
|
||||||
return response
|
return response
|
||||||
|
else:
|
||||||
|
self.stop("false_result")
|
||||||
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,38 @@
|
||||||
from langflow.custom import CustomComponent
|
from langflow.custom import Component
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
from langflow.inputs import StrInput
|
||||||
|
from langflow.template import Output
|
||||||
|
|
||||||
|
|
||||||
class CombineTextComponent(CustomComponent):
|
class CombineTextComponent(Component):
|
||||||
display_name = "Combine Text"
|
display_name = "Combine Text"
|
||||||
description = "Concatenate two text sources into a single text chunk using a specified delimiter."
|
description = "Concatenate two text sources into a single text chunk using a specified delimiter."
|
||||||
icon = "merge"
|
icon = "merge"
|
||||||
|
|
||||||
def build_config(self):
|
inputs = [
|
||||||
return {
|
StrInput(
|
||||||
"text1": {
|
name="text1",
|
||||||
"display_name": "First Text",
|
display_name="First Text",
|
||||||
"info": "The first text input to concatenate.",
|
info="The first text input to concatenate.",
|
||||||
},
|
),
|
||||||
"text2": {
|
StrInput(
|
||||||
"display_name": "Second Text",
|
name="text2",
|
||||||
"info": "The second text input to concatenate.",
|
display_name="Second Text",
|
||||||
},
|
info="The second text input to concatenate.",
|
||||||
"delimiter": {
|
),
|
||||||
"display_name": "Delimiter",
|
StrInput(
|
||||||
"info": "A string used to separate the two text inputs. Defaults to a whitespace.",
|
name="delimiter",
|
||||||
},
|
display_name="Delimiter",
|
||||||
}
|
info="A string used to separate the two text inputs. Defaults to a whitespace.",
|
||||||
|
default=" ",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def build(self, text1: str, text2: str, delimiter: str = " ") -> Text:
|
outputs = [
|
||||||
combined = delimiter.join([text1, text2])
|
Output(display_name="Combined Text", name="combined_text", method="combine_texts"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def combine_texts(self) -> Text:
|
||||||
|
combined = self.delimiter.join([self.text1, self.text2])
|
||||||
self.status = combined
|
self.status = combined
|
||||||
return combined
|
return combined
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
from langflow.custom import CustomComponent
|
|
||||||
from langflow.field_typing import Text
|
|
||||||
|
|
||||||
|
|
||||||
class CombineTextsUnsortedComponent(CustomComponent):
|
|
||||||
display_name = "Combine Texts (Unsorted)"
|
|
||||||
description = "Concatenate text sources into a single text chunk using a specified delimiter."
|
|
||||||
icon = "merge"
|
|
||||||
|
|
||||||
def build_config(self):
|
|
||||||
return {
|
|
||||||
"texts": {
|
|
||||||
"display_name": "Texts",
|
|
||||||
"info": "The first text input to concatenate.",
|
|
||||||
},
|
|
||||||
"delimiter": {
|
|
||||||
"display_name": "Delimiter",
|
|
||||||
"info": "A string used to separate the two text inputs. Defaults to a whitespace.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def build(self, texts: list[str], delimiter: str = " ") -> Text:
|
|
||||||
combined = delimiter.join(texts)
|
|
||||||
self.status = combined
|
|
||||||
return combined
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
from langflow.custom import CustomComponent
|
|
||||||
from langflow.field_typing import Text
|
|
||||||
from langflow.helpers.data import data_to_text
|
|
||||||
from langflow.schema import Data
|
|
||||||
|
|
||||||
|
|
||||||
class DataToTextComponent(CustomComponent):
|
|
||||||
display_name = "Data To Text"
|
|
||||||
description = "Convert Data into plain text following a specified template."
|
|
||||||
|
|
||||||
def build_config(self):
|
|
||||||
return {
|
|
||||||
"data": {
|
|
||||||
"display_name": "Data",
|
|
||||||
"info": "The data to convert to text.",
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"display_name": "Template",
|
|
||||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
|
||||||
"multiline": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def build(
|
|
||||||
self,
|
|
||||||
data: list[Data],
|
|
||||||
template: str = "Text: {text}\nData: {data}",
|
|
||||||
) -> Text:
|
|
||||||
if not data:
|
|
||||||
return ""
|
|
||||||
if isinstance(data, Data):
|
|
||||||
data = [data]
|
|
||||||
|
|
||||||
result_string = data_to_text(template, data)
|
|
||||||
self.status = result_string
|
|
||||||
return result_string
|
|
||||||
|
|
@ -6,9 +6,10 @@ from langflow.custom import CustomComponent
|
||||||
from langflow.schema import Data
|
from langflow.schema import Data
|
||||||
|
|
||||||
|
|
||||||
class DocumentToDataComponent(CustomComponent):
|
class DocumentsToDataComponent(CustomComponent):
|
||||||
display_name = "Documents To Data"
|
display_name = "Documents ⇢ Data"
|
||||||
description = "Convert LangChain Documents into Data."
|
description = "Convert LangChain Documents into Data."
|
||||||
|
icon = "LangChain"
|
||||||
|
|
||||||
field_config = {
|
field_config = {
|
||||||
"documents": {"display_name": "Documents"},
|
"documents": {"display_name": "Documents"},
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from langflow.custom import Component
|
from langflow.custom import Component
|
||||||
from langflow.inputs import StrInput
|
from langflow.inputs import StrInput, HandleInput
|
||||||
from langflow.schema import Data
|
from langflow.schema import Data
|
||||||
from langflow.template import Input, Output
|
from langflow.template import Output
|
||||||
|
|
||||||
|
|
||||||
class FilterDataComponent(Component):
|
class FilterDataComponent(Component):
|
||||||
display_name = "Filter Message"
|
display_name = "Filter Data"
|
||||||
description = "Filters a Message object based on a list of strings."
|
description = "Filters a Data object based on a list of keys."
|
||||||
icon = "filter"
|
icon = "filter"
|
||||||
|
|
||||||
inputs = [
|
inputs = [
|
||||||
Input(name="message", display_name="Message", info="Message object to filter.", input_types=["Message"]),
|
HandleInput(
|
||||||
|
name="data",
|
||||||
|
display_name="Data",
|
||||||
|
info="Data object to filter.",
|
||||||
|
input_types=["Message", "Data"],
|
||||||
|
),
|
||||||
StrInput(
|
StrInput(
|
||||||
name="filter_criteria", display_name="Filter Criteria", info="List of strings to filter by.", is_list=True
|
name="filter_criteria",
|
||||||
|
display_name="Filter Criteria",
|
||||||
|
info="List of keys to filter by.",
|
||||||
|
is_list=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -24,10 +32,12 @@ class FilterDataComponent(Component):
|
||||||
|
|
||||||
def filter_data(self) -> Data:
|
def filter_data(self) -> Data:
|
||||||
filter_criteria: List[str] = self.filter_criteria
|
filter_criteria: List[str] = self.filter_criteria
|
||||||
|
data = self.data.data if isinstance(self.data, Data) else {}
|
||||||
|
|
||||||
# Filter the data
|
# Filter the data
|
||||||
filtered = {key: value for key, value in self.message.data.items() if key == filter_criteria}
|
filtered = {key: value for key, value in data.items() if key in filter_criteria}
|
||||||
|
|
||||||
# Create a new Data object with the filtered data
|
# Create a new Data object with the filtered data
|
||||||
self.status = filtered
|
filtered_data = Data(data=filtered)
|
||||||
return filtered
|
self.status = filtered_data
|
||||||
|
return filtered_data
|
||||||
|
|
|
||||||
34
src/backend/base/langflow/components/helpers/ParseData.py
Normal file
34
src/backend/base/langflow/components/helpers/ParseData.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
from langflow.custom import Component
|
||||||
|
from langflow.helpers.data import data_to_text
|
||||||
|
from langflow.field_typing import Text
|
||||||
|
from langflow.inputs import MultilineInput, HandleInput
|
||||||
|
from langflow.template import Output
|
||||||
|
|
||||||
|
|
||||||
|
class ParseDataComponent(Component):
|
||||||
|
display_name = "Parse Data"
|
||||||
|
description = "Convert Data into plain text following a specified template."
|
||||||
|
icon = "braces"
|
||||||
|
|
||||||
|
inputs = [
|
||||||
|
HandleInput(
|
||||||
|
name="data", display_name="Data", info="The data to convert to text.", input_types=["Message", "Data"]
|
||||||
|
),
|
||||||
|
MultilineInput(
|
||||||
|
name="template",
|
||||||
|
display_name="Template",
|
||||||
|
info="The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
outputs = [
|
||||||
|
Output(display_name="Text", name="text", method="parse_data_to_text"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def parse_data_to_text(self) -> Text:
|
||||||
|
data = self.data if isinstance(self.data, list) else [self.data]
|
||||||
|
template = self.template or "Text: {text}"
|
||||||
|
|
||||||
|
result_string = data_to_text(template, data)
|
||||||
|
self.status = result_string
|
||||||
|
return result_string
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
from .CreateData import CreateDataComponent
|
from .CreateData import CreateDataComponent
|
||||||
from .CustomComponent import Component
|
from .CustomComponent import Component
|
||||||
from .DataToText import DataToTextComponent
|
from .ParseData import ParseDataComponent
|
||||||
from .DocumentToData import DocumentToDataComponent
|
from .DocumentToData import DocumentsToDataComponent
|
||||||
from .IDGenerator import UUIDGeneratorComponent
|
from .IDGenerator import UUIDGeneratorComponent
|
||||||
from .UpdateData import UpdateDataComponent
|
from .UpdateData import UpdateDataComponent
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Component",
|
"Component",
|
||||||
"UpdateDataComponent",
|
"UpdateDataComponent",
|
||||||
"DocumentToDataComponent",
|
"DocumentsToDataComponent",
|
||||||
"UUIDGeneratorComponent",
|
"UUIDGeneratorComponent",
|
||||||
"DataToTextComponent",
|
"ParseDataComponent",
|
||||||
"CreateDataComponent",
|
"CreateDataComponent",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class ChatAntropicSpecsComponent(CustomComponent):
|
||||||
self,
|
self,
|
||||||
model: str,
|
model: str,
|
||||||
anthropic_api_key: Optional[str] = None,
|
anthropic_api_key: Optional[str] = None,
|
||||||
max_tokens: Optional[int] = None,
|
max_tokens: Optional[int] = 1000,
|
||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
api_endpoint: Optional[str] = None,
|
api_endpoint: Optional[str] = None,
|
||||||
) -> BaseLanguageModel:
|
) -> BaseLanguageModel:
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class AnthropicLLM(CustomComponent):
|
||||||
self,
|
self,
|
||||||
model: str,
|
model: str,
|
||||||
anthropic_api_key: Optional[str] = None,
|
anthropic_api_key: Optional[str] = None,
|
||||||
max_tokens: Optional[int] = None,
|
max_tokens: Optional[int] = 1000,
|
||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
anthropic_api_url: Optional[str] = None,
|
anthropic_api_url: Optional[str] = None,
|
||||||
) -> BaseLanguageModel:
|
) -> BaseLanguageModel:
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class AnthropicLLM(LCModelComponent):
|
||||||
input_value: Text,
|
input_value: Text,
|
||||||
system_message: Optional[str] = None,
|
system_message: Optional[str] = None,
|
||||||
anthropic_api_key: Optional[str] = None,
|
anthropic_api_key: Optional[str] = None,
|
||||||
max_tokens: Optional[int] = None,
|
max_tokens: Optional[int] = 1000,
|
||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
anthropic_api_url: Optional[str] = None,
|
anthropic_api_url: Optional[str] = None,
|
||||||
stream: bool = False,
|
stream: bool = False,
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,15 @@ class Component(CustomComponent):
|
||||||
raw = self.status
|
raw = self.status
|
||||||
if hasattr(raw, "data") and raw is not None:
|
if hasattr(raw, "data") and raw is not None:
|
||||||
raw = raw.data
|
raw = raw.data
|
||||||
|
if raw is None:
|
||||||
|
raw = custom_repr
|
||||||
|
|
||||||
elif hasattr(raw, "model_dump") and raw is not None:
|
elif hasattr(raw, "model_dump") and raw is not None:
|
||||||
raw = raw.model_dump()
|
raw = raw.model_dump()
|
||||||
artifact_type = get_artifact_type(self.status, result)
|
if raw is None and isinstance(result, (dict, Data, str)):
|
||||||
|
raw = result.data if isinstance(result, Data) else result
|
||||||
|
|
||||||
|
artifact_type = get_artifact_type(self.repr_value or raw, result)
|
||||||
raw = post_process_raw(raw, artifact_type)
|
raw = post_process_raw(raw, artifact_type)
|
||||||
artifact = {"repr": custom_repr, "raw": raw, "type": artifact_type}
|
artifact = {"repr": custom_repr, "raw": raw, "type": artifact_type}
|
||||||
_artifacts[output.name] = artifact
|
_artifacts[output.name] = artifact
|
||||||
|
|
@ -110,21 +115,15 @@ class Component(CustomComponent):
|
||||||
return _results, _artifacts
|
return _results, _artifacts
|
||||||
|
|
||||||
def custom_repr(self):
|
def custom_repr(self):
|
||||||
# ! Temporary REPR
|
if self.repr_value == "":
|
||||||
# Since all are dict, yaml.dump them
|
self.repr_value = self.status
|
||||||
if isinstance(self._results, dict):
|
if isinstance(self.repr_value, dict):
|
||||||
_build_results = recursive_serialize_or_str(self._results)
|
return yaml.dump(self.repr_value)
|
||||||
try:
|
if isinstance(self.repr_value, str):
|
||||||
custom_repr = yaml.dump(_build_results)
|
return self.repr_value
|
||||||
except Exception as e:
|
if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Data):
|
||||||
logger.error(f"Error while dumping build_result: {e}")
|
return str(self.repr_value)
|
||||||
custom_repr = str(self._results)
|
return self.repr_value
|
||||||
|
|
||||||
if custom_repr is None and isinstance(self._results, (dict, Data, str)):
|
|
||||||
custom_repr = self._results
|
|
||||||
if not isinstance(custom_repr, str):
|
|
||||||
custom_repr = str(custom_repr)
|
|
||||||
return custom_repr
|
|
||||||
|
|
||||||
def build_inputs(self, user_id: Optional[Union[str, UUID]] = None):
|
def build_inputs(self, user_id: Optional[Union[str, UUID]] = None):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ class ResultData(BaseModel):
|
||||||
message = values["artifacts"][key]
|
message = values["artifacts"][key]
|
||||||
|
|
||||||
# ! Temporary fix
|
# ! Temporary fix
|
||||||
if not isinstance(message, dict):
|
if message is None:
|
||||||
message = {"message": message}
|
continue
|
||||||
|
|
||||||
if "stream_url" in message and "type" in message:
|
if "stream_url" in message and "type" in message:
|
||||||
stream_url = StreamURL(location=message["stream_url"])
|
stream_url = StreamURL(location=message["stream_url"])
|
||||||
|
|
|
||||||
|
|
@ -234,16 +234,15 @@ class Vertex:
|
||||||
|
|
||||||
self.has_session_id = "session_id" in template_dicts
|
self.has_session_id = "session_id" in template_dicts
|
||||||
|
|
||||||
self.required_inputs = [
|
self.required_inputs = []
|
||||||
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
self.optional_inputs = []
|
||||||
]
|
for value_dict in template_dicts.values():
|
||||||
self.optional_inputs = [
|
list_to_append = self.required_inputs if value_dict.get("required") else self.optional_inputs
|
||||||
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
|
||||||
]
|
if "type" in value_dict:
|
||||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
list_to_append.append(value_dict["type"])
|
||||||
self.optional_inputs.extend(
|
if "input_types" in value_dict:
|
||||||
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
list_to_append.extend(value_dict["input_types"])
|
||||||
)
|
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
self.vertex_type = (
|
self.vertex_type = (
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ from .inputs import (
|
||||||
FileInput,
|
FileInput,
|
||||||
FloatInput,
|
FloatInput,
|
||||||
IntInput,
|
IntInput,
|
||||||
|
MultilineInput,
|
||||||
NestedDictInput,
|
NestedDictInput,
|
||||||
PromptInput,
|
PromptInput,
|
||||||
SecretStrInput,
|
SecretStrInput,
|
||||||
StrInput,
|
StrInput,
|
||||||
MultilineInput,
|
HandleInput,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -24,4 +25,5 @@ __all__ = [
|
||||||
"FileInput",
|
"FileInput",
|
||||||
"PromptInput",
|
"PromptInput",
|
||||||
"MultilineInput",
|
"MultilineInput",
|
||||||
|
"HandleInput",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ SerializableFieldTypes = Annotated[FieldTypes, PlainSerializer(lambda v: v.value
|
||||||
class BaseInputMixin(BaseModel):
|
class BaseInputMixin(BaseModel):
|
||||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||||
|
|
||||||
field_type: Optional[SerializableFieldTypes] = Field(default=FieldTypes.TEXT)
|
field_type: Optional[SerializableFieldTypes | str] = Field(default=FieldTypes.TEXT)
|
||||||
|
|
||||||
required: bool = False
|
required: bool = False
|
||||||
"""Specifies if the field is required. Defaults to False."""
|
"""Specifies if the field is required. Defaults to False."""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Callable, Optional, Union
|
from typing import Callable, Optional, Union
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field, model_validator
|
||||||
|
|
||||||
from langflow.inputs.validators import StrictBoolean
|
from langflow.inputs.validators import StrictBoolean
|
||||||
|
|
||||||
|
|
@ -16,6 +16,17 @@ from .input_mixin import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HandleInput(BaseInputMixin):
|
||||||
|
input_types: list[str] = Field(default_factory=list)
|
||||||
|
field_type: Optional[str] = ""
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_model_type(self):
|
||||||
|
# FieldType should be a string
|
||||||
|
self.field_type = " | ".join(self.input_types)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class PromptInput(BaseInputMixin, ListableInputMixin):
|
class PromptInput(BaseInputMixin, ListableInputMixin):
|
||||||
field_type: Optional[SerializableFieldTypes] = FieldTypes.PROMPT
|
field_type: Optional[SerializableFieldTypes] = FieldTypes.PROMPT
|
||||||
|
|
||||||
|
|
@ -24,6 +35,7 @@ class PromptInput(BaseInputMixin, ListableInputMixin):
|
||||||
class StrInput(BaseInputMixin, ListableInputMixin, DatabaseLoadMixin): # noqa: F821
|
class StrInput(BaseInputMixin, ListableInputMixin, DatabaseLoadMixin): # noqa: F821
|
||||||
field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT
|
field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT
|
||||||
load_from_db: StrictBoolean = False
|
load_from_db: StrictBoolean = False
|
||||||
|
input_types: list[str] = ["Text"]
|
||||||
"""Defines if the field will allow the user to open a text editor. Default is False."""
|
"""Defines if the field will allow the user to open a text editor. Default is False."""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -83,4 +95,5 @@ InputTypes = Union[
|
||||||
FileInput,
|
FileInput,
|
||||||
PromptInput,
|
PromptInput,
|
||||||
MultilineInput,
|
MultilineInput,
|
||||||
|
HandleInput,
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ class Template(BaseModel):
|
||||||
# first sort alphabetically
|
# first sort alphabetically
|
||||||
# then sort fields so that fields that have .field_type in DIRECT_TYPES are first
|
# then sort fields so that fields that have .field_type in DIRECT_TYPES are first
|
||||||
self.fields.sort(key=lambda x: x.name)
|
self.fields.sort(key=lambda x: x.name)
|
||||||
self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False)
|
self.fields.sort(
|
||||||
|
key=lambda x: x.field_type in DIRECT_TYPES if hasattr(x, "field_type") else False, reverse=False
|
||||||
|
)
|
||||||
|
|
||||||
@model_serializer(mode="wrap")
|
@model_serializer(mode="wrap")
|
||||||
def serialize_model(self, handler):
|
def serialize_model(self, handler):
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,12 @@ export default function GenericNode({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hiddenOutputs && hiddenOutputs.length == 0) {
|
||||||
|
setShowHiddenOutputs(false);
|
||||||
|
}
|
||||||
|
}, [hiddenOutputs]);
|
||||||
|
|
||||||
const memoizedNodeToolbarComponent = useMemo(() => {
|
const memoizedNodeToolbarComponent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<NodeToolbar>
|
<NodeToolbar>
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
title: UPLOAD_ERROR_ALERT,
|
title: UPLOAD_ERROR_ALERT,
|
||||||
list: [error],
|
list: [error],
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -193,7 +193,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
name="RefreshCcw"
|
name="RefreshCcw"
|
||||||
className="header-menu-options"
|
className="header-menu-options"
|
||||||
/>
|
/>
|
||||||
Reload Components
|
Refresh All
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
@ -221,7 +221,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4",
|
||||||
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
|
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{printByBuildStatus()}
|
{printByBuildStatus()}
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ export const nodeColors: { [char: string]: string } = {
|
||||||
outputs: "#AA2411",
|
outputs: "#AA2411",
|
||||||
data: "#198BF6",
|
data: "#198BF6",
|
||||||
prompts: "#4367BF",
|
prompts: "#4367BF",
|
||||||
models: "#6344BE",
|
models: "#ab11ab",
|
||||||
model_specs: "#6344BE",
|
model_specs: "#6344BE",
|
||||||
chains: "#FE7500",
|
chains: "#FE7500",
|
||||||
Document: "#7AAE42",
|
Document: "#7AAE42",
|
||||||
|
|
@ -271,10 +271,10 @@ export const nodeColors: { [char: string]: string } = {
|
||||||
Text: "#4367BF",
|
Text: "#4367BF",
|
||||||
retrievers: "#e6b25a",
|
retrievers: "#e6b25a",
|
||||||
unknown: "#9CA3AF",
|
unknown: "#9CA3AF",
|
||||||
custom_components: "#ab11ab",
|
// custom_components: "#ab11ab",
|
||||||
Data: "#31a3cc",
|
Data: "#9CA3AF",
|
||||||
Data: "#31a3cc",
|
|
||||||
Message: "#4367BF",
|
Message: "#4367BF",
|
||||||
|
BaseLanguageModel: "#ab11ab",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nodeNames: { [char: string]: string } = {
|
export const nodeNames: { [char: string]: string } = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue