Merge branch 'dev' into update_components
This commit is contained in:
commit
724f325e08
106 changed files with 2402 additions and 1322 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
79
src/backend/base/poetry.lock
generated
79
src/backend/base/poetry.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export default function FlowToolbar(): JSX.Element {
|
|||
? "button-disable text-muted-foreground"
|
||||
: "",
|
||||
)}
|
||||
data-testid="shared-button-flow"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Share3"
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
61
src/frontend/src/icons/Firecrawl/FirecrawlLogo.jsx
Normal file
61
src/frontend/src/icons/Firecrawl/FirecrawlLogo.jsx
Normal 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;
|
||||
28
src/frontend/src/icons/Firecrawl/firecraw-logo.svg
Normal file
28
src/frontend/src/icons/Firecrawl/firecraw-logo.svg
Normal 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 |
9
src/frontend/src/icons/Firecrawl/index.tsx
Normal file
9
src/frontend/src/icons/Firecrawl/index.tsx
Normal 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} />;
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const TextAreaWrapper = ({
|
|||
|
||||
return (
|
||||
<Textarea
|
||||
data-testid="input-chat-playground"
|
||||
onFocus={(e) => {
|
||||
setInputFocus(true);
|
||||
e.target.style.borderTopWidth = "0";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
40
src/frontend/src/modals/apiModal/utils/get-codes-obj.tsx
Normal file
40
src/frontend/src/modals/apiModal/utils/get-codes-obj.tsx
Normal 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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
15
src/frontend/src/modals/apiModal/utils/get-tabs-order.tsx
Normal file
15
src/frontend/src/modals/apiModal/utils/get-tabs-order.tsx
Normal 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;
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ export function createTabsArray(
|
|||
includeWebhookCurl = false,
|
||||
includeTweaks = false,
|
||||
) {
|
||||
// console.log(includeTweaks)
|
||||
console.log(includeWebhookCurl);
|
||||
const tabs = [
|
||||
{
|
||||
name: "Run cURL",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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"]],
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
type Message = {
|
||||
artifacts: Record<string, any>;
|
||||
flow_id: string;
|
||||
index: number;
|
||||
message: string;
|
||||
sender: string;
|
||||
sender_name: string;
|
||||
|
|
|
|||
10
src/frontend/src/types/utils/functions.ts
Normal file
10
src/frontend/src/types/utils/functions.ts
Normal 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 }>;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
90
src/frontend/tests/end-to-end/Basic Prompting.ts
Normal file
90
src/frontend/tests/end-to-end/Basic Prompting.ts
Normal 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();
|
||||
});
|
||||
109
src/frontend/tests/end-to-end/Blog Writer.spec.ts
Normal file
109
src/frontend/tests/end-to-end/Blog Writer.spec.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
96
src/frontend/tests/end-to-end/Document QA.spec.ts
Normal file
96
src/frontend/tests/end-to-end/Document QA.spec.ts
Normal 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();
|
||||
});
|
||||
102
src/frontend/tests/end-to-end/Memory Chatbot.spec.ts
Normal file
102
src/frontend/tests/end-to-end/Memory Chatbot.spec.ts
Normal 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();
|
||||
});
|
||||
129
src/frontend/tests/end-to-end/Vector Store.spec.ts
Normal file
129
src/frontend/tests/end-to-end/Vector Store.spec.ts
Normal 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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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?")
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
109
src/frontend/tests/end-to-end/filterSidebar.spec.ts
Normal file
109
src/frontend/tests/end-to-end/filterSidebar.spec.ts
Normal 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();
|
||||
});
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue