Merge branch 'zustand/io/migration' into state_theories

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-03-02 00:48:40 -03:00
commit dc8ad28418
79 changed files with 1598 additions and 1613 deletions

View file

@ -56,6 +56,13 @@ LANGFLOW_REMOVE_API_KEYS=
# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
LANGFLOW_CACHE_TYPE=
# Set AUTO_LOGIN to false if you want to disable auto login
# and use the login form to login. LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD
# must be set if AUTO_LOGIN is set to false
# Values: true, false
LANGFLOW_AUTO_LOGIN=
# Superuser username
# Example: LANGFLOW_SUPERUSER=admin
LANGFLOW_SUPERUSER=

View file

@ -68,8 +68,6 @@ INVALID_CHARACTERS = {
")",
"[",
"]",
"{",
"}",
}
INVALID_NAMES = {
@ -88,73 +86,110 @@ def validate_prompt(template: str):
# Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables):
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
raise ValueError(
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
)
try:
PromptTemplate(template=template, input_variables=input_variables)
except Exception as exc:
raise ValueError(str(exc)) from exc
raise ValueError(f"Invalid prompt: {exc}") from exc
return input_variables
def check_input_variables(input_variables: list):
def is_json_like(var):
if var.startswith("{{") and var.endswith("}}"):
# If it is a double brance variable
# we don't want to validate any of its content
return True
# the above doesn't work on all cases because the json string can be multiline
# or indented which can add \n or spaces at the start or end of the string
# test_case_3 new_var == '\n{{\n "test": "hello",\n "text": "world"\n}}\n'
# what we can do is to remove the \n and spaces from the start and end of the string
# and then check if the string starts with {{ and ends with }}
var = var.strip()
var = var.replace("\n", "")
var = var.replace(" ", "")
# Now it should be a valid json string
return var.startswith("{{") and var.endswith("}}")
def fix_variable(var, invalid_chars, wrong_variables):
if not var:
return var, invalid_chars, wrong_variables
new_var = var
# Handle variables starting with a number
if var[0].isdigit():
invalid_chars.append(var[0])
new_var, invalid_chars, wrong_variables = fix_variable(
var[1:], invalid_chars, wrong_variables
)
# Temporarily replace {{ and }} to avoid treating them as invalid
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘʟsᴇ")
# Remove invalid characters
for char in new_var:
if char in INVALID_CHARACTERS:
invalid_chars.append(char)
new_var = new_var.replace(char, "")
if var not in wrong_variables: # Avoid duplicating entries
wrong_variables.append(var)
# Restore {{ and }}
new_var = new_var.replace("ᴛᴇᴍᴘᴏᴘᴇɴ", "{{").replace("ᴛᴇᴍᴘʟsᴇ", "}}")
return new_var, invalid_chars, wrong_variables
def check_variable(var, invalid_chars, wrong_variables, empty_variables):
if any(char in invalid_chars for char in var):
wrong_variables.append(var)
elif var == "":
empty_variables.append(var)
return wrong_variables, empty_variables
def check_for_errors(
input_variables, fixed_variables, wrong_variables, empty_variables
):
if any(var for var in input_variables if var not in fixed_variables):
error_message = (
f"Error: Input variables contain invalid characters or formats. \n"
f"Invalid variables: {', '.join(wrong_variables)}.\n"
f"Empty variables: {', '.join(empty_variables)}. \n"
f"Fixed variables: {', '.join(fixed_variables)}."
)
raise ValueError(error_message)
def check_input_variables(input_variables):
invalid_chars = []
fixed_variables = []
wrong_variables = []
empty_variables = []
for variable in input_variables:
new_var = variable
variables_to_check = []
# if variable is empty, then we should add that to the wrong variables
if not variable:
empty_variables.append(variable)
for var in input_variables:
# First, let's check if the variable is a JSON string
# because if it is, it won't be considered a variable
# and we don't need to validate it
if is_json_like(var):
continue
# if variable starts with a number we should add that to the invalid chars
# and wrong variables
if variable[0].isdigit():
invalid_chars.append(variable[0])
new_var = new_var.replace(variable[0], "")
wrong_variables.append(variable)
else:
for char in INVALID_CHARACTERS:
if char in variable:
invalid_chars.append(char)
new_var = new_var.replace(char, "")
wrong_variables.append(variable)
fixed_variables.append(new_var)
# If any of the input_variables is not in the fixed_variables, then it means that
# there are invalid characters in the input_variables
if any(var not in fixed_variables for var in input_variables):
error_message = build_error_message(
input_variables,
invalid_chars,
wrong_variables,
fixed_variables,
empty_variables,
new_var, wrong_variables, empty_variables = fix_variable(
var, invalid_chars, wrong_variables
)
raise ValueError(error_message)
return input_variables
wrong_variables, empty_variables = check_variable(
var, INVALID_CHARACTERS, wrong_variables, empty_variables
)
fixed_variables.append(new_var)
variables_to_check.append(var)
check_for_errors(
variables_to_check, fixed_variables, wrong_variables, empty_variables
)
def build_error_message(input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables):
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
error_string = f"Invalid input variables: {input_variables_str}. "
if wrong_variables and invalid_chars:
# fix the wrong variables replacing invalid chars and find them in the fixed variables
error_string_vars = "You can fix them by replacing the invalid characters: "
wvars = wrong_variables.copy()
for i, wrong_var in enumerate(wvars):
for char in invalid_chars:
wrong_var = wrong_var.replace(char, "")
if wrong_var in fixed_variables:
error_string_vars += f"'{wrong_variables[i]}' -> '{wrong_var}'"
error_string += error_string_vars
elif empty_variables:
error_string += f" There are {len(empty_variables)} empty variable{'s' if len(empty_variables) > 1 else ''}."
elif len(set(fixed_variables)) != len(fixed_variables):
error_string += "There are duplicate variables."
return error_string
return fixed_variables

View file

@ -1,6 +1,7 @@
from langchain_core.documents import Document
from typing import List
from langflow import CustomComponent
from langflow.schema import Record
from langflow.utils.constants import LOADERS_INFO
@ -10,7 +11,9 @@ class FileLoaderComponent(CustomComponent):
beta = True
def build_config(self):
loader_options = ["Automatic"] + [loader_info["name"] for loader_info in LOADERS_INFO]
loader_options = ["Automatic"] + [
loader_info["name"] for loader_info in LOADERS_INFO
]
file_types = []
suffixes = []
@ -74,7 +77,7 @@ class FileLoaderComponent(CustomComponent):
"code": {"show": False},
}
def build(self, file_path: str, loader: str) -> Document:
def build(self, file_path: str, loader: str) -> List[Record]:
file_type = file_path.split(".")[-1]
# Map the loader to the correct loader class
@ -102,7 +105,9 @@ class FileLoaderComponent(CustomComponent):
if isinstance(selected_loader_info, dict):
loader_import: str = selected_loader_info["import"]
else:
raise ValueError(f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}")
raise ValueError(
f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}"
)
module_name, class_name = loader_import.rsplit(".", 1)
try:
@ -110,7 +115,10 @@ class FileLoaderComponent(CustomComponent):
loader_module = __import__(module_name, fromlist=[class_name])
loader_instance = getattr(loader_module, class_name)
except ImportError as e:
raise ValueError(f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}") from e
raise ValueError(
f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}"
) from e
result = loader_instance(file_path=file_path)
return result.load()
docs = result.load()
return self.to_records(docs)

View file

@ -70,11 +70,17 @@ class GatherRecordsComponent(CustomComponent):
glob = "**/*" if recursive else "*"
paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
file_paths = [Text(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)]
file_paths = [
Text(p)
for p in paths
if p.is_file() and match_types(p) and is_not_hidden(p)
]
return file_paths
def parse_file_to_record(self, file_path: str, silent_errors: bool) -> Optional[Record]:
def parse_file_to_record(
self, file_path: str, silent_errors: bool
) -> Optional[Record]:
# Use the partition function to load the file
from unstructured.partition.auto import partition # type: ignore
@ -100,9 +106,14 @@ class GatherRecordsComponent(CustomComponent):
use_multithreading: bool,
) -> List[Optional[Record]]:
if use_multithreading:
records = self.parallel_load_records(file_paths, silent_errors, max_concurrency)
records = self.parallel_load_records(
file_paths, silent_errors, max_concurrency
)
else:
records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
records = [
self.parse_file_to_record(file_path, silent_errors)
for file_path in file_paths
]
records = list(filter(None, records))
return records
@ -131,13 +142,20 @@ class GatherRecordsComponent(CustomComponent):
if types is None:
types = []
resolved_path = self.resolve_path(path)
file_paths = self.retrieve_file_paths(resolved_path, types, load_hidden, recursive, depth)
file_paths = self.retrieve_file_paths(
resolved_path, types, load_hidden, recursive, depth
)
loaded_records = []
if use_multithreading:
loaded_records = self.parallel_load_records(file_paths, silent_errors, max_concurrency)
loaded_records = self.parallel_load_records(
file_paths, silent_errors, max_concurrency
)
else:
loaded_records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
loaded_records = [
self.parse_file_to_record(file_path, silent_errors)
for file_path in file_paths
]
loaded_records = list(filter(None, loaded_records))
self.status = loaded_records
return loaded_records

View file

