Merge branch 'zustand/io/migration' of personal:logspace-ai/langflow into zustand/io/migration

This commit is contained in:
anovazzi1 2024-02-16 13:36:38 -03:00
commit 0f22175d6a
49 changed files with 1606 additions and 1241 deletions

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "monthly"

View file

@ -16,10 +16,10 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Cache Docker layers
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}

View file

@ -30,11 +30,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -61,6 +61,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View file

@ -12,8 +12,8 @@ jobs:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm

View file

@ -16,13 +16,14 @@ jobs:
python-version:
- "3.9"
- "3.10"
- "3.11"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install poetry
run: |
pipx install poetry==$POETRY_VERSION
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: poetry

View file

@ -18,11 +18,11 @@ jobs:
if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'pre-release') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry==$POETRY_VERSION
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: "poetry"

View file

@ -17,11 +17,11 @@ jobs:
if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'Release') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry==$POETRY_VERSION
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: "poetry"

View file

@ -16,14 +16,15 @@ jobs:
matrix:
python-version:
- "3.10"
- "3.11"
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry==$POETRY_VERSION
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"

View file

@ -22,6 +22,8 @@ services:
dockerfile: ./cdk.Dockerfile
args:
- BACKEND_URL=http://backend:7860
depends_on:
- backend
environment:
- VITE_PROXY_TARGET=http://backend:7860
ports:

2076
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow"
version = "0.6.7a1"
version = "0.6.7a2"
description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"]
maintainers = [
@ -25,18 +25,20 @@ documentation = "https://docs.langflow.org"
langflow = "langflow.__main__:main"
[tool.poetry.dependencies]
python = ">=3.9,<3.11"
python = ">=3.9,<3.12"
duckdb = "^0.9.2"
fastapi = "^0.109.0"
uvicorn = "^0.27.0"
beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0"
google-api-python-client = "^2.118.0"
typer = "^0.9.0"
gunicorn = "^21.2.0"
langchain = "~0.1.0"
duckdb = "^0.9.2"
openai = "^1.11.0"
pandas = "2.0.3"
openai = "^1.12.0"
pandas = "2.2.0"
chromadb = "^0.4.0"
huggingface-hub = { version = "^0.20.0", extras = ["inference"] }
rich = "^13.7.0"
@ -50,15 +52,14 @@ fake-useragent = "^1.4.0"
docstring-parser = "^0.15"
psycopg2-binary = "^2.9.6"
pyarrow = "^14.0.0"
tiktoken = "~0.5.0"
tiktoken = "~0.6.0"
wikipedia = "^1.4.0"
qdrant-client = "^1.7.0"
weaviate-client = "*"
jina = "*"
sentence-transformers = { version = "^2.3.1", optional = true }
ctransformers = { version = "^0.2.10", optional = true }
cohere = "^4.45.0"
python-multipart = "^0.0.6"
cohere = "^4.47.0"
python-multipart = "^0.0.7"
sqlmodel = "^0.0.14"
faiss-cpu = "^1.7.4"
anthropic = "^0.15.0"
@ -67,17 +68,17 @@ multiprocess = "^0.70.14"
cachetools = "^5.3.1"
types-cachetools = "^5.3.0.5"
platformdirs = "^4.2.0"
pinecone-client = "^2.2.2"
pinecone-client = "^3.0.3"
pymongo = "^4.6.0"
supabase = "^2.3.0"
certifi = "^2023.11.17"
google-cloud-aiplatform = "^1.36.0"
google-cloud-aiplatform = "^1.42.0"
psycopg = "^3.1.9"
psycopg-binary = "^3.1.9"
fastavro = "^1.8.0"
langchain-experimental = "*"
celery = { extras = ["redis"], version = "^5.3.6", optional = true }
redis = { version = "^4.6.0", optional = true }
redis = { version = "^5.0.1", optional = true }
flower = { version = "^2.0.0", optional = true }
alembic = "^1.13.0"
passlib = "^1.7.4"
@ -90,46 +91,46 @@ zep-python = "*"
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
loguru = "^0.7.1"
langfuse = "^2.9.0"
pillow = "^10.0.0"
metal-sdk = "^2.4.0"
pillow = "^10.2.0"
metal-sdk = "^2.5.0"
markupsafe = "^2.1.3"
extract-msg = "^0.45.0"
extract-msg = "^0.47.0"
# jq is not available for windows
jq = { version = "^1.6.0", markers = "sys_platform != 'win32'" }
boto3 = "^1.34.0"
numexpr = "^2.8.6"
qianfan = "0.2.0"
qianfan = "0.3.0"
pgvector = "^0.2.3"
pyautogen = "^0.2.0"
langchain-google-genai = "^0.0.6"
elasticsearch = "^8.11.1"
elasticsearch = "^8.12.0"
pytube = "^15.0.0"
python-socketio = "^5.11.0"
llama-index = "^0.9.44"
langchain-openai = "^0.0.5"
llama-index = "0.9.48"
langchain-openai = "^0.0.6"
[tool.poetry.group.dev.dependencies]
pytest-asyncio = "^0.23.1"
types-redis = "^4.6.0.5"
ipykernel = "^6.27.0"
ipykernel = "^6.29.0"
mypy = "^1.8.0"
ruff = "^0.1.5"
ruff = "^0.2.1"
httpx = "*"
pytest = "^7.4.2"
pytest = "^8.0.0"
types-requests = "^2.31.0"
requests = "^2.31.0"
pytest-cov = "^4.1.0"
pandas-stubs = "^2.0.0.230412"
types-pillow = "^9.5.0.2"
pandas-stubs = "^2.1.4.231227"
types-pillow = "^10.2.0.20240213"
types-pyyaml = "^6.0.12.8"
types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13"
locust = "^2.19.1"
locust = "^2.23.1"
pytest-mock = "^3.12.0"
pytest-xdist = "^3.5.0"
types-pywin32 = "^306.0.0.4"
types-google-cloud-ndb = "^2.2.0.0"
pytest-sugar = "^0.9.7"
pytest-sugar = "^1.0.0"
pytest-instafail = "^0.5.0"

View file

@ -2,8 +2,9 @@ import asyncio
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from uuid import UUID
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langchain.schema import AgentAction, AgentFinish
from langchain_core.callbacks.base import (AsyncCallbackHandler,
BaseCallbackHandler)
from langflow.api.v1.schemas import ChatResponse, PromptResponse
from langflow.services.deps import get_chat_service
from langflow.utils.util import remove_ansi_escape_codes

View file

@ -1,10 +1,8 @@
from langflow import CustomComponent
from typing import Callable, Union
from langchain.chains import LLMCheckerChain
from typing import Union, Callable
from langflow.field_typing import (
BaseLanguageModel,
Chain,
)
from langflow import CustomComponent
from langflow.field_typing import BaseLanguageModel, Chain
class LLMCheckerChainComponent(CustomComponent):
@ -21,4 +19,4 @@ class LLMCheckerChainComponent(CustomComponent):
self,
llm: BaseLanguageModel,
) -> Union[Chain, Callable]:
return LLMCheckerChain(llm=llm)
return LLMCheckerChain.from_llm(llm=llm)

