Merge branch 'dev' into update_components

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-06-27 10:28:30 -07:00 • committed by GitHub
commit 724f325e08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
106 changed files with 2402 additions and 1322 deletions

View file

@ -276,15 +276,15 @@ async def webhook_run_flow(
# get all webhook components in the flow
webhook_components = get_all_webhook_components_in_flow(flow.data)
tweaks = {}
data_dict = await request.json()
for component in webhook_components:
tweaks[component["id"]] = {"data": data.decode() if isinstance(data, bytes) else data}
input_request = SimplifiedAPIRequest(
input_value=data_dict.get("input_value", ""),
input_type=data_dict.get("input_type", "chat"),
output_type=data_dict.get("output_type", "chat"),
input_value="",
input_type="chat",
output_type="chat",
tweaks=tweaks,
session_id=data_dict.get("session_id"),
session_id=None,
)
logger.debug("Starting background task")
background_tasks.add_task( # type: ignore

View file

@ -131,8 +131,8 @@ async def list_profile_pictures(storage_service: StorageService = Depends(get_st
people = await storage_service.list_files(flow_id=people_path) # type: ignore
space = await storage_service.list_files(flow_id=space_path) # type: ignore
files = [Path("People") / i for i in people]
files += [Path("Space") / i for i in space]
files = [f"People/{i}" for i in people]
files += [f"Space/{i}" for i in space]
return {"files": files}

View file

@ -1,5 +1,5 @@
from datetime import datetime, timezone
import re
from datetime import datetime, timezone
from typing import List
from uuid import UUID
@ -211,8 +211,7 @@ def update_flow(
if settings_service.settings.remove_api_keys:
flow_data = remove_api_keys(flow_data)
for key, value in flow_data.items():
if value is not None:
setattr(db_flow, key, value)
setattr(db_flow, key, value)
webhook_component = get_webhook_component_in_flow(db_flow.data)
db_flow.webhook = webhook_component is not None
db_flow.updated_at = datetime.now(timezone.utc)

View file

@ -1,6 +1,6 @@
from typing import Any
from langflow.custom import Component
from langflow.inputs.inputs import DictInput, SecretStrInput, MessageTextInput
from langflow.inputs.inputs import DictInput, SecretStrInput, MessageTextInput, DropdownInput
from langflow.template.field.base import Output
@ -10,32 +10,77 @@ class AstraVectorize(Component):
documentation: str = "https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html"
icon = "AstraDB"
VECTORIZE_PROVIDERS_MAPPING = {
"Azure OpenAI": ["azureOpenAI", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
"Hugging Face - Dedicated": ["huggingfaceDedicated", ["endpoint-defined-model"]],
"Hugging Face - Serverless": [
"huggingface",
[
"sentence-transformers/all-MiniLM-L6-v2",
"intfloat/multilingual-e5-large",
"intfloat/multilingual-e5-large-instruct",
"BAAI/bge-small-en-v1.5",
"BAAI/bge-base-en-v1.5",
"BAAI/bge-large-en-v1.5",
],
],
"Jina AI": [
"jinaAI",
[
"jina-embeddings-v2-base-en",
"jina-embeddings-v2-base-de",
"jina-embeddings-v2-base-es",
"jina-embeddings-v2-base-code",
"jina-embeddings-v2-base-zh",
],
],
"Mistral AI": ["mistral", ["mistral-embed"]],
"NVIDIA": ["nvidia", ["NV-Embed-QA"]],
"OpenAI": ["openai", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
"Upstage": ["upstageAI", ["solar-embedding-1-large"]],
"Voyage AI": [
"voyageAI",
["voyage-large-2-instruct", "voyage-law-2", "voyage-code-2", "voyage-large-2", "voyage-2"],
],
}
VECTORIZE_MODELS_STR = "\n\n".join(
[provider + ": " + (", ".join(models[1])) for provider, models in VECTORIZE_PROVIDERS_MAPPING.items()]
)
inputs = [
MessageTextInput(
DropdownInput(
name="provider",
display_name="Provider name",
info="The embedding provider to use.",
options=VECTORIZE_PROVIDERS_MAPPING.keys(),
value="",
),
MessageTextInput(
name="model_name",
display_name="Model name",
info="The embedding model to use.",
info=f"The embedding model to use for the selected provider. Each provider has a different set of models "
f"available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\n{VECTORIZE_MODELS_STR}",
required=True,
),
MessageTextInput(
name="api_key_name",
display_name="API Key name",
info="The name of the embeddings provider API key stored on Astra. If set, it will override the 'ProviderKey' in the authentication parameters.",
),
DictInput(
name="authentication",
display_name="Authentication",
info="Authentication parameters. Use the Astra Portal to add the embedding provider integration to your Astra organization.",
display_name="Authentication parameters",
is_list=True,
advanced=True,
),
SecretStrInput(
name="provider_api_key",
display_name="Provider API Key",
info="An alternative to the Astra Authentication that let you use directly the API key of the provider.",
advanced=True,
),
DictInput(
name="model_parameters",
display_name="Model parameters",
info="Additional model parameters.",
advanced=True,
is_list=True,
),
@ -45,12 +90,17 @@ class AstraVectorize(Component):
]
def build_options(self) -> dict[str, Any]:
provider_value = self.VECTORIZE_PROVIDERS_MAPPING[self.provider][0]
authentication = {**self.authentication}
api_key_name = self.api_key_name
if api_key_name:
authentication["providerKey"] = api_key_name
return {
# must match exactly astra CollectionVectorServiceOptions
"collection_vector_service_options": {
"provider": self.provider,
"provider": provider_value,
"modelName": self.model_name,
"authentication": self.authentication,
"authentication": authentication,
"parameters": self.model_parameters,
},
"collection_embedding_api_key": self.provider_api_key,

View file

@ -0,0 +1,89 @@
import uuid
from typing import Optional
from langflow.custom import CustomComponent
from langflow.schema import Data
class FirecrawlCrawlApi(CustomComponent):
display_name: str = "FirecrawlCrawlApi"
description: str = "Firecrawl Crawl API."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/crawl"
field_config = {
"api_key": {
"display_name": "API Key",
"field_type": "str",
"required": True,
"password": True,
"info": "The API key to use Firecrawl API.",
},
"url": {
"display_name": "URL",
"field_type": "str",
"required": True,
"info": "The base URL to start crawling from.",
},
"timeout": {
"display_name": "Timeout",
"field_type": "int",
"info": "The timeout in milliseconds.",
},
"crawlerOptions": {
"display_name": "Crawler Options",
"info": "Options for the crawler behavior.",
},
"pageOptions": {
"display_name": "Page Options",
"info": "The page options to send with the request.",
},
"idempotency_key": {
"display_name": "Idempotency Key",
"field_type": "str",
"info": "Optional idempotency key to ensure unique requests.",
},
}
def build(
self,
api_key: str,
url: str,
timeout: int = 30000,
crawlerOptions: Optional[Data] = None,
pageOptions: Optional[Data] = None,
idempotency_key: Optional[str] = None,
) -> Data:
try:
from firecrawl.firecrawl import FirecrawlApp # type: ignore
except ImportError:
raise ImportError(
"Could not import firecrawl integration package. " "Please install it with `pip install firecrawl-py`."
)
if crawlerOptions:
crawler_options_dict = crawlerOptions.__dict__["data"]["text"]
else:
crawler_options_dict = {}
if pageOptions:
page_options_dict = pageOptions.__dict__["data"]["text"]
else:
page_options_dict = {}
if not idempotency_key:
idempotency_key = str(uuid.uuid4())
app = FirecrawlApp(api_key=api_key)
crawl_result = app.crawl_url(
url,
{
"crawlerOptions": crawler_options_dict,
"pageOptions": page_options_dict,
},
True,
int(timeout / 1000),
idempotency_key,
)
records = Data(data={"results": crawl_result})
return records

View file

@ -0,0 +1,77 @@
from typing import Optional
from langflow.custom import CustomComponent
from langflow.schema import Data
class FirecrawlScrapeApi(CustomComponent):
display_name: str = "FirecrawlScrapeApi"
description: str = "Firecrawl Scrape API."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/scrape"
field_config = {
"api_key": {
"display_name": "API Key",
"field_type": "str",
"required": True,
"password": True,
"info": "The API key to use Firecrawl API.",
},
"url": {
"display_name": "URL",
"field_type": "str",
"required": True,
"info": "The URL to scrape.",
},
"timeout": {
"display_name": "Timeout",
"info": "Timeout in milliseconds for the request.",
"field_type": "int",
"default_value": 10000,
},
"pageOptions": {
"display_name": "Page Options",
"info": "The page options to send with the request.",
},
"extractorOptions": {
"display_name": "Extractor Options",
"info": "The extractor options to send with the request.",
},
}
def build(
self,
api_key: str,
url: str,
timeout: Optional[int] = 10000,
pageOptions: Optional[Data] = None,
extractorOptions: Optional[Data] = None,
) -> Data:
try:
from firecrawl.firecrawl import FirecrawlApp # type: ignore
except ImportError:
raise ImportError(
"Could not import firecrawl integration package. " "Please install it with `pip install firecrawl-py`."
)
if extractorOptions:
extractor_options_dict = extractorOptions.__dict__["data"]["text"]
else:
extractor_options_dict = {}
if pageOptions:
page_options_dict = pageOptions.__dict__["data"]["text"]
else:
page_options_dict = {}
app = FirecrawlApp(api_key=api_key)
results = app.scrape_url(
url,
{
"timeout": str(timeout),
"extractorOptions": extractor_options_dict,
"pageOptions": page_options_dict,
},
)
record = Data(data=results)
return record

View file

@ -34,6 +34,12 @@ class OpenAIModelComponent(LCModelComponent):
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
),
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
BoolInput(
name="json_mode",
display_name="JSON Mode",
advanced=True,
info="If True, it will output JSON regardless of passing a schema.",
),
DictInput(
name="output_schema",
is_list=True,
@ -84,7 +90,7 @@ class OpenAIModelComponent(LCModelComponent):
max_tokens = self.max_tokens
model_kwargs = self.model_kwargs or {}
openai_api_base = self.openai_api_base or "https://api.openai.com/v1"
json_mode = bool(output_schema_dict)
json_mode = bool(output_schema_dict) or self.json_mode
seed = self.seed
model_kwargs["seed"] = seed
@ -101,7 +107,10 @@ class OpenAIModelComponent(LCModelComponent):
temperature=temperature or 0.1,
)
if json_mode:
output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore
if output_schema_dict:
output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore
else:
output = output.bind(response_format={"type": "json_object"}) # type: ignore
return output

View file

@ -4,6 +4,7 @@ from langchain_community.vectorstores import Cassandra
from langflow.base.vectorstores.model import LCVectorStoreComponent
from langflow.helpers.data import docs_to_data
from langflow.inputs import DictInput
from langflow.io import (
DataInput,
DropdownInput,
@ -23,24 +24,32 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
icon = "Cassandra"
inputs = [
SecretStrInput(
name="token",
display_name="Token",
info="Authentication token for accessing Cassandra on Astra DB.",
MessageTextInput(
name="database_ref",
display_name="Contact Points / Astra Database ID",
info="Contact points for the database (or AstraDB database ID)",
required=True,
),
MessageTextInput(name="database_id", display_name="Database ID", info="The Astra database ID.", required=True),
MessageTextInput(
name="table_name",
display_name="Table Name",
info="The name of the table where vectors will be stored.",
name="username", display_name="Username", info="Username for the database (leave empty for AstraDB)."
),
SecretStrInput(
name="token",
display_name="Password / AstraDB Token",
info="User password for the database (or AstraDB token).",
required=True,
),
MessageTextInput(
name="keyspace",
display_name="Keyspace",
info="Optional key space within Astra DB. The keyspace should already be created.",
advanced=False,
info="Table Keyspace (or AstraDB namespace).",
required=True,
),
MessageTextInput(
name="table_name",
display_name="Table Name",
info="The name of the table (or AstraDB collection) where vectors will be stored.",
required=True,
),
IntInput(
name="ttl_seconds",
@ -69,6 +78,13 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
value="Sync",
advanced=True,
),
DictInput(
name="cluster_kwargs",
display_name="Cluster arguments",
info="Optional dictionary of additional keyword arguments for the Cassandra cluster.",
advanced=True,
is_list=True,
),
MultilineInput(name="search_query", display_name="Search Query"),
DataInput(
name="ingest_data",
@ -96,10 +112,35 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
"Could not import cassio integration package. " "Please install it with `pip install cassio`."
)
cassio.init(
database_id=self.database_id,
token=self.token,
)
from uuid import UUID
database_ref = self.database_ref
try:
UUID(self.database_ref)
is_astra = True
except ValueError:
is_astra = False
if "," in self.database_ref:
# use a copy because we can't change the type of the parameter
database_ref = self.database_ref.split(",")
if is_astra:
cassio.init(
database_id=database_ref,
token=self.token,
cluster_kwargs=self.cluster_kwargs,
)
else:
cassio.init(
contact_points=database_ref,
username=self.username,
password=self.token,
cluster_kwargs=self.cluster_kwargs,
)
if not self.ttl_seconds: # type: ignore
self.ttl_seconds = None
documents = []

View file

@ -343,10 +343,10 @@ class Graph:
except Exception as exc:
logger.exception(exc)
tb = traceback.format_exc()
await self.end_all_traces(error=f"{exc.__class__.__name__}: {exc}\n\n{tb}")
asyncio.create_task(self.end_all_traces(error=f"{exc.__class__.__name__}: {exc}\n\n{tb}"))
raise ValueError(f"Error running graph: {exc}") from exc
finally:
await self.end_all_traces()
asyncio.create_task(self.end_all_traces())
# Get the outputs
vertex_outputs = []
for vertex in self.vertices:
@ -1444,7 +1444,7 @@ class Graph:
def is_vertex_runnable(self, vertex_id: str) -> bool:
"""Returns whether a vertex is runnable."""
return self.run_manager.is_vertex_runnable(vertex_id)
return self.run_manager.is_vertex_runnable(vertex_id, self.inactivated_vertices)
def build_run_map(self):
"""
@ -1464,7 +1464,7 @@ class Graph:
This checks the direct predecessors of each successor to identify any that are
immediately runnable, expanding the search to ensure progress can be made.
"""
return self.run_manager.find_runnable_predecessors_for_successors(vertex_id)
return self.run_manager.find_runnable_predecessors_for_successors(vertex_id, self.inactivated_vertices)
def remove_from_predecessors(self, vertex_id: str):
self.run_manager.remove_from_predecessors(vertex_id)

View file

@ -1,6 +1,6 @@
import asyncio
from collections import defaultdict
from typing import TYPE_CHECKING, Callable, List, Coroutine
from typing import TYPE_CHECKING, Callable, Coroutine, List
if TYPE_CHECKING:
from langflow.graph.graph.base import Graph
@ -40,19 +40,23 @@ class RunnableVerticesManager:
self.run_predecessors = state["run_predecessors"]
self.vertices_to_run = state["vertices_to_run"]
def is_vertex_runnable(self, vertex_id: str) -> bool:
def is_vertex_runnable(self, vertex_id: str, inactivated_vertices: set[str]) -> bool:
"""Determines if a vertex is runnable."""
return vertex_id in self.vertices_to_run and not self.run_predecessors.get(vertex_id)
return (
vertex_id in self.vertices_to_run
and not self.run_predecessors.get(vertex_id)
and vertex_id not in inactivated_vertices
)
def find_runnable_predecessors_for_successors(self, vertex_id: str) -> List[str]:
def find_runnable_predecessors_for_successors(self, vertex_id: str, inactivated_vertices: set[str]) -> List[str]:
"""Finds runnable predecessors for the successors of a given vertex."""
runnable_vertices = []
visited = set()
for successor_id in self.run_map.get(vertex_id, []):
for predecessor_id in self.run_predecessors.get(successor_id, []):
if predecessor_id not in visited and self.is_vertex_runnable(predecessor_id):
if predecessor_id not in visited and self.is_vertex_runnable(predecessor_id, inactivated_vertices):
runnable_vertices.append(predecessor_id)
visited.add(predecessor_id)
return runnable_vertices
@ -104,10 +108,14 @@ class RunnableVerticesManager:
"""
async with lock:
self.remove_from_predecessors(vertex.id)
direct_successors_ready = [v for v in vertex.successors_ids if self.is_vertex_runnable(v)]
direct_successors_ready = [
v for v in vertex.successors_ids if self.is_vertex_runnable(v, graph.inactivated_vertices)
]
if not direct_successors_ready:
# No direct successors ready, look for runnable predecessors of successors
next_runnable_vertices = self.find_runnable_predecessors_for_successors(vertex.id)
next_runnable_vertices = self.find_runnable_predecessors_for_successors(
vertex.id, graph.inactivated_vertices
)
else:
next_runnable_vertices = direct_successors_ready

View file

@ -1,5 +1,4 @@
import warnings
from typing import List, Optional
from uuid import UUID
from loguru import logger
@ -8,17 +7,18 @@ from sqlmodel import Session, col, select
from langflow.schema.message import Message
from langflow.services.database.models.message.model import MessageRead, MessageTable
from langflow.services.database.utils import migrate_messages_from_monitor_service_to_database
from langflow.services.deps import session_scope
def get_messages(
sender: Optional[str] = None,
sender_name: Optional[str] = None,
session_id: Optional[str] = None,
order_by: Optional[str] = "timestamp",
order: Optional[str] = "DESC",
flow_id: Optional[UUID] = None,
limit: Optional[int] = None,
sender: str | None = None,
sender_name: str | None = None,
session_id: str | None = None,
order_by: str | None = "timestamp",
order: str | None = "DESC",
flow_id: UUID | None = None,
limit: int | None = None,
):
"""
Retrieves messages from the monitor service based on the provided filters.
@ -33,6 +33,8 @@ def get_messages(
Returns:
List[Data]: A list of Data objects representing the retrieved messages.
"""
with session_scope() as session:
migrate_messages_from_monitor_service_to_database(session)
messages_read: list[Message] = []
with session_scope() as session:
stmt = select(MessageTable)
@ -58,7 +60,7 @@ def get_messages(
return messages_read
def add_messages(messages: Message | list[Message], flow_id: Optional[str] = None):
def add_messages(messages: Message | list[Message], flow_id: str | None = None):
"""
Add a message to the monitor service.
"""
@ -111,8 +113,8 @@ def delete_messages(session_id: str):
def store_message(
message: Message,
flow_id: Optional[str] = None,
) -> List[Message]:
flow_id: str | None = None,
) -> list[Message]:
"""
Stores a message in the memory.

View file

@ -41,6 +41,12 @@ class Message(Data):
value = str(value)
return value
@field_serializer("flow_id")
def serialize_flow_id(value):
if isinstance(value, str):
return UUID(value)
return value
@field_validator("files", mode="before")
@classmethod
def validate_files(cls, value):

View file

@ -26,7 +26,7 @@ class MessageBase(SQLModel):
return value
@classmethod
def from_message(cls, message: "Message", flow_id: str | None = None):
def from_message(cls, message: "Message", flow_id: str | UUID | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
@ -34,6 +34,8 @@ class MessageBase(SQLModel):
timestamp = datetime.fromisoformat(message.timestamp)
else:
timestamp = message.timestamp
if not flow_id and message.flow_id:
flow_id = message.flow_id
return cls(
sender=message.sender,
sender_name=message.sender_name,
@ -52,6 +54,15 @@ class MessageTable(MessageBase, table=True):
flow: "Flow" = Relationship(back_populates="messages")
files: List[str] = Field(sa_column=Column(JSON))
@field_validator("flow_id", mode="before")
@classmethod
def validate_flow_id(cls, value):
if value is None:
return value
if isinstance(value, str):
value = UUID(value)
return value
# Needed for Column(JSON)
class Config:
arbitrary_types_allowed = True

View file

@ -6,22 +6,24 @@ from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import command, util
from alembic.config import Config
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
from loguru import logger
from sqlalchemy import event, inspect
from sqlalchemy.engine import Engine
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults, migrate_messages_from_monitor_service_to_database
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
if TYPE_CHECKING:
from langflow.services.settings.service import SettingsService
from sqlalchemy.engine import Engine
from langflow.services.settings.service import SettingsService
class DatabaseService(Service):
name = "database_service"
@ -205,6 +207,10 @@ class DatabaseService(Service):
logger.error(f"AutogenerateDiffsDetected: {exc}")
if not fix:
raise RuntimeError(f"There's a mismatch between the models and the database.\n{exc}")
try:
migrate_messages_from_monitor_service_to_database(session)
except Exception as exc:
logger.error(f"Error migrating messages from monitor service to database: {exc}")
if fix:
self.try_downgrade_upgrade_until_success(alembic_cfg)

View file

@ -4,11 +4,78 @@ from typing import TYPE_CHECKING
from alembic.util.exc import CommandError
from loguru import logger
from sqlmodel import Session, text
from sqlmodel import Session, select, text
from langflow.services.deps import get_monitor_service
if TYPE_CHECKING:
from langflow.services.database.service import DatabaseService
from typing import Dict, List
def migrate_messages_from_monitor_service_to_database(session: Session) -> bool:
from langflow.schema.message import Message
from langflow.services.database.models.message import MessageTable
monitor_service = get_monitor_service()
messages_df = monitor_service.get_messages()
if messages_df.empty:
logger.info("No messages to migrate.")
return True
original_messages: List[Dict] = messages_df.to_dict(orient="records")
db_messages = session.exec(select(MessageTable)).all()
db_messages = [msg[0] for msg in db_messages] # type: ignore
db_msg_dict = {(msg.text, msg.timestamp.isoformat(), str(msg.flow_id), msg.session_id): msg for msg in db_messages}
# Filter out messages that already exist in the database
original_messages_filtered = []
for message in original_messages:
key = (message["text"], message["timestamp"].isoformat(), str(message["flow_id"]), message["session_id"])
if key not in db_msg_dict:
original_messages_filtered.append(message)
if not original_messages_filtered:
logger.info("No messages to migrate.")
return True
try:
# Bulk insert messages
session.bulk_insert_mappings(
MessageTable, # type: ignore
[MessageTable.from_message(Message(**msg)).model_dump() for msg in original_messages_filtered],
)
session.commit()
except Exception as e:
logger.error(f"Error during message insertion: {str(e)}")
session.rollback()
return False
# Create a dictionary for faster lookup
all_ok = True
for orig_msg in original_messages_filtered:
key = (orig_msg["text"], orig_msg["timestamp"].isoformat(), str(orig_msg["flow_id"]), orig_msg["session_id"])
matching_db_msg = db_msg_dict.get(key)
if matching_db_msg is None:
logger.warning(f"Message not found in database: {orig_msg}")
all_ok = False
else:
# Validate other fields
if any(getattr(matching_db_msg, k) != v for k, v in orig_msg.items() if k != "index"):
logger.warning(f"Message mismatch in database: {orig_msg}")
all_ok = False
if all_ok:
messages_ids = [message["index"] for message in original_messages]
monitor_service.delete_messages(messages_ids)
logger.info("Migration completed successfully. Original messages deleted.")
else:
logger.warning("Migration completed with errors. Original messages not deleted.")
return all_ok
def initialize_database(fix_migration: bool = False):
logger.debug("Initializing database")

View file

@ -1,6 +1,6 @@
import json
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any
from uuid import UUID
from pydantic import BaseModel, Field, field_serializer, field_validator
@ -28,15 +28,15 @@ class DefaultModel(BaseModel):
class TransactionModel(DefaultModel):
index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
index: int | None = Field(default=None)
timestamp: datetime | None = Field(default_factory=datetime.now, alias="timestamp")
vertex_id: str
target_id: str | None = None
inputs: dict
outputs: Optional[dict] = None
outputs: dict | None = None
status: str
error: Optional[str] = None
flow_id: Optional[str] = Field(default=None, alias="flow_id")
error: str | None = None
flow_id: str | None = Field(default=None, alias="flow_id")
# validate target_args in case it is a JSON
@field_validator("outputs", "inputs", mode="before")
@ -53,16 +53,16 @@ class TransactionModel(DefaultModel):
class TransactionModelResponse(DefaultModel):
index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
index: int | None = Field(default=None)
timestamp: datetime | None = Field(default_factory=datetime.now, alias="timestamp")
vertex_id: str
inputs: dict
outputs: Optional[dict] = None
outputs: dict | None = None
status: str
error: Optional[str] = None
flow_id: Optional[str] = Field(default=None, alias="flow_id")
source: Optional[str] = None
target: Optional[str] = None
error: str | None = None
flow_id: str | None = Field(default=None, alias="flow_id")
source: str | None = None
target: str | None = None
# validate target_args in case it is a JSON
@field_validator("outputs", "inputs", mode="before")
@ -81,9 +81,9 @@ class TransactionModelResponse(DefaultModel):
return v
class MessageModel(DefaultModel):
id: Optional[str | UUID] = Field(default=None)
flow_id: Optional[UUID] = Field(default=None)
class DuckDbMessageModel(DefaultModel):
index: int | None = Field(default=None, alias="index")
flow_id: str | None = Field(default=None, alias="flow_id")
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
sender: str
sender_name: str
@ -112,7 +112,53 @@ class MessageModel(DefaultModel):
return v
@classmethod
def from_message(cls, message: Message, flow_id: Optional[str] = None):
def from_message(cls, message: Message, flow_id: str | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
return cls(
sender=message.sender,
sender_name=message.sender_name,
text=message.text,
session_id=message.session_id,
files=message.files or [],
timestamp=message.timestamp,
flow_id=flow_id,
)
class MessageModel(DefaultModel):
id: str | UUID | None = Field(default=None)
flow_id: UUID | None = Field(default=None)
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
sender: str
sender_name: str
session_id: str
text: str
files: list[str] = []
@field_validator("files", mode="before")
@classmethod
def validate_files(cls, v):
if isinstance(v, str):
v = json.loads(v)
return v
@field_serializer("timestamp")
@classmethod
def serialize_timestamp(cls, v):
v = v.replace(microsecond=0)
return v.strftime("%Y-%m-%d %H:%M:%S")
@field_serializer("files")
@classmethod
def serialize_files(cls, v):
if isinstance(v, list):
return json.dumps(v)
return v
@classmethod
def from_message(cls, message: Message, flow_id: str | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
@ -139,8 +185,8 @@ class MessageModelRequest(MessageModel):
class VertexBuildModel(DefaultModel):
index: Optional[int] = Field(default=None, alias="index", exclude=True)
id: Optional[str] = Field(default=None, alias="id")
index: int | None = Field(default=None, alias="index", exclude=True)
id: str | None = Field(default=None, alias="id")
flow_id: str
valid: bool
params: Any

View file

@ -1,6 +1,6 @@
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, List, Optional, Union
from typing import TYPE_CHECKING, Union
import duckdb
from loguru import logger
@ -10,7 +10,7 @@ from langflow.services.base import Service
from langflow.services.monitor.utils import add_row_to_table, drop_and_create_table_if_schema_mismatch
if TYPE_CHECKING:
from langflow.services.monitor.schema import MessageModel, TransactionModel, VertexBuildModel
from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel
from langflow.services.settings.service import SettingsService
@ -18,14 +18,14 @@ class MonitorService(Service):
name = "monitor_service"
def __init__(self, settings_service: "SettingsService"):
from langflow.services.monitor.schema import MessageModel, TransactionModel, VertexBuildModel
from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel
self.settings_service = settings_service
self.base_cache_dir = Path(user_cache_dir("langflow"))
self.db_path = self.base_cache_dir / "monitor.duckdb"
self.table_map: dict[str, type[TransactionModel | MessageModel | VertexBuildModel]] = {
self.table_map: dict[str, type[TransactionModel | DuckDbMessageModel | VertexBuildModel]] = {
"transactions": TransactionModel,
"messages": MessageModel,
"messages": DuckDbMessageModel,
"vertex_builds": VertexBuildModel,
}
@ -48,7 +48,7 @@ class MonitorService(Service):
def add_row(
self,
table_name: str,
data: Union[dict, "TransactionModel", "MessageModel", "VertexBuildModel"],
data: Union[dict, "TransactionModel", "DuckDbMessageModel", "VertexBuildModel"],
):
# Make sure the model passed matches the table
@ -68,12 +68,48 @@ class MonitorService(Service):
def get_timestamp():
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def get_messages(
self,
flow_id: str | None = None,
sender: str | None = None,
sender_name: str | None = None,
session_id: str | None = None,
order_by: str | None = "timestamp",
order: str | None = "DESC",
limit: int | None = None,
):
query = "SELECT index, flow_id, sender_name, sender, session_id, text, files, timestamp FROM messages"
conditions = []
if sender:
conditions.append(f"sender = '{sender}'")
if sender_name:
conditions.append(f"sender_name = '{sender_name}'")
if session_id:
conditions.append(f"session_id = '{session_id}'")
if flow_id:
conditions.append(f"flow_id = '{flow_id}'")
if conditions:
query += " WHERE " + " AND ".join(conditions)
if order_by and order:
# Make sure the order is from newest to oldest
query += f" ORDER BY {order_by} {order.upper()}"
if limit is not None:
query += f" LIMIT {limit}"
with duckdb.connect(str(self.db_path), read_only=True) as conn:
df = conn.execute(query).df()
return df
def get_vertex_builds(
self,
flow_id: Optional[str] = None,
vertex_id: Optional[str] = None,
valid: Optional[bool] = None,
order_by: Optional[str] = "timestamp",
flow_id: str | None = None,
vertex_id: str | None = None,
valid: bool | None = None,
order_by: str | None = "timestamp",
):
query = "SELECT id, index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
conditions = []
@ -96,7 +132,7 @@ class MonitorService(Service):
return df.to_dict(orient="records")
def delete_vertex_builds(self, flow_id: Optional[str] = None):
def delete_vertex_builds(self, flow_id: str | None = None):
query = "DELETE FROM vertex_builds"
if flow_id:
query += f" WHERE flow_id = '{flow_id}'"
@ -109,7 +145,7 @@ class MonitorService(Service):
return self.exec_query(query, read_only=False)
def delete_messages(self, message_ids: Union[List[int], str]):
def delete_messages(self, message_ids: list[int] | str):
if isinstance(message_ids, list):
# If message_ids is a list, join the string representations of the integers
ids_str = ",".join(map(str, message_ids))
@ -132,11 +168,11 @@ class MonitorService(Service):
def get_transactions(
self,
source: Optional[str] = None,
target: Optional[str] = None,
status: Optional[str] = None,
order_by: Optional[str] = "timestamp",
flow_id: Optional[str] = None,
source: str | None = None,
target: str | None = None,
status: str | None = None,
order_by: str | None = "timestamp",
flow_id: str | None = None,
):
query = (
"SELECT index,flow_id, status, error, timestamp, vertex_id, inputs, outputs, target_id FROM transactions"

View file

@ -112,13 +112,13 @@ frozenlist = ">=1.1.0"
[[package]]
name = "alembic"
version = "1.13.1"
version = "1.13.2"
description = "A database migration tool for SQLAlchemy."
optional = false
python-versions = ">=3.8"
files = [
{file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"},
{file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"},
{file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"},
{file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"},
]
[package.dependencies]
@ -739,6 +739,20 @@ typer = ">=0.12.3"
[package.extras]
standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"]
[[package]]
name = "firecrawl-py"
version = "0.0.16"
description = "Python SDK for Firecrawl API"
optional = false
python-versions = ">=3.8"
files = [
{file = "firecrawl_py-0.0.16-py3-none-any.whl", hash = "sha256:9024f483b501852a6b9c4e6cdfc9e8dde452d922afac357080bb278a0c9c2a26"},
{file = "firecrawl_py-0.0.16.tar.gz", hash = "sha256:6c662fa0a549bc7f5c0acb704baba6731869ca0451094034264dfc1b4eb086e4"},
]
[package.dependencies]
requests = "*"
[[package]]
name = "frozenlist"
version = "1.4.1"
@ -1158,19 +1172,19 @@ files = [
[[package]]
name = "langchain"
version = "0.2.5"
version = "0.2.6"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain-0.2.5-py3-none-any.whl", hash = "sha256:9aded9a65348254e1c93dcdaacffe4d1b6a5e7f74ef80c160c88ff78ad299228"},
{file = "langchain-0.2.5.tar.gz", hash = "sha256:ffdbf4fcea46a10d461bcbda2402220fcfd72a0c70e9f4161ae0510067b9b3bd"},
{file = "langchain-0.2.6-py3-none-any.whl", hash = "sha256:f86e8a7afd3e56f8eb5ba47f01dd00144fb9fc2f1db9873bd197347be2857aa4"},
{file = "langchain-0.2.6.tar.gz", hash = "sha256:867f6add370c1e3911b0e87d3dd0e36aec1e8f513bf06131340fe8f151d89dc5"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
langchain-core = ">=0.2.7,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
langchain-text-splitters = ">=0.2.0,<0.3.0"
langsmith = ">=0.1.17,<0.2.0"
numpy = [
@ -1181,24 +1195,24 @@ pydantic = ">=1,<3"
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-community"
version = "0.2.5"
version = "0.2.6"
description = "Community contributed LangChain integrations."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_community-0.2.5-py3-none-any.whl", hash = "sha256:bf37a334952e42c7676d083cf2d2c4cbfbb7de1949c4149fe19913e2b06c485f"},
{file = "langchain_community-0.2.5.tar.gz", hash = "sha256:476787b8c8c213b67e7b0eceb53346e787f00fbae12d8e680985bd4f93b0bf64"},
{file = "langchain_community-0.2.6-py3-none-any.whl", hash = "sha256:758cc800acfe5dd396bf8ba1b57c4792639ead0eab48ed0367f0732ec6ee1f68"},
{file = "langchain_community-0.2.6.tar.gz", hash = "sha256:40ce09a50ed798aa651ddb34c8978200fa8589b9813c7a28ce8af027bbf249f0"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
dataclasses-json = ">=0.5.7,<0.7"
langchain = ">=0.2.5,<0.3.0"
langchain-core = ">=0.2.7,<0.3.0"
langchain = ">=0.2.6,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
langsmith = ">=0.1.0,<0.2.0"
numpy = [
{version = ">=1,<2", markers = "python_version < \"3.12\""},
@ -1207,17 +1221,17 @@ numpy = [
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-core"
version = "0.2.9"
version = "0.2.10"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_core-0.2.9-py3-none-any.whl", hash = "sha256:426a5a4fea95a5db995ba5ab560b76edd4998fb6fe52ccc28ac987092a4cbfcd"},
{file = "langchain_core-0.2.9.tar.gz", hash = "sha256:f1c59082642921727844e1cd0eb36d451edd1872c20e193aa3142aac03495986"},
{file = "langchain_core-0.2.10-py3-none-any.whl", hash = "sha256:6eb72086b6bc86db9812da98f79e507c2209a15c0112aefd214a04182ada8586"},
{file = "langchain_core-0.2.10.tar.gz", hash = "sha256:33d1fc234ab58c80476eb5bbde2107ef522a2ce8f46bdf47d9e1bd21e054208f"},
]
[package.dependencies]
@ -1233,35 +1247,32 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-experimental"
version = "0.0.61"
version = "0.0.62"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_experimental-0.0.61-py3-none-any.whl", hash = "sha256:f9c516f528f55919743bd56fe1689a53bf74ae7f8902d64b9d8aebc61249cbe2"},
{file = "langchain_experimental-0.0.61.tar.gz", hash = "sha256:e9538efb994be5db3045cc582cddb9787c8299c86ffeee9d3779b7f58eef2226"},
{file = "langchain_experimental-0.0.62-py3-none-any.whl", hash = "sha256:9240f9e3490e819976f20a37863970036e7baacb7104b9eb6833d19ab6d518c9"},
{file = "langchain_experimental-0.0.62.tar.gz", hash = "sha256:9737fbc8429d24457ea4d368e3c9ba9ed1cace0564fb5f1a96a3027a588bd0ac"},
]
[package.dependencies]
langchain-community = ">=0.2.5,<0.3.0"
langchain-core = ">=0.2.7,<0.3.0"
langchain-community = ">=0.2.6,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
[[package]]
name = "langchain-text-splitters"
version = "0.2.1"
version = "0.2.2"
description = "LangChain text splitting utilities"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_text_splitters-0.2.1-py3-none-any.whl", hash = "sha256:c2774a85f17189eaca50339629d2316d13130d4a8d9f1a1a96f3a03670c4a138"},
{file = "langchain_text_splitters-0.2.1.tar.gz", hash = "sha256:06853d17d7241ecf5c97c7b6ef01f600f9b0fb953dd997838142a527a4f32ea4"},
{file = "langchain_text_splitters-0.2.2-py3-none-any.whl", hash = "sha256:1c80d4b11b55e2995f02d2a326c0323ee1eeff24507329bb22924e420c782dff"},
{file = "langchain_text_splitters-0.2.2.tar.gz", hash = "sha256:a1e45de10919fa6fb080ef0525deab56557e9552083600455cb9fa4238076140"},
]
[package.dependencies]
langchain-core = ">=0.2.0,<0.3.0"
[package.extras]
extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
langchain-core = ">=0.2.10,<0.3.0"
[[package]]
name = "langchainhub"
@ -2468,13 +2479,13 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "sentry-sdk"
version = "2.6.0"
version = "2.7.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
files = [
{file = "sentry_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:422b91cb49378b97e7e8d0e8d5a1069df23689d45262b86f54988a7db264e874"},
{file = "sentry_sdk-2.6.0.tar.gz", hash = "sha256:65cc07e9c6995c5e316109f138570b32da3bd7ff8d0d0ee4aaf2628c3dd8127d"},
{file = "sentry_sdk-2.7.0-py2.py3-none-any.whl", hash = "sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1"},
{file = "sentry_sdk-2.7.0.tar.gz", hash = "sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4"},
]
[package.dependencies]
@ -2506,7 +2517,7 @@ langchain = ["langchain (>=0.0.210)"]
loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"]
@ -3235,4 +3246,4 @@ local = []
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.13"
content-hash = "4f566531a8539ddc81cb91a7e7f9b723c84679f0af5bb8619f7b02f9ffc6cfaa"
content-hash = "7e46144d27c633214f00e73e496c0e4d56db1fb47032a21861677ec275b79d86"

View file

@ -64,6 +64,7 @@ pyperclip = "^1.8.2"
uncurl = "^0.0.11"
sentry-sdk = {extras = ["fastapi", "loguru"], version = "^2.5.1"}
chardet = "^5.2.0"
firecrawl-py = "^0.0.16"
[tool.poetry.extras]

View file

@ -19,9 +19,9 @@ export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
retries: process.env.CI ? 2 : 3,
/* Opt out of parallel tests on CI. */
workers: 1,
workers: 2,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
timeout: 120 * 1000,
// reporter: [

View file

@ -52,7 +52,7 @@ export default function HandleRenderComponent({
side={left ? "left" : "right"}
>
<Handle
data-test-id={`handle-${title.toLowerCase()}-${
data-testid={`handle-${title.toLowerCase()}-${
!showNode ? (left ? "target" : "source") : left ? "left" : "right"
}`}
type={left ? "target" : "source"}

View file

@ -229,6 +229,7 @@ export default function CollectionCardComponent({
render={({ field }) => (
<FormControl>
<Checkbox
data-testid={`checkbox-component`}
aria-label="checkbox-component"
checked={field.value}
onCheckedChange={field.onChange}

View file

@ -67,6 +67,7 @@ export default function FlowToolbar(): JSX.Element {
? "button-disable text-muted-foreground"
: "",
)}
data-testid="shared-button-flow"
>
<ForwardedIconComponent
name="Share3"

View file

@ -4,7 +4,7 @@ import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { InputProps } from "../../types/components";
import { cn } from "../../utils/utils";
import { cn, isEndpointNameValid } from "../../utils/utils";
export const EditFlowSettings: React.FC<InputProps> = ({
name,
@ -17,7 +17,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
setEndpointName,
}: InputProps): JSX.Element => {
const [isMaxLength, setIsMaxLength] = useState(false);
const [isEndpointNameValid, setIsEndpointNameValid] = useState(true);
const [validEndpointName, setValidEndpointName] = useState(true);
const [isInvalidName, setIsInvalidName] = useState(false);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
@ -34,10 +34,6 @@ export const EditFlowSettings: React.FC<InputProps> = ({
invalid = true;
break;
}
if (value === currentFlow?.name) {
invalid = true;
break;
}
invalid = false;
}
setIsInvalidName(invalid);
@ -51,12 +47,8 @@ export const EditFlowSettings: React.FC<InputProps> = ({
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
// Validate the endpoint name
// use this regex r'^[a-zA-Z0-9_-]+$'
const isValid =
(/^[a-zA-Z0-9_-]+$/.test(event.target.value) &&
event.target.value.length <= maxLength) ||
// empty is also valid
event.target.value.length === 0;
setIsEndpointNameValid(isValid);
const isValid = isEndpointNameValid(event.target.value, maxLength);
setValidEndpointName(isValid);
setEndpointName!(event.target.value);
};
@ -115,21 +107,21 @@ export const EditFlowSettings: React.FC<InputProps> = ({
}}
/>
) : (
<span
<div
className={cn(
"font-normal text-muted-foreground word-break-break-word",
"max-h-[250px] overflow-auto font-normal text-muted-foreground word-break-break-word",
description === "" ? "font-light italic" : "",
)}
>
{description === "" ? "No description" : description}
</span>
</div>
)}
</Label>
{setEndpointName && (
<Label>
<div className="edit-flow-arrangement mt-3">
<span className="font-medium">Endpoint Name</span>
{!isEndpointNameValid && (
{!validEndpointName && (
<span className="edit-flow-span">
Invalid endpoint name. Use only letters, numbers, hyphens, and
underscores ({maxLength} characters max).

View file

@ -863,3 +863,11 @@ export const TITLE_ERROR_UPDATING_COMPONENT =
"Error while updating the Component";
export const EMPTY_INPUT_SEND_MESSAGE = "No input message provided.";
export const TABS_ORDER = [
"run curl",
"python api",
"js api",
"python code",
"chat widget html",
];

View file

@ -1112,7 +1112,7 @@ export async function getMessagesTable(
return { rows: rowsOrganized, columns };
}
export async function deleteMessagesFn(ids: number[]) {
export async function deleteMessagesFn(ids: string[]) {
try {
return await api.delete(`${BASE_URL_API}monitor/messages`, {
data: ids,
@ -1124,5 +1124,5 @@ export async function deleteMessagesFn(ids: number[]) {
}
export async function updateMessageApi(data: Message) {
return await api.post(`${BASE_URL_API}monitor/messages/${data.index}`, data);
return await api.post(`${BASE_URL_API}monitor/messages/${data.id}`, data);
}

View file

@ -0,0 +1,61 @@
const SvgFirecrawlLogo = (props) => (
<svg
viewBox="-33 0 255 255"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
preserveAspectRatio="xMidYMid"
>
<defs>
<style>
{`
.cls-3 {
fill: url(#linear-gradient-1);
}
.cls-4 {
fill: #fc9502;
}
.cls-5 {
fill: #fce202;
}
`}
</style>
<linearGradient
id="linear-gradient-1"
gradientUnits="userSpaceOnUse"
x1="94.141"
y1="255"
x2="94.141"
y2="0.188"
>
<stop offset="0" stopColor="#ff4c0d" />
<stop offset="1" stopColor="#fc9502" />
</linearGradient>
</defs>
<g id="fire">
<path
d="M187.899,164.809 C185.803,214.868 144.574,254.812 94.000,254.812 C42.085,254.812 -0.000,211.312 -0.000,160.812 C-0.000,154.062 -0.121,140.572 10.000,117.812 C16.057,104.191 19.856,95.634 22.000,87.812 C23.178,83.513 25.469,76.683 32.000,87.812 C35.851,94.374 36.000,103.812 36.000,103.812 C36.000,103.812 50.328,92.817 60.000,71.812 C74.179,41.019 62.866,22.612 59.000,9.812 C57.662,5.384 56.822,-2.574 66.000,0.812 C75.352,4.263 100.076,21.570 113.000,39.812 C131.445,65.847 138.000,90.812 138.000,90.812 C138.000,90.812 143.906,83.482 146.000,75.812 C148.365,67.151 148.400,58.573 155.999,67.813 C163.226,76.600 173.959,93.113 180.000,108.812 C190.969,137.321 187.899,164.809 187.899,164.809 Z"
id="path-1"
className="cls-3"
fillRule="evenodd"
/>
<path
d="M94.000,254.812 C58.101,254.812 29.000,225.711 29.000,189.812 C29.000,168.151 37.729,155.000 55.896,137.166 C67.528,125.747 78.415,111.722 83.042,102.172 C83.953,100.292 86.026,90.495 94.019,101.966 C98.212,107.982 104.785,118.681 109.000,127.812 C116.266,143.555 118.000,158.812 118.000,158.812 C118.000,158.812 125.121,154.616 130.000,143.812 C131.573,140.330 134.753,127.148 143.643,140.328 C150.166,150.000 159.127,167.390 159.000,189.812 C159.000,225.711 129.898,254.812 94.000,254.812 Z"
id="path-2"
className="cls-4"
fillRule="evenodd"
/>
<path
d="M95.000,183.812 C104.250,183.812 104.250,200.941 116.000,223.812 C123.824,239.041 112.121,254.812 95.000,254.812 C77.879,254.812 69.000,240.933 69.000,223.812 C69.000,206.692 85.750,183.812 95.000,183.812 Z"
id="path-3"
className="cls-5"
fillRule="evenodd"
/>
</g>
</svg>
);
export default SvgFirecrawlLogo;

View file

@ -0,0 +1,28 @@
<svg width="800px" height="800px" viewBox="-33 0 255 255" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<defs>
<style>
.cls-3 {
fill: url(#linear-gradient-1);
}
.cls-4 {
fill: #fc9502;
}
.cls-5 {
fill: #fce202;
}
</style>
<linearGradient id="linear-gradient-1" gradientUnits="userSpaceOnUse" x1="94.141" y1="255" x2="94.141" y2="0.188">
<stop offset="0" stop-color="#ff4c0d"/>
<stop offset="1" stop-color="#fc9502"/>
</linearGradient>
</defs>
<g id="fire">
<path d="M187.899,164.809 C185.803,214.868 144.574,254.812 94.000,254.812 C42.085,254.812 -0.000,211.312 -0.000,160.812 C-0.000,154.062 -0.121,140.572 10.000,117.812 C16.057,104.191 19.856,95.634 22.000,87.812 C23.178,83.513 25.469,76.683 32.000,87.812 C35.851,94.374 36.000,103.812 36.000,103.812 C36.000,103.812 50.328,92.817 60.000,71.812 C74.179,41.019 62.866,22.612 59.000,9.812 C57.662,5.384 56.822,-2.574 66.000,0.812 C75.352,4.263 100.076,21.570 113.000,39.812 C131.445,65.847 138.000,90.812 138.000,90.812 C138.000,90.812 143.906,83.482 146.000,75.812 C148.365,67.151 148.400,58.573 155.999,67.813 C163.226,76.600 173.959,93.113 180.000,108.812 C190.969,137.321 187.899,164.809 187.899,164.809 Z" id="path-1" class="cls-3" fill-rule="evenodd"/>
<path d="M94.000,254.812 C58.101,254.812 29.000,225.711 29.000,189.812 C29.000,168.151 37.729,155.000 55.896,137.166 C67.528,125.747 78.415,111.722 83.042,102.172 C83.953,100.292 86.026,90.495 94.019,101.966 C98.212,107.982 104.785,118.681 109.000,127.812 C116.266,143.555 118.000,158.812 118.000,158.812 C118.000,158.812 125.121,154.616 130.000,143.812 C131.573,140.330 134.753,127.148 143.643,140.328 C150.166,150.000 159.127,167.390 159.000,189.812 C159.000,225.711 129.898,254.812 94.000,254.812 Z" id="path-2" class="cls-4" fill-rule="evenodd"/>
<path d="M95.000,183.812 C104.250,183.812 104.250,200.941 116.000,223.812 C123.824,239.041 112.121,254.812 95.000,254.812 C77.879,254.812 69.000,240.933 69.000,223.812 C69.000,206.692 85.750,183.812 95.000,183.812 Z" id="path-3" class="cls-5" fill-rule="evenodd"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import SvgFirecrawlLogo from "./FirecrawlLogo";
export const FirecrawlIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgFirecrawlLogo ref={ref} {...props} />;
});

View file

@ -33,6 +33,11 @@ test("should able to see and interact with logs", async ({ page }) => {
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").first().click();
await page.waitForTimeout(2000);

View file

@ -10,7 +10,7 @@ const useRemoveSession = (setSuccessData, setErrorData) => {
await deleteMessagesFn(
messages
.filter((msg) => msg.session_id === session_id)
.map((msg) => msg.index),
.map((msg) => msg.id),
);
deleteSession(session_id);
setSuccessData({

View file

@ -17,7 +17,7 @@ export default function SessionView({ rows }: { rows: Array<any> }) {
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const [selectedRows, setSelectedRows] = useState<number[]>([]);
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const { handleRemoveMessages } = useRemoveMessages(
setSelectedRows,
@ -52,7 +52,8 @@ export default function SessionView({ rows }: { rows: Array<any> }) {
]}
overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows(event.api.getSelectedRows().map((row) => row.index));
console.log(event.api.getSelectedRows());
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
}}
rowSelection="multiple"
suppressRowClickSelection={true}

View file

@ -45,6 +45,7 @@ const TextAreaWrapper = ({
return (
<Textarea
data-testid="input-chat-playground"
onFocus={(e) => {
setInputFocus(true);
e.target.style.borderTopWidth = "0";

View file

@ -18,11 +18,13 @@ import { buildContent } from "./utils/build-content";
import { buildTweaks } from "./utils/build-tweaks";
import { checkCanBuildTweakObject } from "./utils/check-can-build-tweak-object";
import { getChangesType } from "./utils/get-changes-types";
import getCodesObj from "./utils/get-codes-obj";
import { getCurlRunCode, getCurlWebhookCode } from "./utils/get-curl-code";
import getJsApiCode from "./utils/get-js-api-code";
import { getNodesWithDefaultValue } from "./utils/get-nodes-with-default-value";
import getPythonApiCode from "./utils/get-python-api-code";
import getPythonCode from "./utils/get-python-code";
import getTabsOrder from "./utils/get-tabs-order";
import { getValue } from "./utils/get-value";
import getWidgetCode from "./utils/get-widget-code";
import { createTabsArray } from "./utils/tabs-array";
@ -213,21 +215,26 @@ const ApiModal = forwardRef(
);
const pythonCode = getPythonCode(flow?.name, cloneTweak);
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
const isThereTweaks = Object.keys(tweaksCode).length > 0;
const codesObj = getCodesObj({
runCurlCode,
webhookCurlCode,
pythonApiCode,
jsApiCode,
pythonCode,
widgetCode,
});
const tabsOrder = getTabsOrder(includeWebhook, isThereTweaks);
if (tabs && tabs?.length > 0) {
let i = 0;
tabs![i].code = runCurlCode;
i++;
if (includeWebhook) {
tabs![i].code = webhookCurlCode;
i++;
}
tabs![i].code = pythonApiCode;
i++;
tabs![i].code = jsApiCode;
i++;
tabs![i].code = pythonCode;
i++;
tabs![i].code = widgetCode;
tabs.forEach((tab, idx) => {
const order = tabsOrder[idx];
if (order && order.toLowerCase() === tab.name.toLowerCase()) {
const codeToFind = codesObj.find(
(c) => c.name.toLowerCase() === tab.name.toLowerCase(),
);
tab.code = codeToFind?.code;
}
});
}
};

View file

@ -0,0 +1,40 @@
import {
getCodesObjProps,
getCodesObjReturn,
} from "../../../types/utils/functions";
export default function getCodesObj({
runCurlCode,
webhookCurlCode,
pythonApiCode,
jsApiCode,
pythonCode,
widgetCode,
}: getCodesObjProps): getCodesObjReturn {
return [
{
name: "run curl",
code: runCurlCode,
},
{
name: "webhook curl",
code: webhookCurlCode,
},
{
name: "python api",
code: pythonApiCode,
},
{
name: "js api",
code: jsApiCode,
},
{
name: "python code",
code: pythonCode,
},
{
name: "chat widget html",
code: widgetCode,
},
];
}

View file

@ -8,7 +8,7 @@ export function getCurlRunCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject,
endpointName?: string,
endpointName?: string | null,
): string {
const tweaksObject = tweaksBuildedObject[0];
// show the endpoint name in the curl command if it exists
@ -35,7 +35,11 @@ export function getCurlRunCode(
* @param {string} options.endpointName - The name of the webhook endpoint.
* @returns {string} The cURL command.
*/
export function getCurlWebhookCode(flowId, isAuth, endpointName?: string) {
export function getCurlWebhookCode(
flowId,
isAuth,
endpointName?: string | null,
) {
return `curl -X POST \\
"${window.location.protocol}//${window.location.host}/api/v1/webhook/${
endpointName || flowId

View file

@ -10,7 +10,7 @@ export default function getJsApiCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject: any[],
endpointName?: string,
endpointName?: string | null,
): string {
let tweaksString = "{}";
if (tweaksBuildedObject && tweaksBuildedObject.length > 0) {

View file

@ -10,7 +10,7 @@ export default function getPythonApiCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject: any[],
endpointName?: string,
endpointName?: string | null,
): string {
let tweaksString = "{}";
if (tweaksBuildedObject && tweaksBuildedObject.length > 0) {

View file

@ -0,0 +1,15 @@
import { TABS_ORDER } from "../../../constants/constants";
export default function getTabsOrder(
isThereWH: boolean = false,
isThereTweaks: boolean = false,
): string[] {
const defaultOrder = TABS_ORDER;
if (isThereTweaks) {
defaultOrder.push("tweaks");
}
if (isThereWH) {
defaultOrder.splice(1, 0, "webhook curl");
}
return defaultOrder;
}

View file

@ -3,6 +3,8 @@ export function createTabsArray(
includeWebhookCurl = false,
includeTweaks = false,
) {
// console.log(includeTweaks)
console.log(includeWebhookCurl);
const tabs = [
{
name: "Run cURL",

View file

@ -6,6 +6,7 @@ import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowSettingsPropsType } from "../../types/components";
import { FlowType } from "../../types/flow";
import { isEndpointNameValid } from "../../utils/utils";
import BaseModal from "../baseModal";
export default function FlowSettingsModal({
@ -22,13 +23,17 @@ export default function FlowSettingsModal({
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
const [endpoint_name, setEndpointName] = useState(currentFlow!.endpoint_name);
const [endpoint_name, setEndpointName] = useState(
currentFlow!.endpoint_name ?? "",
);
const [isSaving, setIsSaving] = useState(false);
const [disableSave, setDisableSave] = useState(true);
function handleClick(): void {
setIsSaving(true);
currentFlow!.name = name;
currentFlow!.description = description;
currentFlow!.endpoint_name = endpoint_name;
currentFlow!.endpoint_name =
endpoint_name && endpoint_name.length > 0 ? endpoint_name : null;
saveFlow(currentFlow!)
?.then(() => {
setOpen(false);
@ -54,6 +59,18 @@ export default function FlowSettingsModal({
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
}, [flows]);
useEffect(() => {
if (
(!nameLists.includes(name) && currentFlow?.name !== name) ||
currentFlow?.description !== description ||
((currentFlow?.endpoint_name ?? "") !== endpoint_name &&
isEndpointNameValid(endpoint_name ?? "", 50))
) {
setDisableSave(false);
} else {
setDisableSave(true);
}
}, [nameLists, currentFlow, description, endpoint_name, name]);
return (
<BaseModal
open={open}
@ -80,8 +97,8 @@ export default function FlowSettingsModal({
<BaseModal.Footer
submit={{
label: "Save",
disabled: nameLists.includes(name) || name === currentFlow!.name,
dataTestId: "save-flow-settings",
disabled: disableSave,
loading: isSaving,
}}
/>

View file

@ -128,7 +128,7 @@ export default function ShareModal({
successShare,
(err) => {
setErrorData({
title: "Error sharing " + is_component ? "component" : "flow",
title: "Error sharing " + (is_component ? "component" : "flow"),
list: [err["response"]["data"]["detail"]],
});
},

View file

@ -2,10 +2,10 @@ import { deleteMessagesFn } from "../../../../../controllers/API";
import { useMessagesStore } from "../../../../../stores/messagesStore";
const useRemoveMessages = (
setSelectedRows: (data: number[]) => void,
setSelectedRows: (data: string[]) => void,
setSuccessData: (data: { title: string }) => void,
setErrorData: (data: { title: string }) => void,
selectedRows: number[],
selectedRows: string[],
) => {
const deleteMessages = useMessagesStore((state) => state.removeMessages);

View file

@ -21,7 +21,7 @@ export default function MessagesPage() {
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const [selectedRows, setSelectedRows] = useState<number[]>([]);
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const { handleRemoveMessages } = useRemoveMessages(
setSelectedRows,

View file

@ -29,7 +29,7 @@ export const useMessagesStore = create<MessagesStoreType>((set, get) => ({
updateMessage: (message) => {
set(() => ({
messages: get().messages.map((msg) =>
msg.index === message.index ? message : msg,
msg.id === message.id ? message : msg,
),
}));
},
@ -41,7 +41,7 @@ export const useMessagesStore = create<MessagesStoreType>((set, get) => ({
try {
set((state) => {
const updatedMessages = state.messages.filter(
(msg) => !ids.includes(msg.index),
(msg) => !ids.includes(msg.id),
);
get().setMessages(updatedMessages);
resolve(updatedMessages);

View file

@ -303,7 +303,7 @@ export type IconComponentProps = {
export type InputProps = {
name: string | null;
description: string | null;
endpointName?: string;
endpointName?: string | null;
maxLength?: number;
setName?: (name: string) => void;
setDescription?: (description: string) => void;

View file

@ -7,7 +7,7 @@ export type FlowType = {
id: string;
data: ReactFlowJsonObject | null;
description: string;
endpoint_name?: string;
endpoint_name?: string | null;
style?: FlowStyleType;
is_component?: boolean;
last_tested_version?: string;

View file

@ -1,7 +1,6 @@
type Message = {
artifacts: Record<string, any>;
flow_id: string;
index: number;
message: string;
sender: string;
sender_name: string;

View file

@ -0,0 +1,10 @@
export type getCodesObjProps = {
runCurlCode: string;
webhookCurlCode: string;
pythonApiCode: string;
jsApiCode: string;
pythonCode: string;
widgetCode: string;
};
export type getCodesObjReturn = Array<{ name: string; code: string }>;

View file

@ -8,7 +8,7 @@ export type MessagesStoreType = {
removeMessage: (message: Message) => void;
updateMessage: (message: Message) => void;
clearMessages: () => void;
removeMessages: (ids: number[]) => void;
removeMessages: (ids: string[]) => void;
columns: Array<ColDef | ColGroupDef>;
setColumns: (columns: Array<ColDef | ColGroupDef>) => void;
deleteSession: (id: string) => void;

View file

@ -171,6 +171,7 @@ import { CouchbaseIcon } from "../icons/Couchbase";
import { ElasticsearchIcon } from "../icons/ElasticsearchStore";
import { EvernoteIcon } from "../icons/Evernote";
import { FBIcon } from "../icons/FacebookMessenger";
import { FirecrawlIcon } from "../icons/Firecrawl";
import { GitBookIcon } from "../icons/GitBook";
import { GoogleIcon } from "../icons/Google";
import { GoogleGenerativeAIIcon } from "../icons/GoogleGenerativeAI";
@ -363,6 +364,8 @@ export const nodeIconsLucide: iconsType = {
CohereEmbeddings: CohereIcon,
EverNoteLoader: EvernoteIcon,
FacebookChatLoader: FBIcon,
FirecrawlCrawlApi: FirecrawlIcon,
FirecrawlScrapeApi: FirecrawlIcon,
GitbookLoader: GitBookIcon,
GoogleSearchAPIWrapper: GoogleIcon,
GoogleSearchResults: GoogleIcon,

View file

@ -463,3 +463,11 @@ export const logTypeIsError = (
return isErrorLog(outputs);
}
};
export function isEndpointNameValid(name: string, maxLength: number): boolean {
return (
(/^[a-zA-Z0-9_-]+$/.test(name) && name.length <= maxLength) ||
// empty is also valid
name.length === 0
);
}

View file

@ -0,0 +1,90 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Basic Prompting (Hello, World)", async ({ page }) => {
if (!process?.env?.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").click();
await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page
.getByTestId("input-chat-playground")
.last()
.fill("Say hello as a pirate");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page.getByText("Ahoy").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").last().isVisible();
});

View file

@ -0,0 +1,109 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Blog Writer", async ({ page }) => {
if (!process?.env?.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Blog Writer" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page
.getByTestId("input-list-input_urls-0")
.nth(0)
.fill(
"https://www.natgeokids.com/uk/discover/animals/sea-life/turtle-facts/",
);
await page
.getByTestId("input-list-input_urls-1")
.nth(0)
.fill("https://www.originaldiving.com/blog/top-ten-turtle-facts");
await page
.getByTestId("popover-anchor-input-input_value")
.nth(0)
.fill(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
);
await page.getByTestId("button_run_chat output").click();
await page.waitForTimeout(5000);
await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.getByText("built successfully").last().click({
timeout: 30000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByPlaceholder(
"No chat input variables found. Click to run your flow.",
{ exact: true },
)
.last()
.isVisible();
await page.waitForTimeout(3000);
await page.getByText("turtles").last().isVisible();
await page.getByText("sea").last().isVisible();
await page.getByText("survival").last().isVisible();
await page.getByText("Instructions").last().click();
const value = await page
.getByPlaceholder("Enter text...")
.last()
.inputValue();
expect(value).toBe(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
);
await page.getByTestId("icon-ExternalLink").last().click();
const count = await page
.getByText(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
)
.count();
if (count <= 1) {
expect(false).toBe(true);
}
});

View file

@ -0,0 +1,96 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Document QA", async ({ page }) => {
if (!process?.env?.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Document QA" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("icon-FileSearch2").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "/assets/test_file.txt"));
await page.getByText("test_file.txt").isVisible();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").click();
await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page
.getByTestId("input-chat-playground")
.last()
.fill("whats the text in the file?");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page.getByText("this is a test file").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").last().isVisible();
});

View file

@ -0,0 +1,102 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Memory Chatbot", async ({ page }) => {
if (!process?.env?.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Memory Chatbot" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").click();
await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page
.getByTestId("input-chat-playground")
.last()
.fill("Remember that I'm a lion");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page
.getByTestId("input-chat-playground")
.last()
.fill("try reproduce the sound I made in words");
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForSelector("text=roar", { timeout: 30000 });
await page.getByText("roar").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").last().isVisible();
});

View file

@ -0,0 +1,129 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Vector Store RAG", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Vector Store RAG" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
// if (
// !process.env.OPENAI_API_KEY ||
// !process.env.ASTRA_DB_API_ENDPOINT ||
// !process.env.ASTRA_DB_APPLICATION_TOKEN
// ) {
// //You must set the OPENAI_API_KEY, ASTRA_DB_API_ENDPOINT and ASTRA_DB_APPLICATION_TOKEN on .env file to run this test
// expect(false).toBe(true);
// }
// await page
// .getByTestId("popover-anchor-input-openai_api_key")
// .nth(0)
// .fill(process.env.OPENAI_API_KEY ?? "");
// await page
// .getByTestId("popover-anchor-input-openai_api_key")
// .nth(1)
// .fill(process.env.OPENAI_API_KEY ?? "");
// await page
// .getByTestId("popover-anchor-input-openai_api_key")
// .nth(2)
// .fill(process.env.OPENAI_API_KEY ?? "");
// await page
// .getByTestId("popover-anchor-input-token")
// .nth(0)
// .fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
// await page
// .getByTestId("popover-anchor-input-token")
// .nth(1)
// .fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
// await page
// .getByTestId("popover-anchor-input-api_endpoint")
// .nth(0)
// .fill(process.env.ASTRA_DB_API_ENDPOINT ?? "");
// await page
// .getByTestId("popover-anchor-input-api_endpoint")
// .nth(1)
// .fill(process.env.ASTRA_DB_API_ENDPOINT ?? "");
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("icon-FileSearch2").last().click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "/assets/test_file.txt"));
await page.getByText("test_file.txt").isVisible();
// await page.getByTestId("button_run_astra db").first().click();
// await page.waitForSelector("text=built successfully", { timeout: 30000 });
// await page.getByText("built successfully").last().click({
// timeout: 30000,
// });
// await page.getByTestId("button_run_chat output").click();
// await page.waitForSelector("text=built successfully", { timeout: 30000 });
// await page.getByText("built successfully").last().click({
// timeout: 30000,
// });
// await page.getByText("Playground", { exact: true }).click();
// await page.waitForSelector('[data-testid="input-chat-playground"]', {
// timeout: 100000,
// });
// await page.getByTestId("input-chat-playground").last().fill("hello");
// await page.getByTestId("icon-LucideSend").last().click();
// await page
// .getByText("This is a test file.", { exact: true })
// .last()
// .isVisible();
// await page.getByText("Memories", { exact: true }).last().click();
// await page.getByText("Default Session").last().click();
// await page.getByText("timestamp", { exact: true }).last().isVisible();
// await page.getByText("text", { exact: true }).last().isVisible();
// await page.getByText("sender", { exact: true }).last().isVisible();
// await page.getByText("sender_name", { exact: true }).last().isVisible();
// await page.getByText("session_id", { exact: true }).last().isVisible();
// await page.getByText("files", { exact: true }).last().isVisible();
// await page.getByRole("gridcell").last().isVisible();
// await page.getByTestId("icon-Trash2").first().click();
// await page.waitForSelector('[data-testid="input-chat-playground"]', {
// timeout: 100000,
// });
// await page.getByTestId("input-chat-playground").last().isVisible();
});

View file

@ -89,6 +89,10 @@ test("search components", async ({ page }) => {
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -157,6 +161,11 @@ test("user should be able to download a flow or a component", async ({
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -234,6 +243,9 @@ test("user should be able to duplicate a flow or a component", async ({
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();

View file

@ -1,440 +0,0 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Basic Prompting (Hello, World)", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("button_run_chat output").click();
await page.waitForTimeout(1000);
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page
.getByPlaceholder("Send a message...")
.last()
.fill("Say hello as a pirate");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page.getByText("Ahoy").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.getByPlaceholder("Send a message...").last().isVisible();
});
test("Memory Chatbot", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Memory Chatbot" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("button_run_chat output").click();
await page.waitForTimeout(1000);
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page
.getByPlaceholder("Send a message...")
.last()
.fill("Remember that I'm a lion");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page
.getByPlaceholder("Send a message...")
.last()
.fill("try reproduce the sound I made in words");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page.getByText("roar").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.getByPlaceholder("Send a message...").last().isVisible();
});
test("Document QA", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Document QA" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("icon-FileSearch2").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "/assets/test_file.txt"));
await page.getByText("test_file.txt").isVisible();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").click();
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page
.getByPlaceholder("Send a message...")
.last()
.fill("whats the text in the file?");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page.getByText("this is a test file").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.getByPlaceholder("Send a message...").last().isVisible();
});
test("Blog Writer", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Blog Writer" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page
.getByTestId("input-list-input_urls-0")
.nth(0)
.fill(
"https://www.natgeokids.com/uk/discover/animals/sea-life/turtle-facts/",
);
await page
.getByTestId("input-list-input_urls-1")
.nth(0)
.fill("https://www.originaldiving.com/blog/top-ten-turtle-facts");
await page
.getByTestId("popover-anchor-input-input_value")
.nth(0)
.fill(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
);
await page.getByTestId("button_run_chat output").click();
await page.waitForTimeout(5000);
await page.getByText("built successfully").last().click({
timeout: 30000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByPlaceholder(
"No chat input variables found. Click to run your flow.",
{ exact: true },
)
.last()
.isVisible();
await page.waitForTimeout(3000);
await page.getByText("turtles").last().isVisible();
await page.getByText("sea").last().isVisible();
await page.getByText("survival").last().isVisible();
await page.getByText("Instructions").last().click();
const value = await page
.getByPlaceholder("Enter text...")
.last()
.inputValue();
expect(value).toBe(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
);
await page.getByTestId("icon-ExternalLink").last().click();
const count = await page
.getByText(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
)
.count();
if (count <= 1) {
expect(false).toBe(true);
}
});
test("Vector Store RAG", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Vector Store RAG" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (
!process.env.OPENAI_API_KEY ||
!process.env.ASTRA_DB_API_ENDPOINT ||
!process.env.ASTRA_DB_APPLICATION_TOKEN
) {
//You must set the OPENAI_API_KEY, ASTRA_DB_API_ENDPOINT and ASTRA_DB_APPLICATION_TOKEN on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.nth(0)
.fill(process.env.OPENAI_API_KEY ?? "");
await page
.getByTestId("popover-anchor-input-openai_api_key")
.nth(1)
.fill(process.env.OPENAI_API_KEY ?? "");
await page
.getByTestId("popover-anchor-input-openai_api_key")
.nth(2)
.fill(process.env.OPENAI_API_KEY ?? "");
await page
.getByTestId("popover-anchor-input-token")
.nth(0)
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
await page
.getByTestId("popover-anchor-input-token")
.nth(1)
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
await page
.getByTestId("popover-anchor-input-api_endpoint")
.nth(0)
.fill(process.env.ASTRA_DB_API_ENDPOINT ?? "");
await page
.getByTestId("popover-anchor-input-api_endpoint")
.nth(1)
.fill(process.env.ASTRA_DB_API_ENDPOINT ?? "");
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("icon-FileSearch2").last().click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "/assets/test_file.txt"));
await page.getByText("test_file.txt").isVisible();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_astra db").first().click();
await page.getByText("built successfully").last().click({
timeout: 30000,
});
await page.getByTestId("button_run_chat output").click();
await page.getByText("built successfully").last().click({
timeout: 30000,
});
await page.getByText("Playground", { exact: true }).click();
await page.getByPlaceholder("Send a message...").last().fill("hello");
await page.getByTestId("icon-LucideSend").last().click();
await page
.getByText("This is a test file.", { exact: true })
.last()
.isVisible();
await page.getByText("Memories", { exact: true }).last().click();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.getByPlaceholder("Send a message...").last().isVisible();
});

View file

@ -28,7 +28,11 @@ test("chat_io_teste", async ({ page }) => {
);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
@ -50,6 +54,10 @@ test("chat_io_teste", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -79,8 +87,11 @@ test("chat_io_teste", async ({ page }) => {
await page.getByLabel("fit view").click();
await page.getByText("Playground", { exact: true }).click();
await page.getByPlaceholder("Send a message...").click();
await page.getByPlaceholder("Send a message...").fill("teste");
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").click();
await page.getByTestId("input-chat-playground").fill("teste");
await page.getByRole("button").nth(1).click();
const chat_output = page.getByTestId("chat-message-AI-teste");
const chat_input = page.getByTestId("chat-message-User-teste");

View file

@ -0,0 +1,108 @@
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import { readFileSync } from "fs";
import path from "path";
test("user must be able to send an image on chat", async ({ page }) => {
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForSelector("text=Chat Input", { timeout: 30000 });
await page.getByText("Chat Input", { exact: true }).click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
await page.getByText("Save Changes").click();
await page.getByText("Playground", { exact: true }).click();
// Read the image file as a binary string
const filePath = "tests/end-to-end/assets/chain.png";
const fileContent = readFileSync(filePath, "base64");
// Create the DataTransfer and File objects within the browser context
const dataTransfer = await page.evaluateHandle(
({ fileContent }) => {
const dt = new DataTransfer();
const byteCharacters = atob(fileContent);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const file = new File([byteArray], "chain.png", { type: "image/png" });
dt.items.add(file);
return dt;
},
{ fileContent },
);
// Locate the target element
const element = await page.getByTestId("input-chat-playground");
// Dispatch the drop event on the target element
await element.dispatchEvent("drop", { dataTransfer });
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
await page.waitForSelector("text=chain.png", { timeout: 30000 });
await page.getByText("chain.png").isVisible();
await page.getByText("Close", { exact: true }).click();
await page.waitForSelector('[data-testid="icon-ScanEye"]', {
timeout: 30000,
});
await page.getByTestId("icon-ScanEye").nth(4).click();
await page.getByText("Restart").isHidden();
});

View file

@ -0,0 +1,67 @@
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import { readFileSync } from "fs";
import path from "path";
test("user must be able to see output inspection", async ({ page }) => {
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").last().click();
await page.waitForTimeout(5000);
await page.waitForSelector('[data-testid="icon-ScanEye"]', {
timeout: 30000,
});
await page.getByTestId("icon-ScanEye").nth(4).click();
await page.getByText("Sender", { exact: true }).isVisible();
await page.getByText("Type", { exact: true }).isVisible();
await page.getByText("User", { exact: true }).last().isVisible();
});

View file

@ -29,7 +29,9 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000);
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
@ -44,8 +46,23 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByText("Playground", { exact: true }).click();
await page.getByPlaceholder("Send a message...").fill("Hello, how are you?");
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").fill("Hello, how are you?");
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
let valueUser = await page.getByTestId("sender_name_user").textContent();
@ -67,6 +84,11 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
);
await page.getByText("Playground", { exact: true }).last().click();
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
await page.getByText("Close", { exact: true }).click();
await page.getByText("Chat Input", { exact: true }).click();
@ -89,6 +111,11 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
.fill("TestSenderNameAI");
await page.getByText("Playground", { exact: true }).last().click();
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
valueUser = await page
@ -110,153 +137,3 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
.isVisible(),
);
});
test("user must be able to see output inspection", async ({ page }) => {
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("button_run_chat output").last().click();
await page.waitForTimeout(5000);
await page.waitForSelector('[data-testid="icon-ScanEye"]', {
timeout: 30000,
});
await page.getByTestId("icon-ScanEye").nth(4).click();
await page.getByText("Sender", { exact: true }).isVisible();
await page.getByText("Type", { exact: true }).isVisible();
await page.getByText("User", { exact: true }).last().isVisible();
});
test("user must be able to send an image on chat", async ({ page }) => {
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByText("Chat Input", { exact: true }).click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
await page.getByText("Save Changes").click();
await page.getByText("Playground", { exact: true }).click();
// Read the image file as a binary string
const filePath = "tests/end-to-end/assets/chain.png";
const fileContent = readFileSync(filePath, "base64");
// Create the DataTransfer and File objects within the browser context
const dataTransfer = await page.evaluateHandle(
({ fileContent }) => {
const dt = new DataTransfer();
const byteCharacters = atob(fileContent);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const file = new File([byteArray], "chain.png", { type: "image/png" });
dt.items.add(file);
return dt;
},
{ fileContent },
);
// Locate the target element
const element = await page.getByPlaceholder("Send a message...");
// Dispatch the drop event on the target element
await element.dispatchEvent("drop", { dataTransfer });
await page.waitForTimeout(4000);
await page.getByText("chain.png").isVisible();
await page.getByTestId("icon-LucideSend").click();
await page.waitForTimeout(2000);
await page.getByText("chain.png").isVisible();
await page.getByText("Close", { exact: true }).click();
await page.waitForSelector('[data-testid="icon-ScanEye"]', {
timeout: 30000,
});
await page.getByTestId("icon-ScanEye").nth(4).click();
await page.getByText("Restart").isHidden();
});

View file

@ -23,7 +23,11 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("python function");

View file

@ -9,21 +9,34 @@ test("should delete a flow", async ({ page }) => {
await page
.getByPlaceholder("Insert your API Key", { exact: true })
.fill(process.env.STORE_API_KEY ?? "");
await page.waitForSelector("text=Save", { timeout: 30000 });
await page.getByText("Save").last().click();
await page.waitForTimeout(8000);
await page.waitForSelector("text=Store", { timeout: 30000 });
await page.getByText("Store").nth(0).click();
await page.getByTestId("install-Website Content QA").click();
await page.waitForTimeout(5000);
await page.getByText("My Collection").nth(0).click();
await page.getByText("Website Content QA").first().isVisible();
await page.getByLabel("checkbox-component").last().waitFor({
state: "visible",
timeout: 30000, // Timeout of 30 seconds
await page.waitForSelector('[data-testid="install-Website Content QA"]', {
timeout: 100000,
});
await page.getByLabel("checkbox-component").first().click();
await page.getByTestId("install-Website Content QA").click();
await page.waitForSelector("text=My Collection", { timeout: 30000 });
await page.getByText("My Collection").nth(0).click();
await page.waitForSelector("text=Website Content QA", { timeout: 30000 });
await page.getByText("Website Content QA").first().isVisible();
await page.waitForSelector('[data-testid="checkbox-component"]', {
timeout: 100000,
});
await page.getByTestId("checkbox-component").first().click();
await page.getByTestId("icon-Trash2").click();
await page
@ -44,12 +57,12 @@ test("should delete a component", async ({ page }) => {
await page.getByText("Components").first().click();
await page.getByText("Basic RAG").first().isVisible();
await page.getByLabel("checkbox-component").last().waitFor({
state: "visible",
timeout: 30000, // Timeout of 30 seconds
await page.waitForSelector('[data-testid="checkbox-component"]', {
timeout: 100000,
});
await page.getByLabel("checkbox-component").first().click();
await page.getByTestId("checkbox-component").first().click();
await page.getByTestId("icon-Trash2").click();
await page
.getByText("Are you sure you want to delete the selected component?")

View file

@ -22,8 +22,9 @@ test("dropDownComponent", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("amazon");

View file

@ -23,7 +23,9 @@ test("should be able to upload a file", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();

View file

@ -0,0 +1,109 @@
import { expect, test } from "@playwright/test";
test("LLMChain - Filter", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("llmchain");
await page.waitForTimeout(1000);
await page
.getByTestId("chainsLLMChain")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.waitForTimeout(500);
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div/div/div[2]/div[7]/button/div[1]',
)
.click();
await expect(page.getByTestId("disclosure-agents")).toBeVisible();
await expect(page.getByTestId("chainsLLMChain").first()).toBeVisible();
await expect(
page.getByTestId("langchain_utilitiesSearchApi").first(),
).toBeVisible();
await expect(
page.getByTestId("memoriesAstra DB Message Reader").first(),
).toBeVisible();
await expect(
page.getByTestId("prototypesFlow as Tool").first(),
).toBeVisible();
await expect(
page.getByTestId("retrieversAmazon Kendra Retriever").first(),
).toBeVisible();
await expect(
page.getByTestId("textsplittersCharacterTextSplitter").first(),
).toBeVisible();
await expect(
page.getByTestId("toolkitsVectorStoreInfo").first(),
).toBeVisible();
await expect(page.getByTestId("toolsSearchApi").first()).toBeVisible();
await page.getByPlaceholder("Search").click();
await expect(page.getByTestId("model_specsVertexAI")).not.toBeVisible();
await expect(page.getByTestId("model_specsCTransformers")).not.toBeVisible();
await expect(page.getByTestId("model_specsAmazon Bedrock")).not.toBeVisible();
await expect(page.getByTestId("modelsAzure OpenAI")).not.toBeVisible();
await expect(
page.getByTestId("model_specsAzureChatOpenAI"),
).not.toBeVisible();
await expect(page.getByTestId("model_specsChatAnthropic")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatLiteLLM")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatOllama")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatOpenAI")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatVertexAI")).not.toBeVisible();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div/div/div[2]/div[4]/div/button/div[1]',
)
.click();
await expect(page.getByTestId("disclosure-models")).toBeVisible();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div/div/div[2]/div[3]/div/button/div[1]',
)
.click();
await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
await expect(page.getByTestId("disclosure-agents")).toBeVisible();
await expect(page.getByTestId("disclosure-chains")).toBeVisible();
await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
});

View file

@ -22,7 +22,9 @@ test("FloatComponent", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");

View file

@ -22,7 +22,9 @@ test.describe("Flow Page tests", () => {
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom");

View file

@ -22,6 +22,10 @@ test("CRUD folders", async ({ page }) => {
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
timeout: 100000,
});
await page.getByTestId("icon-ChevronLeft").first().click();
await page.getByText("My Collection").nth(2).isVisible();
@ -35,9 +39,18 @@ test("CRUD folders", async ({ page }) => {
await page.getByText("New Folder").last().isVisible();
await page.waitForTimeout(1000);
await page.getByText("New Folder").last().dblclick();
await page.getByTestId("input-folder").fill("new folder test name");
await page.keyboard.press("Enter");
await page.getByText("new folder test name").last().isVisible();
const element = await page.getByTestId("input-folder");
await element.fill("new folder test name");
await page.getByText("My Projects").last().click({
force: true,
});
await page.getByText("new folder test name").last().waitFor({
state: "visible",
timeout: 30000,
});
await page
.getByText("new folder test name")

View file

@ -22,7 +22,9 @@ test("should interact with api request", async ({ page }) => {
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
@ -81,8 +83,23 @@ test("erase button should clear the chat messages", async ({ page }) => {
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByText("Playground", { exact: true }).click();
await page.getByPlaceholder("Send a message...").fill("Hello, how are you?");
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").fill("Hello, how are you?");
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
let valueUser = await page.getByTestId("sender_name_user").textContent();

View file

@ -22,7 +22,9 @@ test("GlobalVariables", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
@ -35,6 +37,10 @@ test("GlobalVariables", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -21,7 +21,9 @@ test("LLMChain - Tooltip", async ({ page }) => {
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
@ -33,6 +35,10 @@ test("LLMChain - Tooltip", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -101,6 +107,11 @@ test("LLMChain - Tooltip", async ({ page }) => {
await page.waitForTimeout(500);
});
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -146,112 +157,3 @@ test("LLMChain - Tooltip", async ({ page }) => {
).toBeVisible();
});
});
test("LLMChain - Filter", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(5000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.waitForTimeout(1000);
await page.getByTestId(
"input-list-plus-btn-edit_metadata_indexing_include-2",
);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("llmchain");
await page.waitForTimeout(1000);
await page
.getByTestId("chainsLLMChain")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.waitForTimeout(500);
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div/div/div[2]/div[7]/button/div[1]',
)
.click();
await expect(page.getByTestId("disclosure-models")).toBeVisible();
await expect(
page.getByTestId("modelsGoogle Generative AI").first(),
).toBeVisible();
await expect(page.getByTestId("chainsLLMChain").first()).toBeVisible();
await expect(
page.getByTestId("langchain_utilitiesSearchApi").first(),
).toBeVisible();
await expect(
page.getByTestId("memoriesAstra DB Message Reader").first(),
).toBeVisible();
await expect(
page.getByTestId("prototypesFlow as Tool").first(),
).toBeVisible();
await expect(
page.getByTestId("retrieversAmazon Kendra Retriever").first(),
).toBeVisible();
await expect(
page.getByTestId("textsplittersCharacterTextSplitter").first(),
).toBeVisible();
await expect(
page.getByTestId("toolkitsVectorStoreInfo").first(),
).toBeVisible();
await expect(page.getByTestId("toolsSearchApi").first()).toBeVisible();
await page.getByPlaceholder("Search").click();
await expect(page.getByTestId("model_specsVertexAI")).not.toBeVisible();
await expect(page.getByTestId("model_specsCTransformers")).not.toBeVisible();
await expect(page.getByTestId("model_specsAmazon Bedrock")).not.toBeVisible();
await expect(page.getByTestId("modelsAzure OpenAI")).not.toBeVisible();
await expect(
page.getByTestId("model_specsAzureChatOpenAI"),
).not.toBeVisible();
await expect(page.getByTestId("model_specsChatAnthropic")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatLiteLLM")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatOllama")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatOpenAI")).not.toBeVisible();
await expect(page.getByTestId("model_specsChatVertexAI")).not.toBeVisible();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div/div/div[2]/div[4]/div/button/div[1]',
)
.click();
await expect(page.getByTestId("disclosure-models")).toBeVisible();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div/div/div[2]/div[3]/div/button/div[1]',
)
.click();
await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
await expect(page.getByTestId("disclosure-agents")).toBeVisible();
await expect(page.getByTestId("disclosure-chains")).toBeVisible();
await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
});

View file

@ -22,7 +22,9 @@ test("InputComponent", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
@ -34,6 +36,10 @@ test("InputComponent", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -21,7 +21,9 @@ test("InputListComponent", async ({ page }) => {
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");

View file

@ -22,7 +22,9 @@ test("IntComponent", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
@ -35,6 +37,10 @@ test("IntComponent", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -66,6 +72,11 @@ test("IntComponent", async ({ page }) => {
}
await page.getByTestId("title-OpenAI").click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -2,13 +2,6 @@ import { expect, test } from "@playwright/test";
import uaParser from "ua-parser-js";
test("LangflowShortcuts", async ({ page }) => {
await page.goto("/");
const getUA = await page.evaluate(() => navigator.userAgent);
const userAgentInfo = uaParser(getUA);
let control = "Control";
if (userAgentInfo.os.name.includes("Mac")) {
control = "Meta";
}
await page.waitForTimeout(1000);
@ -28,8 +21,18 @@ test("LangflowShortcuts", async ({ page }) => {
modalCount = await page.getByTestId("modal-title")?.count();
}
const getUA = await page.evaluate(() => navigator.userAgent);
const userAgentInfo = uaParser(getUA);
let control = "Control";
if (userAgentInfo.os.name.includes("Mac")) {
control = "Meta";
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");
@ -43,6 +46,11 @@ test("LangflowShortcuts", async ({ page }) => {
await page.mouse.down();
await page.locator('//*[@id="react-flow-id"]/div/div[2]/button[3]').click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -21,7 +21,9 @@ test("NestedComponent", async ({ page }) => {
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("pinecone");

View file

@ -21,7 +21,9 @@ test("PromptTemplateComponent", async ({ page }) => {
}
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("prompt");

View file

@ -93,8 +93,9 @@ test.describe("save component tests", () => {
if (replaceButton) {
await page.getByTestId("replace-button").click();
}
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("group");

View file

@ -18,111 +18,6 @@ test("should not have an API key", async ({ page }) => {
await page.getByText("API Key Error").isVisible();
});
test("should find a searched Component on Store", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByTestId("search-store-input").fill("File Loader");
await page.getByTestId("search-store-button").click();
await page.getByText("File Loader").isVisible();
await page.getByTestId("search-store-input").fill("Basic RAG");
await page.getByTestId("search-store-button").click();
await page.getByText("Basic RAG").isVisible();
await page.getByTestId("search-store-input").fill("YouTube QA");
await page.getByTestId("search-store-button").click();
await page.getByText("YouTube QA").isVisible();
});
test("should filter by tag", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByTestId("tag-selector-Agent").click();
await page.getByText("File Loader").isVisible();
await page.getByTestId("tag-selector-Agent").click();
await page.getByText("Album Cover Builder").isVisible();
await page.getByTestId("tag-selector-Memory").click();
await page.getByText("MP3 QA12").isVisible();
await page.getByTestId("tag-selector-Chain").click();
await page.getByText("There are no").isVisible();
await page.getByTestId("tag-selector-Chain").click();
await page.getByTestId("tag-selector-Vector Store").click();
await page.getByText("MP3 QA12").isVisible();
await page.getByTestId("tag-selector-Vector Store").click();
await page.getByTestId("tag-selector-Memory").click();
await page.getByText("Basic RAG").isVisible();
});
test("should order the visualization", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByText("Basic RAG").isVisible();
await page.getByTestId("select-order-store").click();
await page.waitForTimeout(2000);
await page.getByText("Alphabetical").click();
await page.getByText("Album Cover Builder").isVisible();
await page.getByTestId("select-order-store").click();
await page.getByText("Popular").click();
await page.getByText("Basic RAG").isVisible();
});
test("should filter by type", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByText("Website Content QA").isVisible();
await page.getByTestId("flows-button-store").click();
await page.waitForTimeout(8000);
let iconGroup = await page.getByTestId("icon-Group")?.count();
expect(iconGroup).not.toBe(0);
await page.getByText("icon-ToyBrick").last().isHidden();
await page.getByTestId("components-button-store").click();
await page.waitForTimeout(8000);
await page.getByTestId("icon-Group").last().isHidden();
let toyBrick = await page.getByTestId("icon-ToyBrick")?.count();
expect(toyBrick).not.toBe(0);
await page.getByTestId("all-button-store").click();
await page.waitForTimeout(8000);
let iconGroupAllCount = await page.getByTestId("icon-Group")?.count();
await page.waitForTimeout(2000);
let toyBrickAllCount = await page.getByTestId("icon-ToyBrick")?.count();
await page.waitForTimeout(2000);
if (iconGroupAllCount === 0 || toyBrickAllCount === 0) {
expect(false).toBe(true);
}
});
test("should add API-KEY", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
@ -140,9 +35,6 @@ test("should add API-KEY", async ({ page }) => {
await page.waitForTimeout(2000);
await page.getByText("Success! Your API Key has been saved.").isVisible();
await page.waitForTimeout(2000);
await page.getByText("API Key Error").isVisible();
await page
.getByPlaceholder("Insert your API Key")
.fill(process.env.STORE_API_KEY ?? "");
@ -224,6 +116,53 @@ test("should like and add components and flows", async ({ page }) => {
await page.getByText("Basic RAG").first().isVisible();
});
test("should find a searched Component on Store", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByTestId("search-store-input").fill("File Loader");
await page.getByTestId("search-store-button").click();
await page.getByText("File Loader").isVisible();
await page.getByTestId("search-store-input").fill("Basic RAG");
await page.getByTestId("search-store-button").click();
await page.getByText("Basic RAG").isVisible();
await page.getByTestId("search-store-input").fill("YouTube QA");
await page.getByTestId("search-store-button").click();
await page.getByText("YouTube QA").isVisible();
});
test("should filter by tag", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByTestId("tag-selector-Agent").click();
await page.getByText("File Loader").isVisible();
await page.getByTestId("tag-selector-Agent").click();
await page.getByText("Album Cover Builder").isVisible();
await page.getByTestId("tag-selector-Memory").click();
await page.getByText("MP3 QA12").isVisible();
await page.getByTestId("tag-selector-Chain").click();
await page.getByText("There are no").isVisible();
await page.getByTestId("tag-selector-Chain").click();
await page.getByTestId("tag-selector-Vector Store").click();
await page.getByText("MP3 QA12").isVisible();
await page.getByTestId("tag-selector-Vector Store").click();
await page.getByTestId("tag-selector-Memory").click();
await page.getByText("Basic RAG").isVisible();
});
test("should share component with share button", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(2000);
@ -259,11 +198,11 @@ test("should share component with share button", async ({ page }) => {
await page.getByText("Save").last().click();
await page.getByText("Close").last().click();
await page.waitForSelector('[data-testid="icon-Share3"]', {
await page.waitForSelector('[data-testid="shared-button-flow"]', {
timeout: 100000,
});
await page.getByTestId("icon-Share3").first().click();
await page.getByTestId("shared-button-flow").first().click();
await page.getByText("Name:").isVisible();
await page.getByText("Description:").isVisible();
await page.getByText("Set workflow status to public").isVisible();
@ -288,3 +227,61 @@ test("should share component with share button", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByText("Flow shared successfully").last().isVisible();
});
test("should order the visualization", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByText("Basic RAG").isVisible();
await page.getByTestId("select-order-store").click();
await page.waitForTimeout(2000);
await page.getByText("Alphabetical").click();
await page.getByText("Album Cover Builder").isVisible();
await page.getByTestId("select-order-store").click();
await page.getByText("Popular").click();
await page.getByText("Basic RAG").isVisible();
});
test("should filter by type", async ({ page }) => {
await page.goto("/");
await page.waitForTimeout(1000);
await page.getByTestId("button-store").click();
await page.waitForTimeout(1000);
await page.getByText("Website Content QA").isVisible();
await page.getByTestId("flows-button-store").click();
await page.waitForTimeout(8000);
let iconGroup = await page.getByTestId("icon-Group")?.count();
expect(iconGroup).not.toBe(0);
await page.getByText("icon-ToyBrick").last().isHidden();
await page.getByTestId("components-button-store").click();
await page.waitForTimeout(8000);
await page.getByTestId("icon-Group").last().isHidden();
let toyBrick = await page.getByTestId("icon-ToyBrick")?.count();
expect(toyBrick).not.toBe(0);
await page.getByTestId("all-button-store").click();
await page.waitForTimeout(8000);
let iconGroupAllCount = await page.getByTestId("icon-Group")?.count();
await page.waitForTimeout(2000);
let toyBrickAllCount = await page.getByTestId("icon-ToyBrick")?.count();
await page.waitForTimeout(2000);
if (iconGroupAllCount === 0 || toyBrickAllCount === 0) {
expect(false).toBe(true);
}
});

View file

@ -42,6 +42,10 @@ test("TextAreaModalComponent", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -60,42 +60,42 @@ test("TextInputOutputComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[1]/div/div[2]/div[5]/button/div[1]',
)
.waitFor({
state: "visible",
timeout: 30000,
});
const component1 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[1]',
);
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[1]/div/div[2]/div[5]/button/div[1]',
)
.hover();
const element1 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[1]/div/div[2]/div[5]/button/div[1]',
);
const component2 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[2]',
);
const element2 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[2]/div/div[2]/div[3]/div/button/div[1]',
);
// ensure elements popups are not blocking
component1.blur();
component2.blur();
await page.mouse.up();
await element1.hover();
await page.mouse.down();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[2]/div/div[2]/div[3]/div/button/div[1]',
)
.waitFor({
state: "visible",
timeout: 30000,
});
// Move to the second element
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[2]/div/div[2]/div[3]/div/button/div[1]',
)
.hover();
await element2.hover();
// Release the mouse
await page.mouse.up();
@ -109,43 +109,40 @@ test("TextInputOutputComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.getByTitle("fit view").click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[2]/div/div[2]/div[15]/button/div[1]',
)
.waitFor({
state: "visible",
timeout: 30000,
});
const component3 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div[3]',
);
// Click and hold on the first element
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[2]/div/div[2]/div[15]/button/div[1]',
)
.hover();
const element3 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[3]/div/div[2]/div[3]/div/button/div[1]',
);
const element4 = await page.locator(
'//*[@id="react-flow-id"]/div/div[1]/div[1]/div/div[2]/div[2]/div/div[2]/div[16]/button/div[1]',
);
// ensure elements popups are not blocking
component2.blur();
component3.blur();
await page.mouse.up();
await element4.hover();
await page.mouse.down();
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[3]/div/div[2]/div[3]/div/button/div[1]',
)
.waitFor({
state: "visible",
timeout: 30000,
});
// Move to the second element
await page
.locator(
'//*[@id="react-flow-id"]/div/div[1]/div/div/div[2]/div[3]/div/div[2]/div[3]/div/button/div[1]',
)
.hover();
await element3.hover();
// Release the mouse
await page.mouse.up();
@ -163,6 +160,11 @@ test("TextInputOutputComponent", async ({ page }) => {
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByText("Playground", { exact: true }).click();
await page.getByText("Run Flow", { exact: true }).click();

View file

@ -41,6 +41,10 @@ test("ToggleComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
@ -58,6 +62,10 @@ test("ToggleComponent", async ({ page }) => {
await page.getByText("Save Changes", { exact: true }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTestId("toggle-load_hidden").click();
@ -77,6 +85,10 @@ test("ToggleComponent", async ({ page }) => {
await page.getByTestId("div-generic-node").click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -82,7 +82,9 @@ test("check if tweaks are updating when someothing on the flow changes", async (
await page.waitForTimeout(1000);
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
@ -94,6 +96,11 @@ test("check if tweaks are updating when someothing on the flow changes", async (
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();

View file

@ -17,5 +17,41 @@
"jsx": "react-jsx",
"noImplicitAny": false
},
"include": ["src"]
"include": [
"src",
"tests/end-to-end/saveComponents.spec.ts",
"tests/end-to-end/actionsMainPage.spec.ts",
"tests/end-to-end/auto_login.spec.ts",
"tests/end-to-end/chatInputOutput.spec.ts",
"tests/end-to-end/chatInputOutputUser - Interact.spec.ts",
"tests/end-to-end/codeAreaModalComponent.spec.ts",
"tests/end-to-end/curlApiGeneration.spec.ts",
"tests/end-to-end/deleteComponentFlows.spec.ts",
"tests/end-to-end/dragAndDrop.spec.ts",
"tests/end-to-end/dropdownComponent.spec.ts",
"tests/end-to-end/fileUploadComponent.spec.ts",
"tests/end-to-end/filterEdge.spec.ts",
"tests/end-to-end/floatComponent.spec.ts",
"tests/end-to-end/flowPage.spec.ts",
"tests/end-to-end/flowSettings.spec.ts",
"tests/end-to-end/generalBugs.spec.ts",
"tests/end-to-end/globalVariables.spec.ts",
"tests/end-to-end/group.spec.ts",
"tests/end-to-end/folders.spec.ts",
"tests/end-to-end/inputComponent.spec.ts",
"tests/end-to-end/inputListComponent.spec.ts",
"tests/end-to-end/intComponent.spec.ts",
"tests/end-to-end/keyPairListComponent.spec.ts",
"tests/end-to-end/langflowShortcuts.spec.ts",
"tests/end-to-end/nestedComponent.spec.ts",
"tests/end-to-end/promptModalComponent.spec.ts",
"tests/end-to-end/python_api_generation.spec.ts",
"tests/end-to-end/textAreaModalComponent.spec.ts",
"tests/end-to-end/textInputOutput.spec.ts",
"tests/end-to-end/toggleComponent.spec.ts",
"tests/end-to-end/tweaks_test.spec.ts",
"tests/end-to-end/twoEdges.spec.ts",
"tests/end-to-end/userSettings.spec.ts",
"tests/end-to-end/store.spec.ts"
]
}