@ -11,8 +11,8 @@ class ChatOutput(ChatComponent):
def build(
self,
sender: Optional[str] = "User",
sender_name: Optional[str] = "User",
sender: Optional[str] = "Machine",
sender_name: Optional[str] = "AI",
input_value: Optional[str] = None,
session_id: Optional[str] = None,
return_record: Optional[bool] = False,

View file

@ -1,19 +1,12 @@
from typing import Optional
from langflow import CustomComponent
from langflow.components.io.base.text import TextComponent
from langflow.field_typing import Text
class TextInput(CustomComponent):
class TextInput(TextComponent):
display_name = "Text Input"
description = "Used to pass text input to the next component."
field_config = {
"input_value": {"display_name": "Value", "multiline": True},
}
def build(self, input_value: Optional[str] = "") -> Text:
self.status = input_value
if not input_value:
input_value = ""
return input_value
return super().build(input_value=input_value)

View file

@ -1,19 +1,16 @@
from typing import Optional
from langflow import CustomComponent
from langflow.components.io.base.text import TextComponent
from langflow.field_typing import Text
class TextOutput(CustomComponent):
class TextOutput(TextComponent):
display_name = "Text Output"
description = "Used to pass text output to the next component."
field_config = {
"value": {"display_name": "Value"},
"input_value": {"display_name": "Value"},
}
def build(self, value: Optional[str] = "") -> Text:
self.status = value
if not value:
value = ""
return value
def build(self, input_value: Optional[Text] = "") -> Text:
return super().build(input_value=input_value)

View file

@ -45,7 +45,9 @@ class ChatComponent(CustomComponent):
return []
if not session_id or not sender or not sender_name:
raise ValueError("All of session_id, sender, and sender_name must be provided.")
raise ValueError(
"All of session_id, sender, and sender_name must be provided."
)
if isinstance(message, Record):
record = message
record.data.update(

View file

@ -0,0 +1,19 @@
from typing import Optional
from langflow import CustomComponent
from langflow.field_typing import Text
class TextComponent(CustomComponent):
display_name = "Text Component"
description = "Used to pass text to the next component."
field_config = {
"input_value": {"display_name": "Value", "multiline": True},
}
def build(self, input_value: Optional[str] = "") -> Text:
self.status = input_value
if not input_value:
input_value = ""
return input_value

View file

@ -1,5 +1,6 @@
from langflow import CustomComponent
from langflow.field_typing import Text
from langflow.helpers.record import records_to_text
from langflow.schema import Record
@ -27,7 +28,6 @@ class RecordsAsTextComponent(CustomComponent):
if isinstance(records, Record):
records = [records]
formated_records = [template.format(text=record.text, data=record.data, **record.data) for record in records]
result_string = "\n".join(formated_records)
result_string = records_to_text(template, records)
self.status = result_string
return result_string

View file

@ -1,7 +1,6 @@
from typing import Optional
from langflow import CustomComponent
from langflow.field_typing import Text
from langflow.schema import Record

View file

@ -93,7 +93,8 @@ class ChromaSearchComponent(LCVectorStoreComponent):
if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None,

View file

@ -34,7 +34,9 @@ class FAISSSearchComponent(LCVectorStoreComponent):
if not folder_path:
raise ValueError("Folder path is required to save the FAISS index.")
path = self.resolve_path(folder_path)
vector_store = FAISS.load_local(folder_path=Text(path), embeddings=embedding, index_name=index_name)
vector_store = FAISS.load_local(
folder_path=Text(path), embeddings=embedding, index_name=index_name
)
if not vector_store:
raise ValueError("Failed to load the FAISS index.")

View file

@ -38,7 +38,9 @@ class SupabaseSearchComponent(LCVectorStoreComponent):
supabase_url: str = "",
table_name: str = "",
) -> List[Record]:
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
supabase: Client = create_client(
supabase_url, supabase_key=supabase_service_key
)
vector_store = SupabaseVectorStore(
client=supabase,
embedding=embedding,

View file

@ -11,7 +11,9 @@ from langflow.schema import Record
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
display_name: str = "Vectara Search"
description: str = "Search a Vectara Vector Store for similar documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
)
beta = True
icon = "Vectara"

View file

@ -11,7 +11,9 @@ from langflow.schema import Record
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
display_name: str = "Weaviate Search"
description: str = "Search a Weaviate Vector Store for similar documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
)
beta = True
icon = "Weaviate"

View file

@ -6,7 +6,8 @@ from langchain_core.vectorstores import VectorStore
from langflow import CustomComponent
from langflow.field_typing import Text
from langflow.schema import Record, docs_to_records
from langflow.helpers.record import docs_to_records
from langflow.schema import Record
class LCVectorStoreComponent(CustomComponent):

View file

