Merge branch 'saveComponent' into feature/store

This commit is contained in:
cristhianzl 2023-10-19 00:02:21 -03:00
commit e473570c5d
58 changed files with 6451 additions and 1281 deletions

View file

@ -71,3 +71,7 @@ LANGFLOW_SUPERUSER=
# Superuser password
# Example: LANGFLOW_SUPERUSER_PASSWORD=123456
LANGFLOW_SUPERUSER_PASSWORD=
# STORE_URL
# Example: LANGFLOW_STORE_URL=https://langflow.store
LANGFLOW_STORE_URL=

4
.gitignore vendored
View file

@ -254,4 +254,6 @@ langflow.db
/tmp/*
src/backend/langflow/frontend/
.docker
.docker
.idea

View file

@ -74,10 +74,10 @@ backend:
make install_backend
ifeq ($(login),1)
@echo "Running backend without autologin";
poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser
poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env
else
@echo "Running backend with autologin";
LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser
LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env
endif
build_and_run:

View file

@ -217,4 +217,40 @@ Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP).
- **top_k:** How the model selects tokens for output, the next token is selected from defaults to `40`.
- **top_p:** Tokens are selected from most probable to least until the sum of their defaults to `0.95`.
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output defaults to `False`.
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output defaults to `False`.
---
### QianfanLLMEndpoint
Wrapper around [Baidu Qianfan](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) large language models.
:::info
The Qianfan Big Model Platform is a one-stop platform for enterprise developers to develop and operate large models and services. It provides data management based on ERNIE Bot's underlying model (Ernie Bot), automatic model customization and fine-tuning, and one-stop large-scale model customization services for cloud deployment of prediction services, and provides ERNIE Bot's enterprise level service API that can be quickly called, helping to implement the demand for generative AI applications in various industries.
:::
- **Model Name:** Model name. you could get from https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu preset models are mapping to an endpoint. `Model Name` will be ignored if `Endpoint` is set.
- **Qianfan Ak:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
- **Qianfan Sk:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
- **Top p:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. The diversity of the output text is affected, and the larger the value, the stronger the diversity of the generated text - defaults to `0.8`.
- **Temperature:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. Higher values make the output more random, while lower values make it more concentrated and deterministic - defaults to `0.95`.
- **Penalty Score:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. By increasing the penalty for generated tokens, the phenomenon of duplicate generation is reduced. A higher value indicates a higher penalty - defaults to `1.0`.
- **Endpoint:** Endpoint of the Qianfan LLM, required if custom model used.
---
### QianfanChatEndpoint
Wrapper around [Baidu Qianfan](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) chat large language models. This component supports some of the LLMs (Large Language Models) available by Baidu qianfan and is used for tasks such as chatbots, Generative Question-Answering (GQA), and summarization.
:::info
The Qianfan Big Model Platform is a one-stop platform for enterprise developers to develop and operate large models and services. It provides data management based on ERNIE Bot's underlying model (Ernie Bot), automatic model customization and fine-tuning, and one-stop large-scale model customization services for cloud deployment of prediction services, and provides ERNIE Bot's enterprise level service API that can be quickly called, helping to implement the demand for generative AI applications in various industries.
:::
- **Model Name:** Model name. you could get from https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu preset models are mapping to an endpoint. `Model Name` will be ignored if `Endpoint` is set.
- **Qianfan Ak:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
- **Qianfan Sk:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
- **Top p:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. The diversity of the output text is affected, and the larger the value, the stronger the diversity of the generated text - defaults to `0.8`.
- **Temperature:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. Higher values make the output more random, while lower values make it more concentrated and deterministic - defaults to `0.95`.
- **Penalty Score:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. By increasing the penalty for generated tokens, the phenomenon of duplicate generation is reduced. A higher value indicates a higher penalty - defaults to `1.0`.
- **Endpoint:** Endpoint of the Qianfan LLM, required if custom model used.

1559
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,7 @@ google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0"
typer = "^0.9.0"
gunicorn = "^21.2.0"
langchain = "^0.0.308"
langchain = "^0.0.312"
openai = "^0.27.8"
pandas = "2.0.3"
chromadb = "^0.3.21"
@ -91,6 +91,7 @@ pillow = "^10.0.0"
metal-sdk = "^2.2.0"
markupsafe = "^2.1.3"
numexpr = "^2.8.6"
qianfan = "0.0.5"
[tool.poetry.group.dev.dependencies]

View file

@ -0,0 +1,48 @@
"""Store updates
Revision ID: 7843803a87b5
Revises: eb5866d51fd2
Create Date: 2023-10-18 23:08:57.744906
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = "7843803a87b5"
down_revision: Union[str, None] = "eb5866d51fd2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
try:
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"store_api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=True
)
)
except Exception:
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.drop_column("store_api_key")
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.drop_column("is_component")
# ### end Alembic commands ###

View file

@ -0,0 +1,42 @@
"""User id can be null in Flow
Revision ID: f5ee9749d1a6
Revises: 7843803a87b5
Create Date: 2023-10-18 23:12:27.297016
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = "f5ee9749d1a6"
down_revision: Union[str, None] = "7843803a87b5"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
try:
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.alter_column(
"user_id", existing_type=sa.CHAR(length=32), nullable=True
)
except Exception:
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.alter_column(
"user_id", existing_type=sa.CHAR(length=32), nullable=False
)
# ### end Alembic commands ###

View file

@ -5,7 +5,7 @@ from langflow.api.v1 import (
endpoints_router,
validate_router,
flows_router,
component_router,
store_router,
users_router,
api_key_router,
login_router,
@ -17,7 +17,7 @@ router = APIRouter(
router.include_router(chat_router)
router.include_router(endpoints_router)
router.include_router(validate_router)
router.include_router(component_router)
router.include_router(store_router)
router.include_router(flows_router)
router.include_router(users_router)
router.include_router(api_key_router)

View file

@ -2,7 +2,7 @@ from langflow.api.v1.endpoints import router as endpoints_router
from langflow.api.v1.validate import router as validate_router
from langflow.api.v1.chat import router as chat_router
from langflow.api.v1.flows import router as flows_router
from langflow.api.v1.components import router as component_router
from langflow.api.v1.store import router as store_router
from langflow.api.v1.users import router as users_router
from langflow.api.v1.api_key import router as api_key_router
from langflow.api.v1.login import router as login_router
@ -10,7 +10,7 @@ from langflow.api.v1.login import router as login_router
__all__ = [
"chat_router",
"endpoints_router",
"component_router",
"store_router",
"validate_router",
"flows_router",
"users_router",

View file

@ -1,7 +1,7 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException, Depends
from langflow.api.v1.schemas import ApiKeysResponse
from langflow.services.auth.utils import get_current_active_user
from langflow.api.v1.schemas import ApiKeysResponse, ApiKeyCreateRequest
from langflow.services.auth import utils as auth_utils
from langflow.services.database.models.api_key.api_key import (
ApiKeyCreate,
UnmaskedApiKeyRead,
@ -14,9 +14,17 @@ from langflow.services.database.models.api_key.crud import (
delete_api_key,
)
from langflow.services.database.models.user.user import User
from langflow.services.deps import get_session
from langflow.services.deps import (
get_session,
get_settings_service,
)
from typing import TYPE_CHECKING
from sqlmodel import Session
if TYPE_CHECKING:
pass
router = APIRouter(tags=["APIKey"], prefix="/api_key")
@ -24,7 +32,7 @@ router = APIRouter(tags=["APIKey"], prefix="/api_key")
@router.get("/", response_model=ApiKeysResponse)
def get_api_keys_route(
db: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user),
current_user: User = Depends(auth_utils.get_current_active_user),
):
try:
user_id = current_user.id
@ -38,7 +46,7 @@ def get_api_keys_route(
@router.post("/", response_model=UnmaskedApiKeyRead)
def create_api_key_route(
req: ApiKeyCreate,
current_user: User = Depends(get_current_active_user),
current_user: User = Depends(auth_utils.get_current_active_user),
db: Session = Depends(get_session),
):
try:
@ -51,7 +59,7 @@ def create_api_key_route(
@router.delete("/{api_key_id}")
def delete_api_key_route(
api_key_id: UUID,
current_user=Depends(get_current_active_user),
current_user=Depends(auth_utils.get_current_active_user),
db: Session = Depends(get_session),
):
try:
@ -59,3 +67,22 @@ def delete_api_key_route(
return {"detail": "API Key deleted"}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e
@router.post("/store")
def save_store_api_key(
api_key: ApiKeyCreateRequest,
current_user: User = Depends(auth_utils.get_current_active_user),
db: Session = Depends(get_session),
settings_service=Depends(get_settings_service),
):
try:
# Encrypt the API key
encrypted = auth_utils.encrypt_api_key(
api_key, fernet=auth_utils.get_fernet(settings_service)
)
current_user.store_api_key = encrypted
db.commit()
return {"detail": "API Key saved"}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e

View file

@ -1,77 +0,0 @@
from datetime import timezone
from typing import List
from uuid import UUID
from langflow.services.database.models.component import Component, ComponentModel
from langflow.services.deps import get_session
from sqlmodel import Session, select
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from datetime import datetime
COMPONENT_NOT_FOUND = "Component not found"
COMPONENT_ALREADY_EXISTS = "A component with the same id already exists."
COMPONENT_DELETED = "Component deleted"
router = APIRouter(prefix="/components", tags=["Components"])
@router.post("/", response_model=Component)
def create_component(component: ComponentModel, db: Session = Depends(get_session)):
db_component = Component(**component.dict())
try:
db.add(db_component)
db.commit()
db.refresh(db_component)
except IntegrityError as e:
db.rollback()
raise HTTPException(
status_code=400,
detail=COMPONENT_ALREADY_EXISTS,
) from e
return db_component
@router.get("/{component_id}", response_model=Component)
def read_component(component_id: UUID, db: Session = Depends(get_session)):
if component := db.get(Component, component_id):
return component
else:
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
@router.get("/", response_model=List[Component])
def read_components(skip: int = 0, limit: int = 50, db: Session = Depends(get_session)):
query = select(Component)
query = query.offset(skip).limit(limit)
return db.execute(query).fetchall()
@router.patch("/{component_id}", response_model=Component)
def update_component(
component_id: UUID, component: ComponentModel, db: Session = Depends(get_session)
):
db_component = db.get(Component, component_id)
if not db_component:
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
component_data = component.dict(exclude_unset=True)
for key, value in component_data.items():
setattr(db_component, key, value)
db_component.update_at = datetime.now(timezone.utc)
db.commit()
db.refresh(db_component)
return db_component
@router.delete("/{component_id}")
def delete_component(component_id: UUID, db: Session = Depends(get_session)):
component = db.get(Component, component_id)
if not component:
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
db.delete(component)
db.commit()
return {"detail": COMPONENT_DELETED}

View file

@ -198,3 +198,7 @@ class Token(BaseModel):
access_token: str
refresh_token: str
token_type: str
class ApiKeyCreateRequest(BaseModel):
api_key: str

View file

@ -0,0 +1,120 @@
from typing import List, Optional
from uuid import UUID
from langflow.services.auth import utils as auth_utils
from langflow.services.database.models.flow.flow import Flow
from langflow.services.database.models.user.user import User
from langflow.services.deps import (
get_session,
get_store_service,
get_settings_service,
)
from langflow.services.store.schema import ComponentResponse
from fastapi import APIRouter, Depends, HTTPException, Query
from datetime import datetime
from langflow.services.store.service import StoreService
router = APIRouter(prefix="/store", tags=["Components Store"])
@router.post("/", response_model=ComponentResponse)
def create_component(
component: Flow,
store_service: StoreService = Depends(get_store_service),
user=Depends(auth_utils.get_current_active_user),
settings_service=Depends(get_settings_service),
):
try:
api_key = user.store_api_key
decrypted = auth_utils.decrypt_api_key(api_key, settings_service)
return store_service.upload(decrypted, component.dict())
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/{component_id}", response_model=ComponentResponse)
def read_component(
component_id: UUID,
store_service: StoreService = Depends(get_store_service),
user: User = Depends(auth_utils.get_current_active_user),
session=Depends(get_session),
):
if not user.store_api_key:
raise HTTPException(
status_code=400, detail="You must have a store API key set."
)
# If the component is from the store, we need to get it from the store
try:
api_key = user.store_api_key
component = store_service.get(api_key, component_id)
if component is not None:
# Turn component into a Flow
required_fields = ["data", "name", "description", "is_component"]
if all(field in component for field in required_fields):
component = Flow(
name=component["name"],
description=component["description"],
data=component["data"],
user_id=user.id,
)
session.add(component)
session.commit()
session.refresh(component)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
if component is None:
raise HTTPException(status_code=400, detail="Component not found")
return component
@router.get("/", response_model=List[ComponentResponse])
def list_components(
page: int = 1,
limit: int = 10,
store_service: StoreService = Depends(get_store_service),
user=Depends(auth_utils.get_current_active_user),
settings_service=Depends(get_settings_service),
):
if user.store_api_key:
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
else:
decrypted = None
return store_service.list_components(decrypted, page, limit)
@router.get("/search", response_model=List[ComponentResponse])
async def search_endpoint(
api_key: Optional[str] = Query(None),
query: str = Query(...),
page: int = Query(1),
limit: int = Query(10),
status: Optional[str] = Query(None),
tags: Optional[List[str]] = Query(None),
date_from: Optional[datetime] = Query(None),
date_to: Optional[datetime] = Query(None),
sort_by: Optional[str] = Query("likes"),
sort: Optional[List[str]] = Query(None),
fields: Optional[List[str]] = Query(None),
store_service: "StoreService" = Depends(get_store_service),
):
try:
return await store_service.search(
api_key=api_key,
query=query,
page=page,
limit=limit,
status=status,
tags=tags,
date_from=date_from,
date_to=date_to,
sort_by=sort_by,
sort=sort,
fields=fields,
)
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
)

View file

@ -0,0 +1,92 @@
from typing import Optional
from langflow import CustomComponent
from langchain.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
from langchain.llms.base import BaseLLM
class QianfanChatEndpointComponent(CustomComponent):
display_name: str = "QianfanChatEndpoint"
description: str = (
"Baidu Qianfan chat models. Get more detail from "
"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint."
)
def build_config(self):
return {
"model": {
"display_name": "Model Name",
"options": [
"ERNIE-Bot",
"ERNIE-Bot-turbo",
"BLOOMZ-7B",
"Llama-2-7b-chat",
"Llama-2-13b-chat",
"Llama-2-70b-chat",
"Qianfan-BLOOMZ-7B-compressed",
"Qianfan-Chinese-Llama-2-7B",
"ChatGLM2-6B-32K",
"AquilaChat-7B",
],
"info": "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
"required": True,
},
"qianfan_ak": {
"display_name": "Qianfan Ak",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"qianfan_sk": {
"display_name": "Qianfan Sk",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"top_p": {
"display_name": "Top p",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.8,
},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.95,
},
"penalty_score": {
"display_name": "Penalty Score",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 1.0,
},
"endpoint": {
"display_name": "Endpoint",
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
},
"code": {"show": False},
}
def build(
self,
model: str = "ERNIE-Bot-turbo",
qianfan_ak: Optional[str] = None,
qianfan_sk: Optional[str] = None,
top_p: Optional[float] = None,
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
) -> BaseLLM:
try:
output = QianfanChatEndpoint( # type: ignore
model=model,
qianfan_ak=qianfan_ak,
qianfan_sk=qianfan_sk,
top_p=top_p,
temperature=temperature,
penalty_score=penalty_score,
endpoint=endpoint,
)
except Exception as e:
raise ValueError("Could not connect to Baidu Qianfan API.") from e
return output # type: ignore

View file

@ -0,0 +1,92 @@
from typing import Optional
from langflow import CustomComponent
from langchain.llms.baidu_qianfan_endpoint import QianfanLLMEndpoint
from langchain.llms.base import BaseLLM
class QianfanLLMEndpointComponent(CustomComponent):
display_name: str = "QianfanLLMEndpoint"
description: str = (
"Baidu Qianfan hosted open source or customized models. "
"Get more detail from https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint"
)
def build_config(self):
return {
"model": {
"display_name": "Model Name",
"options": [
"ERNIE-Bot",
"ERNIE-Bot-turbo",
"BLOOMZ-7B",
"Llama-2-7b-chat",
"Llama-2-13b-chat",
"Llama-2-70b-chat",
"Qianfan-BLOOMZ-7B-compressed",
"Qianfan-Chinese-Llama-2-7B",
"ChatGLM2-6B-32K",
"AquilaChat-7B",
],
"info": "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
"required": True,
},
"qianfan_ak": {
"display_name": "Qianfan Ak",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"qianfan_sk": {
"display_name": "Qianfan Sk",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"top_p": {
"display_name": "Top p",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.8,
},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.95,
},
"penalty_score": {
"display_name": "Penalty Score",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 1.0,
},
"endpoint": {
"display_name": "Endpoint",
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
},
"code": {"show": False},
}
def build(
self,
model: str = "ERNIE-Bot-turbo",
qianfan_ak: Optional[str] = None,
qianfan_sk: Optional[str] = None,
top_p: Optional[float] = None,
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
) -> BaseLLM:
try:
output = QianfanLLMEndpoint( # type: ignore
model=model,
qianfan_ak=qianfan_ak,
qianfan_sk=qianfan_sk,
top_p=top_p,
temperature=temperature,
penalty_score=penalty_score,
endpoint=endpoint,
)
except Exception as e:
raise ValueError("Could not connect to Baidu Qianfan API.") from e
return output # type: ignore

View file

@ -5,7 +5,6 @@ from langchain.vectorstores import Vectara
from langchain.schema import Document
from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseRetriever
from langchain.embeddings.base import Embeddings
class VectaraComponent(CustomComponent):
@ -22,7 +21,6 @@ class VectaraComponent(CustomComponent):
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
"code": {"show": False},
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
}
def build(
@ -30,21 +28,21 @@ class VectaraComponent(CustomComponent):
vectara_customer_id: str,
vectara_corpus_id: str,
vectara_api_key: str,
embedding: Optional[Embeddings] = None,
documents: Optional[Document] = None,
) -> Union[VectorStore, BaseRetriever]:
# If documents, then we need to create a Vectara instance using .from_documents
if documents is not None and embedding is not None:
if documents is not None:
return Vectara.from_documents(
documents=documents, # type: ignore
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
embedding=embedding,
source="langflow",
)
return Vectara(
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
source="langflow",
)

View file

@ -14,6 +14,7 @@ from langflow.services.database.models.user.crud import (
)
from langflow.services.deps import get_session, get_settings_service
from sqlmodel import Session
from cryptography.fernet import Fernet
oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login")
@ -294,3 +295,26 @@ def authenticate_user(
raise HTTPException(status_code=400, detail="Inactive user")
return user if verify_password(password, user.password) else None
def get_fernet(settings_service=Depends(get_settings_service)):
SECRET_KEY = settings_service.auth_settings.SECRET_KEY
# It's important that your secret key is 32 url-safe base64-encoded bytes
fernet = Fernet(SECRET_KEY)
return fernet
def encrypt_api_key(api_key: str, settings_service=Depends(get_settings_service)):
fernet = get_fernet(settings_service)
# Two-way encryption
encrypted_key = fernet.encrypt(api_key.encode())
return encrypted_key
def decrypt_api_key(
encrypted_api_key: str, settings_service=Depends(get_settings_service)
):
fernet = get_fernet(settings_service)
# Two-way decryption
decrypted_key = fernet.decrypt(encrypted_api_key.encode()).decode()
return decrypted_key

View file

@ -15,6 +15,7 @@ class FlowBase(SQLModelSerializable):
name: str = Field(index=True)
description: Optional[str] = Field(index=True)
data: Optional[Dict] = Field(default=None, nullable=True)
is_component: bool = Field(default=False, nullable=True)
@validator("data")
def validate_json(v):
@ -35,7 +36,7 @@ class FlowBase(SQLModelSerializable):
class Flow(FlowBase, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
user_id: UUID = Field(index=True, foreign_key="user.id")
user_id: UUID = Field(index=True, foreign_key="user.id", nullable=True)
user: "User" = Relationship(back_populates="flows")

View file

@ -25,6 +25,7 @@ class User(SQLModelSerializable, table=True):
back_populates="user",
sa_relationship_kwargs={"cascade": "delete"},
)
store_api_key: str = Field(default=None, nullable=True)
flows: list["Flow"] = Relationship(back_populates="user")

View file

@ -10,8 +10,9 @@ from sqlalchemy.exc import OperationalError
from sqlmodel import SQLModel, Session, create_engine
from loguru import logger
from alembic.config import Config
from alembic import command
from alembic import command, util
from langflow.services.database import models # noqa
import time
if TYPE_CHECKING:
from sqlalchemy.engine import Engine
@ -120,7 +121,24 @@ class DatabaseService(Service):
alembic_cfg = Config()
alembic_cfg.set_main_option("script_location", str(self.script_location))
alembic_cfg.set_main_option("sqlalchemy.url", self.database_url)
command.upgrade(alembic_cfg, "head")
try:
command.check(alembic_cfg)
except Exception as exc:
if isinstance(exc, util.exc.CommandError) or isinstance(
exc, util.exc.AutogenerateDiffsDetected
):
command.upgrade(alembic_cfg, "head")
# We should check the schema health after running migrations
try:
command.check(alembic_cfg)
except util.exc.AutogenerateDiffsDetected:
# downgrade to base and upgrade again
logger.warning("Autogenerate diffs detected, downgrading and upgrading")
command.downgrade(alembic_cfg, "-1")
# wait for the database to be ready
time.sleep(5)
command.upgrade(alembic_cfg, "head")
def run_migrations_test(self):
# This method is used for testing purposes only

View file

@ -9,7 +9,7 @@ if TYPE_CHECKING:
from langflow.services.session.service import SessionService
from langflow.services.task.service import TaskService
from langflow.services.chat.service import ChatService
from langflow.services.marketplace.service import MarketplaceService
from langflow.services.store.service import StoreService
from sqlmodel import Session
@ -49,5 +49,5 @@ def get_chat_service() -> "ChatService":
return service_manager.get(ServiceType.CHAT_SERVICE)
def get_marketplace_service() -> "MarketplaceService":
return service_manager.get(ServiceType.MARKETPLACE_SERVICE)
def get_store_service() -> "StoreService":
return service_manager.get(ServiceType.STORE_SERVICE)

View file

@ -14,4 +14,4 @@ class ServiceType(str, Enum):
CHAT_SERVICE = "chat_service"
SESSION_SERVICE = "session_service"
TASK_SERVICE = "task_service"
MARKETPLACE_SERVICE = "marketplace_service"
STORE_SERVICE = "store_service"

View file

@ -52,7 +52,7 @@ class Settings(BaseSettings):
LANGFUSE_PUBLIC_KEY: Optional[str] = None
LANGFUSE_HOST: Optional[str] = None
MARKETPLACE_URL: Optional[str] = None
STORE_URL: Optional[str] = None
@validator("CONFIG_DIR", pre=True, allow_reuse=True)
def set_langflow_dir(cls, value):

View file

@ -18,7 +18,7 @@ class StoreService(Service):
def __init__(self, settings_service: "SettingsService"):
self.settings_service = settings_service
self.base_url = self.settings_service.settings.MARKETPLACE_URL
self.base_url = self.settings_service.settings.STORE_URL
self.components_url = f"{self.base_url}/items/components"
def _get(
@ -101,8 +101,3 @@ class StoreService(Service):
return ComponentResponse(**component)
except HTTPError as exc:
raise ValueError(f"Upload failed: {exc}")
def get_api_key(self, hashed_api_key: str):
# We will use the settings_service.auth_settings.SECRET_KEY to decode the hashed_api_key
# and return the api_key
pass

View file

@ -19,6 +19,7 @@ def get_factories_and_deps():
from langflow.services.auth import factory as auth_factory
from langflow.services.task import factory as task_factory
from langflow.services.session import factory as session_service_factory # type: ignore
from langflow.services.store import factory as store_factory
return [
(settings_factory.SettingsServiceFactory(), []),
@ -40,6 +41,7 @@ def get_factories_and_deps():
session_service_factory.SessionServiceFactory(),
[ServiceType.CACHE_SERVICE],
),
(store_factory.StoreServiceFactory(), [ServiceType.SETTINGS_SERVICE]),
]

View file

@ -44,7 +44,7 @@ class FieldFormatters(BaseModel):
class FrontendNode(BaseModel):
template: Template
description: str
description: Optional[str] = None
base_classes: List[str]
name: str = ""
display_name: str = ""

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,9 @@ terminate_process_by_port() {
# Trap signals to ensure cleanup on script termination
trap 'terminate_process_by_port 7860; terminate_process_by_port 3000' EXIT
# install playwright if there is not installed yet
npx playwright install
# Navigate to the project root directory (where the Makefile is located)
cd ../../
@ -56,7 +59,7 @@ cd ../../
make backend &
# Give some time for the backend to start (adjust sleep duration as needed)
sleep 10
sleep 25
# Navigate back to the test directory
cd src/frontend

View file

@ -56,6 +56,7 @@ export default function ParameterComponent({
info = "",
proxy,
showNode,
index = "",
}: ParameterComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
@ -351,9 +352,11 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + index}
/>
) : (
<InputComponent
id={"input-" + index}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
@ -364,6 +367,7 @@ export default function ParameterComponent({
) : left === true && type === "bool" ? (
<div className="mt-2 w-full">
<ToggleShadComponent
id={"toggle-" + index}
disabled={disabled}
enabled={data.node?.template[name].value ?? false}
setEnabled={(isEnabled) => {
@ -406,6 +410,7 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-" + index}
/>
</div>
) : left === true && type === "file" ? (
@ -427,6 +432,7 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"int-input-" + index}
/>
</div>
) : left === true && type === "prompt" ? (
@ -450,6 +456,7 @@ export default function ParameterComponent({
onChange={(e) => {
handleOnNewValue(e);
}}
id={"prompt-input-" + index}
/>
</div>
) : left === true && type === "NestedDict" ? (

View file

@ -219,6 +219,7 @@ export default function GenericNode({
data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced && (
<ParameterComponent
index={idx.toString()}
key={scapedJSONStringfy({
inputTypes:
data.node!.template[templateField].input_types,
@ -421,11 +422,13 @@ export default function GenericNode({
<>
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.sort()
.map((templateField: string, idx) => (
<div key={idx}>
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
<ParameterComponent
index={idx.toString()}
key={scapedJSONStringfy({
inputTypes:
data.node!.template[templateField].input_types,

View file

@ -12,6 +12,7 @@ export default function CodeAreaComponent({
nodeClass,
dynamic,
setNodeClass,
id = "",
readonly = false,
}: CodeAreaComponentType) {
const [myValue, setMyValue] = useState(
@ -43,6 +44,7 @@ export default function CodeAreaComponent({
>
<div className="flex w-full items-center">
<span
id={id}
className={
editNode
? "input-edit-node input-dialog"

View file

@ -22,7 +22,6 @@ export default function DictComponent({
}, [value]);
const ref = useRef(value);
debugger;
return (
<div
className={classNames(

View file

@ -17,6 +17,7 @@ export default function InputComponent({
editNode = false,
placeholder = "Type something...",
className,
id = "",
blurOnEnter = false,
}: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false);
@ -33,6 +34,7 @@ export default function InputComponent({
{isForm ? (
<Form.Control asChild>
<Input
id={"form-" + id}
ref={refInput}
onBlur={onBlur}
autoFocus={autoFocus}
@ -61,6 +63,7 @@ export default function InputComponent({
</Form.Control>
) : (
<Input
id={id}
ref={refInput}
type="text"
onBlur={onBlur}

View file

@ -8,6 +8,7 @@ export default function IntComponent({
onChange,
disabled,
editNode = false,
id = "",
}: FloatComponentType): JSX.Element {
const min = 0;
@ -21,6 +22,7 @@ export default function IntComponent({
return (
<div className="w-full">
<Input
id={id}
onKeyDown={(event) => {
if (
event.key !== "Backspace" &&

View file

@ -14,6 +14,7 @@ export default function PromptAreaComponent({
onChange,
disabled,
editNode = false,
id = "",
readonly = false,
}: PromptAreaComponentType): JSX.Element {
useEffect(() => {
@ -36,6 +37,7 @@ export default function PromptAreaComponent({
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<GenericModal
id={id}
readonly={readonly}
type={TypeModal.PROMPT}
value={value}
@ -49,6 +51,7 @@ export default function PromptAreaComponent({
>
<div className="flex w-full items-center">
<span
id={id}
className={
editNode
? "input-edit-node input-dialog"

View file

@ -10,6 +10,7 @@ export default function TextAreaComponent({
onChange,
disabled,
editNode = false,
id = "",
}: TextAreaComponentType): JSX.Element {
// Clear text area
useEffect(() => {
@ -21,6 +22,7 @@ export default function TextAreaComponent({
return (
<div className="flex w-full items-center">
<Input
id={id}
value={value}
disabled={disabled}
className={editNode ? "input-edit-node" : ""}

View file

@ -522,3 +522,17 @@ export async function deleteApiKey(api_key: string) {
throw error;
}
}
export async function addApiKeyStore(key: string) {
try {
const res = await api.post(`${BASE_URL_API}api_key/store`, {
api_key: key,
});
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}

View file

@ -220,6 +220,7 @@ const EditNodeModal = forwardRef(
templateParam
].multiline ? (
<TextAreaComponent
id={"textarea-edit-" + index}
disabled={disabled}
editNode={true}
value={
@ -233,6 +234,7 @@ const EditNodeModal = forwardRef(
/>
) : (
<InputComponent
id={"input-" + index}
editNode={true}
disabled={disabled}
password={
@ -328,6 +330,7 @@ const EditNodeModal = forwardRef(
<div className="ml-auto">
{" "}
<ToggleShadComponent
id={"toggle-edit-" + index}
disabled={disabled}
enabled={
myData.current.node.template[
@ -386,6 +389,7 @@ const EditNodeModal = forwardRef(
.type === "int" ? (
<div className="mx-auto">
<IntComponent
id={"int-input-" + index}
disabled={disabled}
editNode={true}
value={
@ -456,6 +460,7 @@ const EditNodeModal = forwardRef(
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
id={"prompt-area-edit" + index}
/>
</div>
) : myData.current.node?.template[templateParam]
@ -488,6 +493,7 @@ const EditNodeModal = forwardRef(
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
id={"code-area-edit" + index}
/>
</div>
) : myData.current.node?.template[templateParam]

View file

@ -8,6 +8,7 @@ import { useContext, useEffect, useState } from "react";
import AceEditor from "react-ace";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
@ -144,6 +145,11 @@ export default function CodeAreaModal({
/>
</BaseModal.Header>
<BaseModal.Content>
<Input
value={code}
className="absolute left-[500%] top-[500%]"
id="codeValue"
/>
<div className="flex h-full w-full flex-col transition-all">
<div className="h-full w-full">
<AceEditor
@ -183,10 +189,11 @@ export default function CodeAreaModal({
</div>
<div className="flex h-fit w-full justify-end">
<Button
disabled={readonly}
className="mt-3"
onClick={handleClick}
type="submit"
id="checkAndSaveBtn"
disabled={readonly}
>
Check & Save
</Button>

View file

@ -30,6 +30,7 @@ export default function GenericModal({
nodeClass,
setNodeClass,
children,
id = "",
readonly = false,
}: genericModalPropsType): JSX.Element {
const [myButtonText] = useState(buttonText);
@ -211,6 +212,7 @@ export default function GenericModal({
>
{type === TypeModal.PROMPT && isEdit && !readonly ? (
<Textarea
id={"modal-" + id}
ref={divRefPrompt}
className="form-input h-full w-full rounded-lg custom-scroll focus-visible:ring-1"
value={inputValue}
@ -286,7 +288,7 @@ export default function GenericModal({
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
<span id={"badge" + index.toString()}>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) +
"..."
@ -306,6 +308,7 @@ export default function GenericModal({
)}
</div>
<Button
id="genericModalBtnSave"
disabled={readonly}
onClick={() => {
switch (myModalType) {

View file

@ -57,8 +57,10 @@ export default function ExtraSidebar(): JSX.Element {
let ret = {};
Object.keys(data).forEach((d: keyof APIObjectType, i) => {
ret[d] = {};
let keys = Object.keys(data[d]).filter((nd) =>
nd.toLowerCase().includes(e.toLowerCase())
let keys = Object.keys(data[d]).filter(
(nd) =>
nd.toLowerCase().includes(e.toLowerCase()) ||
data[d][nd].display_name?.toLowerCase().includes(e.toLowerCase())
);
keys.forEach((element) => {
ret[d][element] = data[d][element];
@ -87,14 +89,13 @@ export default function ExtraSidebar(): JSX.Element {
setSearch("");
}
}
useEffect(() => {
if (getFilterEdge.length === 0 && search === "") {
setFilterData(data);
setFilterEdge([]);
setSearch("");
}
}, [getFilterEdge]);
}, [getFilterEdge, data]);
useEffect(() => {
if (getFilterEdge?.length > 0) {
@ -131,7 +132,7 @@ export default function ExtraSidebar(): JSX.Element {
return ret;
});
}
}, [getFilterEdge]);
}, [getFilterEdge, data]);
return (
<div className="side-bar-arrangement">
@ -252,6 +253,7 @@ export default function ExtraSidebar(): JSX.Element {
key={index}
>
<SidebarDraggableComponent
sectionName={SBSectionName as string}
apiClass={dataFilter[SBSectionName][SBItemName]}
key={SBItemName}
onDragStart={(event) =>

View file

@ -17,6 +17,7 @@ import {
import { removeCountFromString } from "../../../../../utils/utils";
export default function SidebarDraggableComponent({
sectionName,
display_name,
itemName,
error,
@ -25,6 +26,7 @@ export default function SidebarDraggableComponent({
apiClass,
official,
}: {
sectionName: string;
apiClass: APIClassType;
display_name: string;
itemName: string;
@ -84,7 +86,10 @@ export default function SidebarDraggableComponent({
);
}}
>
<div id={display_name} className="side-bar-components-div-form">
<div
id={sectionName + display_name}
className="side-bar-components-div-form"
>
<span className="side-bar-components-text">{display_name}</span>
<div>
<SelectTrigger>

View file

@ -19,6 +19,7 @@ export type InputComponentType = {
showPass?: boolean;
placeholder?: string;
className?: string;
id?: string;
blurOnEnter?: boolean;
};
export type ToggleComponentType = {
@ -52,6 +53,7 @@ export type ParameterComponentType = {
info?: string;
proxy?: { field: string; id: string };
showNode?: boolean;
index?: string;
};
export type InputListComponentType = {
value: string[];
@ -85,6 +87,7 @@ export type TextAreaComponentType = {
onChange: (value: string[] | string) => void;
value: string;
editNode?: boolean;
id?: string;
readonly?: boolean;
};
@ -97,6 +100,7 @@ export type PromptAreaComponentType = {
value: string;
readonly?: boolean;
editNode?: boolean;
id?: string;
};
export type CodeAreaComponentType = {
@ -107,6 +111,7 @@ export type CodeAreaComponentType = {
nodeClass?: APIClassType;
setNodeClass?: (value: APIClassType) => void;
dynamic?: boolean;
id?: string;
readonly?: boolean;
};
@ -138,6 +143,7 @@ export type FloatComponentType = {
disabled?: boolean;
onChange: (value: string) => void;
editNode?: boolean;
id?: string;
};
export type TooltipComponentType = {
@ -500,6 +506,7 @@ export type genericModalPropsType = {
nodeClass?: APIClassType;
setNodeClass?: (Class: APIClassType) => void;
children: ReactNode;
id?: string;
readonly?: boolean;
};

View file

@ -0,0 +1,140 @@
import { expect, test } from "@playwright/test";
test("CodeAreaModalComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("pythonfunctiontool");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sidePythonFunctionTool"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="code-input-0"]').click();
let value = await page.locator('//*[@id="codeValue"]').inputValue();
if (
value !=
'def python_function(text: str) -> str: """This is a default python function that returns the input text""" return text'
) {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="checkAndSaveBtn"]').click();
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="code-input-0"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="code-area-edit0"]').click();
let value = await page.locator('//*[@id="codeValue"]').inputValue();
if (
value !=
'def python_function(text: str) -> str: """This is a default python function that returns the input text""" return text'
) {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="checkAndSaveBtn"]').click();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="code-input-0"]').click();
}
});

View file

@ -1,153 +1,5 @@
import { expect, test } from "@playwright/test";
test("KeypairListComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("csv");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sideCSVLoader"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="keypair0"]').click();
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
await page.locator('//*[@id="keypair100"]').click();
await page.locator('//*[@id="keypair100"]').fill("testtesttesttesttesttest");
const plusButtonLocatorNode = page.locator('//*[@id="plusbtn0"]');
const elementCountNode = await plusButtonLocatorNode.count();
if (elementCountNode > 0) {
await plusButtonLocatorNode.click();
}
await page.locator('//*[@id="keypair1"]').click();
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
await page.locator('//*[@id="keypair101"]').click();
await page.locator('//*[@id="keypair101"]').fill("testtesttesttesttesttest1");
await page.locator('//*[@id="plusbtn1"]').click();
await page.locator('//*[@id="keypair2"]').click();
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
await page.locator('//*[@id="keypair102"]').click();
await page.locator('//*[@id="keypair102"]').fill("testtesttesttesttesttest2");
await page.locator('//*[@id="minusbtn1"]').click();
const keyPairVerification = page.locator('//*[@id="keypair102"]');
const elementKeyCount = await keyPairVerification.count();
if (elementKeyCount === 0) {
expect(true).toBeTruthy();
} else {
expect(false).toBeTruthy();
}
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showfile_path"]').click();
expect(
await page.locator('//*[@id="showfile_path"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmetadata"]').click();
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showfile_path"]').click();
expect(
await page.locator('//*[@id="showfile_path"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmetadata"]').click();
expect(
await page.locator('//*[@id="showmetadata"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="keypair0"]').click();
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
await page.locator('//*[@id="keypair100"]').click();
await page
.locator('//*[@id="keypair100"]')
.fill("testtesttesttesttesttest");
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
const elementCount = await plusButtonLocator.count();
if (elementCount > 0) {
await plusButtonLocator.click();
}
await page.locator('//*[@id="keypair1"]').click();
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
await page.locator('//*[@id="keypair101"]').click();
await page
.locator('//*[@id="keypair101"]')
.fill("testtesttesttesttesttest1");
await page.locator('//*[@id="plusbtn1"]').click();
await page.locator('//*[@id="keypair2"]').click();
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
await page.locator('//*[@id="keypair102"]').click();
await page
.locator('//*[@id="keypair102"]')
.fill("testtesttesttesttesttest2");
await page.locator('//*[@id="minusbtn1"]').click();
const keyPairVerification = page.locator('//*[@id="keypair102"]');
const elementKeyCount = await keyPairVerification.count();
if (elementKeyCount === 0) {
await page.locator('//*[@id="saveChangesBtn"]').click();
const key1 = await page.locator('//*[@id="keypair0"]').inputValue();
const value1 = await page.locator('//*[@id="keypair100"]').inputValue();
const key2 = await page.locator('//*[@id="keypair1"]').inputValue();
const value2 = await page.locator('//*[@id="keypair101"]').inputValue();
if (
key1 === "testtesttesttest" &&
value1 === "testtesttesttesttesttest" &&
key2 === "testtesttesttest2" &&
value2 === "testtesttesttesttesttest2"
) {
expect(true).toBeTruthy();
} else {
expect(false).toBeTruthy();
}
} else {
expect(false).toBeTruthy();
}
} else {
expect(false).toBeTruthy();
}
});
test("FloatComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -0,0 +1,118 @@
import { expect, test } from "@playwright/test";
test("IntComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("getrequest");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sideGET Request"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="int-input-2"]').click();
await page
.locator('//*[@id="int-input-2"]')
.fill("123456789123456789123456789");
let value = await page.locator('//*[@id="int-input-2"]').inputValue();
if (value != "123456789123456789123456789") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="int-input-2"]').click();
await page.locator('//*[@id="int-input-2"]').fill("-3");
value = await page.locator('//*[@id="int-input-2"]').inputValue();
if (value != "0") {
expect(false).toBeTruthy();
}
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
value = await page.locator('//*[@id="int-input-1"]').inputValue();
if (value != "0") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="int-input-1"]').click();
await page
.locator('//*[@id="int-input-1"]')
.fill("123456789123456789123456789");
await page.locator('//*[@id="showheaders"]').click();
expect(await page.locator('//*[@id="showheaders"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showtimeout"]').click();
expect(await page.locator('//*[@id="showtimeout"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showurl"]').click();
expect(await page.locator('//*[@id="showurl"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showheaders"]').click();
expect(await page.locator('//*[@id="showheaders"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showurl"]').click();
expect(await page.locator('//*[@id="showurl"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="int-input-2"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showtimeout"]').click();
expect(
await page.locator('//*[@id="showtimeout"]').isChecked()
).toBeTruthy();
const valueEditNode = await page
.locator('//*[@id="int-input-1"]')
.inputValue();
if (valueEditNode != "123456789123456789123456789") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="int-input-2"]').click();
await page.locator('//*[@id="int-input-2"]').fill("3");
let value = await page.locator('//*[@id="int-input-2"]').inputValue();
if (value != "3") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="int-input-2"]').click();
await page.locator('//*[@id="int-input-2"]').fill("-3");
value = await page.locator('//*[@id="int-input-2"]').inputValue();
if (value != "0") {
expect(false).toBeTruthy();
}
}
});

View file

@ -0,0 +1,149 @@
import { expect, test } from "@playwright/test";
test("KeypairListComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("csv");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sideCSVLoader"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="keypair0"]').click();
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
await page.locator('//*[@id="keypair100"]').click();
await page.locator('//*[@id="keypair100"]').fill("testtesttesttesttesttest");
const plusButtonLocatorNode = page.locator('//*[@id="plusbtn0"]');
const elementCountNode = await plusButtonLocatorNode.count();
if (elementCountNode > 0) {
await plusButtonLocatorNode.click();
}
await page.locator('//*[@id="keypair1"]').click();
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
await page.locator('//*[@id="keypair101"]').click();
await page.locator('//*[@id="keypair101"]').fill("testtesttesttesttesttest1");
await page.locator('//*[@id="plusbtn1"]').click();
await page.locator('//*[@id="keypair2"]').click();
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
await page.locator('//*[@id="keypair102"]').click();
await page.locator('//*[@id="keypair102"]').fill("testtesttesttesttesttest2");
await page.locator('//*[@id="minusbtn1"]').click();
const keyPairVerification = page.locator('//*[@id="keypair102"]');
const elementKeyCount = await keyPairVerification.count();
if (elementKeyCount === 0) {
expect(true).toBeTruthy();
} else {
expect(false).toBeTruthy();
}
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showfile_path"]').click();
expect(
await page.locator('//*[@id="showfile_path"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmetadata"]').click();
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showfile_path"]').click();
expect(
await page.locator('//*[@id="showfile_path"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmetadata"]').click();
expect(
await page.locator('//*[@id="showmetadata"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="keypair0"]').click();
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
await page.locator('//*[@id="keypair100"]').click();
await page
.locator('//*[@id="keypair100"]')
.fill("testtesttesttesttesttest");
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
const elementCount = await plusButtonLocator.count();
if (elementCount > 0) {
await plusButtonLocator.click();
}
await page.locator('//*[@id="keypair1"]').click();
await page.locator('//*[@id="keypair1"]').fill("testtesttesttest1");
await page.locator('//*[@id="keypair101"]').click();
await page
.locator('//*[@id="keypair101"]')
.fill("testtesttesttesttesttest1");
await page.locator('//*[@id="plusbtn1"]').click();
await page.locator('//*[@id="keypair2"]').click();
await page.locator('//*[@id="keypair2"]').fill("testtesttesttest2");
await page.locator('//*[@id="keypair102"]').click();
await page
.locator('//*[@id="keypair102"]')
.fill("testtesttesttesttesttest2");
await page.locator('//*[@id="minusbtn1"]').click();
const keyPairVerification = page.locator('//*[@id="keypair102"]');
const elementKeyCount = await keyPairVerification.count();
if (elementKeyCount === 0) {
await page.locator('//*[@id="saveChangesBtn"]').click();
const key1 = await page.locator('//*[@id="keypair0"]').inputValue();
const value1 = await page.locator('//*[@id="keypair100"]').inputValue();
const key2 = await page.locator('//*[@id="keypair1"]').inputValue();
const value2 = await page.locator('//*[@id="keypair101"]').inputValue();
if (
key1 === "testtesttesttest" &&
value1 === "testtesttesttesttesttest" &&
key2 === "testtesttesttest2" &&
value2 === "testtesttesttesttesttest2"
) {
expect(true).toBeTruthy();
} else {
expect(false).toBeTruthy();
}
} else {
expect(false).toBeTruthy();
}
} else {
expect(false).toBeTruthy();
}
});

View file

@ -0,0 +1,174 @@
import { expect, test } from "@playwright/test";
test("PromptTemplateComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("promptTemplate");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sidePromptTemplate"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="prompt-input-4"]').click();
await page
.locator('//*[@id="modal-prompt-input-4"]')
.fill("{prompt} example {prompt1}");
let value = await page
.locator('//*[@id="modal-prompt-input-4"]')
.inputValue();
if (value != "{prompt} example {prompt1}") {
expect(false).toBeTruthy();
}
let valueBadgeOne = await page.locator('//*[@id="badge0"]').innerText();
if (valueBadgeOne != "prompt") {
expect(false).toBeTruthy();
}
let valueBadgeTwo = await page.locator('//*[@id="badge1"]').innerText();
if (valueBadgeTwo != "prompt1") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="genericModalBtnSave"]').click();
await page.locator('//*[@id="textarea-7"]').click();
await page.locator('//*[@id="textarea-7"]').fill("prompt_value_!@#!@#");
value = await page.locator('//*[@id="textarea-7"]').inputValue();
if (value != "prompt_value_!@#!@#") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="textarea-8"]').click();
await page
.locator('//*[@id="textarea-8"]')
.fill("prompt_name_test_123123!@#!@#");
value = await page.locator('//*[@id="textarea-8"]').inputValue();
if (value != "prompt_name_test_123123!@#!@#") {
expect(false).toBeTruthy();
}
value = await page.locator('//*[@id="prompt-input-4"]').innerText();
if (value != "{prompt} example {prompt1}") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="editAdvancedIcon"]').click();
value = await page.locator('//*[@id="textarea-edit-1"]').inputValue();
if (value != "prompt_value_!@#!@#") {
expect(false).toBeTruthy();
}
value = await page.locator('//*[@id="textarea-edit-2"]').inputValue();
if (value != "prompt_name_test_123123!@#!@#") {
expect(false).toBeTruthy();
}
value = await page.locator('//*[@id="prompt-area-edit0"]').innerText();
if (value != "{prompt} example {prompt1}") {
expect(false).toBeTruthy();
}
await page
.locator('//*[@id="textarea-edit-2"]')
.fill("prompt_edit_test_12312312321!@#$");
await page
.locator('//*[@id="textarea-edit-1"]')
.fill("prompt_edit_test_44444444444!@#$");
await page.locator('//*[@id="showtemplate"]').click();
expect(await page.locator('//*[@id="showtemplate"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showprompt"]').click();
expect(await page.locator('//*[@id="showprompt"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showprompt1"]').click();
expect(await page.locator('//*[@id="showprompt1"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showtemplate"]').click();
expect(
await page.locator('//*[@id="showtemplate"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showprompt"]').click();
expect(await page.locator('//*[@id="showprompt"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showprompt1"]').click();
expect(await page.locator('//*[@id="showprompt1"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showtemplate"]').click();
expect(await page.locator('//*[@id="showtemplate"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showprompt"]').click();
expect(await page.locator('//*[@id="showprompt"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showprompt1"]').click();
expect(await page.locator('//*[@id="showprompt1"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showtemplate"]').click();
expect(
await page.locator('//*[@id="showtemplate"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showprompt"]').click();
expect(await page.locator('//*[@id="showprompt"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="textarea-8"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]'
)
.click();
await page.locator('//*[@id="editAdvancedIcon"]').click();
await page.locator('//*[@id="showprompt1"]').click();
expect(
await page.locator('//*[@id="showprompt1"]').isChecked()
).toBeTruthy();
value = await page.locator('//*[@id="textarea-edit-1"]').inputValue();
if (value != "prompt_edit_test_44444444444!@#$") {
expect(false).toBeTruthy();
}
value = await page.locator('//*[@id="textarea-edit-2"]').inputValue();
if (value != "prompt_edit_test_12312312321!@#$") {
expect(false).toBeTruthy();
}
value = await page.locator('//*[@id="prompt-area-edit0"]').innerText();
if (value != "{prompt} example {prompt1}") {
expect(false).toBeTruthy();
}
}
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,419 @@
{
"description": "Use this Tool on every query",
"name": "Getting Started: Simple python function applied to each output",
"data": {
"nodes": [
{
"width": 384,
"height": 631,
"id": "ChatOpenAI-tRw3A",
"type": "genericNode",
"position": {
"x": 543.1816229116944,
"y": 942.891611351432
},
"data": {
"type": "ChatOpenAI",
"node": {
"template": {
"lc_kwargs": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "lc_kwargs",
"advanced": true,
"type": "code",
"list": false
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "verbose",
"advanced": false,
"type": "bool",
"list": false
},
"callbacks": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "callbacks",
"advanced": false,
"type": "langchain.callbacks.base.BaseCallbackHandler",
"list": true
},
"client": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "client",
"advanced": false,
"type": "Any",
"list": false
},
"model_name": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "gpt-3.5-turbo",
"password": false,
"options": [
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-16k",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4",
"gpt-4-32k"
],
"name": "model_name",
"advanced": false,
"type": "str",
"list": true
},
"temperature": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "0.2",
"password": false,
"name": "temperature",
"advanced": false,
"type": "float",
"list": false
},
"model_kwargs": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "model_kwargs",
"advanced": true,
"type": "code",
"list": false
},
"openai_api_key": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "",
"password": true,
"name": "openai_api_key",
"display_name": "OpenAI API Key",
"advanced": false,
"type": "str",
"list": false
},
"openai_api_base": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "openai_api_base",
"display_name": "OpenAI API Base",
"advanced": false,
"type": "str",
"list": false
},
"openai_organization": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_organization",
"display_name": "OpenAI Organization",
"advanced": false,
"type": "str",
"list": false
},
"openai_proxy": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_proxy",
"display_name": "OpenAI Proxy",
"advanced": false,
"type": "str",
"list": false
},
"request_timeout": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "request_timeout",
"advanced": false,
"type": "float",
"list": false
},
"max_retries": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 6,
"password": false,
"name": "max_retries",
"advanced": false,
"type": "int",
"list": false
},
"streaming": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "streaming",
"advanced": false,
"type": "bool",
"list": false
},
"n": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 1,
"password": false,
"name": "n",
"advanced": false,
"type": "int",
"list": false
},
"max_tokens": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": true,
"name": "max_tokens",
"advanced": false,
"type": "int",
"list": false
},
"_type": "ChatOpenAI"
},
"description": "Wrapper around OpenAI Chat large language models.",
"base_classes": [
"Serializable",
"BaseChatModel",
"ChatOpenAI",
"BaseLanguageModel"
],
"display_name": "ChatOpenAI"
},
"id": "ChatOpenAI-tRw3A",
"value": null
},
"selected": false,
"dragging": false,
"positionAbsolute": {
"x": 543.1816229116944,
"y": 942.891611351432
}
},
{
"width": 384,
"height": 387,
"id": "AgentInitializer-KcVTt",
"type": "genericNode",
"position": {
"x": 1036.6064439140812,
"y": 645.1919693466587
},
"data": {
"type": "AgentInitializer",
"node": {
"template": {
"agent": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": "zero-shot-react-description",
"password": false,
"options": [
"zero-shot-react-description",
"react-docstore",
"self-ask-with-search",
"conversational-react-description",
"openai-functions"
],
"name": "agent",
"advanced": false,
"type": "str",
"list": true
},
"memory": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "memory",
"advanced": false,
"type": "BaseChatMemory",
"list": false
},
"tools": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "tools",
"advanced": false,
"type": "Tool",
"list": true
},
"llm": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "llm",
"display_name": "LLM",
"advanced": false,
"type": "BaseLanguageModel",
"list": false
},
"_type": "initialize_agent"
},
"description": "Construct a zero shot agent from an LLM and tools.",
"base_classes": ["AgentExecutor", "function"],
"display_name": "AgentInitializer"
},
"id": "AgentInitializer-KcVTt",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": 1036.6064439140812,
"y": 645.1919693466587
}
},
{
"width": 384,
"height": 437,
"id": "PythonFunctionTool-FwZVF",
"type": "genericNode",
"position": {
"x": 553.050119331742,
"y": 412.9533535948685
},
"data": {
"type": "PythonFunctionTool",
"node": {
"template": {
"name": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": "PythonFunction",
"password": false,
"name": "name",
"advanced": false,
"type": "str",
"list": false
},
"description": {
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"value": "Returns the Text you send. This is a testing tool.",
"password": false,
"name": "description",
"advanced": false,
"type": "str",
"list": false
},
"code": {
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"value": "\ndef python_function(text: str) -> str:\n \"\"\"This is a default python function that returns the input text\"\"\"\n return text\n",
"password": false,
"name": "code",
"advanced": false,
"type": "code",
"list": false
},
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": ["Tool"],
"display_name": "PythonFunctionTool"
},
"id": "PythonFunctionTool-FwZVF",
"value": null
},
"selected": false,
"dragging": false,
"positionAbsolute": {
"x": 553.050119331742,
"y": 412.9533535948685
}
}
],
"edges": [
{
"source": "ChatOpenAI-tRw3A",
"sourceHandle": "ChatOpenAI|ChatOpenAI-tRw3A|Serializable|BaseChatModel|ChatOpenAI|BaseLanguageModel",
"target": "AgentInitializer-KcVTt",
"targetHandle": "BaseLanguageModel|llm|AgentInitializer-KcVTt",
"style": {
"stroke": "inherit"
},
"className": "stroke-gray-900 dark:stroke-gray-200",
"animated": false,
"id": "reactflow__edge-ChatOpenAI-tRw3AChatOpenAI|ChatOpenAI-tRw3A|Serializable|BaseChatModel|ChatOpenAI|BaseLanguageModel-AgentInitializer-KcVTtBaseLanguageModel|llm|AgentInitializer-KcVTt",
"selected": false
},
{
"source": "PythonFunctionTool-FwZVF",
"sourceHandle": "PythonFunctionTool|PythonFunctionTool-FwZVF|Tool",
"target": "AgentInitializer-KcVTt",
"targetHandle": "Tool|tools|AgentInitializer-KcVTt",
"style": {
"stroke": "inherit"
},
"className": "stroke-gray-900 dark:stroke-gray-200",
"animated": false,
"id": "reactflow__edge-PythonFunctionTool-FwZVFPythonFunctionTool|PythonFunctionTool-FwZVF|Tool-AgentInitializer-KcVTtTool|tools|AgentInitializer-KcVTt",
"selected": false
}
],
"viewport": {
"x": 4.748095479939138,
"y": -155.65184647754464,
"zoom": 0.6079953565987085
}
},
"id": "15030b3c-570d-4658-8473-58138077e9b0"
}

View file

@ -0,0 +1,87 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "fs";
test.describe("drag and drop test", () => {
/// <reference lib="dom"/>
test("drop collection", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/collection.json",
"utf-8"
);
// Create the DataTransfer and File
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "collection.json", {
type: "application/json",
});
dt.items.add(file);
return dt;
}, jsonContent);
// Now dispatch
await page.dispatchEvent('//*[@id="root"]/div/div[2]/div[2]', "drop", {
dataTransfer,
});
expect(
await page
.locator(".main-page-flows-display")
.evaluate((el) => el.children)
).toBeTruthy();
});
test("drop flow", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/flow.json",
"utf-8"
);
// Create the DataTransfer and File
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "flow.json", {
type: "application/json",
});
dt.items.add(file);
return dt;
}, jsonContent);
// Now dispatch
await page.dispatchEvent('//*[@id="root"]/div/div[2]/div[2]', "drop", {
dataTransfer,
});
expect(
await page
.locator(".main-page-flows-display")
.evaluate((el) => el.children)
).toBeTruthy();
});
});

View file

@ -1,14 +1,49 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "fs";
test.describe("Group component tests", () => {
test("group test", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.getByRole("button", { name: "Community Examples" }).click();
await page
.locator(
"div:nth-child(7) > div:nth-child(2) > .card-component-footer-arrangement > .inline-flex"
)
.click();
test.describe("group node test", () => {
/// <reference lib="dom"/>
test("group and ungroup updating values", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/flow.json",
"utf-8"
);
// Create the DataTransfer and File
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "flow.json", {
type: "application/json",
});
dt.items.add(file);
return dt;
}, jsonContent);
// Now dispatch
await page.dispatchEvent('//*[@id="root"]/div/div[2]/div[2]', "drop", {
dataTransfer,
});
expect(
await page
.locator(".main-page-flows-display")
.evaluate((el) => el.children)
).toBeTruthy();
await page.getByRole("button", { name: "Edit Flow" }).click();
//inside the flow
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div/div[1]/div"
@ -84,16 +119,11 @@ test.describe("Group component tests", () => {
)
.click();
await page.getByLabel("Ungroup").click();
await expect(
page.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div/div/div[2]/div[3]/div/div[2]/div[4]/div/div[2]/div/input"
)
).toHaveValue("fieldValue");
await expect(page.locator('//*[@id="input-2"]')).toHaveValue("fieldValue");
expect(
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div/div/div[2]/div[2]/div/div[2]/div[5]/div/div[2]/div/button/span[1]"
)
.getByTestId(/.*rf__node-AgentInitializer.*/)
.getByRole("button", { name: "openai-functions" })
.textContent()
).toBe("openai-functions");
});