View file

@ -45,4 +45,6 @@ class RetrievalQAComponent(CustomComponent):
self.status = runnable
result = runnable.invoke({input_key: inputs})
result = result.content if hasattr(result, "content") else result
# Result is a dict with keys "query", "result" and "source_documents"
# for now we just return the result
return result.get("result")

View file

@ -1,6 +1,8 @@
from langflow import CustomComponent
from typing import Any, Dict, List
from langchain.docstore.document import Document
from typing import Optional, Dict, Any
from langchain.document_loaders.directory import DirectoryLoader
from langflow import CustomComponent
class DirectoryLoaderComponent(CustomComponent):
@ -23,20 +25,18 @@ class DirectoryLoaderComponent(CustomComponent):
self,
glob: str,
path: str,
load_hidden: Optional[bool] = False,
max_concurrency: Optional[int] = 10,
metadata: Optional[dict] = {},
recursive: Optional[bool] = True,
silent_errors: Optional[bool] = False,
use_multithreading: Optional[bool] = True,
) -> Document:
return Document(
max_concurrency: int = 2,
load_hidden: bool = False,
recursive: bool = True,
silent_errors: bool = False,
use_multithreading: bool = True,
) -> List[Document]:
return DirectoryLoader(
glob=glob,
path=path,
load_hidden=load_hidden,
max_concurrency=max_concurrency,
metadata=metadata,
recursive=recursive,
silent_errors=silent_errors,
use_multithreading=use_multithreading,
)
).load()

View file

@ -0,0 +1,42 @@
from typing import Dict, Optional
from langchain_community.embeddings.huggingface import HuggingFaceInferenceAPIEmbeddings
from langflow import CustomComponent
from pydantic.v1.types import SecretStr
class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent):
display_name = "HuggingFaceInferenceAPIEmbeddings"
description = "HuggingFace sentence_transformers embedding models, API version."
documentation = "https://github.com/huggingface/text-embeddings-inference"
def build_config(self):
return {
"api_key": {"display_name": "API Key", "password": True, "advanced": True},
"api_url": {"display_name": "API URL", "advanced": True},
"model_name": {"display_name": "Model Name"},
"cache_folder": {"display_name": "Cache Folder", "advanced": True},
"encode_kwargs": {"display_name": "Encode Kwargs", "advanced": True, "field_type": "dict"},
"model_kwargs": {"display_name": "Model Kwargs", "field_type": "dict", "advanced": True},
"multi_process": {"display_name": "Multi Process", "advanced": True},
}
def build(
self,
api_key: Optional[str] = "",
api_url: str = "http://localhost:8080",
model_name: str = "BAAI/bge-large-en-v1.5",
cache_folder: Optional[str] = None,
encode_kwargs: Optional[Dict] = {},
model_kwargs: Optional[Dict] = {},
multi_process: bool = False,
) -> HuggingFaceInferenceAPIEmbeddings:
if api_key:
secret_api_key = SecretStr(api_key)
else:
raise ValueError("API Key is required")
return HuggingFaceInferenceAPIEmbeddings(
api_key=secret_api_key,
api_url=api_url,
model_name=model_name,
)

View file

@ -1,9 +1,9 @@
from typing import Any, Callable, Dict, List, Optional, Union
from langchain_openai.embeddings.base import OpenAIEmbeddings
from langflow import CustomComponent
from langflow.field_typing import NestedDict
from pydantic.v1.types import SecretStr
class OpenAIEmbeddingsComponent(CustomComponent):
@ -67,7 +67,7 @@ class OpenAIEmbeddingsComponent(CustomComponent):
},
"skip_empty": {"display_name": "Skip Empty", "advanced": True},
"tiktoken_model_name": {"display_name": "TikToken Model Name"},
"tikToken_enable": {"display_name": "TikToken Enable"},
"tikToken_enable": {"display_name": "TikToken Enable", "advanced": True},
}
def build(
@ -92,14 +92,17 @@ class OpenAIEmbeddingsComponent(CustomComponent):
request_timeout: Optional[float] = None,
show_progress_bar: bool = False,
skip_empty: bool = False,
tikToken_enable: bool = True,
tiktoken_enable: bool = True,
tiktoken_model_name: Optional[str] = None,
) -> Union[OpenAIEmbeddings, Callable]:
# This is to avoid errors with Vector Stores (e.g Chroma)
if disallowed_special == ["all"]:
disallowed_special = "all"
disallowed_special = "all" # type: ignore
api_key = SecretStr(openai_api_key) if openai_api_key else None
return OpenAIEmbeddings(
tiktoken_enabled=tikToken_enable,
tiktoken_enabled=tiktoken_enable,
default_headers=default_headers,
default_query=default_query,
allowed_special=set(allowed_special),
@ -112,7 +115,7 @@ class OpenAIEmbeddingsComponent(CustomComponent):
model=model,
model_kwargs=model_kwargs,
base_url=openai_api_base,
api_key=openai_api_key,
api_key=api_key,
openai_api_type=openai_api_type,
api_version=openai_api_version,
organization=openai_organization,

View file

@ -1,4 +1,4 @@
from pydantic import SecretStr
from pydantic.v1.types import SecretStr
from langflow import CustomComponent
from typing import Optional, Union, Callable
from langflow.field_typing import BaseLanguageModel

View file

@ -1,9 +1,9 @@
from typing import Optional
from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore
from langflow import CustomComponent
from langflow.field_typing import BaseLanguageModel, RangeSpec, TemplateField
from pydantic.v1.types import SecretStr
class GoogleGenerativeAIComponent(CustomComponent):
@ -63,10 +63,10 @@ class GoogleGenerativeAIComponent(CustomComponent):
) -> BaseLanguageModel:
return ChatGoogleGenerativeAI(
model=model,
max_output_tokens=max_output_tokens or None,
max_output_tokens=max_output_tokens or None, # type: ignore
temperature=temperature,
top_k=top_k or None,
top_p=top_p or None,
top_p=top_p or None, # type: ignore
n=n or 1,
google_api_key=google_api_key,
google_api_key=SecretStr(google_api_key),
)