@ -15,7 +15,9 @@ class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
display_name: str = "PGVector Search"
description: str = "Search a PGVector Store for similar documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
)
def build_config(self):
"""

View file

@ -1,19 +1,20 @@
from typing import TYPE_CHECKING, Any, List, Optional
from loguru import logger
from pydantic import BaseModel, Field
from langflow.graph.edge.utils import build_clean_params
from langflow.graph.schema import INPUT_FIELD_NAME
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.utils import log_message
from loguru import logger
from pydantic import BaseModel, Field
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
class SourceHandle(BaseModel):
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
baseClasses: List[str] = Field(
..., description="List of base classes for the source handle."
)
dataType: str = Field(..., description="Data type for the source handle.")
id: str = Field(..., description="Unique identifier for the source handle.")
@ -21,7 +22,9 @@ class SourceHandle(BaseModel):
class TargetHandle(BaseModel):
fieldName: str = Field(..., description="Field name for the target handle.")
id: str = Field(..., description="Unique identifier for the target handle.")
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
inputTypes: Optional[List[str]] = Field(
None, description="List of input types for the target handle."
)
type: str = Field(..., description="Type of the target handle.")
@ -50,16 +53,24 @@ class Edge:
def validate_handles(self, source, target) -> None:
if self.target_handle.inputTypes is None:
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
self.valid_handles = (
self.target_handle.type in self.source_handle.baseClasses
)
else:
self.valid_handles = (
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
any(
baseClass in self.target_handle.inputTypes
for baseClass in self.source_handle.baseClasses
)
or self.target_handle.type in self.source_handle.baseClasses
)
if not self.valid_handles:
logger.debug(self.source_handle)
logger.debug(self.target_handle)
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
raise ValueError(
f"Edge between {source.vertex_type} and {target.vertex_type} "
f"has invalid handles"
)
def __setstate__(self, state):
self.source_id = state["source_id"]
@ -76,7 +87,11 @@ class Edge:
# Both lists contain strings and sometimes a string contains the value we are
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
# so we need to check if any of the strings in source_types is in target_reqs
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
self.valid = any(
output in target_req
for output in self.source_types
for target_req in self.target_reqs
)
# Get what type of input the target node is expecting
self.matched_type = next(
@ -87,7 +102,10 @@ class Edge:
if no_matched_type:
logger.debug(self.source_types)
logger.debug(self.target_reqs)
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has no matched type")
raise ValueError(
f"Edge between {source.vertex_type} and {target.vertex_type} "
f"has no matched type"
)
def __repr__(self) -> str:
return (
@ -98,8 +116,12 @@ class Edge:
def __hash__(self) -> int:
return hash(self.__repr__())
def __eq__(self, __value: object) -> bool:
return self.__repr__() == __value.__repr__() if isinstance(__value, Edge) else False
def __eq__(self, __o: object) -> bool:
# Create a better way to compare edges
return (
self._source_handle == __o._source_handle
and self._target_handle == __o._target_handle
)
class ContractEdge(Edge):
@ -156,7 +178,9 @@ class ContractEdge(Edge):
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
def log_transaction(
edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None
):
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)

View file

@ -3,8 +3,6 @@ from collections import defaultdict, deque
from typing import TYPE_CHECKING, Dict, Generator, List, Optional, Type, Union
from langchain.chains.base import Chain
from loguru import logger
from langflow.graph.edge.base import ContractEdge
from langflow.graph.graph.constants import lazy_load_vertex_dict
from langflow.graph.graph.state_manager import GraphStateManager
@ -22,6 +20,7 @@ from langflow.graph.vertex.types import (
from langflow.interface.tools.constants import FILE_TOOLS
from langflow.schema import Record
from langflow.utils import payload
from loguru import logger
if TYPE_CHECKING:
from langflow.graph.schema import ResultData
@ -278,7 +277,7 @@ class Graph:
self.edges = new_edges
def vertex_data_is_identical(self, vertex: Vertex, other_vertex: Vertex) -> bool:
data_is_equivalent = vertex.__repr__() == other_vertex.__repr__()
data_is_equivalent = vertex == other_vertex
if not data_is_equivalent:
return False
return self.vertex_edges_are_identical(vertex, other_vertex)
@ -304,40 +303,52 @@ class Graph:
# Find vertices that are in self but not in other (removed vertices)
removed_vertex_ids = existing_vertex_ids - other_vertex_ids
# Update existing vertices that have changed
for vertex_id in existing_vertex_ids.intersection(other_vertex_ids):
self_vertex = self.get_vertex(vertex_id)
other_vertex = other.get_vertex(vertex_id)
if not self.vertex_data_is_identical(self_vertex, other_vertex):
self_vertex._data = other_vertex._data
self_vertex._parse_data()
# Now we update the edges of the vertex
self.update_edges_from_vertex(self_vertex, other_vertex)
self_vertex.params = {}
self_vertex._build_params()
self_vertex.graph = self
# If the vertex is pinned, we don't want
# to reset the results nor the _built attribute
if not self_vertex.pinned:
self_vertex._built = False
self_vertex.result = None
self_vertex.artifacts = {}
self_vertex.set_top_level(self.top_level_vertices)
self.reset_all_edges_of_vertex(self_vertex)
# Remove vertices
# Remove vertices that are not in the other graph
for vertex_id in removed_vertex_ids:
self.remove_vertex(vertex_id)
# Add new vertices
for vertex_id in new_vertex_ids:
new_vertex = other.get_vertex(vertex_id)
new_vertex.graph = self
self._add_vertex(new_vertex)
# Update existing vertices that have changed
for vertex_id in existing_vertex_ids.intersection(other_vertex_ids):
self_vertex = self.get_vertex(vertex_id)
other_vertex = other.get_vertex(vertex_id)
# If the vertices are not identical, update the vertex
if not self.vertex_data_is_identical(self_vertex, other_vertex):
self.update_vertex_from_another(self_vertex, other_vertex)
self.build_graph_maps()
self.increment_update_count()
return self
def update_vertex_from_another(self, vertex: Vertex, other_vertex: Vertex) -> None:
"""
Updates a vertex from another vertex.
Args:
vertex (Vertex): The vertex to be updated.
other_vertex (Vertex): The vertex to update from.
"""
vertex._data = other_vertex._data
vertex._parse_data()
# Now we update the edges of the vertex
self.update_edges_from_vertex(vertex, other_vertex)
vertex.params = {}
vertex._build_params()
vertex.graph = self
# If the vertex is pinned, we don't want
# to reset the results nor the _built attribute
if not vertex.pinned:
vertex._built = False
vertex.result = None
vertex.artifacts = {}
vertex.set_top_level(self.top_level_vertices)
self.reset_all_edges_of_vertex(vertex)
def reset_all_edges_of_vertex(self, vertex: Vertex) -> None:
"""Resets all the edges of a vertex."""
for edge in vertex.edges:
@ -666,18 +677,42 @@ class Graph:
"""Cuts the graph up to a given vertex and sorts the resulting subgraph."""
# Initial setup
visited = set() # To keep track of visited vertices
excluded = set() # To keep track of vertices that should be excluded
stack = [vertex_id] # Use a list as a stack for DFS
def get_successors(vertex, recursive=True):
# Recursively get the successors of the current vertex
successors = vertex.successors
if not successors:
return []
successors_result = []
for successor in successors:
# Just return a list of successors
if recursive:
next_successors = get_successors(successor)
successors_result.extend(next_successors)
successors_result.append(successor)
return successors_result
# DFS to collect all vertices that can reach the specified vertex
while stack:
current_id = stack.pop()
if current_id not in visited:
if current_id not in visited and current_id not in excluded:
visited.add(current_id)
current_vertex = self.get_vertex(current_id)
# Assuming get_predecessors is a method that returns all vertices with edges to current_vertex
for predecessor in current_vertex.predecessors:
stack.append(predecessor.id)
if current_id == vertex_id:
# We should add to visited all the vertices that are successors of the current vertex
# and their successors and so on
for successor in current_vertex.successors:
excluded.add(successor.id)
all_successors = get_successors(successor)
for successor in all_successors:
excluded.add(successor.id)
# Filter the original graph's vertices and edges to keep only those in `visited`
vertices_to_keep = [self.get_vertex(vid) for vid in visited]

View file

@ -2,7 +2,17 @@ import ast
import inspect
import types
from enum import Enum
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional
from typing import (
TYPE_CHECKING,
Any,
AsyncIterator,
Callable,
Coroutine,
Dict,
Iterator,
List,
Optional,
)
from loguru import logger
@ -503,9 +513,12 @@ class Vertex:
self.params[key] = []
for node in nodes:
built = await node.get_result(requester=self, user_id=user_id)
# Weird check to see if the params[key] is a list
# because sometimes it is a Record and breaks the code
if not isinstance(self.params[key], list):
self.params[key] = [self.params[key]]
if isinstance(built, list):
if key not in self.params:
self.params[key] = []
self.params[key].extend(built)
else:
try:
@ -517,6 +530,7 @@ class Vertex:
logger.exception(e)
raise ValueError(
f"Params {key} ({self.params[key]}) is not a list and cannot be extended with {built}"
f"Error building node {self.display_name}: {str(e)}"
) from e
def _handle_func(self, key, result):
@ -587,6 +601,14 @@ class Vertex:
message += " Make sure your build method returns a component."
logger.warning(message)
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
if self.display_name in ["Text Output"]:
raise ValueError(
f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
)
raise ValueError(
f"{self.display_name}: You are trying to stream to a non-streamable component."
)
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
self._built = False
@ -662,7 +684,15 @@ class Vertex:
def __eq__(self, __o: object) -> bool:
try:
return self.id == __o.id if isinstance(__o, Vertex) else False
if not isinstance(__o, Vertex):
return False
# We should create a more robust comparison
# for the Vertex class
ids_are_equal = self.id == __o.id
# self._data is a dict and we need to compare them
# to check if they are equal
data_are_equal = self.data == __o.data
return ids_are_equal and data_are_equal
except AttributeError:
return False

View file

@ -0,0 +1,3 @@
from .record import docs_to_records, records_to_text
__all__ = ["docs_to_records", "records_to_text"]

View file

@ -0,0 +1,37 @@
from langchain_core.documents import Document
from langflow.schema import Record
def docs_to_records(documents: list[Document]) -> list[Record]:
"""
Converts a list of Documents to a list of Records.
Args:
documents (list[Document]): The list of Documents to convert.
Returns:
list[Record]: The converted list of Records.
"""
return [Record.from_document(document) for document in documents]
def records_to_text(template: str, records: list[Record]) -> list[str]:
"""
Converts a list of Records to a list of texts.
Args:
records (list[Record]): The list of Records to convert.
Returns:
list[str]: The converted list of texts.
"""
if isinstance(records, Record):
records = [records]
# Check if there are any format strings in the template
formated_records = [
template.format(text=record.text, data=record.data, **record.data)
for record in records
]
return "\n".join(formated_records)

View file

@ -15,6 +15,7 @@ from uuid import UUID
import yaml
from cachetools import TTLCache, cachedmethod
from langchain_core.documents import Document
from pydantic import BaseModel
from sqlmodel import select
from langflow.interface.custom.code_parser.utils import (
@ -144,29 +145,48 @@ class CustomComponent(Component):
self, data: Any, text_key: str = "text", data_key: str = "data"
) -> List[Record]:
"""
Convert data into a list of records.
Converts input data into a list of Record objects.
Args:
data (Any): The input data to be converted.
text_key (str, optional): The key to extract the text from a dictionary item. Defaults to "text".
data_key (str, optional): The key to extract the data from a dictionary item. Defaults to "data".
data (Any): The input data to be converted. It can be a single item or a sequence of items.
If the input data is a Langchain Document, text_key and data_key are ignored.
text_key (str, optional): The key to access the text value in each item. Defaults to "text".
data_key (str, optional): The key to access the data value in each item. Defaults to "data".
Returns:
List[dict]: A list of records, where each record is a dictionary with 'text' and 'data' keys.
List[Record]: A list of Record objects.
Raises:
ValueError: If the input data is not of a valid type or if the specified keys are not found in the data.
"""
records = []
if not isinstance(data, Sequence):
data = [data]
for item in data:
if isinstance(item, str):
records.append(Record(text=item))
if isinstance(item, Document):
item = {"text": item.page_content, "data": item.metadata}
elif isinstance(item, BaseModel):
model_dump = item.model_dump()
if text_key not in model_dump:
raise ValueError(f"Key '{text_key}' not found in BaseModel item.")
if data_key not in model_dump:
raise ValueError(f"Key '{data_key}' not found in BaseModel item.")
item = {"text": model_dump[text_key], "data": model_dump[data_key]}
elif isinstance(item, str):
item = {"text": item, "data": {}}
elif isinstance(item, dict):
records.append(Record(text=item.get(text_key), data=item.get(data_key)))
elif isinstance(item, Document):
records.append(Record(text=item.page_content, data=item.metadata))
if text_key not in item:
raise ValueError(f"Key '{text_key}' not found in dictionary item.")
if data_key not in item:
raise ValueError(f"Key '{data_key}' not found in dictionary item.")
item = {"text": item[text_key], "data": item[data_key]}
else:
raise ValueError(f"Invalid data type: {type(item)}")
records.append(Record(**item))
return records
def create_references_from_records(

View file

@ -7,8 +7,6 @@ from typing import Any, Dict, List, Optional, Union
from uuid import UUID
from fastapi import HTTPException
from loguru import logger
from langflow.field_typing.range_spec import RangeSpec
from langflow.interface.custom.attributes import ATTR_FUNC_MAPPING
from langflow.interface.custom.code_parser.utils import extract_inner_type
@ -25,6 +23,7 @@ from langflow.template.frontend_node.custom_components import (
)
from langflow.utils import validate
from langflow.utils.util import get_base_classes
from loguru import logger
def add_output_types(
@ -86,6 +85,8 @@ def add_base_classes(
)
base_classes = get_base_classes(return_type_instance)
if return_type_instance == str:
base_classes.append("Text")
for base_class in base_classes:
frontend_node.add_base_class(base_class)

View file

@ -1,14 +1,14 @@
import base64
import json
import os
from io import BytesIO
import re
from io import BytesIO
import yaml
from langchain.base_language import BaseLanguageModel
from PIL.Image import Image
from loguru import logger
from PIL.Image import Image
from langflow.services.chat.config import ChatConfig
from langflow.services.deps import get_settings_service
@ -43,7 +43,9 @@ def try_setting_streaming_options(langchain_object):
llm = None
if hasattr(langchain_object, "llm"):
llm = langchain_object.llm
elif hasattr(langchain_object, "llm_chain") and hasattr(langchain_object.llm_chain, "llm"):
elif hasattr(langchain_object, "llm_chain") and hasattr(
langchain_object.llm_chain, "llm"
):
llm = langchain_object.llm_chain.llm
if isinstance(llm, BaseLanguageModel):
@ -56,8 +58,37 @@ def try_setting_streaming_options(langchain_object):
def extract_input_variables_from_prompt(prompt: str) -> list[str]:
"""Extract input variables from prompt."""
return re.findall(r"{(.*?)}", prompt)
variables = []
remaining_text = prompt
# Pattern to match single {var} and double {{var}} braces.
pattern = r"\{\{(.*?)\}\}|\{([^{}]+)\}"
while True:
match = re.search(pattern, remaining_text)
if not match:
break
# Extract the variable name from either the single or double brace match
if match.group(1): # Match found in double braces
variable_name = (
"{{" + match.group(1) + "}}"
) # Re-add single braces for JSON strings
else: # Match found in single braces
variable_name = match.group(2)
if variable_name is not None:
# This means there is a match
# but there is nothing inside the braces
variables.append(variable_name)
# Remove the matched text from the remaining_text
start, end = match.span()
remaining_text = remaining_text[:start] + remaining_text[end:]
# Proceed to the next match until no more matches are found
# No need to compare remaining "{}" instances because we are re-adding braces for JSON compatibility
return variables
def setup_llm_caching():
@ -73,11 +104,14 @@ def setup_llm_caching():
def set_langchain_cache(settings):
from langchain.globals import set_llm_cache
from langflow.interface.importing.utils import import_class
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
try:
cache_class = import_class(f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}")
cache_class = import_class(
f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}"
)
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
set_llm_cache(cache_class())

View file

@ -1,9 +1,10 @@
from typing import Optional, Union
from loguru import logger
from langflow.schema import Record
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import MessageModel
from loguru import logger
def get_messages(

View file

@ -0,0 +1,3 @@
from .schema import Record
__all__ = ["Record"]

View file

@ -57,14 +57,3 @@ class Record(BaseModel):
return self.text
def docs_to_records(documents: list[Document]) -> list[Record]:
"""
Converts a list of Documents to a list of Records.
Args:
documents (list[Document]): The list of Documents to convert.
Returns:
list[Record]: The converted list of Records.
"""
return [Record.from_document(document) for document in documents]

View file

@ -36,7 +36,10 @@ class DatabaseService(Service):
def _create_engine(self) -> "Engine":
"""Create the engine for the database."""
settings_service = get_settings_service()
if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"):
if (
settings_service.settings.DATABASE_URL
and settings_service.settings.DATABASE_URL.startswith("sqlite")
):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
@ -48,7 +51,9 @@ class DatabaseService(Service):
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None: # If an exception has been raised
logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}")
logger.error(
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
)
self._session.rollback()
else:
self._session.commit()
@ -65,7 +70,9 @@ class DatabaseService(Service):
settings_service = get_settings_service()
if settings_service.auth_settings.AUTO_LOGIN:
with Session(self.engine) as session:
flows = session.exec(select(models.Flow).where(models.Flow.user_id is None)).all()
flows = session.exec(
select(models.Flow).where(models.Flow.user_id is None)
).all()
if flows:
logger.debug("Migrating flows to default superuser")
username = settings_service.auth_settings.SUPERUSER
@ -95,7 +102,9 @@ class DatabaseService(Service):
expected_columns = list(model.model_fields.keys())
try:
available_columns = [col["name"] for col in inspector.get_columns(table)]
available_columns = [
col["name"] for col in inspector.get_columns(table)
]
except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table}")
return False
@ -152,14 +161,16 @@ class DatabaseService(Service):
try:
command.check(alembic_cfg)
except Exception as exc:
if isinstance(exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)):
if isinstance(
exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)
):
command.upgrade(alembic_cfg, "head")
time.sleep(3)
try:
command.check(alembic_cfg)
except util.exc.AutogenerateDiffsDetected as e:
logger.error("AutogenerateDiffsDetected: {exc}")
logger.error(f"AutogenerateDiffsDetected: {exc}")
if not fix:
raise RuntimeError(
"Something went wrong running migrations. Please, run `langflow migration --fix`"
@ -188,7 +199,10 @@ class DatabaseService(Service):
# We will check that all models are in the database
# and that the database is up to date with all columns
sql_models = [models.Flow, models.User, models.ApiKey]
return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models]
return [
TableResults(sql_model.__tablename__, self.check_table(sql_model))
for sql_model in sql_models
]
def check_table(self, model):
results = []
@ -197,7 +211,9 @@ class DatabaseService(Service):
expected_columns = list(model.__fields__.keys())
available_columns = []
try:
available_columns = [col["name"] for col in inspector.get_columns(table_name)]
available_columns = [
col["name"] for col in inspector.get_columns(table_name)
]
results.append(Result(name=table_name, type="table", success=True))
except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table_name}")
@ -228,7 +244,9 @@ class DatabaseService(Service):
try:
table.create(self.engine, checkfirst=True)
except OperationalError as oe:
logger.warning(f"Table {table} already exists, skipping. Exception: {oe}")
logger.warning(
f"Table {table} already exists, skipping. Exception: {oe}"
)
except Exception as exc:
logger.error(f"Error creating table {table}: {exc}")
raise RuntimeError(f"Error creating table {table}") from exc
@ -240,7 +258,9 @@ class DatabaseService(Service):
if table not in table_names:
logger.error("Something went wrong creating the database and tables.")
logger.error("Please check your database settings.")
raise RuntimeError("Something went wrong creating the database and tables.")
raise RuntimeError(
"Something went wrong creating the database and tables."
)
logger.debug("Database and tables created successfully")

View file

@ -10,7 +10,9 @@ if TYPE_CHECKING:
class TransactionModel(BaseModel):
id: Optional[int] = Field(default=None, alias="id")
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
timestamp: Optional[datetime] = Field(
default_factory=datetime.now, alias="timestamp"
)
source: str
target: str
target_args: dict
@ -51,8 +53,12 @@ class MessageModel(BaseModel):
@classmethod
def from_record(cls, record: "Record"):
# first check if the record has all the required fields
if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
if not record.data or (
"sender" not in record.data and "sender_name" not in record.data
):
raise ValueError(
"The record does not have the required fields 'sender' and 'sender_name' in the data."
)
return cls(
sender=record.data["sender"],
sender_name=record.data["sender_name"],

View file

@ -24,3 +24,7 @@ yarn-error.log*
/test-results/
/playwright-report/*/
/playwright/.cache/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -48,6 +48,7 @@
"lodash": "^4.17.21",
"lucide-react": "^0.331.0",
"moment": "^2.29.4",
"playwright": "^1.42.0",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-cookie": "^4.1.1",
@ -77,7 +78,7 @@
"zustand": "^4.4.7"
},
"devDependencies": {
"@playwright/test": "^1.41.2",
"@playwright/test": "^1.42.0",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9",
@ -1492,12 +1493,12 @@
}
},
"node_modules/@playwright/test": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
"integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.0.tgz",
"integrity": "sha512-2k1HzC28Fs+HiwbJOQDUwrWMttqSLUVdjCqitBOjdCD0svWOMQUVqrXX6iFD7POps6xXAojsX/dGBpKnjZctLA==",
"dev": true,
"dependencies": {
"playwright": "1.41.2"
"playwright": "1.42.0"
},
"bin": {
"playwright": "cli.js"
@ -8719,12 +8720,11 @@
}
},
"node_modules/playwright": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
"integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
"dev": true,
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz",
"integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==",
"dependencies": {
"playwright-core": "1.41.2"
"playwright-core": "1.42.0"
},
"bin": {
"playwright": "cli.js"
@ -8737,10 +8737,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
"integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
"dev": true,
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz",
"integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==",
"bin": {
"playwright-core": "cli.js"
},

View file

@ -43,6 +43,7 @@
"lodash": "^4.17.21",
"lucide-react": "^0.331.0",
"moment": "^2.29.4",
"playwright": "^1.42.0",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-cookie": "^4.1.1",
@ -99,7 +100,7 @@
},
"proxy": "http://127.0.0.1:7860",
"devDependencies": {
"@playwright/test": "^1.41.2",
"@playwright/test": "^1.42.0",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9",

View file

@ -10,9 +10,6 @@
<li>
<a href="./e2e/index.html">e2e report</a>
</li>
<li>
<a href="./onlyFront/index.html">frontEnd Only report</a>
</li>
</ul>
</body>
</html>

View file

@ -27,8 +27,16 @@ terminate_process_by_port() {
echo "Process terminated."
}
delete_temp() {
cd ../../
echo "Deleting temp database"
rm temp
echo "Temp database deleted."
}
# Trap signals to ensure cleanup on script termination
trap 'terminate_process_by_port 7860; terminate_process_by_port 3000' EXIT
trap 'terminate_process_by_port 7860; terminate_process_by_port 3000; delete_temp' EXIT
# install playwright if there is not installed yet
npx playwright install
@ -42,26 +50,16 @@ make frontend &
# Give some time for the frontend to start (adjust sleep duration as needed)
sleep 10
# Navigate to the test directory
cd src/frontend
# Run frontend only Playwright tests with or without UI based on the --ui flag
if [ "$ui" = true ]; then
PLAYWRIGHT_HTML_REPORT=playwright-report/onlyFront npx playwright test tests/onlyFront --ui --project=chromium
else
PLAYWRIGHT_HTML_REPORT=playwright-report/onlyFront npx playwright test tests/onlyFront --project=chromium
fi
# Navigate back to the project root directory
cd ../../
#install backend
poetry install --extras deploy
# Start the backend using 'make backend' in the background
make backend &
LANGFLOW_DATABASE_URL=sqlite:///./temp LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser &
# Give some time for the backend to start (adjust sleep duration as needed)
sleep 25
# Navigate back to the test directory
# Navigate to the test directory
cd src/frontend
# Run Playwright tests with or without UI based on the --ui flag

View file

@ -212,7 +212,7 @@ export default function ParameterComponent({
{item.display_name === "" ? "" : " - "}
{item.display_name.split(", ").length > 2
? item.display_name.split(", ").map((el, index) => (
<React.Fragment key={el + index}>
<React.Fragment key={el + name}>
<span>
{index ===
item.display_name.split(", ").length - 1
@ -229,7 +229,7 @@ export default function ParameterComponent({
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, index) => (
<React.Fragment key={el + index}>
<React.Fragment key={el + name}>
<span>
{index === item.type.split(", ").length - 1
? el
@ -409,7 +409,7 @@ export default function ParameterComponent({
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
<InputComponent
id={"input-" + index}
id={"input-" + name}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
@ -432,11 +432,12 @@ export default function ParameterComponent({
) : left === true && type === "bool" ? (
<div className="mt-2 w-full">
<ToggleShadComponent
id={"toggle-" + index}
id={"toggle-" + name}
disabled={disabled}
enabled={data.node?.template[name].value ?? false}
setEnabled={handleOnNewValue}
size="large"
editNode={false}
/>
</div>
) : left === true && type === "float" ? (
@ -458,7 +459,7 @@ export default function ParameterComponent({
options={data.node.template[name].options}
onSelect={handleOnNewValue}
value={data.node.template[name].value ?? "Choose an option"}
id={"dropdown-" + index}
id={"dropdown-" + name}
/>
</div>
{data.node?.template[name].refresh && (
@ -486,7 +487,7 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-" + index}
id={"code-input-" + name}
/>
</div>
) : left === true && type === "file" ? (
@ -507,7 +508,7 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"int-input-" + index}
id={"int-input-" + name}
/>
</div>
) : left === true && type === "prompt" ? (
@ -520,8 +521,8 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"prompt-input-" + index}
data-testid={"prompt-input-" + index}
id={"prompt-input-" + name}
data-testid={"prompt-input-" + name}
/>
</div>
) : left === true && type === "NestedDict" ? (

View file

@ -49,8 +49,10 @@ export default function AccordionComponent({
>
{trigger}
</AccordionTrigger>
<AccordionContent className="AccordionContent flex flex-col">
{children}
<AccordionContent>
<div className="AccordionContent flex flex-col">
{children}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>

View file

@ -17,7 +17,7 @@ export default function IOOutputView({
case "TextOutput":
return (
<Textarea
className="w-full custom-scroll"
className="w-full h-full custom-scroll"
placeholder={"Empty"}
// update to real value on flowPool
value={
@ -31,7 +31,7 @@ export default function IOOutputView({
default:
return (
<Textarea
className="w-full custom-scroll"
className="w-full h-full custom-scroll"
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"]}
onChange={(e) => {

View file

@ -12,6 +12,7 @@ import { cn } from "../../utils/utils";
import AccordionComponent from "../AccordionComponent";
import IOInputField from "../IOInputField";
import IOOutputView from "../IOOutputView";
import ShadTooltip from "../ShadTooltipComponent";
import IconComponent from "../genericIconComponent";
import NewChatView from "../newChatView";
import { Badge } from "../ui/badge";
@ -213,9 +214,16 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{output.id}
</Badge>
<ShadTooltip
content={output.id}
styleClasses="z-50"
>
<div>
<Badge variant="gray" size="md">
{output.displayName}
</Badge>
</div>
</ShadTooltip>
{haveChat && (
<div
className="-mb-1 pr-4"
@ -272,7 +280,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
</button>
{selectedViewField.type}
</div>
<div className="h-full">
<div className="h-full w-full">
{inputs.some(
(input) => input.id === selectedViewField.id
) ? (
@ -308,28 +316,30 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
<div className="absolute bottom-8 right-8"></div>
)}
</div>
{!haveChat && (
<div className="flex w-full justify-end pt-6">
<Button
variant={"outline"}
className="flex gap-2 px-3"
onClick={() => sendMessage(1)}
>
<IconComponent
name={isBuilding ? "Loader2" : "Play"}
className={cn(
"h-4 w-4",
isBuilding
? "animate-spin"
: "fill-current text-medium-indigo"
)}
/>
Run Flow
</Button>
</div>
)}
</div>
</BaseModal.Content>
<BaseModal.Footer>
{!haveChat && (
<div className="flex w-full justify-end pt-6">
<Button
variant={"outline"}
className="flex gap-2 px-3"
onClick={() => sendMessage(1)}
>
<IconComponent
name={isBuilding ? "Loader2" : "Play"}
className={cn(
"h-4 w-4",
isBuilding
? "animate-spin"
: "fill-current text-medium-indigo"
)}
/>
Run Flow
</Button>
</div>
)}
</BaseModal.Footer>
</BaseModal>
);
}

View file

@ -126,7 +126,7 @@ export default function CodeTabsComponent({
<Tabs
value={activeTab}
className={
"api-modal-tabs " +
"api-modal-tabs m-0 inset-0 " +
(isMessage ? "dark " : "") +
(dark && isMessage ? "bg-background" : "")
}
@ -166,7 +166,7 @@ export default function CodeTabsComponent({
) : (
<IconComponent name="Clipboard" className="h-4 w-4" />
)}
{isCopied ? "Copied!" : "Copy code"}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"

View file

@ -91,6 +91,7 @@ export default function InputComponent({
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
data-testid={editNode ? id + "-edit" : id}
/>
)}
{password && (

View file

@ -45,6 +45,7 @@ export default function IntComponent({
onChange={(event) => {
onChange(event.target.value);
}}
data-testid={id}
/>
</div>
);

View file

@ -50,7 +50,7 @@ export function CodeBlock({ language, value }: Props): JSX.Element {
<div className="flex items-center">
<button className="code-block-modal-button" onClick={copyToClipboard}>
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
{isCopied ? "Copied!" : "Copy code"}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button className="code-block-modal-button" onClick={downloadAsFile}>
<IconDownload size={18} />

View file

@ -7,6 +7,7 @@ export default function ToggleShadComponent({
disabled,
size,
id = "",
editNode = false,
}: ToggleComponentType): JSX.Element {
let scaleX, scaleY;
switch (size) {
@ -31,6 +32,7 @@ export default function ToggleShadComponent({
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch
id={id}
data-testid={id}
style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}}

View file

@ -25,10 +25,18 @@ export const INVALID_CHARACTERS = [
/**
* regex to highlight the variables in the text
* @constant
* @constant regexHighlight
* @type {RegExp}
* @default
* @example
* {{variable}} or {variable}
* @returns {RegExp}
* @description
* This regex is used to highlight the variables in the text.
* It matches the variables in the text that are between {{}} or {}.
*/
export const regexHighlight = /\{([^}]+)\}/g;
export const regexHighlight = /\{\{(.*?)\}\}|\{([^{}]+)\}/g;
export const specialCharsRegex = /[!@#$%^&*()\-_=+[\]{}|;:'",.<>/?\\`´]/;
export const programmingLanguages: languageMap = {
@ -708,8 +716,8 @@ export const chatInputPlaceholder =
export const chatInputPlaceholderSend = "Send a message...";
export const editCodeTitle = "Edit Code";
export const myCollectionDesc =
"Manage your personal projects. Download or upload your collection.";
export const storeDesc = "Search flows and components from the community.";
"Manage your personal projects. Download and upload entire collections.";
export const storeDesc = "Explore community-shared flows and components.";
export const storeTitle = "Langflow Store";
export const noApi = "You don't have an API key. ";
export const insertApi = "Insert your Langflow API key.";

View file

@ -89,6 +89,7 @@ function ConfirmationModal({
setModalOpen(false);
onConfirm(index, data);
}}
data-testid="replace-button"
>
{confirmationText}
</Button>

View file

@ -35,9 +35,9 @@ export default function DeleteConfirmationModal({
</DialogTitle>
</DialogHeader>
<span>
Are you sure you want to delete this {description ?? "component"}?
Confirm deletion of {description ?? "component"}?
<br></br>
This action cannot be undone.
Note: This action is irreversible.
</span>
<DialogFooter>
<DialogClose>

View file

@ -242,7 +242,11 @@ const EditNodeModal = forwardRef(
/>
) : (
<InputComponent
id={"input-" + index}
id={
"input-" +
myData.node.template[templateParam]
.name
}
editNode={true}
disabled={disabled}
password={
@ -338,7 +342,10 @@ const EditNodeModal = forwardRef(
<div className="ml-auto">
{" "}
<ToggleShadComponent
id={"toggle-edit-" + index}
id={
"toggle-edit-" +
myData.node.template[templateParam].name
}
disabled={disabled}
enabled={
myData.node.template[templateParam]
@ -351,6 +358,7 @@ const EditNodeModal = forwardRef(
);
}}
size="small"
editNode={true}
/>
</div>
) : myData.node?.template[templateParam]
@ -391,14 +399,20 @@ const EditNodeModal = forwardRef(
myData.node.template[templateParam]
.value ?? "Choose an option"
}
id={"dropdown-edit-" + index}
id={
"dropdown-edit-" +
myData.node.template[templateParam].name
}
></Dropdown>
</div>
) : myData.node?.template[templateParam]
.type === "int" ? (
<div className="mx-auto">
<IntComponent
id={"edit-int-input-" + index}
id={
"edit-int-input-" +
myData.node.template[templateParam].name
}
disabled={disabled}
editNode={true}
value={
@ -493,7 +507,10 @@ const EditNodeModal = forwardRef(
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
id={"code-area-edit" + index}
id={
"code-area-edit" +
myData.node.template[templateParam].name
}
/>
</div>
) : myData.node?.template[templateParam]
@ -519,6 +536,7 @@ const EditNodeModal = forwardRef(
}}
disabled={disabled}
size="small"
editNode={true}
/>
</div>
</TableCell>

View file

@ -50,7 +50,7 @@ export function CodeBlock({ language, value }: Props): JSX.Element {
<div className="flex items-center">
<button className="code-block-modal-button" onClick={copyToClipboard}>
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
{isCopied ? "Copied!" : "Copy code"}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button className="code-block-modal-button" onClick={downloadAsFile}>
<IconDownload size={18} />

View file

@ -97,13 +97,23 @@ export default function GenericModal({
useEffect(() => {
setInputValue(value);
}, [value, modalOpen]);
const coloredContent = (inputValue || "")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(regexHighlight, varHighlightHTML({ name: "$1" }))
.replace(/\n/g, "<br />");
.replace(regexHighlight, (match, p1, p2) => {
// Decide which group was matched. If p1 is not undefined, do nothing
// we don't want to change the text. If p2 is not undefined, then we
// have a variable, so we should highlight it.
// ! This will not work with multiline or indented json yet
if (p1 !== undefined) {
return match;
} else if (p2 !== undefined) {
return varHighlightHTML({ name: p2 });
}
return match;
})
.replace(/\n/g, "<br />");
function getClassByNumberLength(): string {
let sumOfCaracteres: number = 0;
wordsHighlight.forEach((element) => {
@ -159,7 +169,7 @@ export default function GenericModal({
setIsEdit(true);
return setErrorData({
title: PROMPT_ERROR_ALERT,
list: [error.toString()],
list: [error.response.data.detail ?? ""],
});
});
}

View file

@ -48,7 +48,7 @@ export default function ShareModal({
const setErrorData = useAlertStore((state) => state.setErrorData);
const [internalOpen, internalSetOpen] = useState(children ? false : true);
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
const nameComponent = is_component ? "component" : "flow";
const nameComponent = is_component ? "component" : "workflow";
const [tags, setTags] = useState<{ id: string; name: string }[]>([]);
const [loadingTags, setLoadingTags] = useState<boolean>(false);
@ -179,7 +179,7 @@ export default function ShareModal({
</span>
<br></br>
<span className=" text-xs text-destructive ">
Warning: This action cannot be undone.
Note: This action is irreversible.
</span>
</ConfirmationModal.Content>
</ConfirmationModal>
@ -204,7 +204,7 @@ export default function ShareModal({
{children ? children : <></>}
</BaseModal.Trigger>
<BaseModal.Header
description={`Share your ${nameComponent} to the Langflow Store.`}
description={`Publish ${is_component ? "your component" : "workflow"} to the Langflow Store.`}
>
<span className="pr-2">Share</span>
<IconComponent
@ -235,12 +235,11 @@ export default function ShareModal({
}}
/>
<label htmlFor="public" className="export-modal-save-api text-sm ">
Make {nameComponent} public
Set {nameComponent} status to public
</label>
</div>
<span className=" text-xs text-destructive ">
<b>Warning:</b> API keys in designated fields are removed when
sharing.
<b>Attention:</b> API keys in specified fields are automatically removed upon sharing.
</span>
</BaseModal.Content>

View file

@ -386,7 +386,7 @@ export default function NodeToolbarComponent({
value={"Share"}
disabled={!hasApiKey || !validApiKey}
>
<div className="flex" data-testid="save-button-modal">
<div className="flex" data-testid="share-button-modal">
<IconComponent
name="Share3"
className="relative top-0.5 -m-1 mr-1 h-6 w-6"

View file

@ -223,10 +223,12 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
selection.nodes.some((node) => node.data.type === "ChatInput") &&
checkChatInput(get().nodes)
) {
useAlertStore.getState().setErrorData({
title: "Error pasting components",
list: ["You can only have one ChatInput component in the flow"],
});
useAlertStore
.getState()
.setErrorData({
title: "Error pasting components",
list: ["You can only have one ChatInput component in the flow"],
});
return;
}
let minimumX = Infinity;
@ -500,7 +502,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
updateVerticesBuild: (
vertices: {
verticesIds: string[];
verticesOrder: string[][];
verticesLayers: string[][];
runId: string;
} | null

View file

@ -27,6 +27,7 @@ export type ToggleComponentType = {
disabled: boolean | undefined;
size: "small" | "medium" | "large";
id?: string;
editNode?: boolean;
};
export type DropDownComponentType = {
value: string;

View file

@ -41,8 +41,8 @@ export type FlowPoolType = {
export type FlowStoreType = {
flowPool: FlowPoolType;
inputs: Array<{ type: string; id: string }>;
outputs: Array<{ type: string; id: string }>;
inputs: Array<{ type: string; id: string; displayName: string }>;
outputs: Array<{ type: string; id: string; displayName: string }>;
hasIO: boolean;
setFlowPool: (flowPool: FlowPoolType) => void;
addDataToFlowPool: (data: FlowPoolObjectType, nodeId: string) => void;
@ -101,7 +101,6 @@ export type FlowStoreType = {
vertices: {
verticesIds: string[];
verticesLayers: string[][];
verticesOrder: string[][];
runId: string;
} | null
) => void;
@ -110,7 +109,6 @@ export type FlowStoreType = {
verticesBuild: {
verticesIds: string[];
verticesLayers: string[][];
verticesOrder: string[][];
runId: string;
} | null;
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;

View file

@ -46,7 +46,6 @@ export async function updateVerticesOrder(
): Promise<{
verticesLayers: string[][];
verticesIds: string[];
verticesOrder: string[][];
runId: string;
}> {
return new Promise(async (resolve, reject) => {
@ -64,9 +63,8 @@ export async function updateVerticesOrder(
throw new Error("Invalid nodes");
}
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
const runId = orderResponse.data.run_id;
let verticesLayers: Array<Array<string>> = [];
const runId = orderResponse.data.run_id;
if (nodeId) {
for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i];
@ -84,15 +82,13 @@ export async function updateVerticesOrder(
} else {
verticesLayers = verticesOrder;
}
const verticesIds = verticesLayers.flat();
const verticesIds = verticesOrder.flat();
useFlowStore.getState().updateVerticesBuild({
verticesLayers,
verticesIds,
verticesOrder,
runId,
});
resolve({ verticesLayers, verticesIds, verticesOrder, runId });
resolve({ verticesLayers, verticesIds, runId });
});
}
@ -108,12 +104,13 @@ export async function buildVertices({
validateNodes,
}: BuildVerticesParams) {
let verticesBuild = useFlowStore.getState().verticesBuild;
if (!verticesBuild || nodeId) {
verticesBuild = await updateVerticesOrder(flowId, nodeId);
}
let verticesIds = verticesBuild?.verticesIds!;
const verticesIds = verticesBuild?.verticesIds!;
const verticesLayers = verticesBuild?.verticesLayers!;
const verticesOrder = verticesBuild?.verticesOrder!;
const runId = verticesBuild?.runId!;
let stop = false;
@ -121,7 +118,7 @@ export async function buildVertices({
if (validateNodes) {
try {
validateNodes(verticesOrder.flatMap((id) => id));
validateNodes(verticesIds);
} catch (e) {
return;
}
@ -242,6 +239,7 @@ async function buildVertex({
}) {
try {
const buildRes = await postBuildVertex(flowId, id, input_value);
const buildData: VertexBuildTypeAPI = buildRes.data;
if (onBuildUpdate) {
if (!buildData.valid) {

View file

@ -25,15 +25,23 @@ export function getTagsIds(
}
export function getInputsAndOutputs(nodes: Node[]) {
let inputs: { type: string; id: string }[] = [];
let outputs: { type: string; id: string }[] = [];
let inputs: { type: string; id: string; displayName: string }[] = [];
let outputs: { type: string; id: string; displayName: string }[] = [];
nodes.forEach((node) => {
const nodeData: NodeDataType = node.data as NodeDataType;
if (isOutputNode(nodeData)) {
outputs.push({ type: nodeData.type, id: nodeData.id });
outputs.push({
type: nodeData.type,
id: nodeData.id,
displayName: nodeData.node?.display_name ?? nodeData.id,
});
}
if (isInputNode(nodeData)) {
inputs.push({ type: nodeData.type, id: nodeData.id });
inputs.push({
type: nodeData.type,
id: nodeData.id,
displayName: nodeData.node?.display_name ?? nodeData.id,
});
}
});
return { inputs, outputs };

File diff suppressed because one or more lines are too long

View file

@ -2,19 +2,11 @@ import { test } from "@playwright/test";
test.describe("Auto_login tests", () => {
test("auto_login sign in", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.goto("http:localhost:3000/");
await page.locator('//*[@id="new-project-btn"]').click();
});
test("auto_login block_admin", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.goto("http:localhost:3000/");
await page.locator('//*[@id="new-project-btn"]').click();
await page.goto("http:localhost:3000/login");

View file

@ -117,7 +117,7 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="code-area-edit0"]').click();
await page.locator('//*[@id="code-area-editcode"]').click();
let value = await page.locator('//*[@id="codeValue"]').inputValue();
@ -132,6 +132,7 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="code-input-0"]').click();
await page.getByTestId("div-generic-node").click();
await page.getByTestId("code-button-modal").click();
}
});

View file

@ -4,21 +4,11 @@ import { readFileSync } from "fs";
test.describe("drag and drop test", () => {
/// <reference lib="dom"/>
test("drop collection", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/collection.json",
"tests/end-to-end/assets/collection.json",
"utf-8"
);
@ -42,9 +32,7 @@ test.describe("drag and drop test", () => {
}
);
await page
.getByTestId("edit-flow-button-e9ac1bdc-429b-475d-ac03-d26f9a2a3210-0")
.click();
await page.getByText("Edit Flow").first().click();
await page.waitForTimeout(2000);
const genericNoda = page.getByTestId("div-generic-node");

View file

@ -1,17 +1,6 @@
import { expect, test } from "@playwright/test";
test("dropDownComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/backend_12112023.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
@ -24,24 +13,24 @@ test("dropDownComponent", async ({ page }) => {
await page.waitForTimeout(2000);
await page
.getByTestId("llmsAmazon Bedrock")
.getByTestId("model_specsAmazon Bedrock")
.first()
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("dropdown-2-display").click();
await page.getByTestId("dropdown-model_id-display").click();
await page.getByTestId("ai21.j2-grande-instruct-0-option").click();
let value = await page.getByTestId("dropdown-2-display").innerText();
let value = await page.getByTestId("dropdown-model_id-display").innerText();
if (value !== "ai21.j2-grande-instruct") {
expect(false).toBeTruthy();
}
await page.getByTestId("dropdown-2-display").click();
await page.getByTestId("dropdown-model_id-display").click();
await page.getByTestId("ai21.j2-jumbo-instruct-1-option").click();
value = await page.getByTestId("dropdown-2-display").innerText();
value = await page.getByTestId("dropdown-model_id-display").innerText();
if (value !== "ai21.j2-jumbo-instruct") {
expect(false).toBeTruthy();
}
@ -49,14 +38,14 @@ test("dropDownComponent", async ({ page }) => {
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
value = await page.getByTestId("dropdown-edit-1-display").innerText();
value = await page.getByTestId("dropdown-edit-model_id-display").innerText();
if (value !== "ai21.j2-jumbo-instruct") {
expect(false).toBeTruthy();
}
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
@ -64,7 +53,7 @@ test("dropDownComponent", async ({ page }) => {
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
@ -74,7 +63,7 @@ test("dropDownComponent", async ({ page }) => {
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
@ -82,7 +71,7 @@ test("dropDownComponent", async ({ page }) => {
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
@ -90,17 +79,17 @@ test("dropDownComponent", async ({ page }) => {
await page.locator('//*[@id="showmodel_id"]').isChecked()
).toBeTruthy();
await page.getByTestId("dropdown-edit-1-display").click();
await page.getByTestId("dropdown-edit-model_id-display").click();
await page.getByTestId("ai21.j2-ultra-v1-5-option").click();
value = await page.getByTestId("dropdown-edit-1-display").innerText();
value = await page.getByTestId("dropdown-edit-model_id-display").innerText();
if (value !== "ai21.j2-ultra-v1") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="saveChangesBtn"]').click();
value = await page.getByTestId("dropdown-2-display").innerText();
value = await page.getByTestId("dropdown-model_id-display").innerText();
if (value !== "ai21.j2-ultra-v1") {
expect(false).toBeTruthy();
}

View file

@ -1,16 +1,6 @@
import { expect, test } from "@playwright/test";
test("FloatComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/backend_12112023.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
@ -23,7 +13,7 @@ test("FloatComponent", async ({ page }) => {
await page.waitForTimeout(2000);
await page
.locator('//*[@id="llmsLlamaCpp"]')
.locator('//*[@id="model_specsLlamaCpp"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
@ -102,18 +92,6 @@ test("FloatComponent", async ({ page }) => {
await page.locator('//*[@id="showmax_tokens"]').isChecked()
).toBeTruthy();
// showmetadata
await page.locator('//*[@id="showmetadata"]').click();
expect(
await page.locator('//*[@id="showmetadata"]').isChecked()
).toBeTruthy();
// showmodel_kwargs
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeTruthy();
// showmodel_path
await page.locator('//*[@id="showmodel_path"]').click();
expect(
@ -271,16 +249,6 @@ test("FloatComponent", async ({ page }) => {
await page.locator('//*[@id="showmax_tokens"]').isChecked()
).toBeFalsy();
// showmetadata
await page.locator('//*[@id="showmetadata"]').click();
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
// showmodel_kwargs
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeFalsy();
// showmodel_path
await page.locator('//*[@id="showmodel_path"]').click();
expect(

View file

@ -7,16 +7,6 @@ test.describe("Flow Page tests", () => {
}
test("save", async ({ page }) => {
await page.routeFromHAR("harFiles/backend_12112023.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -4,21 +4,11 @@ import { readFileSync } from "fs";
test.describe("group node test", () => {
/// <reference lib="dom"/>
test("group and ungroup updating values", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
await page.locator('//*[@id="new-project-btn"]').click();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/collection.json",
"tests/end-to-end/assets/flow_group_test.json",
"utf-8"
);
@ -26,7 +16,7 @@ test.describe("group node test", () => {
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "flowtest.json", {
const file = new File([data], "flow_group_test.json", {
type: "application/json",
});
dt.items.add(file);
@ -35,24 +25,27 @@ test.describe("group node test", () => {
page.waitForTimeout(2000);
// Now dispatch
await page.dispatchEvent(
'//*[@id="root"]/div/div[1]/div[2]/div[3]/div/div',
"//*[@id='react-flow-id']/div[1]/div[1]/div",
"drop",
{
dataTransfer,
}
);
await page
.getByTestId("edit-flow-button-e9ac1bdc-429b-475d-ac03-d26f9a2a3210-0")
.click();
await page.waitForTimeout(2000);
const genericNoda = page.getByTestId("div-generic-node");
const elementCount = await genericNoda.count();
if (elementCount > 0) {
expect(true).toBeTruthy();
}
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[2]/button[3]')
.click();
await page.getByTestId("title-Agent Initializer").click({
modifiers: ["Control"],
});
await page.getByTestId("title-PythonFunctionTool").click({
modifiers: ["Control"],
@ -61,10 +54,6 @@ test.describe("group node test", () => {
modifiers: ["Control"],
});
await page.getByTestId("title-AgentInitializer").click({
modifiers: ["Control"],
});
await page.getByRole("button", { name: "Group" }).click();
const textArea = page.getByTestId("div-textarea-description");

View file

@ -1,16 +1,6 @@
import { expect, test } from "@playwright/test";
test("InputComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
@ -28,12 +18,12 @@ test("InputComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.locator("#input-8").click();
await page.getByTestId("input-collection_name").click();
await page
.locator("#input-8")
.getByTestId("input-collection_name")
.fill("collection_name_test_123123123!@#$&*(&%$@");
let value = await page.locator("#input-8").inputValue();
let value = await page.getByTestId("input-collection_name").inputValue();
if (value != "collection_name_test_123123123!@#$&*(&%$@") {
expect(false).toBeTruthy();
@ -61,9 +51,9 @@ test("InputComponent", async ({ page }) => {
await page.locator('//*[@id="showchroma_server_host"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_http_port"]').click();
await page.locator('//*[@id="showchroma_server_port"]').click();
expect(
await page.locator('//*[@id="showchroma_server_http_port"]').isChecked()
await page.locator('//*[@id="showchroma_server_port"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').click();
@ -76,19 +66,11 @@ test("InputComponent", async ({ page }) => {
await page.locator('//*[@id="showcollection_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showpersist"]').click();
expect(await page.locator('//*[@id="showpersist"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showpersist_directory"]').click();
await page.locator('//*[@id="showindex_directory"]').click();
expect(
await page.locator('//*[@id="showpersist_directory"]').isChecked()
await page.locator('//*[@id="showindex_directory"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_cors_allow_origins"]').click();
expect(
await page
@ -106,9 +88,9 @@ test("InputComponent", async ({ page }) => {
await page.locator('//*[@id="showchroma_server_host"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showchroma_server_http_port"]').click();
await page.locator('//*[@id="showchroma_server_port"]').click();
expect(
await page.locator('//*[@id="showchroma_server_http_port"]').isChecked()
await page.locator('//*[@id="showchroma_server_port"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').click();
@ -116,33 +98,27 @@ test("InputComponent", async ({ page }) => {
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showpersist"]').click();
expect(await page.locator('//*[@id="showpersist"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showpersist_directory"]').click();
await page.locator('//*[@id="showindex_directory"]').click();
expect(
await page.locator('//*[@id="showpersist_directory"]').isChecked()
await page.locator('//*[@id="showindex_directory"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeFalsy();
let valueEditNode = await page.locator('//*[@id="input-5"]').inputValue();
let valueEditNode = await page
.getByTestId("input-collection_name-edit")
.inputValue();
if (valueEditNode != "collection_name_test_123123123!@#$&*(&%$@") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="input-5"]').click();
await page.getByTestId("input-collection_name-edit").click();
await page
.locator('//*[@id="input-5"]')
.getByTestId("input-collection_name-edit")
.fill("NEW_collection_name_test_123123123!@#$&*(&%$@");
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator("#input-8");
const plusButtonLocator = page.getByTestId("input-collection_name");
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
@ -159,7 +135,7 @@ test("InputComponent", async ({ page }) => {
await page.locator('//*[@id="saveChangesBtn"]').click();
let value = await page.locator("#input-8").inputValue();
let value = await page.getByTestId("input-collection_name").inputValue();
if (value != "NEW_collection_name_test_123123123!@#$&*(&%$@") {
expect(false).toBeTruthy();

View file

@ -1,17 +1,6 @@
import { expect, test } from "@playwright/test";
test("IntComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/backend_12112023.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
@ -30,21 +19,21 @@ test("IntComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="int-input-2"]').click();
await page.getByTestId("int-input-timeout").click();
await page
.locator('//*[@id="int-input-2"]')
.getByTestId("int-input-timeout")
.fill("123456789123456789123456789");
let value = await page.locator('//*[@id="int-input-2"]').inputValue();
let value = await page.getByTestId("int-input-timeout").inputValue();
if (value != "123456789123456789123456789") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="int-input-2"]').click();
await page.locator('//*[@id="int-input-2"]').fill("0");
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-timeout").fill("0");
value = await page.locator('//*[@id="int-input-2"]').inputValue();
value = await page.getByTestId("int-input-timeout").inputValue();
if (value != "0") {
expect(false).toBeTruthy();
@ -53,15 +42,15 @@ test("IntComponent", async ({ page }) => {
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
value = await page.locator('//*[@id="edit-int-input-2"]').inputValue();
value = await page.getByTestId("edit-int-input-timeout").inputValue();
if (value != "0") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="edit-int-input-2"]').click();
await page.getByTestId("edit-int-input-timeout").click();
await page
.locator('//*[@id="edit-int-input-2"]')
.getByTestId("edit-int-input-timeout")
.fill("123456789123456789123456789");
await page.locator('//*[@id="showheaders"]').click();
@ -81,7 +70,7 @@ test("IntComponent", async ({ page }) => {
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="int-input-2"]');
const plusButtonLocator = page.getByTestId("int-input-timeout");
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
@ -95,7 +84,7 @@ test("IntComponent", async ({ page }) => {
).toBeTruthy();
const valueEditNode = await page
.locator('//*[@id="edit-int-input-2"]')
.getByTestId("edit-int-input-timeout")
.inputValue();
if (valueEditNode != "123456789123456789123456789") {
@ -103,19 +92,19 @@ test("IntComponent", async ({ page }) => {
}
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="int-input-2"]').click();
await page.locator('//*[@id="int-input-2"]').fill("3");
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-timeout").fill("3");
let value = await page.locator('//*[@id="int-input-2"]').inputValue();
let value = await page.getByTestId("int-input-timeout").inputValue();
if (value != "3") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="int-input-2"]').click();
await page.locator('//*[@id="int-input-2"]').fill("-3");
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-timeout").fill("-3");
value = await page.locator('//*[@id="int-input-2"]').inputValue();
value = await page.getByTestId("int-input-timeout").inputValue();
if (value != "0") {
expect(false).toBeTruthy();

View file

@ -1,16 +1,6 @@
import { expect, test } from "@playwright/test";
test("KeypairListComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/backend_12112023.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -1,16 +1,6 @@
import { expect, test } from "@playwright/test";
test("NestedComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/backend_12112023.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
@ -31,75 +21,17 @@ test("NestedComponent", async ({ page }) => {
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
//showpool_threads
await page.locator('//*[@id="showpool_threads"]').click();
expect(
await page.locator('//*[@id="showindex_name"]').isChecked()
).toBeFalsy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
await page.locator('//*[@id="shownamespace"]').isChecked()
).toBeFalsy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
await page.locator('//*[@id="showpool_threads"]').isChecked()
).toBeTruthy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
//showtext_key
await page.locator('//*[@id="showtext_key"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeTruthy();
// showsearch_kwargs
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeTruthy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
await page.locator('//*[@id="showindex_name"]').isChecked()
).toBeTruthy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
await page.locator('//*[@id="shownamespace"]').isChecked()
).toBeTruthy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeFalsy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeFalsy();
// showsearch_kwargs
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeFalsy();
expect(await page.locator('//*[@id="showtext_key"]').isChecked()).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
@ -120,21 +52,14 @@ test("NestedComponent", async ({ page }) => {
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeTruthy();
).toBeFalsy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeTruthy();
// showsearch_kwargs
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeTruthy();
).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
@ -155,21 +80,14 @@ test("NestedComponent", async ({ page }) => {
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeFalsy();
).toBeTruthy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeFalsy();
// showsearch_kwargs
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeFalsy();
).toBeTruthy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
@ -190,21 +108,14 @@ test("NestedComponent", async ({ page }) => {
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeTruthy();
).toBeFalsy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeTruthy();
// showsearch_kwargs
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeTruthy();
).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
@ -223,6 +134,34 @@ test("NestedComponent", async ({ page }) => {
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeTruthy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeTruthy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
await page.locator('//*[@id="showindex_name"]').isChecked()
).toBeFalsy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
await page.locator('//*[@id="shownamespace"]').isChecked()
).toBeFalsy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeFalsy();
@ -234,7 +173,47 @@ test("NestedComponent", async ({ page }) => {
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeFalsy();
// showindex_name
await page.locator('//*[@id="showindex_name"]').click();
expect(
await page.locator('//*[@id="showindex_name"]').isChecked()
).toBeTruthy();
// shownamespace
await page.locator('//*[@id="shownamespace"]').click();
expect(
await page.locator('//*[@id="shownamespace"]').isChecked()
).toBeTruthy();
// showpinecone_api_key
await page.locator('//*[@id="showpinecone_api_key"]').click();
expect(
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
).toBeTruthy();
// showpinecone_env
await page.locator('//*[@id="showpinecone_env"]').click();
expect(
await page.locator('//*[@id="showpinecone_env"]').isChecked()
).toBeTruthy();
//showpool_threads
await page.locator('//*[@id="showpool_threads"]').click();
expect(
await page.locator('//*[@id="showpool_threads"]').isChecked()
).toBeFalsy();
//showtext_key
await page.locator('//*[@id="showtext_key"]').click();
expect(
await page.locator('//*[@id="showtext_key"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.getByTestId("div-dict-input").click();
});

View file

@ -8,25 +8,27 @@ test("PromptTemplateComponent", async ({ page }) => {
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("promptTemplate");
await page.getByPlaceholder("Search").fill("prompt");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="promptsPromptTemplate"]')
.locator('//*[@id="promptsPrompt"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("prompt-input-0").click();
await page.getByTestId("prompt-input-template").click();
// await page.getByTestId("edit-prompt-sanitized").click();
// await page.getByTestId("modal-title").click();
await page
.getByTestId("modal-prompt-input-0")
.getByTestId("modal-prompt-input-template")
.fill("{prompt} example {prompt1}");
let value = await page.getByTestId("modal-prompt-input-0").inputValue();
let value = await page
.getByTestId("modal-prompt-input-template")
.inputValue();
if (value != "{prompt} example {prompt1}") {
expect(false).toBeTruthy();
@ -45,35 +47,39 @@ test("PromptTemplateComponent", async ({ page }) => {
await page.getByTestId("genericModalBtnSave").click();
await page.getByTestId("div-textarea-prompt").click();
await page.getByTestId("text-area-modal").fill("prompt_value_!@#!@#");
await page.getByTestId("textarea-prompt").fill("prompt_value_!@#!@#");
value = await page.getByTestId("text-area-modal").inputValue();
value = await page.getByTestId("textarea-prompt").inputValue();
if (value != "prompt_value_!@#!@#") {
expect(false).toBeTruthy();
}
await page.getByTestId("genericModalBtnSave").click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("save-button-modal").click();
const replace = await page.getByTestId("replace-button");
if (replace) {
await page.getByTestId("replace-button").click();
}
await page.getByTestId("div-textarea-prompt1").click();
await page
.getByTestId("text-area-modal")
.getByTestId("textarea-prompt1")
.fill("prompt_name_test_123123!@#!@#");
value = await page.getByTestId("text-area-modal").inputValue();
value = await page.getByTestId("textarea-prompt1").inputValue();
if (value != "prompt_name_test_123123!@#!@#") {
expect(false).toBeTruthy();
}
value = await page.getByTestId("text-area-modal").inputValue();
value = await page.getByTestId("textarea-prompt1").inputValue();
if (value != "prompt_name_test_123123!@#!@#") {
expect(false).toBeTruthy();
}
await page.getByTestId("genericModalBtnSave").click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();

View file

@ -11,22 +11,12 @@ test.describe("save component tests", () => {
/// <reference lib="dom"/>
test("save group component tests", async ({ page }) => {
//make front work withoput backend
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
await page.locator('//*[@id="new-project-btn"]').click();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/collection.json",
"tests/end-to-end/assets/flow_group_test.json",
"utf-8"
);
@ -34,32 +24,32 @@ test.describe("save component tests", () => {
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "flowtest.json", {
const file = new File([data], "flow_group_test.json", {
type: "application/json",
});
dt.items.add(file);
return dt;
}, jsonContent);
page.waitForTimeout(2000);
// Now dispatch
await page.dispatchEvent(
'//*[@id="root"]/div/div[1]/div[2]/div[3]/div/div',
"//*[@id='react-flow-id']/div[1]/div[1]/div",
"drop",
{
dataTransfer,
}
);
await page
.getByTestId("edit-flow-button-e9ac1bdc-429b-475d-ac03-d26f9a2a3210-0")
.click();
await page.waitForTimeout(2000);
const genericNoda = page.getByTestId("div-generic-node");
const elementCount = await genericNoda.count();
if (elementCount > 0) {
expect(true).toBeTruthy();
}
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[2]/button[3]')
.click();
await page.getByTestId("title-PythonFunctionTool").click({
modifiers: ["Control"],
@ -68,7 +58,7 @@ test.describe("save component tests", () => {
modifiers: ["Control"],
});
await page.getByTestId("title-AgentInitializer").click({
await page.getByTestId("title-Agent Initializer").click({
modifiers: ["Control"],
});
@ -87,9 +77,13 @@ test.describe("save component tests", () => {
}
await page.getByTestId("title-Group").click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("save-button-modal").click();
await page.getByTestId("delete-button-modal").click();
await page.getByTestId("icon-SaveAll").click();
const replaceButton = page.getByTestId("replace-button");
if (replaceButton) {
await replaceButton.click();
}
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("group");

View file

@ -1,16 +1,6 @@
import { expect, test } from "@playwright/test";
test("ToggleComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
@ -38,29 +28,27 @@ test("ToggleComponent", async ({ page }) => {
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.getByTestId("toggle-load_hidden").click();
expect(await page.getByTestId("toggle-load_hidden").isChecked()).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.getByTestId("toggle-load_hidden").click();
expect(await page.getByTestId("toggle-load_hidden").isChecked()).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.getByTestId("toggle-load_hidden").click();
expect(await page.getByTestId("toggle-load_hidden").isChecked()).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.getByTestId("toggle-load_hidden").click();
expect(await page.getByTestId("toggle-load_hidden").isChecked()).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.getByTestId("toggle-load_hidden").click();
expect(await page.getByTestId("toggle-load_hidden").isChecked()).toBeFalsy();
await page.getByTestId("div-generic-node").click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
expect(
await page.locator('//*[@id="toggle-edit-1"]').isChecked()
).toBeFalsy();
expect(await page.getByTestId("toggle-load_hidden").isChecked()).toBeFalsy();
await page.locator('//*[@id="showglob"]').click();
expect(await page.locator('//*[@id="showglob"]').isChecked()).toBeFalsy();
@ -129,7 +117,7 @@ test("ToggleComponent", async ({ page }) => {
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="toggle-1"]');
const plusButtonLocator = page.getByTestId("toggle-load_hidden");
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
@ -145,24 +133,34 @@ test("ToggleComponent", async ({ page }) => {
).toBeTruthy();
expect(
await page.locator('//*[@id="toggle-edit-1"]').isChecked()
await page.getByTestId("toggle-edit-load_hidden").isChecked()
).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.getByTestId("toggle-load_hidden").click();
expect(
await page.getByTestId("toggle-load_hidden").isChecked()
).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.getByTestId("toggle-load_hidden").click();
expect(
await page.getByTestId("toggle-load_hidden").isChecked()
).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.getByTestId("toggle-load_hidden").click();
expect(
await page.getByTestId("toggle-load_hidden").isChecked()
).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.getByTestId("toggle-load_hidden").click();
expect(
await page.getByTestId("toggle-load_hidden").isChecked()
).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.getByTestId("toggle-load_hidden").click();
expect(
await page.getByTestId("toggle-load_hidden").isChecked()
).toBeTruthy();
}
});

View file

@ -1,121 +0,0 @@
import { test } from "@playwright/test";
test.describe("Login Tests", () => {
test("Login_Success", async ({ page }) => {
// await page.route("**/api/v1/login", async (route) => {
// const json = {
// access_token:
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWNlM2FkOS1iZTE2LTRiNjgtOGRhYi1hYjA4YTVjMmZjZTkiLCJleHAiOjE2OTUyNTIwNTh9.MBYFwMhTcZnsW_L7p4qavUhSDylCllJQWUCJdU1wX8o",
// refresh_token:
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWNlM2FkOS1iZTE2LTRiNjgtOGRhYi1hYjA4YTVjMmZjZTkiLCJ0eXBlIjoicmYiLCJleHAiOjE2OTUyNTI2NTh9.a4wL9-XK_zyTyrXduBFgCsODFXrqiByVr5HOeiCbiQA",
// token_type: "bearer",
// };
// await route.fulfill({ json });
// });
// await page.goto("http://localhost:3000/");
// await page.waitForURL("http://localhost:3000/login");
// await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
// await page.getByPlaceholder("Username").click();
// await page.getByPlaceholder("Username").fill("test");
// await page.getByPlaceholder("Password").click();
// await page.getByPlaceholder("Password").fill("test");
// await page.getByRole("button", { name: "Sign in" }).click();
// await page.getByRole("button", { name: "Community Examples" }).click();
// await page.waitForSelector(".community-pages-flows-panel");
// expect(
// await page
// .locator(".community-pages-flows-panel")
// .evaluate((el) => el.children)
// ).toBeTruthy();
// });
// test("Login Error", async ({ page }) => {
// await page.route("**/api/v1/login", async (route) => {
// const json = { detail: "Incorrect username or password" };
// await route.fulfill({ json, status: 401 });
// });
// await page.goto("http://localhost:3000/");
// await page.waitForURL("http://localhost:3000/login");
// await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
// await page.getByPlaceholder("Username").click();
// await page.getByPlaceholder("Username").fill("test");
// await page.getByPlaceholder("Password").click();
// await page.getByPlaceholder("Password").fill("test5");
// await page.getByRole("button", { name: "Sign in" }).click();
// await page.getByRole("heading", { name: "Error signing in" }).click();
// });
// test("Login create account wrong form", async ({ page }) => {
// const fullfillForm = async (username, password, confirmPassword) => {
// await page.getByPlaceholder("Username").click();
// await page.getByPlaceholder("Username").fill(username);
// await page.getByPlaceholder("Password", { exact: true }).click();
// await page.getByPlaceholder("Password", { exact: true }).fill(password);
// await page.getByPlaceholder("Confirm your password").click();
// await page
// .getByPlaceholder("Confirm your password")
// .fill(confirmPassword);
// };
// await page.goto("http://localhost:3000/");
// await page.waitForURL("http://localhost:3000/login");
// await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
// await page
// .getByRole("button", { name: "Don't have an account? Sign Up" })
// .click();
// await page.getByText("Sign up to Langflow").click();
// await page.goto("http://localhost:3000/signup");
// await page.getByText("Sign up to Langflow").click();
// await fullfillForm("name", "vazz", "vazz5");
// expect(
// await page.getByRole("button", { name: "Sign up" }).isDisabled()
// ).toBeTruthy();
// await fullfillForm("", "vazz", "vazz");
// expect(
// await page.getByRole("button", { name: "Sign up" }).isDisabled()
// ).toBeTruthy();
// await fullfillForm("name", "", "");
// expect(
// await page.getByRole("button", { name: "Sign up" }).isDisabled()
// ).toBeTruthy();
// await fullfillForm("", "", "");
// expect(
// await page.getByRole("button", { name: "Sign up" }).isDisabled()
// ).toBeTruthy();
// });
// test("Login create account success", async ({ page }) => {
// await page.route("**/api/v1/users/", async (route) => {
// const json = {
// id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
// username: "teste",
// profile_image: null,
// is_active: false,
// is_superuser: false,
// create_at: "2023-09-21T01:45:51.873303",
// updated_at: "2023-09-21T01:45:51.873305",
// last_login_at: null,
// };
// await route.fulfill({ json, status: 201 });
// });
// const submitForm = async (username, password, confirmPassword) => {
// await page.getByPlaceholder("Username").click();
// await page.getByPlaceholder("Username").fill(username);
// await page.getByPlaceholder("Password", { exact: true }).click();
// await page.getByPlaceholder("Password", { exact: true }).fill(password);
// await page.getByPlaceholder("Confirm your password").click();
// await page
// .getByPlaceholder("Confirm your password")
// .fill(confirmPassword);
// };
// await page.goto("http://localhost:3000/");
// await page.waitForURL("http://localhost:3000/login");
// await page.waitForURL("http://localhost:3000/login", { timeout: 100 });
// await page
// .getByRole("button", { name: "Don't have an account? Sign Up" })
// .click();
// await page.getByText("Sign up to Langflow").click();
// await page.goto("http://localhost:3000/signup");
// await page.getByText("Sign up to Langflow").click();
// await submitForm("teste", "pass", "pass");
// await page.getByRole("button", { name: "Sign up" }).click();
// await page.waitForURL("http://localhost:3000/login", { timeout: 1000 });
// await page.getByText("Account created! Await admin activation.").click();
});
});