View file

@ -0,0 +1,172 @@
import { expect, test } from "@playwright/test";
test("InputComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sideChroma"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator("#input-8").click();
await page
.locator("#input-8")
.fill("collection_name_test_123123123!@#$&*(&%$@");
let value = await page.locator("#input-8").inputValue();
if (value != "collection_name_test_123123123!@#$&*(&%$@") {
expect(false).toBeTruthy();
}
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="editAdvancedIcon"]').click();
await page.locator('//*[@id="showchroma_server_cors_allow_origins"]').click();
expect(
await page
.locator('//*[@id="showchroma_server_cors_allow_origins"]')
.isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_grpc_port"]').click();
expect(
await page.locator('//*[@id="showchroma_server_grpc_port"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_host"]').click();
expect(
await page.locator('//*[@id="showchroma_server_host"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_http_port"]').click();
expect(
await page.locator('//*[@id="showchroma_server_http_port"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').click();
expect(
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcollection_name"]').click();
expect(
await page.locator('//*[@id="showcollection_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showpersist"]').click();
expect(await page.locator('//*[@id="showpersist"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showpersist_directory"]').click();
expect(
await page.locator('//*[@id="showpersist_directory"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showchroma_server_cors_allow_origins"]').click();
expect(
await page
.locator('//*[@id="showchroma_server_cors_allow_origins"]')
.isChecked()
).toBeFalsy();
await page.locator('//*[@id="showchroma_server_grpc_port"]').click();
expect(
await page.locator('//*[@id="showchroma_server_grpc_port"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showchroma_server_host"]').click();
expect(
await page.locator('//*[@id="showchroma_server_host"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showchroma_server_http_port"]').click();
expect(
await page.locator('//*[@id="showchroma_server_http_port"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').click();
expect(
await page.locator('//*[@id="showchroma_server_ssl_enabled"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showpersist"]').click();
expect(await page.locator('//*[@id="showpersist"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showpersist_directory"]').click();
expect(
await page.locator('//*[@id="showpersist_directory"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showsearch_kwargs"]').click();
expect(
await page.locator('//*[@id="showsearch_kwargs"]').isChecked()
).toBeFalsy();
let valueEditNode = await page.locator('//*[@id="input-5"]').inputValue();
if (valueEditNode != "collection_name_test_123123123!@#$&*(&%$@") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="input-5"]').click();
await page
.locator('//*[@id="input-5"]')
.fill("NEW_collection_name_test_123123123!@#$&*(&%$@");
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator("#input-8");
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="editAdvancedIcon"]').click();
await page.locator('//*[@id="showcollection_name"]').click();
expect(
await page.locator('//*[@id="showcollection_name"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
let value = await page.locator("#input-8").inputValue();
if (value != "NEW_collection_name_test_123123123!@#$&*(&%$@") {
expect(false).toBeTruthy();
}
}
});

View file

@ -0,0 +1,254 @@
import { Page, expect, test } from "@playwright/test";
import { readFileSync } from "fs";
test.describe("save component tests", () => {
async function saveComponent(page: Page, pattern: RegExp, n: number) {
for (let i = 0; i < n; i++) {
await page.getByTestId(pattern).click();
//more node options
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div"
)
.click();
await page.getByLabel("Save").click();
}
}
/// <reference lib="dom"/>
test("save group component tests", async ({ page }) => {
//make front work withoput backend
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http:localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/onlyFront/assets/flow.json",
"utf-8"
);
// Create the DataTransfer and File
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "flow.json", {
type: "application/json",
});
dt.items.add(file);
return dt;
}, jsonContent);
// Now dispatch
await page.dispatchEvent('//*[@id="root"]/div/div[2]/div[2]', "drop", {
dataTransfer,
});
expect(
await page
.locator(".main-page-flows-display")
.evaluate((el) => el.children)
).toBeTruthy();
await page.getByRole("button", { name: "Edit Flow" }).click();
//inside the flow
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div/div[1]/div"
)
.click({
modifiers: ["Control"],
});
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[2]/div/div[1]/div"
)
.click({
modifiers: ["Control"],
});
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[3]/div/div[1]/div"
)
.click({
modifiers: ["Control"],
});
await page.getByRole("button", { name: "Group" }).click();
expect(
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div/div"
)
.isVisible()
).toBeTruthy();
await page.getByPlaceholder("Type something...").first().click();
await page.getByPlaceholder("Type something...").first().fill("save");
await page.locator(".react-flow__pane").click();
await page
.locator(".side-bar-buttons-arrangement > div:nth-child(3)")
.click();
//more option click
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div"
)
.click();
await page.getByLabel("Save").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("save");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="custom_componentssave"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.waitForTimeout(2000);
expect(
(await page.getByTestId(/.*rf__node-AgentInitializer.*/).all()).length
).toBe(2);
await page.locator(".isolate > button").first().click();
expect(
(await page.getByTestId(/.*rf__node-AgentInitializer.*/).all()).length
).toBe(1);
await page.getByTestId(/.*rf__node-AgentInitializer.*/).click();
await page.getByTestId(/.*rf__node-AgentInitializer.*/).press("Backspace");
await page
.locator('//*[@id="custom_componentssave"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.getByTestId(/.*rf__node-AgentInitializer.*/).click();
await page
.locator(
"//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div"
)
.click();
await page.getByLabel("Ungroup").click();
expect((await page.getByTestId(/.*rf__node-.*/).all()).length).toBe(3);
expect(
(await page.getByTestId(/.*rf__edge-reactflow.*/).all()).length
).toBe(2);
});
test("save default component with custom values", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page
.locator('//*[@id="vectorstoresChroma"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.locator("#input-8").click();
await page.locator("#input-8").fill("test");
await saveComponent(page, /.*rf__node-Chroma.*/, 1);
await page.getByTestId(/.*rf__node-Chroma.*/).press("Backspace");
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("");
await page.getByPlaceholder("Search").fill("Chroma");
await page
.locator('//*[@id="custom_componentsChroma"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
expect(await page.locator("#input-8").inputValue()).toBe("test");
});
test("save same component multiple times", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page
.locator('//*[@id="vectorstoresChroma"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await saveComponent(page, /.*rf__node-Chroma.*/, 3);
await page.getByTestId(/.*rf__node-Chroma.*/).press("Backspace");
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("");
await page.getByPlaceholder("Search").fill("Chroma");
expect(
await page.locator('//*[@id="custom_componentsChroma"]').isVisible()
).toBeTruthy();
expect(
await page.locator('[id="custom_componentsChroma\\ \\(1\\)"]').isVisible()
).toBeTruthy();
expect(
await page.locator('[id="custom_componentsChroma\\ \\(2\\)"]').isVisible()
).toBeTruthy();
await page
.locator('[id="custom_componentsChroma\\ \\(2\\)"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
expect(
(await page.getByTestId(/.*rf__node-Chroma.*/).allInnerTexts()).includes(
"Chroma (2)"
)
).toBeTruthy();
});
test("save default component and delete it", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page
.locator('//*[@id="vectorstoresChroma"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await saveComponent(page, /.*rf__node-Chroma.*/, 1);
await page.getByTestId(/.*rf__node-Chroma.*/).press("Backspace");
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("");
await page.getByPlaceholder("Search").fill("Chroma");
await page.locator("#custom_componentsChroma").getByRole("combobox").click({
button: "right",
});
await page.getByLabel("Delete").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill(" ");
await page.getByPlaceholder("Search").fill("Chroma");
expect(
await page.locator("#custom_componentsChroma").isVisible()
).toBeFalsy();
});
});

View file

@ -0,0 +1,176 @@
import { expect, test } from "@playwright/test";
test("ToggleComponent", async ({ page }) => {
await page.routeFromHAR("harFiles/langflow.har", {
url: "**/api/v1/**",
update: false,
});
await page.route("**/api/v1/flows/", async (route) => {
const json = {
id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210",
};
await route.fulfill({ json, status: 201 });
});
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("directoryLoader");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sideDirectoryLoader"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div/div/div[2]/div/div/div[1]/div'
)
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showload_hidden"]').click();
expect(
await page.locator('//*[@id="showload_hidden"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div/div/div[2]/div/div/div[1]/div'
)
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
expect(
await page.locator('//*[@id="toggle-edit-1"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showglob"]').click();
expect(await page.locator('//*[@id="showglob"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showload_hidden"]').click();
expect(
await page.locator('//*[@id="showload_hidden"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmax_concurrency"]').click();
expect(
await page.locator('//*[@id="showmax_concurrency"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmetadata"]').click();
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showpath"]').click();
expect(await page.locator('//*[@id="showpath"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showrecursive"]').click();
expect(
await page.locator('//*[@id="showrecursive"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showsilent_errors"]').click();
expect(
await page.locator('//*[@id="showsilent_errors"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showuse_multithreading"]').click();
expect(
await page.locator('//*[@id="showuse_multithreading"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showglob"]').click();
expect(await page.locator('//*[@id="showglob"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showmax_concurrency"]').click();
expect(
await page.locator('//*[@id="showmax_concurrency"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmetadata"]').click();
expect(
await page.locator('//*[@id="showmetadata"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showpath"]').click();
expect(await page.locator('//*[@id="showpath"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showrecursive"]').click();
expect(
await page.locator('//*[@id="showrecursive"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showsilent_errors"]').click();
expect(
await page.locator('//*[@id="showsilent_errors"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showuse_multithreading"]').click();
expect(
await page.locator('//*[@id="showuse_multithreading"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="toggle-1"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showload_hidden"]').click();
expect(
await page.locator('//*[@id="showload_hidden"]').isChecked()
).toBeTruthy();
expect(
await page.locator('//*[@id="toggle-edit-1"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="toggle-1"]').click();
expect(await page.locator('//*[@id="toggle-1"]').isChecked()).toBeTruthy();
}
});

View file

@ -1,5 +0,0 @@
import { test } from "@playwright/test";
test("test", async ({ page }) => {
// Recording...
});

57
tests/test_store.py Normal file
View file

@ -0,0 +1,57 @@
# FILEPATH: /Users/ogabrielluiz/Projects/langflow2/tests/test_store_service.py
from datetime import datetime
from unittest.mock import patch, Mock
from langflow.services.deps import get_store_service
@patch("langflow.services.store.service.httpx")
def test_search_components(mock_httpx: Mock, client):
# Mock the response from the HTTP GET request
from langflow.services.store.schema import ComponentResponse
mock_response = Mock()
mock_response.json.return_value = {
"data": [
{
"id": "1",
"name": "Test Component 1",
"description": "This is a test component.",
"tags": ["test"],
"status": "published",
"date_updated": datetime.now().isoformat(),
"is_component": False,
},
{
"id": "2",
"name": "Test Component 2",
"description": "This is another test component.",
"tags": ["test"],
"status": "published",
"date_updated": datetime.now().isoformat(),
"is_component": True,
},
]
}
mock_httpx.get.return_value = mock_response
# Create an instance of the StoreService class and call the search method
store_service = get_store_service()
components = store_service.search(api_key=None, query="test", limit=5)
# Assert that the HTTP GET request was made with the correct parameters
mock_httpx.get.assert_called_once_with(
store_service.components_url,
headers={},
params={
"filter[name][_like]": "test",
"page": 1,
"limit": 5,
"sort": "likes",
},
)
# Assert that the search method returns a list of ComponentResponse objects
assert len(components) == 2
assert all(isinstance(component, ComponentResponse) for component in components)