View file

@ -24,6 +24,8 @@ class CharacterTextSplitterComponent(CustomComponent):
chunk_size: int = 1000,
separator: str = "\n",
) -> List[Document]:
# separator may come escaped from the frontend
separator = separator.encode().decode("unicode_escape")
docs = CharacterTextSplitter(
chunk_overlap=chunk_overlap,
chunk_size=chunk_size,

View file

@ -1,8 +1,7 @@
from langchain_community.agent_toolkits.openapi.toolkit import BaseToolkit, OpenAPIToolkit
from langchain_community.utilities.requests import TextRequestsWrapper
from langflow import CustomComponent
from langflow.field_typing import AgentExecutor
from typing import Callable
from langchain_community.utilities.requests import TextRequestsWrapper
from langchain_community.agent_toolkits.openapi.toolkit import OpenAPIToolkit
class OpenAPIToolkitComponent(CustomComponent):
@ -19,5 +18,5 @@ class OpenAPIToolkitComponent(CustomComponent):
self,
json_agent: AgentExecutor,
requests_wrapper: TextRequestsWrapper,
) -> Callable:
) -> BaseToolkit:
return OpenAPIToolkit(json_agent=json_agent, requests_wrapper=requests_wrapper)

View file

@ -1,6 +1,7 @@
from langflow import CustomComponent
from typing import Union, Callable
from typing import Callable, Union
from langchain_community.utilities.google_search import GoogleSearchAPIWrapper
from langflow import CustomComponent
class GoogleSearchAPIWrapperComponent(CustomComponent):
@ -18,4 +19,4 @@ class GoogleSearchAPIWrapperComponent(CustomComponent):
google_api_key: str,
google_cse_id: str,
) -> Union[GoogleSearchAPIWrapper, Callable]:
return GoogleSearchAPIWrapper(google_api_key=google_api_key, google_cse_id=google_cse_id)
return GoogleSearchAPIWrapper(google_api_key=google_api_key, google_cse_id=google_cse_id) # type: ignore

View file

@ -1,9 +1,9 @@
from langflow import CustomComponent
from typing import Dict, Optional
from typing import Dict
# Assuming the existence of GoogleSerperAPIWrapper class in the serper module
# If this class does not exist, you would need to create it or import the appropriate class from another module
from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper
from langflow import CustomComponent
class GoogleSerperAPIWrapperComponent(CustomComponent):
@ -42,6 +42,5 @@ class GoogleSerperAPIWrapperComponent(CustomComponent):
def build(
self,
serper_api_key: str,
result_key_for_type: Optional[Dict[str, str]] = None,
) -> GoogleSerperAPIWrapper:
return GoogleSerperAPIWrapper(result_key_for_type=result_key_for_type, serper_api_key=serper_api_key)
return GoogleSerperAPIWrapper(serper_api_key=serper_api_key)

View file

@ -17,6 +17,7 @@ class ChromaComponent(CustomComponent):
description: str = "Implementation of Vector Store using Chroma"
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
beta: bool = True
icon = "Chroma"
def build_config(self):
"""
@ -28,7 +29,7 @@ class ChromaComponent(CustomComponent):
return {
"collection_name": {"display_name": "Collection Name", "value": "langflow"},
"persist": {"display_name": "Persist"},
"persist_directory": {"display_name": "Persist Directory"},
"index_directory": {"display_name": "Persist Directory"},
"code": {"advanced": True, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
@ -54,7 +55,7 @@ class ChromaComponent(CustomComponent):
persist: bool,
embedding: Embeddings,
chroma_server_ssl_enabled: bool,
persist_directory: Optional[str] = None,
index_directory: Optional[str] = None,
documents: Optional[List[Document]] = None,
chroma_server_cors_allow_origins: Optional[str] = None,
chroma_server_host: Optional[str] = None,
@ -66,7 +67,7 @@ class ChromaComponent(CustomComponent):
Args:
- collection_name (str): The name of the collection.
- persist_directory (Optional[str]): The directory to persist the Vector Store to.
- index_directory (Optional[str]): The directory to persist the Vector Store to.
- chroma_server_ssl_enabled (bool): Whether to enable SSL for the Chroma server.
- persist (bool): Whether to persist the Vector Store or not.
- embedding (Optional[Embeddings]): The embeddings to use for the Vector Store.
@ -85,7 +86,8 @@ class ChromaComponent(CustomComponent):
if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None,
@ -93,15 +95,25 @@ class ChromaComponent(CustomComponent):
)
# If documents, then we need to create a Chroma instance using .from_documents
# Check index_directory and expand it if it is a relative path
index_directory = self.resolve_path(index_directory)
if documents is not None and embedding is not None:
if len(documents) == 0:
raise ValueError("If documents are provided, there must be at least one document.")
return Chroma.from_documents(
raise ValueError(
"If documents are provided, there must be at least one document."
)
chroma = Chroma.from_documents(
documents=documents, # type: ignore
persist_directory=persist_directory if persist else None,
persist_directory=index_directory if persist else None,
collection_name=collection_name,
embedding=embedding,
client_settings=chroma_settings,
)
return Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
else:
chroma = Chroma(
persist_directory=index_directory, client_settings=chroma_settings
)
return chroma

View file

@ -3,7 +3,7 @@ from typing import List, Optional
import chromadb # type: ignore
from langchain_community.vectorstores.chroma import Chroma
from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings, Text
from langflow.field_typing import Embeddings, Text
from langflow.schema import Record, docs_to_records
@ -15,6 +15,7 @@ class ChromaSearchComponent(CustomComponent):
display_name: str = "Chroma Search"
description: str = "Search a Chroma collection for similar documents."
beta: bool = True
icon = "Chroma"
def build_config(self):
"""
@ -25,13 +26,19 @@ class ChromaSearchComponent(CustomComponent):
"""
return {
"inputs": {"display_name": "Input"},
"search_type": {"display_name": "Search Type", "options": ["Similarity", "MMR"]},
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"collection_name": {"display_name": "Collection Name", "value": "langflow"},
"persist": {"display_name": "Persist"},
"persist_directory": {"display_name": "Persist Directory"},
# "persist": {"display_name": "Persist"},
"index_directory": {"display_name": "Index Directory"},
"code": {"show": False, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
"embedding": {
"display_name": "Embedding",
"info": "Embedding model to vectorize inputs (make sure to use same as index)",
},
"chroma_server_cors_allow_origins": {
"display_name": "Server CORS Allow Origins",
"advanced": True,
@ -53,11 +60,9 @@ class ChromaSearchComponent(CustomComponent):
inputs: Text,
search_type: str,
collection_name: str,
persist: bool,
embedding: Embeddings,
chroma_server_ssl_enabled: bool,
persist_directory: Optional[str] = None,
documents: Optional[List[Document]] = None,
index_directory: Optional[str] = None,
chroma_server_cors_allow_origins: Optional[str] = None,
chroma_server_host: Optional[str] = None,
chroma_server_port: Optional[int] = None,
@ -87,26 +92,20 @@ class ChromaSearchComponent(CustomComponent):
if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None,
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
)
# If documents, then we need to create a Chroma instance using .from_documents
if documents is not None and embedding is not None:
if len(documents) == 0:
raise ValueError("If documents are provided, there must be at least one document.")
chroma = Chroma.from_documents(
documents=documents, # type: ignore
persist_directory=persist_directory if persist else None,
collection_name=collection_name,
embedding=embedding,
client_settings=chroma_settings,
)
else:
chroma = Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
chroma = Chroma(
embedding_function=embedding,
collection_name=collection_name,
persist_directory=index_directory,
client_settings=chroma_settings,
)
# Validate the inputs
docs = []

View file

@ -5,7 +5,6 @@ import pinecone # type: ignore
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pinecone import Pinecone
from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings
@ -31,11 +30,11 @@ class PineconeComponent(CustomComponent):
embedding: Embeddings,
pinecone_env: str,
documents: List[Document],
text_key: str = "text",
pool_threads: int = 4,
index_name: Optional[str] = None,
pinecone_api_key: Optional[str] = None,
text_key: Optional[str] = "text",
namespace: Optional[str] = "default",
pool_threads: Optional[int] = None,
) -> Union[VectorStore, Pinecone, BaseRetriever]:
if pinecone_api_key is None or pinecone_env is None:
raise ValueError("Pinecone API Key and Environment are required.")
@ -43,6 +42,8 @@ class PineconeComponent(CustomComponent):
raise ValueError("Pinecone API Key is required.")
pinecone.init(api_key=pinecone_api_key, environment=pinecone_env) # type: ignore
if not index_name:
raise ValueError("Index Name is required.")
if documents:
return Pinecone.from_documents(
documents=documents,

View file

@ -1,4 +1,4 @@
from typing import List, Optional, Union
from typing import Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
@ -15,7 +15,7 @@ class QdrantComponent(CustomComponent):
return {
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
"api_key": {"display_name": "API Key", "password": True},
"api_key": {"display_name": "API Key", "password": True, "advanced": True},
"collection_name": {"display_name": "Collection Name"},
"content_payload_key": {"display_name": "Content Payload Key", "advanced": True},
"distance_func": {"display_name": "Distance Function", "advanced": True},
@ -36,41 +36,68 @@ class QdrantComponent(CustomComponent):
def build(
self,
embedding: Embeddings,
documents: List[Document],
collection_name: str,
documents: Optional[Document] = None,
api_key: Optional[str] = None,
collection_name: Optional[str] = None,
content_payload_key: str = "page_content",
distance_func: str = "Cosine",
grpc_port: Optional[int] = 6334,
host: Optional[str] = None,
grpc_port: int = 6334,
https: bool = False,
location: str = ":memory:",
host: Optional[str] = None,
location: Optional[str] = None,
metadata_payload_key: str = "metadata",
path: Optional[str] = None,
port: Optional[int] = 6333,
prefer_grpc: bool = False,
prefix: Optional[str] = None,
search_kwargs: Optional[NestedDict] = None,
timeout: Optional[float] = None,
timeout: Optional[int] = None,
url: Optional[str] = None,
) -> Union[VectorStore, Qdrant, BaseRetriever]:
return Qdrant.from_documents(
documents=documents,
embedding=embedding,
api_key=api_key,
collection_name=collection_name,
content_payload_key=content_payload_key,
distance_func=distance_func,
grpc_port=grpc_port,
host=host,
https=https,
location=location,
metadata_payload_key=metadata_payload_key,
path=path,
port=port,
prefer_grpc=prefer_grpc,
prefix=prefix,
search_kwargs=search_kwargs,
timeout=timeout,
url=url,
)
if documents is None:
from qdrant_client import QdrantClient
client = QdrantClient(
location=location,
url=host,
port=port,
grpc_port=grpc_port,
https=https,
prefix=prefix,
timeout=timeout,
prefer_grpc=prefer_grpc,
metadata_payload_key=metadata_payload_key,
content_payload_key=content_payload_key,
api_key=api_key,
collection_name=collection_name,
host=host,
path=path,
)
vs = Qdrant(
client=client,
collection_name=collection_name,
embeddings=embedding,
)
return vs
else:
vs = Qdrant.from_documents(
documents=documents, # type: ignore
embedding=embedding,
api_key=api_key,
collection_name=collection_name,
content_payload_key=content_payload_key,
distance_func=distance_func,
grpc_port=grpc_port,
host=host,
https=https,
location=location,
metadata_payload_key=metadata_payload_key,
path=path,
port=port,
prefer_grpc=prefer_grpc,
prefix=prefix,
search_kwargs=search_kwargs,
timeout=timeout,
url=url,
)
return vs

View file

@ -5,7 +5,6 @@ from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.redis import Redis
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
from langflow import CustomComponent
@ -31,6 +30,7 @@ class RedisComponent(CustomComponent):
"code": {"show": False, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
"redis_server_url": {
"display_name": "Redis Server Connection String",
"advanced": False,
@ -43,6 +43,7 @@ class RedisComponent(CustomComponent):
embedding: Embeddings,
redis_server_url: str,
redis_index_name: str,
schema: Optional[str] = None,
documents: Optional[Document] = None,
) -> Union[VectorStore, BaseRetriever]:
"""
@ -58,10 +59,12 @@ class RedisComponent(CustomComponent):
- VectorStore: The Vector Store object.
"""
if documents is None:
if schema is None:
raise ValueError("If no documents are provided, a schema must be provided.")
redis_vs = Redis.from_existing_index(
embedding=embedding,
index_name=redis_index_name,
schema=None,
schema=schema,
key_prefix=None,
redis_url=redis_server_url,
)

View file

@ -6,7 +6,6 @@ from typing import List, Optional, Union
from langchain_community.embeddings import FakeEmbeddings
from langchain_community.vectorstores.vectara import Vectara
from langchain_core.vectorstores import VectorStore
from langflow import CustomComponent
from langflow.field_typing import BaseRetriever, Document
@ -46,7 +45,7 @@ class VectaraComponent(CustomComponent):
if documents is not None:
return Vectara.from_documents(
documents=documents,
documents=documents, # type: ignore
embedding=FakeEmbeddings(size=768),
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,

View file

@ -5,7 +5,6 @@ from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pgvector import PGVector
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
from langflow import CustomComponent
@ -63,13 +62,13 @@ class PGVectorComponent(CustomComponent):
collection_name=collection_name,
connection_string=pg_server_url,
)
vector_store = PGVector.from_documents(
embedding=embedding,
documents=documents,
collection_name=collection_name,
connection_string=pg_server_url,
)
else:
vector_store = PGVector.from_documents(
embedding=embedding,
documents=documents, # type: ignore
collection_name=collection_name,
connection_string=pg_server_url,
)
except Exception as e:
raise RuntimeError(f"Failed to build PGVector: {e}")
return vector_store

View file

@ -6,7 +6,12 @@ from langflow.graph.edge.base import ContractEdge
from langflow.graph.graph.constants import lazy_load_vertex_dict
from langflow.graph.graph.utils import process_flow
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ChatVertex, FileToolVertex, LLMVertex, ToolkitVertex
from langflow.graph.vertex.types import (
ChatVertex,
FileToolVertex,
LLMVertex,
ToolkitVertex,
)
from langflow.interface.tools.constants import FILE_TOOLS
from langflow.utils import payload
from loguru import logger
@ -127,7 +132,9 @@ class Graph:
return
for vertex in self.vertices:
if not self._validate_vertex(vertex):
raise ValueError(f"{vertex.vertex_type} is not connected to any other components")
raise ValueError(
f"{vertex.vertex_type} is not connected to any other components"
)
def _validate_vertex(self, vertex: Vertex) -> bool:
"""Validates a vertex."""
@ -140,7 +147,11 @@ class Graph:
def get_vertex_edges(self, vertex_id: str) -> List[ContractEdge]:
"""Returns a list of edges for a given vertex."""
return [edge for edge in self.edges if edge.source_id == vertex_id or edge.target_id == vertex_id]
return [
edge
for edge in self.edges
if edge.source_id == vertex_id or edge.target_id == vertex_id
]
def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]:
"""Returns the vertices connected to a vertex."""
@ -178,7 +189,9 @@ class Graph:
def dfs(vertex):
if state[vertex] == 1:
# We have a cycle
raise ValueError("Graph contains a cycle, cannot perform topological sort")
raise ValueError(
"Graph contains a cycle, cannot perform topological sort"
)
if state[vertex] == 0:
state[vertex] = 1
for edge in vertex.edges:
@ -237,7 +250,9 @@ class Graph:
edges.append(ContractEdge(source, target, edge))
return edges
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
def _get_vertex_class(
self, node_type: str, node_base_type: str, node_id: str
) -> Type[Vertex]:
"""Returns the node class based on the node type."""
# First we check for the node_base_type
node_name = node_id.split("-")[0]
@ -267,14 +282,18 @@ class Graph:
vertex_type: str = vertex_data["type"] # type: ignore
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
VertexClass = self._get_vertex_class(
vertex_type, vertex_base_type, vertex_data["id"]
)
vertex_instance = VertexClass(vertex, graph=self)
vertex_instance.set_top_level(self.top_level_vertices)
vertices.append(vertex_instance)
return vertices
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
def get_children_by_vertex_type(
self, vertex: Vertex, vertex_type: str
) -> List[Vertex]:
"""Returns the children of a vertex based on the vertex type."""
children = []
vertex_types = [vertex.data["type"]]
@ -286,7 +305,9 @@ class Graph:
def __repr__(self):
vertex_ids = [vertex.id for vertex in self.vertices]
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
edges_repr = "\n".join(
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
)
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
def layered_topological_sort(self):
@ -299,7 +320,9 @@ class Graph:
in_degree[edge.target_id] += 1
# Queue for vertices with no incoming edges
queue = deque(vertex.id for vertex in self.vertices if in_degree[vertex.id] == 0)
queue = deque(
vertex.id for vertex in self.vertices if in_degree[vertex.id] == 0
)
layers = []
current_layer = 0
@ -314,9 +337,40 @@ class Graph:
if in_degree[neighbor] == 0:
queue.append(neighbor)
current_layer += 1 # Next layer
new_layers = self.refine_layers(graph, layers)
return new_layers
return layers
return layers
return layers
return layers
return layers
def refine_layers(self, graph, initial_layers):
# Map each vertex to its current layer
vertex_to_layer = {}
for layer_index, layer in enumerate(initial_layers):
for vertex in layer:
vertex_to_layer[vertex] = layer_index
# Build the adjacency list for reverse lookup (dependencies)
refined_layers = [[] for _ in initial_layers] # Start with empty layers
new_layer_index_map = defaultdict(
int
) # Map each vertex to its highest dependency layer
for vertex_id, deps in graph.items():
for dep in deps:
new_layer_index_map[vertex_id] = (
max(new_layer_index_map[vertex_id], vertex_to_layer[dep]) - 1
)
for layer_index, layer in enumerate(initial_layers):
for vertex_id in layer:
# Place the vertex in the highest possible layer where its dependencies are met
new_layer_index = new_layer_index_map[vertex_id]
if new_layer_index > layer_index:
refined_layers[new_layer_index].append(vertex_id)
vertex_to_layer[vertex_id] = new_layer_index
else:
refined_layers[layer_index].append(vertex_id)
# Remove empty layers if any
refined_layers = [layer for layer in refined_layers if layer]
return refined_layers

View file

@ -5,7 +5,6 @@ from typing import Any, ClassVar, Optional
import emoji
from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException
from langflow.interface.custom.code_parser import CodeParser
from langflow.interface.custom.eval import eval_custom_component_code
from langflow.utils import validate
@ -21,7 +20,9 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
class Component:
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = (
"The name of the entrypoint function must be provided."
)
code: Optional[str] = None
_function_entrypoint_name: str = "build"
@ -36,10 +37,6 @@ class Component:
else:
setattr(self, key, value)
# Validate the emoji at the icon field
if self.icon:
self.icon = self.validate_icon(self.icon)
def __setattr__(self, key, value):
if key == "_user_id" and hasattr(self, "_user_id"):
warnings.warn("user_id is immutable and cannot be changed.")
@ -68,8 +65,8 @@ class Component:
return validate.create_function(self.code, self._function_entrypoint_name)
def getattr_return_str(self, component, value):
value = getattr(component, value)
def getattr_return_str(self, value):
return str(value) if value else ""
def build_template_config(self) -> dict:
@ -89,19 +86,22 @@ class Component:
for attribute, func in attributes_func_mapping.items():
if hasattr(component_instance, attribute):
template_config[attribute] = func(component=component_instance, value=attribute)
value = getattr(component_instance, attribute)
if value is not None:
template_config[attribute] = func(value=value)
return template_config
return template_config
def validate_icon(self, value: str, *args, **kwargs):
# we are going to use the emoji library to validate the emoji
# emojis can be defined using the :emoji_name: syntax
if not value.startswith(":") or not value.endswith(":"):
raise ValueError("Invalid emoji. Please use the :emoji_name: syntax.")
warnings.warn("Invalid emoji. Please use the :emoji_name: syntax.")
return value
emoji_value = emoji.emojize(value, variant="emoji_type")
if value == emoji_value:
raise ValueError(f"Invalid emoji. {value} is not a valid emoji.")
warnings.warn(f"Invalid emoji. {value} is not a valid emoji.")
return value
return emoji_value
def build(self, *args: Any, **kwargs: Any) -> Any:

View file

@ -1,11 +1,11 @@
import operator
from pathlib import Path
from typing import Any, Callable, ClassVar, List, Optional, Union
from uuid import UUID
import yaml
from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException
from langflow.interface.custom.code_parser.utils import (
extract_inner_type_from_generic_alias,
extract_union_types_from_generic_alias,
@ -13,7 +13,11 @@ from langflow.interface.custom.code_parser.utils import (
from langflow.interface.custom.custom_component.component import Component
from langflow.services.database.models.flow import Flow
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_credential_service, get_db_service, get_storage_service
from langflow.services.deps import (
get_credential_service,
get_db_service,
get_storage_service,
)
from langflow.services.storage.service import StorageService
from langflow.utils import validate
@ -42,6 +46,16 @@ class CustomComponent(Component):
self.cache = TTLCache(maxsize=1024, ttl=60)
super().__init__(**data)
@staticmethod
def resolve_path(path: str) -> str:
"""Resolves the path to an absolute path."""
path_object = Path(path)
if path_object.parts[0] == "~":
path_object = path_object.expanduser()
elif path_object.is_relative_to("."):
path_object = path_object.resolve()
return str(path_object)
def get_full_path(self, path: str) -> str:
storage_svc: "StorageService" = get_storage_service()
@ -78,7 +92,8 @@ class CustomComponent(Component):
detail={
"error": "Type hint Error",
"traceback": (
"Prompt type is not supported in the build method." " Try using PromptTemplate instead."
"Prompt type is not supported in the build method."
" Try using PromptTemplate instead."
),
},
)
@ -92,14 +107,20 @@ class CustomComponent(Component):
if not self.code:
return {}
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
component_classes = [
cls
for cls in self.tree["classes"]
if self.code_class_base_inheritance in cls["bases"]
]
if not component_classes:
return {}
# Assume the first Component class is the one we're interested in
component_class = component_classes[0]
build_methods = [
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
method
for method in component_class["methods"]
if method["name"] == self.function_entrypoint_name
]
return build_methods[0] if build_methods else {}
@ -112,7 +133,10 @@ class CustomComponent(Component):
return_type = build_method["return_type"]
# If list or List is in the return type, then we remove it and return the inner type
if hasattr(return_type, "__origin__") and return_type.__origin__ in [list, List]:
if hasattr(return_type, "__origin__") and return_type.__origin__ in [
list,
List,
]:
return_type = extract_inner_type_from_generic_alias(return_type)
# If the return type is not a Union, then we just return it as a list
@ -153,7 +177,9 @@ class CustomComponent(Component):
# Retrieve and decrypt the credential by name for the current user
db_service = get_db_service()
with session_getter(db_service) as session:
return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session)
return credential_service.get_credential(
user_id=self._user_id or "", name=name, session=session
)
return get_credential
@ -163,7 +189,9 @@ class CustomComponent(Component):
credential_service = get_credential_service()
db_service = get_db_service()
with session_getter(db_service) as session:
return credential_service.list_credentials(user_id=self._user_id, session=session)
return credential_service.list_credentials(
user_id=self._user_id, session=session
)
def index(self, value: int = 0):
"""Returns a function that returns the value at the given index in the iterable."""
@ -214,7 +242,11 @@ class CustomComponent(Component):
if flow_id:
flow = session.query(Flow).get(flow_id)
elif flow_name:
flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first()
flow = (
session.query(Flow)
.filter(Flow.name == flow_name)
.filter(Flow.user_id == self.user_id)
).first()
else:
raise ValueError("Either flow_name or flow_id must be provided")

View file

@ -7,8 +7,6 @@ from typing import Any, Dict, List, Optional, Union
from uuid import UUID
from fastapi import HTTPException
from loguru import logger
from langflow.field_typing.range_spec import RangeSpec
from langflow.interface.custom.code_parser.utils import extract_inner_type
from langflow.interface.custom.custom_component import CustomComponent
@ -22,6 +20,7 @@ from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
from langflow.utils import validate
from langflow.utils.util import get_base_classes
from loguru import logger
def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
@ -151,9 +150,6 @@ def add_extra_fields(frontend_node, field_config, function_args):
if not function_args:
return
# sort function_args which is a list of dicts
function_args.sort(key=lambda x: x["name"])
for extra_field in function_args:
if "name" not in extra_field or extra_field["name"] == "self":
continue

View file

@ -132,12 +132,12 @@ async def instantiate_custom_component(node_type, class_object, params, user_id)
if is_async:
# Await the build method directly if it's async
built_object = await custom_component.build(**params_copy)
build_result = await custom_component.build(**params_copy)
else:
# Call the build method directly if it's sync
built_object = custom_component.build(**params_copy)
build_result = custom_component.build(**params_copy)
return built_object, {"repr": custom_component.custom_repr()}
return build_result, {"repr": custom_component.custom_repr()}
def instantiate_wrapper(node_type, class_object, params):

View file

@ -2,10 +2,11 @@ from typing import TYPE_CHECKING, List, Union
from langchain.agents.agent import AgentExecutor
from langchain.callbacks.base import BaseCallbackHandler
from loguru import logger
from langflow.api.v1.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler
from langflow.processing.process import fix_memory_inputs, format_actions
from langflow.services.deps import get_plugins_service
from loguru import logger
if TYPE_CHECKING:
from langfuse.callback import CallbackHandler # type: ignore
@ -28,13 +29,12 @@ def setup_callbacks(sync, trace_id, **kwargs):
def get_langfuse_callback(trace_id):
from langflow.services.deps import get_plugins_service
from langfuse.callback import CreateTrace
logger.debug("Initializing langfuse callback")
if langfuse := get_plugins_service().get("langfuse"):
logger.debug("Langfuse credentials found")
try:
trace = langfuse.trace(CreateTrace(name="langflow-" + trace_id, id=trace_id))
trace = langfuse.trace(name="langflow-" + trace_id, id=trace_id)
return trace.getNewHandler()
except Exception as exc:
logger.error(f"Error initializing langfuse callback: {exc}")

View file

@ -64,14 +64,13 @@ class LangfusePlugin(CallbackPlugin):
def get_callback(self, _id: Optional[str] = None):
if _id is None:
_id = "default"
from langfuse.callback import CreateTrace # type: ignore
logger.debug("Initializing langfuse callback")
try:
langfuse_instance = self.get()
if langfuse_instance is not None and hasattr(langfuse_instance, "trace"):
trace = langfuse_instance.trace(CreateTrace(name="langflow-" + _id, id=_id))
trace = langfuse_instance.trace(name="langflow-" + _id, id=_id)
if trace:
return trace.getNewHandler()

View file

@ -45,7 +45,7 @@
"dompurify": "^3.0.5",
"esbuild": "^0.17.19",
"lodash": "^4.17.21",
"lucide-react": "^0.233.0",
"lucide-react": "^0.331.0",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-ace": "^10.1.0",
@ -99,7 +99,7 @@
"pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
"vite": "^4.5.1"
"vite": "^4.5.2"
}
},
"node_modules/@adobe/css-tools": {
@ -7220,9 +7220,9 @@
}
},
"node_modules/lucide-react": {
"version": "0.233.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.233.0.tgz",
"integrity": "sha512-r0jMHF0vPDq2wBbZ0B3rtIcBjDyWDKpHu+vAjD2OHn2WLUr3HN5IHovtO0EMgQXuSI7YrMZbjsEZWC2uBHr8nQ==",
"version": "0.331.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.331.0.tgz",
"integrity": "sha512-CHFJ0ve9vaZ7bB2VRAl27SlX1ELh6pfNC0jS96qGpPEEzLkLDGq4pDBFU8RhOoRMqsjXqTzLm9U6bZ1OcIHq7Q==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}

View file

@ -40,7 +40,7 @@
"dompurify": "^3.0.5",
"esbuild": "^0.17.19",
"lodash": "^4.17.21",
"lucide-react": "^0.233.0",
"lucide-react": "^0.331.0",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-ace": "^10.1.0",
@ -121,6 +121,6 @@
"pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
"vite": "^4.5.1"
"vite": "^4.5.2"
}
}

View file

@ -30,6 +30,7 @@ export default function App() {
);
const loading = useAlertStore((state) => state.loading);
const [fetchError, setFetchError] = useState(false);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const removeAlert = (id: string) => {
removeFromTempNotificationList(id);
@ -86,7 +87,7 @@ export default function App() {
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : loading ? (
) : isLoading ? (
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
</div>

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { NodeToolbar } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
@ -112,6 +112,39 @@ export default function GenericNode({
const nameEditable = data.node?.flow || data.type === "CustomComponent";
const emojiRegex = /\p{Emoji}/u;
const isEmoji = emojiRegex.test(data?.node?.icon!);
const iconNodeRender = useCallback(() => {
const iconElement = data?.node?.icon;
const iconColor = nodeColors[types[data.type]];
const iconName =
iconElement || (data.node?.flow ? "group_components" : name);
const iconClassName = `generic-node-icon ${
!showNode ? "absolute inset-x-6 h-12 w-12" : ""
}`;
if (iconElement && isEmoji) {
return nodeIconFragment(iconElement);
} else {
return checkNodeIconFragment(iconColor, iconName, iconClassName);
}
}, [data, isEmoji, name, showNode]);
const nodeIconFragment = (icon) => {
return <span className="text-lg">{icon}</span>;
};
const checkNodeIconFragment = (iconColor, iconName, iconClassName) => {
return (
<IconComponent
name={iconName}
className={iconClassName}
iconColor={iconColor}
/>
);
};
return (
<>
<NodeToolbar>
@ -164,19 +197,7 @@ export default function GenericNode({
(!showNode && "justify-center")
}
>
{data?.node?.icon ? (
<span className="text-lg">{data?.node?.icon}</span>
) : (
<IconComponent
name={data.node?.flow ? "group_components" : name}
className={
"generic-node-icon " +
(!showNode ? "absolute inset-x-6 h-12 w-12" : "")
}
iconColor={`${nodeColors[types[data.type]]}`}
/>
)}
{iconNodeRender()}
{showNode && (
<div className="generic-node-tooltip-div">
{nameEditable && inputName ? (
@ -370,7 +391,7 @@ export default function GenericNode({
<span className="flex">
Build{" "}
<IconComponent
name="Zap"
name="Play"
className=" h-5 fill-build-trigger stroke-build-trigger stroke-1"
/>{" "}
flow to validate status.
@ -390,7 +411,7 @@ export default function GenericNode({
>
<div className="generic-node-status-position flex items-center justify-center">
<IconComponent
name="Zap"
name="Play"
className={classNames(
validationStatus && validationStatus.valid
? "green-status"
@ -399,7 +420,7 @@ export default function GenericNode({
)}
/>
<IconComponent
name="Zap"
name="Play"
className={classNames(
validationStatus && !validationStatus.valid
? "red-status"
@ -408,7 +429,7 @@ export default function GenericNode({
)}
/>
<IconComponent
name="Zap"
name="Play"
className={classNames(
!validationStatus || isBuilding
? "yellow-status"

View file

@ -12,7 +12,7 @@ const buttonVariants = cva(
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input hover:bg-accent hover:text-accent-foreground",
"border border-input hover:bg-input hover:text-accent-foreground",
primary:
"border bg-background text-secondary-foreground hover:bg-secondary-foreground/5 dark:hover:bg-background/10 hover:shadow-sm",
secondary:

View file

@ -94,12 +94,6 @@ export default function ComponentsComponent({
setPageSize(10);
}
useEffect(() => {
setTimeout(() => {
setLoadingScreen(false);
}, 600);
}, []);
return (
<CardsWrapComponent
onFileDrop={onFileDrop}
@ -107,7 +101,7 @@ export default function ComponentsComponent({
>
<div className="flex h-full w-full flex-col justify-between">
<div className="flex w-full flex-col gap-4">
{!loadingScreen && data.length === 0 ? (
{!isLoading && data.length === 0 ? (
<div className="mt-6 flex w-full items-center justify-center text-center">
<div className="flex-max-width h-full flex-col">
<div className="flex w-full flex-col gap-4">
@ -136,7 +130,7 @@ export default function ComponentsComponent({
</div>
) : (
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2">
{loadingScreen === false && data?.length > 0 ? (
{isLoading === false && data?.length > 0 ? (
data?.map((item, idx) => (
<CollectionCardComponent
onDelete={() => {
@ -185,7 +179,7 @@ export default function ComponentsComponent({
</div>
)}
</div>
{!loadingScreen && data.length > 0 && (
{!isLoading && data.length > 0 && (
<div className="relative py-6">
<PaginatorComponent
storeComponent={true}

View file

@ -62,10 +62,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
if (dbData) {
const { data, flows } = processFlows(dbData, false);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
set({ isLoading: false });
resolve();
}
})

View file

@ -4,6 +4,7 @@ import { APIDataType } from "../types/api";
import { TypesStoreType } from "../types/zustand/types";
import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import useFlowsManagerStore from "./flowsManagerStore";
export const useTypesStore = create<TypesStoreType>((set, get) => ({
types: {},
@ -11,6 +12,8 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
data: {},
getTypes: () => {
return new Promise<void>(async (resolve, reject) => {
const setLoading = useFlowsManagerStore.getState().setIsLoading;
setLoading(true);
getAll()
.then((response) => {
const data = response.data;
@ -20,6 +23,7 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
data: { ...old.data, ...data },
templates: templatesGenerator(data),
}));
setLoading(false)
resolve();
})
.catch((error) => {

View file

@ -26,10 +26,11 @@ export async function buildVertices({
for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i];
const idIndex = innerArray.indexOf(nodeId);
if (idIndex !== -1) {
// If the targetId is found in the inner array, cut the array before the id
vertices.push(innerArray.slice(0, idIndex + 1));
// If there's a nodeId, we want to run just that component and not the entire layer
// because a layer contains dependencies for the next layer
// and we are stopping at the layer that contains the nodeId
vertices.push([innerArray[idIndex]]);
break; // Stop searching after finding the first occurrence
}
// If the targetId is not found, include the entire inner array
@ -38,7 +39,7 @@ export async function buildVertices({
} else {
vertices = verticesOrder;
}
console.log("Vertices: ", vertices);
const buildResults: Array<boolean> = [];
for (let i = 0; i < vertices.length; i += 1) {
await Promise.all(

View file

@ -34,6 +34,7 @@ import {
FileSearch,
FileSearch2,
FileText,
Cable,
FileUp,
Fingerprint,
FolderPlus,
@ -70,6 +71,7 @@ import {
Paperclip,
Pencil,
Pin,
Play,
Plus,
Redo,
RefreshCcw,
@ -237,6 +239,7 @@ export const nodeNames: { [char: string]: string } = {
};
export const nodeIconsLucide: iconsType = {
Play,
Vectara: VectaraIcon,
ArrowUpToLine: ArrowUpToLine,
Chroma: ChromaIcon,
@ -394,7 +397,7 @@ export const nodeIconsLucide: iconsType = {
Combine,
TerminalIcon,
Repeat,
io: ArrowDownUp,
io: Cable,
ScreenShare,
Code,
};

View file

@ -139,12 +139,16 @@ export function groupByFamily(
})))
);
};
console.log(flow);
if (flow) {
// se existir o flow
for (const node of flow) {
// para cada node do flow
if (node!.data!.node!.flow) break; // não faz nada se o node for um group
const nodeData = node.data;
const foundNode = checkedNodes.get(nodeData.type);
const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado
checkedNodes.set(nodeData.type, {
hasBaseClassInTemplate:
foundNode?.hasBaseClassInTemplate ||
@ -153,7 +157,7 @@ export function groupByFamily(
foundNode?.hasBaseClassInBaseClasses ||
nodeData.node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
), //seta como anterior ou verifica se o node tem base class
displayName: nodeData.node?.display_name,
});
}

View file

@ -611,35 +611,36 @@ def test_async_task_processing(distributed_client, flow, created_api_key):
assert "Gabriel" in task_status_json["result"]["text"], task_status_json["result"]
# ! Deactivating this until updating the test
# Test function without loop
@pytest.mark.async_test
def test_async_task_processing_vector_store(client, added_vector_store, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
post_data = {"inputs": {"input": "How do I upload examples?"}}
# @pytest.mark.async_test
# def test_async_task_processing_vector_store(client, added_vector_store, created_api_key):
# headers = {"x-api-key": created_api_key.api_key}
# post_data = {"inputs": {"input": "How do I upload examples?"}}
# Run the /api/v1/process/{flow_id} endpoint with sync=False
response = client.post(
f"api/v1/process/{added_vector_store.get('id')}",
headers=headers,
json={**post_data, "sync": False},
)
assert response.status_code == 200, response.json()
assert "result" in response.json()
assert "FAILURE" not in response.json()["result"]
# # Run the /api/v1/process/{flow_id} endpoint with sync=False
# response = client.post(
# f"api/v1/process/{added_vector_store.get('id')}",
# headers=headers,
# json={**post_data, "sync": False},
# )
# assert response.status_code == 200, response.json()
# assert "result" in response.json()
# assert "FAILURE" not in response.json()["result"]
# Extract the task ID from the response
task = response.json().get("task")
task_id = task.get("id")
task_href = task.get("href")
assert task_id is not None
assert task_href is not None
assert task_href == f"api/v1/task/{task_id}"
# # Extract the task ID from the response
# task = response.json().get("task")
# task_id = task.get("id")
# task_href = task.get("href")
# assert task_id is not None
# assert task_href is not None
# assert task_href == f"api/v1/task/{task_id}"
# Polling the task status using the helper function
task_status_json = poll_task_status(client, headers, task_href)
assert task_status_json is not None, "Task did not complete in time"
# # Polling the task status using the helper function
# task_status_json = poll_task_status(client, headers, task_href)
# assert task_status_json is not None, "Task did not complete in time"
# Validate that the task completed successfully and the result is as expected
assert "result" in task_status_json, task_status_json
assert "output" in task_status_json["result"], task_status_json["result"]
assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"]
# # Validate that the task completed successfully and the result is as expected
# assert "result" in task_status_json, task_status_json
# assert "output" in task_status_json["result"], task_status_json["result"]
# assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"]