Merge branch 'dev' into python_custom_node_component

This commit is contained in:
gustavoschaedler 2023-07-12 16:34:21 +01:00
commit a6fe4091ac
78 changed files with 1415 additions and 1669 deletions

View file

@ -31,16 +31,17 @@
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [📦 Installation](#-installation) - [📦 Installation](#-installation)
- [Locally](#locally) - [Locally](#locally)
- [HuggingFace Spaces](#huggingface-spaces)
- [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli) - [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli)
- [Usage](#usage) - [Usage](#usage)
- [Environment Variables](#environment-variables) - [Environment Variables](#environment-variables)
- [Deployment](#deployment) - [Deployment](#deployment)
- [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform) - [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform)
- [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud) - [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud)
- [API Usage](#api-usage) - [API Usage](#api-usage)
- [🎨 Creating Flows](#-creating-flows) - [🎨 Creating Flows](#-creating-flows)
- [👋 Contributing](#-contributing) - [👋 Contributing](#-contributing)
- [📄 License](#-license) - [📄 License](#-license)
# 📦 Installation # 📦 Installation
@ -61,6 +62,8 @@ or
langflow # or langflow --help langflow # or langflow --help
``` ```
### HuggingFace Spaces
You can also check it out on [HuggingFace Spaces](https://huggingface.co/spaces/Logspace/Langflow) and run it in your browser! You can even clone it and have your own copy of Langflow to play with.
# 🖥️ Command Line Interface (CLI) # 🖥️ Command Line Interface (CLI)
@ -103,7 +106,7 @@ A sample `.env` file named `.env.example` is included with the project. Copy thi
# Deployment # Deployment
### Deploy Langflow on Google Cloud Platform ## Deploy Langflow on Google Cloud Platform
Follow our step-by-step guide to deploy Langflow on Google Cloud Platform (GCP) using Google Cloud Shell. The guide is available in the [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) document. Follow our step-by-step guide to deploy Langflow on Google Cloud Platform (GCP) using Google Cloud Shell. The guide is available in the [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) document.
@ -112,7 +115,7 @@ Alternatively, click the **"Open in Cloud Shell"** button below to launch Google
[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md) [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
### Deploy Langflow on [Jina AI Cloud](https://github.com/jina-ai/langchain-serve) ## Deploy Langflow on [Jina AI Cloud](https://github.com/jina-ai/langchain-serve)
Langflow integrates with langchain-serve to provide a one-command deployment to Jina AI Cloud. Langflow integrates with langchain-serve to provide a one-command deployment to Jina AI Cloud.
@ -219,8 +222,15 @@ print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))
> Read more about resource customization, cost, and management of Langflow apps on Jina AI Cloud in the **[langchain-serve](https://github.com/jina-ai/langchain-serve)** repository. > Read more about resource customization, cost, and management of Langflow apps on Jina AI Cloud in the **[langchain-serve](https://github.com/jina-ai/langchain-serve)** repository.
## Deploy on Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/Emy2sU?referralCode=MnPSdg)
## 🎨 Creating Flows ## Deploy on Render
<a href="https://render.com/deploy?repo=https://github.com/logspace-ai/langflow/tree/main">
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" />
</a>
# 🎨 Creating Flows
Creating flows with Langflow is easy. Simply drag sidebar components onto the canvas and connect them together to create your pipeline. Langflow provides a range of [LangChain components](https://langchain.readthedocs.io/en/latest/reference.html) to choose from, including LLMs, prompt serializers, agents, and chains. Creating flows with Langflow is easy. Simply drag sidebar components onto the canvas and connect them together to create your pipeline. Langflow provides a range of [LangChain components](https://langchain.readthedocs.io/en/latest/reference.html) to choose from, including LLMs, prompt serializers, agents, and chains.
@ -239,7 +249,7 @@ flow("Hey, have you heard of Langflow?")
``` ```
## 👋 Contributing # 👋 Contributing
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible. We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible.
@ -252,6 +262,6 @@ Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask question
[![Star History Chart](https://api.star-history.com/svg?repos=logspace-ai/langflow&type=Timeline)](https://star-history.com/#logspace-ai/langflow&Date) [![Star History Chart](https://api.star-history.com/svg?repos=logspace-ai/langflow&type=Timeline)](https://star-history.com/#logspace-ai/langflow&Date)
## 📄 License # 📄 License
Langflow is released under the MIT License. See the LICENSE file for details. Langflow is released under the MIT License. See the LICENSE file for details.

38
poetry.lock generated
View file

@ -739,13 +739,13 @@ sqlalchemy = ["sqlalchemy (>1.3.21,<2.0)"]
[[package]] [[package]]
name = "cohere" name = "cohere"
version = "4.12.1" version = "4.13.0"
description = "" description = ""
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
{file = "cohere-4.12.1-py3-none-any.whl", hash = "sha256:80d17ae928873cdf63883a338618e477de5c71b3d510d7891af7dfdabc25186e"}, {file = "cohere-4.13.0-py3-none-any.whl", hash = "sha256:ac8faf352c9e8794dfd05002ce52b7d4f6da8f47a20172c8640ed58e8dfd3f10"},
{file = "cohere-4.12.1.tar.gz", hash = "sha256:2e93a094757576d6c8d42e76363aa7841eb4166c5b0de8e5ed7272783982d2a4"}, {file = "cohere-4.13.0.tar.gz", hash = "sha256:7c8e65aa4fc50fe6a9e8fe19d64e18b77d4250ec70845d480c68644d5a903253"},
] ]
[package.dependencies] [package.dependencies]
@ -1332,22 +1332,22 @@ importlib-resources = {version = ">=5.0", markers = "python_version < \"3.10\""}
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.99.1" version = "0.100.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "fastapi-0.99.1-py3-none-any.whl", hash = "sha256:976df7bab51ac7beda9f68c4513b8c4490b5c1135c72aafd0a5ee4023ec5282e"}, {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"},
{file = "fastapi-0.99.1.tar.gz", hash = "sha256:ac78f717cd80d657bd183f94d33b9bda84aa376a46a9dab513586b8eef1dc6fc"}, {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"},
] ]
[package.dependencies] [package.dependencies]
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0"
starlette = ">=0.27.0,<0.28.0" starlette = ">=0.27.0,<0.28.0"
typing-extensions = ">=4.5.0" typing-extensions = ">=4.5.0"
[package.extras] [package.extras]
all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
[[package]] [[package]]
name = "filelock" name = "filelock"
@ -2826,20 +2826,20 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)",
[[package]] [[package]]
name = "langchain" name = "langchain"
version = "0.0.219" version = "0.0.229"
description = "Building applications with LLMs through composability" description = "Building applications with LLMs through composability"
optional = false optional = false
python-versions = ">=3.8.1,<4.0" python-versions = ">=3.8.1,<4.0"
files = [ files = [
{file = "langchain-0.0.219-py3-none-any.whl", hash = "sha256:1f08a00e622f1c75087d6013f34e82be3f8dd1859266eb583a0fd7bc045090cf"}, {file = "langchain-0.0.229-py3-none-any.whl", hash = "sha256:a7ca79e4ab892756ede95d212bd42243303f91b172535cefd02b0b8965e4e7b7"},
{file = "langchain-0.0.219.tar.gz", hash = "sha256:842f8212939e5ac4005906d2215574ffb3e34d2fe28f5bc0f46eb3b28fb29c5d"}, {file = "langchain-0.0.229.tar.gz", hash = "sha256:ab1beac7f3fc1f06ab1a0b545ef0d47a3d5efef3b2b4c646aafaefc2eb3151d3"},
] ]
[package.dependencies] [package.dependencies]
aiohttp = ">=3.8.3,<4.0.0" aiohttp = ">=3.8.3,<4.0.0"
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
dataclasses-json = ">=0.5.7,<0.6.0" dataclasses-json = ">=0.5.7,<0.6.0"
langchainplus-sdk = ">=0.0.17" langchainplus-sdk = ">=0.0.20,<0.0.21"
numexpr = ">=2.8.4,<3.0.0" numexpr = ">=2.8.4,<3.0.0"
numpy = ">=1,<2" numpy = ">=1,<2"
openapi-schema-pydantic = ">=1.2,<2.0" openapi-schema-pydantic = ">=1.2,<2.0"
@ -2850,27 +2850,27 @@ SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0" tenacity = ">=8.1.0,<9.0.0"
[package.extras] [package.extras]
all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.6,<0.3.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.3,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (==9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.6.2,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.1.dev3,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "momento (>=1.5.0,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "octoai-sdk (>=0.1.1,<0.2.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.1.2,<2.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "spacy (>=3,<4)", "steamship (>=2.16.9,<3.0.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3,<0.4)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.3,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.6.2,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.1.dev3,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=0.11.0,<0.12.0)", "momento (>=1.5.0,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "octoai-sdk (>=0.1.1,<0.2.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.1.2,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "spacy (>=3,<4)", "steamship (>=2.16.9,<3.0.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0a20230509004)", "openai (>=0,<1)"] azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0a20230509004)", "openai (>=0,<1)"]
clarifai = ["clarifai (==9.1.0)"] clarifai = ["clarifai (>=9.1.0)"]
cohere = ["cohere (>=3,<4)"] cohere = ["cohere (>=3,<4)"]
docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
embeddings = ["sentence-transformers (>=2,<3)"] embeddings = ["sentence-transformers (>=2,<3)"]
extended-testing = ["atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "chardet (>=5.1.0,<6.0.0)", "esprima (>=4.0.1,<5.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "openai (>=0,<1)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "streamlit (>=1.18.0,<2.0.0)", "telethon (>=1.28.5,<2.0.0)", "tqdm (>=4.48.0)", "zep-python (>=0.31)"] extended-testing = ["atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.0.7,<0.0.8)", "chardet (>=5.1.0,<6.0.0)", "esprima (>=4.0.1,<5.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "openai (>=0,<1)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "streamlit (>=1.18.0,<2.0.0)", "telethon (>=1.28.5,<2.0.0)", "tqdm (>=4.48.0)", "zep-python (>=0.32)"]
javascript = ["esprima (>=4.0.1,<5.0.0)"] javascript = ["esprima (>=4.0.1,<5.0.0)"]
llms = ["anthropic (>=0.2.6,<0.3.0)", "clarifai (==9.1.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openllm (>=0.1.6)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] llms = ["anthropic (>=0.3,<0.4)", "clarifai (>=9.1.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openllm (>=0.1.19)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.4.0)"] openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.4.0)"]
qdrant = ["qdrant-client (>=1.1.2,<2.0.0)"] qdrant = ["qdrant-client (>=1.1.2,<2.0.0)"]
text-helpers = ["chardet (>=5.1.0,<6.0.0)"] text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
[[package]] [[package]]
name = "langchain-serve" name = "langchain-serve"
version = "0.0.54" version = "0.0.56"
description = "Langchain Serve - serve your langchain apps on Jina AI Cloud." description = "Langchain Serve - serve your langchain apps on Jina AI Cloud."
optional = true optional = true
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "langchain-serve-0.0.54.tar.gz", hash = "sha256:5cbc980886c81f3bac7ed3337adeb0b94fc9f3645e4501dd7f0702f90766bbaa"}, {file = "langchain-serve-0.0.56.tar.gz", hash = "sha256:47c1e0290aec07c7e6366bb1b6b866d7d3f5446fb296160ca1bf2ef522c33fac"},
] ]
[package.dependencies] [package.dependencies]
@ -7552,4 +7552,4 @@ deploy = ["langchain-serve"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "e25e43fde8f96f57beab702ac4c51cb3e569b81f85c540a7b4b5fb7b6388d04e" content-hash = "3ef18bc73e595f6aa8c3ee4b4c9666f3328c601933aef1bf225b865f39504e3c"

View file

@ -23,14 +23,14 @@ langflow = "langflow.__main__:main"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<3.11" python = ">=3.9,<3.11"
fastapi = "^0.99.0" fastapi = "^0.100.0"
uvicorn = "^0.22.0" uvicorn = "^0.22.0"
beautifulsoup4 = "^4.12.2" beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1" google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0" google-api-python-client = "^2.79.0"
typer = "^0.9.0" typer = "^0.9.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
langchain = "^0.0.219" langchain = "^0.0.229"
openai = "^0.27.8" openai = "^0.27.8"
pandas = "^2.0.0" pandas = "^2.0.0"
chromadb = "^0.3.21" chromadb = "^0.3.21"
@ -78,7 +78,7 @@ black = "^23.1.0"
ipykernel = "^6.21.2" ipykernel = "^6.21.2"
mypy = "^1.1.1" mypy = "^1.1.1"
ruff = "^0.0.254" ruff = "^0.0.254"
httpx = "^0.23.3" httpx = "*"
pytest = "^7.2.2" pytest = "^7.2.2"
types-requests = "^2.28.11" types-requests = "^2.28.11"
requests = "^2.28.0" requests = "^2.28.0"

11
render.yaml Normal file
View file

@ -0,0 +1,11 @@
services:
# A Docker web service
- type: web
name: langflow
runtime: docker
plan: free
dockerfilePath: ./Dockerfile
repo: https://github.com/logspace-ai/langflow
branch: main
healthCheckPath: /health
autoDeploy: false

View file

@ -60,12 +60,26 @@ INVALID_CHARACTERS = {
"}", "}",
} }
INVALID_NAMES = {
"input_variables",
"output_parser",
"partial_variables",
"template",
"template_format",
"validate_template",
}
def validate_prompt(template: str): def validate_prompt(template: str):
input_variables = extract_input_variables_from_prompt(template) input_variables = extract_input_variables_from_prompt(template)
# Check if there are invalid characters in the input_variables # Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables) input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables):
raise ValueError(
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
)
try: try:
PromptTemplate(template=template, input_variables=input_variables) PromptTemplate(template=template, input_variables=input_variables)
except Exception as exc: except Exception as exc:

View file

@ -10,6 +10,7 @@ from fastapi import WebSocket
from langchain.schema import AgentAction, LLMResult, AgentFinish from langchain.schema import AgentAction, LLMResult, AgentFinish
from langflow.utils.logger import logger
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py # https://github.com/hwchase17/chat-langchain/blob/master/callback.py
@ -62,12 +63,36 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
async def on_tool_end(self, output: str, **kwargs: Any) -> Any: async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
"""Run when tool ends running.""" """Run when tool ends running."""
observation_prefix = kwargs.get("observation_prefix", "Tool output: ")
split_output = output.split()
first_word = split_output[0]
rest_of_output = split_output[1:]
# Create a formatted message.
intermediate_steps = f"{observation_prefix}{first_word}"
# Create a ChatResponse instance.
resp = ChatResponse( resp = ChatResponse(
message="", message="",
type="stream", type="stream",
intermediate_steps=f"Tool output: {output}", intermediate_steps=intermediate_steps,
) )
await self.websocket.send_json(resp.dict()) rest_of_resps = [
ChatResponse(
message="",
type="stream",
intermediate_steps=f"{word}",
)
for word in rest_of_output
]
resps = [resp] + rest_of_resps
# Try to send the response, handle potential errors.
try:
# This is to emulate the stream of tokens
for resp in resps:
await self.websocket.send_json(resp.dict())
except Exception as e:
logger.error(e)
async def on_tool_error( async def on_tool_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any

View file

@ -115,13 +115,6 @@ async def stream_build(flow_id: str):
number_of_nodes = len(graph.nodes) number_of_nodes = len(graph.nodes)
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
# To deal with the ZeroShotAgent case
# we need to build the root node first
# and then the rest of the graph
# This is a big problem because certain nodes require
# params that are not connected to it.
# We should consider connecting the tools to the ZeroShotPrompt
graph.build()
for i, vertex in enumerate(graph.generator_build(), 1): for i, vertex in enumerate(graph.generator_build(), 1):
try: try:
@ -133,7 +126,7 @@ async def stream_build(flow_id: str):
params = vertex._built_object_repr() params = vertex._built_object_repr()
valid = True valid = True
logger.debug( logger.debug(
f"Building node {params[:50]}{'...' if len(params) > 50 else ''}" f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}"
) )
if vertex.artifacts: if vertex.artifacts:
# The artifacts will be prompt variables # The artifacts will be prompt variables

View file

@ -104,8 +104,14 @@ class ChatManager:
async def close_connection(self, client_id: str, code: int, reason: str): async def close_connection(self, client_id: str, code: int, reason: str):
if websocket := self.active_connections[client_id]: if websocket := self.active_connections[client_id]:
await websocket.close(code=code, reason=reason) try:
self.disconnect(client_id) await websocket.close(code=code, reason=reason)
self.disconnect(client_id)
except RuntimeError as exc:
# This is to catch the following error:
# Unexpected ASGI message 'websocket.close', after sending 'websocket.close'
if "after sending" in str(exc):
logger.error(exc)
async def process_message( async def process_message(
self, client_id: str, payload: Dict, langchain_object: Any self, client_id: str, payload: Dict, langchain_object: Any

View file

@ -277,9 +277,14 @@ vectorstores:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase" documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase"
MongoDBAtlasVectorSearch: MongoDBAtlasVectorSearch:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas" documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas"
# Requires docarray >=0.32.0 but langchain-serve requires jina 3.15.2 which doesn't support docarray >=0.32.0
# DocArrayInMemorySearch:
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/docarray_in_memory"
wrappers: wrappers:
RequestsWrapper: RequestsWrapper:
documentation: "" documentation: ""
SQLDatabase:
documentation: ""
output_parsers: output_parsers:
StructuredOutputParser: StructuredOutputParser:
documentation: "https://python.langchain.com/docs/modules/model_io/output_parsers/structured" documentation: "https://python.langchain.com/docs/modules/model_io/output_parsers/structured"

View file

@ -214,3 +214,10 @@ class Graph:
if node_type in node_types: if node_type in node_types:
children.append(node) children.append(node)
return children return children
def __repr__(self):
node_ids = [node.id for node in self.nodes]
edges_repr = "\n".join(
[f"{edge.source.id} --> {edge.target.id}" for edge in self.edges]
)
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"

View file

@ -123,89 +123,119 @@ class Vertex:
self.params = params self.params = params
def _build(self): def _build(self):
# The params dict is used to build the module """
# it contains values and keys that point to nodes which Initiate the build process.
# have their own params dict """
# When build is called, we iterate through the params dict
# and if the value is a node, we call build on that node
# and use the output of that build as the value for the param
# if the value is not a node, then we use the value as the param
# and continue
# Another aspect is that the node_type is the class that we need to import
# and instantiate with these built params
logger.debug(f"Building {self.vertex_type}") logger.debug(f"Building {self.vertex_type}")
# Build each node in the params dict self._build_each_node_in_params_dict()
self._get_and_instantiate_class()
self._validate_built_object()
self._built = True
def _build_each_node_in_params_dict(self):
"""
Iterates over each node in the params dictionary and builds it.
"""
for key, value in self.params.copy().items(): for key, value in self.params.copy().items():
# Check if Node or list of Nodes and not self if self._is_node(value):
# to avoid recursion
if isinstance(value, Vertex):
if value == self: if value == self:
del self.params[key] del self.params[key]
continue continue
result = value.build() self._build_node_and_update_params(key, value)
# If the key is "func", then we need to use the run method elif isinstance(value, list) and self._is_list_of_nodes(value):
if key == "func": self._build_list_of_nodes_and_update_params(key, value)
if not isinstance(result, types.FunctionType):
# func can be
# PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n')
# so we need to check if there is an attribute called run
if hasattr(result, "run"):
result = result.run # type: ignore
elif hasattr(result, "get_function"):
result = result.get_function() # type: ignore
elif inspect.iscoroutinefunction(result):
self.params["coroutine"] = result
else:
# turn result which is a function into a coroutine
# so that it can be awaited
self.params["coroutine"] = sync_to_async(result)
if isinstance(result, list):
# If the result is a list, then we need to extend the list
# with the result but first check if the key exists
# if it doesn't, then we need to create a new list
if isinstance(self.params[key], list):
self.params[key].extend(result)
self.params[key] = result def _is_node(self, value):
elif isinstance(value, list) and all( """
isinstance(node, Vertex) for node in value Checks if the provided value is an instance of Vertex.
): """
self.params[key] = [] return isinstance(value, Vertex)
for node in value:
built = node.build()
if isinstance(built, list):
self.params[key].extend(built)
else:
self.params[key].append(built)
# Get the class from LANGCHAIN_TYPES_DICT def _is_list_of_nodes(self, value):
# and instantiate it with the params """
# and return the instance Checks if the provided value is a list of Vertex instances.
"""
return all(self._is_node(node) for node in value)
def _build_node_and_update_params(self, key, node):
"""
Builds a given node and updates the params dictionary accordingly.
"""
result = node.build()
self._handle_func(key, result)
if isinstance(result, list):
self._extend_params_list_with_result(key, result)
self.params[key] = result
def _build_list_of_nodes_and_update_params(self, key, nodes):
"""
Iterates over a list of nodes, builds each and updates the params dictionary.
"""
self.params[key] = []
for node in nodes:
built = node.build()
if isinstance(built, list):
self.params[key].extend(built)
else:
self.params[key].append(built)
def _handle_func(self, key, result):
"""
Handles 'func' key by checking if the result is a function and setting it as coroutine.
"""
if key == "func":
if not isinstance(result, types.FunctionType):
if hasattr(result, "run"):
result = result.run # type: ignore
elif hasattr(result, "get_function"):
result = result.get_function() # type: ignore
elif inspect.iscoroutinefunction(result):
self.params["coroutine"] = result
else:
self.params["coroutine"] = sync_to_async(result)
def _extend_params_list_with_result(self, key, result):
"""
Extends a list in the params dictionary with the given result if it exists.
"""
if isinstance(self.params[key], list):
self.params[key].extend(result)
def _get_and_instantiate_class(self):
"""
Gets the class from a dictionary and instantiates it with the params.
"""
if self.base_type is None:
raise ValueError(f"Base type for node {self.vertex_type} not found")
try: try:
if self.base_type is None:
raise ValueError(f"Base type for node {self.vertex_type} not found")
result = loading.instantiate_class( result = loading.instantiate_class(
node_type=self.vertex_type, node_type=self.vertex_type,
base_type=self.base_type, base_type=self.base_type,
params=self.params, params=self.params,
) )
# Result could be the _built_object or self._update_built_object_and_artifacts(result)
# (_built_object, dict) tuple
if isinstance(result, tuple):
self._built_object, self.artifacts = result
else:
self._built_object = result
except Exception as exc: except Exception as exc:
raise ValueError( raise ValueError(
f"Error building node {self.vertex_type}: {str(exc)}" f"Error building node {self.vertex_type}: {str(exc)}"
) from exc ) from exc
def _update_built_object_and_artifacts(self, result):
"""
Updates the built object and its artifacts.
"""
if isinstance(result, tuple):
self._built_object, self.artifacts = result
else:
self._built_object = result
def _validate_built_object(self):
"""
Checks if the built object is None and raises a ValueError if so.
"""
if self._built_object is None: if self._built_object is None:
raise ValueError(f"Node type {self.vertex_type} not found") raise ValueError(f"Node type {self.vertex_type} not found")
self._built = True
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
if not self._built or force: if not self._built or force:
self._build() self._build()

View file

@ -1,3 +1,4 @@
import ast
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
@ -79,7 +80,7 @@ class WrapperVertex(Vertex):
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
if not self._built or force: if not self._built or force:
if "headers" in self.params: if "headers" in self.params:
self.params["headers"] = eval(self.params["headers"]) self.params["headers"] = ast.literal_eval(self.params["headers"])
self._build() self._build()
return self._built_object return self._built_object
@ -93,7 +94,11 @@ class DocumentLoaderVertex(Vertex):
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {avg_length}
Documents: {self._built_object[:3]}...""" Documents: {self._built_object[:3]}..."""
return f"{self.vertex_type}()" return f"{self.vertex_type}()"
@ -125,8 +130,13 @@ class TextSplitterVertex(Vertex):
def _built_object_repr(self): def _built_object_repr(self):
# This built_object is a list of documents. Maybe we should # This built_object is a list of documents. Maybe we should
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {avg_length}
\nDocuments: {self._built_object[:3]}...""" \nDocuments: {self._built_object[:3]}..."""
return f"{self.vertex_type}()" return f"{self.vertex_type}()"
@ -202,13 +212,28 @@ class PromptVertex(Vertex):
return self._built_object return self._built_object
def _built_object_repr(self): def _built_object_repr(self):
if self.artifacts and hasattr(self._built_object, "format"): if (
# We'll build the prompt with the artifacts not self.artifacts
# to show the user what the prompt looks like or self._built_object is None
# with the variables filled in or not hasattr(self._built_object, "format")
return self._built_object.format(**self.artifacts) ):
else:
return super()._built_object_repr() return super()._built_object_repr()
# We'll build the prompt with the artifacts
# to show the user what the prompt looks like
# with the variables filled in
artifacts = self.artifacts.copy()
# Remove the handle_keys from the artifacts
# so the prompt format doesn't break
artifacts.pop("handle_keys", None)
try:
template = self._built_object.format(**artifacts)
return (
template
if isinstance(template, str)
else f"{self.vertex_type}({template})"
)
except KeyError:
return str(self._built_object)
class OutputParserVertex(Vertex): class OutputParserVertex(Vertex):

View file

@ -157,7 +157,7 @@ class VectorStoreAgent(CustomAgentExecutor):
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
) )
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
) )
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
@ -232,6 +232,7 @@ class SQLAgent(CustomAgentExecutor):
verbose=True, verbose=True,
max_iterations=15, max_iterations=15,
early_stopping_method="force", early_stopping_method="force",
handle_parsing_errors=True,
) )
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
@ -276,7 +277,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
) )
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
) )
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
@ -308,6 +309,7 @@ class InitializeAgent(CustomAgentExecutor):
agent=agent, # type: ignore agent=agent, # type: ignore
memory=memory, memory=memory,
return_intermediate_steps=True, return_intermediate_steps=True,
handle_parsing_errors=True,
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View file

@ -23,6 +23,7 @@ class ChainCreator(LangChainTypeCreator):
from_method_nodes = { from_method_nodes = {
"ConversationalRetrievalChain": "from_llm", "ConversationalRetrievalChain": "from_llm",
"LLMCheckerChain": "from_llm", "LLMCheckerChain": "from_llm",
"SQLDatabaseChain": "from_llm",
} }
@property @property

View file

@ -11,6 +11,7 @@ from langchain.chat_models.base import BaseChatModel
from langchain.tools import BaseTool from langchain.tools import BaseTool
from langflow.interface.custom.custom import CustomComponent from langflow.interface.custom.custom import CustomComponent
from langflow.utils import validate from langflow.utils import validate
from langflow.interface.wrappers.base import wrapper_creator
def import_module(module_path: str) -> Any: def import_module(module_path: str) -> Any:
@ -103,7 +104,11 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]:
def import_wrapper(wrapper: str) -> Any: def import_wrapper(wrapper: str) -> Any:
"""Import wrapper from wrapper name""" """Import wrapper from wrapper name"""
return import_module(f"from langchain.requests import {wrapper}") if (
isinstance(wrapper_creator.type_dict, dict)
and wrapper in wrapper_creator.type_dict
):
return wrapper_creator.type_dict.get(wrapper)
def import_toolkit(toolkit: str) -> Any: def import_toolkit(toolkit: str) -> Any:

View file

@ -1,3 +1,4 @@
import contextlib
import json import json
from typing import Any, Callable, Dict, List, Sequence, Type from typing import Any, Callable, Dict, List, Sequence, Type
@ -24,6 +25,7 @@ from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.output_parsers.base import output_parser_creator from langflow.interface.output_parsers.base import output_parser_creator
from langflow.interface.retrievers.base import retriever_creator from langflow.interface.retrievers.base import retriever_creator
from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.utils import load_file_into_dict from langflow.interface.utils import load_file_into_dict
from langflow.utils import validate from langflow.utils import validate
from langchain.chains.base import Chain from langchain.chains.base import Chain
@ -70,7 +72,11 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
elif base_type == "prompts": elif base_type == "prompts":
return instantiate_prompt(node_type, class_object, params) return instantiate_prompt(node_type, class_object, params)
elif base_type == "tools": elif base_type == "tools":
return instantiate_tool(node_type, class_object, params) tool = instantiate_tool(node_type, class_object, params)
if hasattr(tool, "name") and isinstance(tool, BaseTool):
# tool name shouldn't contain spaces
tool.name = tool.name.replace(" ", "_")
return tool
elif base_type == "toolkits": elif base_type == "toolkits":
return instantiate_toolkit(node_type, class_object, params) return instantiate_toolkit(node_type, class_object, params)
elif base_type == "embeddings": elif base_type == "embeddings":
@ -95,6 +101,8 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
return instantiate_memory(node_type, class_object, params) return instantiate_memory(node_type, class_object, params)
elif base_type == "custom_components": elif base_type == "custom_components":
return instantiate_custom_component(node_type, class_object, params) return instantiate_custom_component(node_type, class_object, params)
elif base_type == "wrappers":
return instantiate_wrapper(node_type, class_object, params)
else: else:
return class_object(**params) return class_object(**params)
@ -104,6 +112,15 @@ def instantiate_custom_component(node_type, class_object, params):
return class_object().build(**params) return class_object().build(**params)
def instantiate_wrapper(node_type, class_object, params):
if node_type in wrapper_creator.from_method_nodes:
method = wrapper_creator.from_method_nodes[node_type]
if class_method := getattr(class_object, method, None):
return class_method(**params)
raise ValueError(f"Method {method} not found in {class_object}")
return class_object(**params)
def instantiate_output_parser(node_type, class_object, params): def instantiate_output_parser(node_type, class_object, params):
if node_type in output_parser_creator.from_method_nodes: if node_type in output_parser_creator.from_method_nodes:
method = output_parser_creator.from_method_nodes[node_type] method = output_parser_creator.from_method_nodes[node_type]
@ -119,12 +136,21 @@ def instantiate_llm(node_type, class_object, params: Dict):
# False if condition is True # False if condition is True
if node_type == "VertexAI": if node_type == "VertexAI":
return initialize_vertexai(class_object=class_object, params=params) return initialize_vertexai(class_object=class_object, params=params)
# max_tokens sometimes is a string and should be an int
if "max_tokens" in params:
if isinstance(params["max_tokens"], str) and params["max_tokens"].isdigit():
params["max_tokens"] = int(params["max_tokens"])
elif not isinstance(params.get("max_tokens"), int):
params.pop("max_tokens", None)
return class_object(**params) return class_object(**params)
def instantiate_memory(node_type, class_object, params): def instantiate_memory(node_type, class_object, params):
# process input_key and output_key to remove them if # process input_key and output_key to remove them if
# they are empty strings # they are empty strings
if node_type == "ConversationEntityMemory":
params.pop("memory_key", None)
for key in ["input_key", "output_key"]: for key in ["input_key", "output_key"]:
if key in params and (params[key] == "" or not params[key]): if key in params and (params[key] == "" or not params[key]):
params.pop(key) params.pop(key)
@ -178,8 +204,7 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
agent = class_method(**params) agent = class_method(**params)
tools = params.get("tools", []) tools = params.get("tools", [])
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, agent=agent, tools=tools, handle_parsing_errors=True
tools=tools,
) )
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)
@ -189,7 +214,7 @@ def instantiate_prompt(node_type, class_object, params: Dict):
if "tools" not in params: if "tools" not in params:
params["tools"] = [] params["tools"] = []
return ZeroShotAgent.create_prompt(**params) return ZeroShotAgent.create_prompt(**params)
if "MessagePromptTemplate" in node_type: elif "MessagePromptTemplate" in node_type:
# Then we only need the template # Then we only need the template
from_template_params = { from_template_params = {
"template": params.pop("prompt", params.pop("template", "")) "template": params.pop("prompt", params.pop("template", ""))
@ -197,12 +222,12 @@ def instantiate_prompt(node_type, class_object, params: Dict):
if not from_template_params.get("template"): if not from_template_params.get("template"):
raise ValueError("Prompt template is required") raise ValueError("Prompt template is required")
return class_object.from_template(**from_template_params) prompt = class_object.from_template(**from_template_params)
if node_type == "ChatPromptTemplate": elif node_type == "ChatPromptTemplate":
return class_object.from_messages(**params) prompt = class_object.from_messages(**params)
else:
prompt = class_object(**params) prompt = class_object(**params)
format_kwargs: Dict[str, Any] = {} format_kwargs: Dict[str, Any] = {}
for input_variable in prompt.input_variables: for input_variable in prompt.input_variables:
@ -214,18 +239,23 @@ def instantiate_prompt(node_type, class_object, params: Dict):
variable, "get_format_instructions" variable, "get_format_instructions"
): ):
format_kwargs[input_variable] = variable.get_format_instructions() format_kwargs[input_variable] = variable.get_format_instructions()
# check if is a list of Document
elif isinstance(variable, List) and all( elif isinstance(variable, List) and all(
isinstance(item, Document) for item in variable isinstance(item, Document) for item in variable
): ):
# Format document to contain page_content and metadata # Format document to contain page_content and metadata
# as one string separated by a newline # as one string separated by a newline
format_kwargs[input_variable] = "\n".join( if len(variable) > 1:
[ content = "\n".join(
f"Document:{item.page_content}\nMetadata:{item.metadata}" [item.page_content for item in variable if item.page_content]
for item in variable )
] else:
) content = variable[0].page_content
# content could be a json list of strings
with contextlib.suppress(json.JSONDecodeError):
content = json.loads(content)
if isinstance(content, list):
content = ",".join([str(item) for item in content])
format_kwargs[input_variable] = content
# handle_keys will be a list but it does not exist yet # handle_keys will be a list but it does not exist yet
# so we need to create it # so we need to create it
@ -247,12 +277,14 @@ def instantiate_prompt(node_type, class_object, params: Dict):
def instantiate_tool(node_type, class_object: Type[BaseTool], params: Dict): def instantiate_tool(node_type, class_object: Type[BaseTool], params: Dict):
if node_type == "JsonSpec": if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path")) if file_dict := load_file_into_dict(params.pop("path")):
params["dict_"] = file_dict
else:
raise ValueError("Invalid file")
return class_object(**params) return class_object(**params)
elif node_type == "PythonFunctionTool": elif node_type == "PythonFunctionTool":
params["func"] = get_function(params.get("code")) params["func"] = get_function(params.get("code"))
return class_object(**params) return class_object(**params)
# For backward compatibility
elif node_type == "PythonFunction": elif node_type == "PythonFunction":
function_string = params["code"] function_string = params["code"]
if isinstance(function_string, str): if isinstance(function_string, str):
@ -354,6 +386,12 @@ def instantiate_textsplitter(
"separator_type" in params and params["separator_type"] == "Text" "separator_type" in params and params["separator_type"] == "Text"
) or "separator_type" not in params: ) or "separator_type" not in params:
params.pop("separator_type", None) params.pop("separator_type", None)
# separators might come in as an escaped string like \\n
# so we need to convert it to a string
if "separators" in params:
params["separators"] = (
params["separators"].encode().decode("unicode-escape")
)
text_splitter = class_object(**params) text_splitter = class_object(**params)
else: else:
from langchain.text_splitter import Language from langchain.text_splitter import Language
@ -406,6 +444,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, agent=agent,
tools=allowed_tools, tools=allowed_tools,
handle_parsing_errors=True,
# memory=memory, # memory=memory,
**kwargs, **kwargs,
) )

View file

@ -16,17 +16,15 @@ def load_file_into_dict(file_path: str) -> dict:
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}") raise FileNotFoundError(f"File not found: {file_path}")
file_extension = os.path.splitext(file_path)[1].lower() # Files names are UUID, so we can't find the extension
with open(file_path, "r") as file:
if file_extension == ".json": try:
with open(file_path, "r") as json_file: data = json.load(file)
data = json.load(json_file) except json.JSONDecodeError:
elif file_extension in [".yaml", ".yml"]: file.seek(0)
with open(file_path, "r") as yaml_file: data = yaml.safe_load(file)
data = yaml.safe_load(yaml_file) except ValueError as exc:
else: raise ValueError("Invalid file type. Expected .json or .yaml.") from exc
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
return data return data

View file

@ -1,25 +1,36 @@
from typing import Dict, List, Optional from typing import Dict, List, Optional
from langchain import requests from langchain import requests, sql_database
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class, build_template_from_method
class WrapperCreator(LangChainTypeCreator): class WrapperCreator(LangChainTypeCreator):
type_name: str = "wrappers" type_name: str = "wrappers"
from_method_nodes = {"SQLDatabase": "from_uri"}
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
self.type_dict = { self.type_dict = {
wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper] wrapper.__name__: wrapper
for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
} }
return self.type_dict return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:
try: try:
if name in self.from_method_nodes:
return build_template_from_method(
name,
type_to_cls_dict=self.type_to_loader_dict,
add_function=True,
method_name=self.from_method_nodes[name],
)
return build_template_from_class(name, self.type_to_loader_dict) return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Wrapper not found") from exc raise ValueError("Wrapper not found") from exc

View file

@ -19,8 +19,11 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
# Deactivating until we have a frontend solution # Deactivating until we have a frontend solution
# to display intermediate steps # to display intermediate steps
langchain_object.return_intermediate_steps = True langchain_object.return_intermediate_steps = True
try:
fix_memory_inputs(langchain_object)
except Exception as exc:
logger.error(exc)
fix_memory_inputs(langchain_object)
try: try:
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)] async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
output = await langchain_object.acall(inputs, callbacks=async_callbacks) output = await langchain_object.acall(inputs, callbacks=async_callbacks)
@ -39,7 +42,11 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
if isinstance(output, dict) if isinstance(output, dict)
else output else output
) )
thought = format_actions(intermediate_steps) if intermediate_steps else "" try:
thought = format_actions(intermediate_steps) if intermediate_steps else ""
except Exception as exc:
logger.exception(exc)
thought = ""
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
raise ValueError(f"Error: {str(exc)}") from exc raise ValueError(f"Error: {str(exc)}") from exc

View file

@ -22,7 +22,10 @@ def fix_memory_inputs(langchain_object):
if not hasattr(langchain_object, "memory") or langchain_object.memory is None: if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
return return
try: try:
if langchain_object.memory.memory_key in langchain_object.input_variables: if (
hasattr(langchain_object.memory, "memory_key")
and langchain_object.memory.memory_key in langchain_object.input_variables
):
return return
except AttributeError: except AttributeError:
input_variables = ( input_variables = (
@ -92,6 +95,10 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
logger.debug("Loaded LangChain object") logger.debug("Loaded LangChain object")
if inputs is None: if inputs is None:
inputs = {} inputs = {}
# Add artifacts to inputs
# artifacts can be documents loaded when building
# the flow
for ( for (
key, key,
value, value,
@ -113,8 +120,7 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
result = get_result_and_thought(langchain_object, inputs) result = get_result_and_thought(langchain_object, inputs)
logger.debug("Generated result and thought") logger.debug("Generated result and thought")
elif isinstance(langchain_object, VectorStore): elif isinstance(langchain_object, VectorStore):
class_name = langchain_object.__class__.__name__ result = langchain_object.search(**inputs)
result = {"message": f"Processed {class_name} successfully"}
else: else:
raise ValueError( raise ValueError(
f"Unknown langchain_object type: {type(langchain_object).__name__}" f"Unknown langchain_object type: {type(langchain_object).__name__}"
@ -123,23 +129,23 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
def load_flow_from_json( def load_flow_from_json(
input: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
): ):
""" """
Load flow from a JSON file or a JSON object. Load flow from a JSON file or a JSON object.
:param input: JSON file path or JSON object :param flow: JSON file path or JSON object
:param tweaks: Optional tweaks to be processed :param tweaks: Optional tweaks to be processed
:param build: If True, build the graph, otherwise return the graph object :param build: If True, build the graph, otherwise return the graph object
:return: Langchain object or Graph object depending on the build parameter :return: Langchain object or Graph object depending on the build parameter
""" """
# If input is a file path, load JSON from the file # If input is a file path, load JSON from the file
if isinstance(input, (str, Path)): if isinstance(flow, (str, Path)):
with open(input, "r", encoding="utf-8") as f: with open(flow, "r", encoding="utf-8") as f:
flow_graph = json.load(f) flow_graph = json.load(f)
# If input is a dictionary, assume it's a JSON object # If input is a dictionary, assume it's a JSON object
elif isinstance(input, dict): elif isinstance(flow, dict):
flow_graph = input flow_graph = flow
else: else:
raise TypeError( raise TypeError(
"Input must be either a file path (str) or a JSON object (dict)" "Input must be either a file path (str) or a JSON object (dict)"

View file

@ -81,7 +81,7 @@ class ChainFrontendNode(FrontendNode):
field.advanced = False field.advanced = False
if field.name == "verbose": if field.name == "verbose":
field.required = False field.required = False
field.show = True field.show = False
field.advanced = True field.advanced = True
if field.name == "llm": if field.name == "llm":
field.required = True field.required = True

View file

@ -58,3 +58,7 @@ The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.
You can change this to use other APIs like JinaChat, LocalAI and Prem. You can change this to use other APIs like JinaChat, LocalAI and Prem.
""" """
INPUT_KEY_INFO = """The variable to be used as Chat Input when more than one variable is available."""
OUTPUT_KEY_INFO = """The variable to be used as Chat Output (e.g. answer in a ConversationalRetrievalChain)"""

View file

@ -2,6 +2,7 @@ from typing import Optional
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.constants import INPUT_KEY_INFO, OUTPUT_KEY_INFO
from langflow.template.template.base import Template from langflow.template.template.base import Template
from langchain.memory.chat_message_histories.postgres import DEFAULT_CONNECTION_STRING from langchain.memory.chat_message_histories.postgres import DEFAULT_CONNECTION_STRING
from langchain.memory.chat_message_histories.mongodb import ( from langchain.memory.chat_message_histories.mongodb import (
@ -70,11 +71,15 @@ class MemoryFrontendNode(FrontendNode):
field.required = False field.required = False
field.show = True field.show = True
field.advanced = False field.advanced = False
if field.name in ["input_key", "output_key"]: if field.name in {"input_key", "output_key"}:
field.required = False field.required = False
field.show = True field.show = True
field.advanced = False field.advanced = False
field.value = "" field.value = ""
field.info = (
INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
)
if field.name == "memory_key": if field.name == "memory_key":
field.value = "chat_history" field.value = "chat_history"
if field.name == "chat_memory": if field.name == "chat_memory":
@ -84,9 +89,10 @@ class MemoryFrontendNode(FrontendNode):
if field.name == "url": if field.name == "url":
field.show = True field.show = True
if field.name == "entity_store": if field.name == "entity_store":
field.show = True field.show = False
if name == "SQLiteEntityStore": if name == "ConversationEntityMemory" and field.name == "memory_key":
field.show = True field.show = False
field.required = False
class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode): class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):

View file

@ -45,7 +45,7 @@ class TextSplittersFrontendNode(FrontendNode):
field_type="str", field_type="str",
required=True, required=True,
show=True, show=True,
value=".", value="\\n",
name=name, name=name,
display_name="Separator", display_name="Separator",
) )

View file

@ -112,7 +112,7 @@ class PythonFunctionToolNode(FrontendNode):
], ],
) )
description: str = "Python function to be executed." description: str = "Python function to be executed."
base_classes: list[str] = ["Tool"] base_classes: list[str] = ["BaseTool", "Tool"]
def to_dict(self): def to_dict(self):
return super().to_dict() return super().to_dict()

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<script src="node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script> <script src="/node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script>
<title>Langflow</title> <title>Langflow</title>
</head> </head>
<body id='body' style="width: 100%; height:100%"> <body id='body' style="width: 100%; height:100%">

View file

@ -12,10 +12,12 @@ import IntComponent from "../../../../components/intComponent";
import PromptAreaComponent from "../../../../components/promptComponent"; import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent"; import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent"; import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants";
import { PopUpContext } from "../../../../contexts/popUpContext"; import { PopUpContext } from "../../../../contexts/popUpContext";
import { TabsContext } from "../../../../contexts/tabsContext"; import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext"; import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components"; import { ParameterComponentType } from "../../../../types/components";
import { cleanEdges } from "../../../../util/reactflowUtils";
import { import {
classNames, classNames,
getRandomKeyByssmm, getRandomKeyByssmm,
@ -41,6 +43,7 @@ export default function ParameterComponent({
}: ParameterComponentType) { }: ParameterComponentType) {
const ref = useRef(null); const ref = useRef(null);
const refHtml = useRef(null); const refHtml = useRef(null);
const refNumberComponents = useRef(0);
const infoHtml = useRef(null); const infoHtml = useRef(null);
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0); const [position, setPosition] = useState(0);
@ -58,10 +61,6 @@ export default function ParameterComponent({
updateNodeInternals(data.id); updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]); }, [data.id, position, updateNodeInternals]);
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
useEffect(() => {}, [closePopUp, data.node.template]); useEffect(() => {}, [closePopUp, data.node.template]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
@ -99,6 +98,8 @@ export default function ParameterComponent({
useEffect(() => { useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type); const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
refNumberComponents.current = groupedObj[0]?.type?.length;
refHtml.current = groupedObj.map((item, i) => { refHtml.current = groupedObj.map((item, i) => {
const Icon: any = nodeIconsLucide[item.family]; const Icon: any = nodeIconsLucide[item.family];
@ -127,7 +128,7 @@ export default function ParameterComponent({
{nodeNames[item.family] ?? ""}{" "} {nodeNames[item.family] ?? ""}{" "}
<span className="text-xs"> <span className="text-xs">
{" "} {" "}
{item.type == "" ? "" : " - "} {item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2 {item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => ( ? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}> <React.Fragment key={el + i}>
@ -160,7 +161,7 @@ export default function ParameterComponent({
} }
> >
{title} {title}
<span className="text-destructive">{required ? " *" : ""}</span> <span className="text-status-red">{required ? " *" : ""}</span>
<div className=""> <div className="">
{info !== "" && ( {info !== "" && (
<ShadTooltip content={infoHtml.current}> <ShadTooltip content={infoHtml.current}>
@ -181,7 +182,11 @@ export default function ParameterComponent({
<></> <></>
) : ( ) : (
<ShadTooltip <ShadTooltip
style="max-w-[30vw] max-h-[20vh] overflow-auto custom-scroll" styleClasses={
refNumberComponents.current > MAX_LENGTH_TO_SCROLL_TOOLTIP
? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel"
: "tooltip-fixed-width"
}
delayDuration={0} delayDuration={0}
content={refHtml.current} content={refHtml.current}
side={left ? "left" : "right"} side={left ? "left" : "right"}
@ -222,7 +227,7 @@ export default function ParameterComponent({
/> />
) : data.node.template[name].multiline ? ( ) : data.node.template[name].multiline ? (
<TextAreaComponent <TextAreaComponent
disabled={false} disabled={disabled}
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue} onChange={handleOnNewValue}
/> />
@ -240,10 +245,9 @@ export default function ParameterComponent({
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<ToggleShadComponent <ToggleShadComponent
disabled={disabled} disabled={disabled}
enabled={enabled} enabled={data.node.template[name].value}
setEnabled={(t) => { setEnabled={(t) => {
handleOnNewValue(t); handleOnNewValue(t);
setEnabled(t);
}} }}
size="large" size="large"
/> />
@ -309,6 +313,15 @@ export default function ParameterComponent({
field_name={name} field_name={name}
setNodeClass={(nodeClass) => { setNodeClass={(nodeClass) => {
data.node = nodeClass; data.node = nodeClass;
if (reactFlowInstance) {
cleanEdges({
flow: {
edges: reactFlowInstance.getEdges(),
nodes: reactFlowInstance.getNodes(),
},
updateEdge: (edge) => reactFlowInstance.setEdges(edge),
});
}
}} }}
nodeClass={data.node} nodeClass={data.node}
disabled={disabled} disabled={disabled}

View file

@ -114,7 +114,9 @@ export default function GenericNode({
<div> <div>
<Tooltip <Tooltip
title={ title={
!validationStatus ? ( isBuilding ? (
<span>Building...</span>
) : !validationStatus ? (
<span className="flex"> <span className="flex">
Build{" "} Build{" "}
<Zap <Zap

View file

@ -24,7 +24,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
return ( return (
<div <div
ref={componentRef} ref={componentRef}
className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-background px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-muted px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
> >
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground"> <div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
Notifications Notifications

View file

@ -20,6 +20,7 @@ export default function ErrorAlert({
}, 5000); }, 5000);
} }
}, [id, removeAlert, show]); }, [id, removeAlert, show]);
return ( return (
<Transition <Transition
className="relative" className="relative"
@ -50,7 +51,8 @@ export default function ErrorAlert({
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="error-build-foreground">{title}</h3> <h3 className="error-build-foreground">{title}</h3>
{list.length !== 0 ? ( {list?.length !== 0 &&
list?.some((item) => item !== null && item !== undefined) ? (
<div className="error-build-message-div"> <div className="error-build-message-div">
<ul className="error-build-message-list"> <ul className="error-build-message-list">
{list.map((item, index) => ( {list.map((item, index) => (

View file

@ -13,7 +13,7 @@ export default function AccordionComponent({
open = [], open = [],
}: AccordionComponentType) { }: AccordionComponentType) {
const [value, setValue] = useState( const [value, setValue] = useState(
open.length == 0 ? "" : getOpenAccordion() open.length === 0 ? "" : getOpenAccordion()
); );
function getOpenAccordion() { function getOpenAccordion() {

View file

@ -0,0 +1,21 @@
import DOMPurify from "dompurify";
const SanitizedHTMLWrapper = ({
className,
content,
onClick,
suppressWarning = false,
}) => {
const sanitizedHTML = DOMPurify.sanitize(content);
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
suppressContentEditableWarning={suppressWarning}
onClick={onClick}
/>
);
};
export default SanitizedHTMLWrapper;

View file

@ -1,33 +1,26 @@
import { ShadToolTipType } from "../../types/components"; import { ShadToolTipType } from "../../types/components";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
export default function ShadTooltip({ export default function ShadTooltip({
content, content,
side, side,
asChild = true, asChild = true,
children, children,
style, styleClasses,
delayDuration = 500, delayDuration = 500,
}: ShadToolTipType) { }: ShadToolTipType) {
return ( return (
<TooltipProvider> <Tooltip delayDuration={delayDuration}>
<Tooltip delayDuration={delayDuration}> <TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent <TooltipContent
className={style} className={styleClasses}
side={side} side={side}
avoidCollisions={false} avoidCollisions={false}
sticky="always" sticky="always"
> >
{content} {content}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
} }

View file

@ -72,7 +72,7 @@ export default function Chat({ flow }: ChatType) {
} }
prevNodesRef.current = currentNodes; prevNodesRef.current = currentNodes;
}, [tabsState]); }, [tabsState, flow.id]);
return ( return (
<> <>
@ -83,9 +83,17 @@ export default function Chat({ flow }: ChatType) {
setIsBuilt={setIsBuilt} setIsBuilt={setIsBuilt}
isBuilt={isBuilt} isBuilt={isBuilt}
/> />
{isBuilt && canOpen && ( {isBuilt &&
<FormModal key={flow.id} flow={flow} open={open} setOpen={setOpen} /> tabsState[flow.id] &&
)} tabsState[flow.id].formKeysData &&
canOpen && (
<FormModal
key={flow.id}
flow={flow}
open={open}
setOpen={setOpen}
/>
)}
<ChatTrigger <ChatTrigger
canOpen={canOpen} canOpen={canOpen}
open={open} open={open}

View file

@ -63,8 +63,8 @@ export default function Dropdown({
<Listbox.Options <Listbox.Options
className={classNames( className={classNames(
editNode editNode
? "dropdown-component-true-options nowheel" ? "dropdown-component-true-options nowheel custom-scroll"
: "dropdown-component-false-options nowheel", : "dropdown-component-false-options nowheel custom-scroll",
apiModal ? "mb-2 w-[250px]" : "absolute" apiModal ? "mb-2 w-[250px]" : "absolute"
)} )}
> >

View file

@ -14,7 +14,6 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../../../ui/dropdown-menu"; } from "../../../ui/dropdown-menu";
@ -52,13 +51,11 @@ export const MenuBar = ({ flows, tabId }) => {
<div className="header-menu-bar"> <div className="header-menu-bar">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button asChild variant="primary" size="sm">
className="header-menu-bar-display" <div className="header-menu-bar-display">
variant="primary" <div className="header-menu-flow-name">{current_flow.name}</div>
size="sm" <ChevronDown className="h-4 w-4" />
> </div>
<div className="header-menu-flow-name">{current_flow.name}</div>
<ChevronDown className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-44"> <DropdownMenuContent className="w-44">
@ -99,7 +96,7 @@ export const MenuBar = ({ flows, tabId }) => {
<Redo className="header-menu-options " /> <Redo className="header-menu-options " />
Redo Redo
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> {/* <DropdownMenuSeparator /> */}
{/* <DropdownMenuLabel>Projects</DropdownMenuLabel> */} {/* <DropdownMenuLabel>Projects</DropdownMenuLabel> */}
{/* <DropdownMenuRadioGroup className="max-h-full overflow-scroll" {/* <DropdownMenuRadioGroup className="max-h-full overflow-scroll"
value={tabId} value={tabId}

View file

@ -121,7 +121,7 @@ export default function Header() {
<> <>
<div <div
className="absolute z-10" className="absolute z-10"
style={{ top: top + 34, left: left - AlertWidth }} style={{ top: top + 40, left: left - AlertWidth }}
> >
<AlertDropdown /> <AlertDropdown />
</div> </div>

View file

@ -39,11 +39,13 @@ export default function InputComponent({
if (disableCopyPaste) setDisableCopyPaste(false); if (disableCopyPaste) setDisableCopyPaste(false);
}} }}
className={classNames( className={classNames(
" pr-9 ",
disabled ? " input-disable " : "", disabled ? " input-disable " : "",
password && !pwdVisible && myValue !== "" ? "password" : "", password && !pwdVisible && myValue !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : " input-primary ", editNode ? " input-edit-node " : " input-primary ",
password && editNode ? "pr-8" : "pr-3" password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : ""
)} )}
placeholder={password && editNode ? "Key" : "Type something..."} placeholder={password && editNode ? "Key" : "Type something..."}
onChange={(e) => { onChange={(e) => {

View file

@ -98,10 +98,10 @@ export default function InputFileComponent({
onClick={handleButtonClick} onClick={handleButtonClick}
className={ className={
editNode editNode
? " input-edit-node " + " input-dialog " ? "input-edit-node input-dialog text-muted-foreground"
: (disabled ? " input-disable " : "") + : disabled
" input-primary " + ? "input-disable input-dialog input-primary"
" input-dialog " : "input-dialog input-primary text-muted-foreground"
} }
> >
{myValue !== "" ? myValue : "No file"} {myValue !== "" ? myValue : "No file"}

View file

@ -30,7 +30,7 @@ export default function IntComponent({
<div <div
className={ className={
"w-full " + "w-full " +
(disabled ? "pointer-events-none w-full cursor-not-allowed" : "w-full") (disabled ? "pointer-events-none w-full cursor-not-allowed" : "")
} }
> >
<input <input
@ -70,7 +70,7 @@ export default function IntComponent({
className={ className={
editNode editNode
? " input-edit-node " ? " input-edit-node "
: " input-primary " + (disabled ? " input-disable " : "") : " input-primary " + (disabled ? " input-disable" : "")
} }
placeholder={editNode ? "Integer number" : "Type an integer number"} placeholder={editNode ? "Integer number" : "Type an integer number"}
onChange={(e) => { onChange={(e) => {

View file

@ -4,7 +4,6 @@ import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components"; import { TextAreaComponentType } from "../../types/components";
import { TypeModal } from "../../utils"; import { TypeModal } from "../../utils";
import * as _ from "lodash";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import { postValidatePrompt } from "../../controllers/API"; import { postValidatePrompt } from "../../controllers/API";
@ -18,7 +17,7 @@ export default function PromptAreaComponent({
disabled, disabled,
editNode = false, editNode = false,
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState(""); const [myValue, setMyValue] = useState(value);
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
useEffect(() => { useEffect(() => {
@ -29,84 +28,93 @@ export default function PromptAreaComponent({
}, [disabled, onChange]); }, [disabled, onChange]);
useEffect(() => { useEffect(() => {
if (value !== "" && myValue !== value && reactFlowInstance) { setMyValue(value);
// only executed once if (value !== "" && !editNode) {
setMyValue(value); postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
postValidatePrompt(field_name, value, nodeClass) if (apiReturn.data) {
.then((apiReturn) => { setNodeClass(apiReturn.data.frontend_node);
if (apiReturn.data) { // need to update reactFlowInstance to re-render the nodes.
setNodeClass(apiReturn.data.frontend_node); }
// need to update reactFlowInstance to re-render the nodes. });
reactFlowInstance.setEdges(
_.cloneDeep(reactFlowInstance.getEdges())
);
}
})
.catch((error) => {});
} }
}, [reactFlowInstance, field_name]); }, [value, reactFlowInstance]);
// useEffect(() => {
// if (value !== "" && myValue !== value && reactFlowInstance) {
// // only executed once
// setMyValue(value);
// postValidatePrompt(field_name, value, nodeClass)
// .then((apiReturn) => {
// if (apiReturn.data) {
// setNodeClass(apiReturn.data.frontend_node);
// // need to update reactFlowInstance to re-render the nodes.
// reactFlowInstance.setEdges(
// _.cloneDeep(reactFlowInstance.getEdges())
// );
// }
// })
// .catch((error) => {});
// }
// }, [reactFlowInstance, field_name, myValue, nodeClass, setNodeClass, value]);
return ( return (
<div className={disabled ? "cursor-not-allowed" : ""}> <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className={disabled ? "pointer-events-none w-full " : " w-full"}> <div className="flex w-full items-center">
<div className="flex w-full items-center"> <span
<span onClick={() => {
onClick={() => { openPopUp(
openPopUp( <GenericModal
<GenericModal type={TypeModal.PROMPT}
type={TypeModal.PROMPT} value={myValue}
value={myValue} buttonText="Check & Save"
buttonText="Check & Save" modalTitle="Edit Prompt"
modalTitle="Edit Prompt" setValue={(t: string) => {
setValue={(t: string) => { setMyValue(t);
setMyValue(t); onChange(t);
onChange(t); }}
}} nodeClass={nodeClass}
nodeClass={nodeClass} setNodeClass={setNodeClass}
setNodeClass={setNodeClass}
/>
);
}}
className={
editNode
? " input-edit-node " + " input-dialog "
: (disabled ? " input-disable " : "") +
" input-primary " +
" input-dialog "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
{!editNode && (
<ExternalLink
strokeWidth={1.5}
className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/> />
)} );
</button> }}
</div> className={
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable text-ring " : "") +
" input-primary text-muted-foreground "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
{!editNode && (
<ExternalLink
strokeWidth={1.5}
className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
)}
</button>
</div> </div>
</div> </div>
); );

View file

@ -5,6 +5,7 @@ import { TextAreaComponentType } from "../../types/components";
import { TypeModal } from "../../utils"; import { TypeModal } from "../../utils";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { TabsContext } from "../../contexts/tabsContext";
export default function TextAreaComponent({ export default function TextAreaComponent({
value, value,
@ -14,6 +15,7 @@ export default function TextAreaComponent({
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value); const [myValue, setMyValue] = useState(value);
const { openPopUp, closePopUp } = useContext(PopUpContext); const { openPopUp, closePopUp } = useContext(PopUpContext);
const { setDisableCopyPaste } = useContext(TabsContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
@ -27,19 +29,20 @@ export default function TextAreaComponent({
}, [closePopUp]); }, [closePopUp]);
return ( return (
<div className={disabled ? "cursor-not-allowed" : ""}> <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div <div className="flex w-full items-center">
className={
(editNode ? "relative top-2 w-full" : "flex w-full items-center") +
(disabled ? " pointer-events-none" : "")
}
>
<input <input
value={myValue} value={myValue}
onFocus={() => {
setDisableCopyPaste(true);
}}
onBlur={() => {
setDisableCopyPaste(false);
}}
className={ className={
editNode editNode
? "input-edit-node" ? " input-edit-node "
: "input-primary" + (disabled ? " input-disable " : "") : " input-primary " + (disabled ? " input-disable" : "")
} }
placeholder={"Type something..."} placeholder={"Type something..."}
onChange={(e) => { onChange={(e) => {

View file

@ -17,7 +17,7 @@ const Separator = React.forwardRef<
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"shrink-0 bg-border", "shrink-0 bg-ring/40",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className className
)} )}

View file

@ -14,15 +14,17 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>, React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content <TooltipPrimitive.Portal>
ref={ref} <TooltipPrimitive.Content
sideOffset={sideOffset} ref={ref}
className={cn( sideOffset={sideOffset}
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1", className={cn(
className "overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
)} className
{...props} )}
/> {...props}
/>
</TooltipPrimitive.Portal>
)); ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName; TooltipContent.displayName = TooltipPrimitive.Content.displayName;

View file

@ -5,6 +5,12 @@ import { FlowType } from "./types/flow";
import { TabsState } from "./types/tabs"; import { TabsState } from "./types/tabs";
import { buildInputs, buildTweaks } from "./utils"; import { buildInputs, buildTweaks } from "./utils";
/**
* Number maximum of components to scroll on tooltips
* @constant
*/
export const MAX_LENGTH_TO_SCROLL_TOOLTIP = 200;
/** /**
* The base text for subtitle of Export Dialog (Toolbar) * The base text for subtitle of Export Dialog (Toolbar)
* @constant * @constant
@ -545,10 +551,3 @@ export const NOUNS: string[] = [
* *
*/ */
export const USER_PROJECTS_HEADER = "My Collection"; export const USER_PROJECTS_HEADER = "My Collection";
/**
* CSS for highlight HTML
* @constant
*
*/
export const HIGHLIGH_CSS =
"block pl-3 pr-14 py-2 w-full h-full text-sm outline-0 border-0 break-all overflow-y-hidden max-w-[75vw]";

View file

@ -1,5 +1,6 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { ReactFlowProvider } from "reactflow"; import { ReactFlowProvider } from "reactflow";
import { TooltipProvider } from "../components/ui/tooltip";
import { SSEProvider } from "./SSEContext"; import { SSEProvider } from "./SSEContext";
import { AlertProvider } from "./alertContext"; import { AlertProvider } from "./alertContext";
import { DarkProvider } from "./darkContext"; import { DarkProvider } from "./darkContext";
@ -13,23 +14,25 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context //element to wrap all context
return ( return (
<> <>
<ReactFlowProvider> <TooltipProvider>
<DarkProvider> <ReactFlowProvider>
<TypesProvider> <DarkProvider>
<LocationProvider> <TypesProvider>
<AlertProvider> <LocationProvider>
<SSEProvider> <AlertProvider>
<TabsProvider> <SSEProvider>
<UndoRedoProvider> <TabsProvider>
<PopUpProvider>{children}</PopUpProvider> <UndoRedoProvider>
</UndoRedoProvider> <PopUpProvider>{children}</PopUpProvider>
</TabsProvider> </UndoRedoProvider>
</SSEProvider> </TabsProvider>
</AlertProvider> </SSEProvider>
</LocationProvider> </AlertProvider>
</TypesProvider> </LocationProvider>
</DarkProvider> </TypesProvider>
</ReactFlowProvider> </DarkProvider>
</ReactFlowProvider>
</TooltipProvider>
</> </>
); );
} }

View file

@ -54,7 +54,7 @@ const TabsContextInitialValue: TabsContextType = {
setTabsState: (state: TabsState) => {}, setTabsState: (state: TabsState) => {},
getNodeId: (nodeType: string) => "", getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {}, setTweak: (tweak: any) => {},
getTweak: {}, getTweak: [],
paste: ( paste: (
selection: { nodes: any; edges: any }, selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number } position: { x: number; y: number; paneX?: number; paneY?: number }
@ -75,7 +75,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const { templates, reactFlowInstance } = useContext(typesContext); const { templates, reactFlowInstance } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null); const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const [tabsState, setTabsState] = useState<TabsState>({}); const [tabsState, setTabsState] = useState<TabsState>({});
const [getTweak, setTweak] = useState({}); const [getTweak, setTweak] = useState([]);
const newNodeId = useRef(uid()); const newNodeId = useRef(uid());
function incrementNodeId() { function incrementNodeId() {
@ -539,11 +539,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const updateEdges = (edges) => { const updateEdges = (edges) => {
edges.forEach((edge) => { edges.forEach((edge) => {
edge.style = { stroke: "inherit" };
edge.className = edge.className =
edge.targetHandle.split("|")[0] === "Text" (edge.targetHandle.split("|")[0] === "Text"
? "stroke-gray-800 " ? "stroke-gray-800 "
: "stroke-gray-900 "; : "stroke-gray-900 ") + " stroke-connection";
edge.animated = edge.targetHandle.split("|")[0] === "Text"; edge.animated = edge.targetHandle.split("|")[0] === "Text";
}); });
}; };

View file

@ -76,7 +76,7 @@ export async function postValidatePrompt(
*/ */
export async function getExamples(): Promise<FlowType[]> { export async function getExamples(): Promise<FlowType[]> {
const url = const url =
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples"; "https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples?ref=fix_examples";
const response = await axios.get(url); const response = await axios.get(url);
const jsonFiles = response.data.filter((file: any) => { const jsonFiles = response.data.filter((file: any) => {

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"/><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"/><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"/><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="2443" height="2500" preserveAspectRatio="xMidYMid" viewBox="0 0 256 262" id="google"><path fill="#4285F4" d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"></path><path fill="#34A853" d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"></path><path fill="#FBBC05" d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"></path><path fill="#EB4335" d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"></path></svg>

Before

Width:  |  Height:  |  Size: 688 B

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
import { ReactComponent as HugginFaceSVG } from "./hf-logo.svg"; import { ReactComponent as HugginFaceSVG } from "./hf-logo.svg";
export const HugginFaceIcon = forwardRef< export const HuggingFaceIcon = forwardRef<
SVGSVGElement, SVGSVGElement,
React.PropsWithChildren<{}> React.PropsWithChildren<{}>
>((props, ref) => { >((props, ref) => {

View file

@ -6,8 +6,8 @@
/* TODO: Confirm that all colors here are found in tailwind config */ /* TODO: Confirm that all colors here are found in tailwind config */
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; /* hsl(0 0% 100%) */ --background: 0 0% 100%; /* hsl(0 0% 100%) */
--foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */ --foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--muted: 210 40% 98%; /* hsl(210 40% 98%) */ --muted: 210 40% 98%; /* hsl(210 40% 98%) */
@ -27,36 +27,40 @@
--destructive: 0 100% 50%; /* hsl(0 100% 50%) */ --destructive: 0 100% 50%; /* hsl(0 100% 50%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */ --destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--radius: 0.5rem; --radius: 0.5rem;
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */ --ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--round-btn-shadow: #00000063; --round-btn-shadow: #00000063;
--error-background: #fef2f2; --error-background: #fef2f2;
--error-foreground: #991b1b; --error-foreground: #991b1b;
--success-background: #f0fdf4; --success-background: #f0fdf4;
--success-foreground: #14532d; --success-foreground: #14532d;
--info-background: #f0f4fd; --info-background: #f0f4fd;
--info-foreground: #141653; --info-foreground: #141653;
--high-indigo: #4338ca; --high-indigo: #4338ca;
--medium-indigo: #6366f1; --medium-indigo: #6366f1;
--chat-bot-icon: #afe6ef;
--chat-user-icon: #aface9;
/* Colors that are shared in dark and light mode */ /* Colors that are shared in dark and light mode */
--blur-shared: #151923de; --blur-shared: #151923de;
--build-trigger: #dc735b; --build-trigger: #dc735b;
--chat-trigger: #5c8be1; --chat-trigger: #5c8be1;
--chat-trigger-disabled: #9db7e8; --chat-trigger-disabled: #b4c3da;
--status-red: #ef4444; --status-red: #ef4444;
--status-yellow: #eab308; --status-yellow: #eab308;
--status-green: #4ade80; --status-green: #4ade80;
--status-blue:#2563eb; --status-blue:#2563eb;
--connection: #555;
} }
.dark { .dark {
--background: 224 35% 7.5%; /* hsl(224 40% 10%) */ --background: 224 35% 7.5%; /* hsl(224 40% 10%) */
--foreground: 213 31% 85%; /* hsl(213 31% 91%) */ --foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--muted: 223 27% 11%; /* hsl(223 27% 11%) */ --muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */ --muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
@ -70,13 +74,13 @@
--border: 216 24% 17%; /* hsl(216 34% 17%) */ --border: 216 24% 17%; /* hsl(216 34% 17%) */
--input: 216 24% 17%; /* hsl(216 34% 17%) */ --input: 216 24% 17%; /* hsl(216 34% 17%) */
--primary: 210 20% 80%; /* hsl(210 40% 98%) */ --primary: 210 20% 80%; /* hsl(210 20% 80%) */
--primary-foreground: 222.2 27.4% 1.2%; /* hsl(222 47% 1%) */ --primary-foreground: 222.2 27.4% 1.2%; /* hsl(222 47% 1%) */
--secondary: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */ --secondary: 222.2 37.4% 7.2%; /* hsl(222 47% 11%) */
--secondary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */ --secondary-foreground: 210 40% 80%; /* hsl(210 40% 80%) */
--accent: 216 24% 17%; /* hsl(216 34% 17%) */ --accent: 216 24% 20%; /* hsl(216 34% 17%) */
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */ --accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */ --destructive: 0 63% 31%; /* hsl(0 63% 31%) */
@ -87,7 +91,7 @@
--radius: 0.5rem; --radius: 0.5rem;
--round-btn-shadow: #00000063; --round-btn-shadow: #00000063;
--success-background: #022c22; --success-background: #022c22;
--success-foreground: #ecfdf5; --success-foreground: #ecfdf5;
@ -105,11 +109,13 @@
--blur-shared: #151923d2; --blur-shared: #151923d2;
--build-trigger: #dc735b; --build-trigger: #dc735b;
--chat-trigger: #5c8be1; --chat-trigger: #5c8be1;
--chat-trigger-disabled: #2b4470; --chat-trigger-disabled: #2d3b54;
--status-red: #ef4444; --status-red: #ef4444;
--status-yellow: #eab308; --status-yellow: #eab308;
--status-green: #4ade80; --status-green: #4ade80;
--status-blue: #2563eb; --status-blue: #2563eb;
--connection: #555;
}} }}
@layer base { @layer base {
@ -205,6 +211,7 @@ The cursor: default; property value restores the browser's default cursor style
.input-primary { .input-primary {
@apply bg-background block border-ring form-input px-3 placeholder:text-muted-foreground rounded-md shadow-sm sm:text-sm truncate w-full; @apply bg-background block border-ring form-input px-3 placeholder:text-muted-foreground rounded-md shadow-sm sm:text-sm truncate w-full;
} }
.input-edit-node{ .input-edit-node{
@apply input-primary placeholder:text-center pt-0.5 pb-0.5 text-center @apply input-primary placeholder:text-center pt-0.5 pb-0.5 text-center
} }
@ -212,7 +219,7 @@ The cursor: default; property value restores the browser's default cursor style
@apply input-primary pr-7 mx-2 @apply input-primary pr-7 mx-2
} }
.input-disable{ .input-disable{
@apply bg-input @apply bg-border placeholder:text-ring border-transparent
} }
.input-dialog{ .input-dialog{
@apply text-ring cursor-pointer bg-transparent @apply text-ring cursor-pointer bg-transparent
@ -545,7 +552,7 @@ The cursor: default; property value restores the browser's default cursor style
@apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium @apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium
} }
.header-menu-bar-display { .header-menu-bar-display {
@apply flex max-w-[200px] items-center gap-2 @apply flex max-w-[200px] items-center gap-2 cursor-pointer
} }
.header-menu-flow-name { .header-menu-flow-name {
@apply flex-1 truncate @apply flex-1 truncate
@ -913,5 +920,172 @@ The cursor: default; property value restores the browser's default cursor style
@apply ml-3 h-6 w-6 @apply ml-3 h-6 w-6
} }
.form-modal-lock-true {
@apply bg-input text-primary
}
.form-modal-no-input {
@apply bg-input text-center text-primary dark:bg-gray-700 dark:text-gray-300
}
.form-modal-lock-false {
@apply bg-white text-primary
}
.code-highlight{
@apply block px-3 py-2 w-full h-full text-sm outline-0 border-0 break-all overflow-y-hidden
}
.form-modal-lockchat {
@apply form-input focus:ring-ring focus:border-ring block w-full rounded-md border-border p-4 pr-16 custom-scroll sm:text-sm
}
.form-modal-send-icon-position {
@apply absolute bottom-2 right-4
}
.form-modal-send-button {
@apply rounded-md p-2 px-1 transition-all duration-300
}
.form-modal-lock-icon {
@apply ml-1 mr-1 h-5 w-5 animate-pulse
}
.form-modal-send-icon {
@apply mr-2 h-5 w-5 rotate-[44deg]
}
.form-modal-play-icon {
@apply h-5 w-5 mx-1
}
.form-modal-chat-position {
@apply flex-max-width px-2 py-6 pl-4 pr-9
}
.form-modal-chatbot-icon {
@apply mb-3 ml-3 mr-6 mt-1
}
.form-modal-chat-image {
@apply flex flex-col items-center gap-1
}
.form-modal-chat-img-box {
@apply relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl
}
.form-modal-chat-bot-icon {
@apply form-modal-chat-img-box bg-chat-bot-icon
}
.form-modal-chat-user-icon {
@apply form-modal-chat-img-box bg-chat-user-icon
}
.form-modal-chat-icon-img {
@apply absolute scale-[60%]
}
.form-modal-chat-text-position {
@apply flex w-full flex-1 text-start
}
.form-modal-chat-text {
@apply relative flex w-full flex-col text-start text-sm font-normal text-muted-foreground
}
.form-modal-chat-icon-div {
@apply absolute -left-6 -top-3 cursor-pointer
}
.form-modal-chat-icon {
@apply h-4 w-4 animate-bounce
}
.form-modal-chat-thought-border {
@apply rounded-md border border-ring/60
}
.form-modal-chat-thought-size {
@apply inline-block h-full w-[95%]
}
.form-modal-chat-thought {
@apply cursor-pointer overflow-scroll bg-background text-start text-primary scrollbar-hide form-modal-chat-thought-border form-modal-chat-thought-size py-2 px-2
}
.form-modal-markdown-span {
@apply mt-1 animate-pulse cursor-default
}
.form-modal-initial-prompt-btn {
@apply mb-2 flex items-center gap-2 rounded-md border border-border bg-background shadow-sm px-4 py-2 text-sm font-semibold
}
.form-modal-iv-box {
@apply mt-2 flex-max-width h-[80vh]
}
.form-modal-iv-size {
@apply mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide
}
.file-component-arrangement {
@apply flex items-center py-2
}
.file-component-variable {
@apply -ml-px mr-1 h-4 w-4 text-primary
}
.file-component-variables-span {
@apply font-semibold text-primary
}
.file-component-variables-title {
@apply flex items-center justify-between pt-2
}
.file-component-variables-div {
@apply mr-2.5 flex items-center
}
.file-component-variables-title-txt {
@apply text-sm font-medium text-primary
}
.file-component-accordion-div {
@apply flex items-start gap-3
}
.file-component-badge-div {
@apply flex-max-width items-center justify-between
}
.file-component-tab-column {
@apply flex flex-col gap-2 p-1
}
.tab-accordion-badge-div {
@apply flex flex-1 items-center justify-between py-4 text-sm font-normal text-muted-foreground transition-all
}
.eraser-column-arrangement {
@apply flex-max-width flex-1 flex-col
}
.eraser-size {
@apply relative flex h-full w-full flex-col rounded-md border bg-muted
}
.eraser-position {
@apply absolute right-3 top-3 z-50
}
.chat-message-div {
@apply flex-max-width h-full flex-col items-center overflow-scroll scrollbar-hide
}
.chat-alert-box {
@apply flex-max-width h-full flex-col items-center justify-center text-center align-middle
}
.langflow-chat-span {
@apply text-lg text-foreground
}
.langflow-chat-desc {
@apply w-2/4 rounded-md border border-border bg-muted px-6 py-8
}
.langflow-chat-desc-span {
@apply text-base text-muted-foreground
}
.langflow-chat-input-div {
@apply flex-max-width flex-col items-center justify-between px-8 pb-6
}
.langflow-chat-input {
@apply relative w-full rounded-md shadow-sm
}
} .tooltip-fixed-width{
@apply max-w-[30vw] max-h-[20vh] overflow-auto
}
.ace-editor-arrangement {
@apply flex-max-width h-full flex-col transition-all
}
.ace-editor {
@apply h-full w-full rounded-lg border-[1px] border-border custom-scroll
}
.ace-editor-save-btn {
@apply flex-max-width h-fit justify-end
}
.export-modal-save-api {
@apply font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70
}
.chat-message-highlight {
@apply px-0.5 rounded-md bg-indigo-100 dark:bg-indigo-900
}
}

View file

@ -142,7 +142,8 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
} }
function startTweaks() { function startTweaks() {
tweak?.current?.push(buildTweaks(flow)); const t = buildTweaks(flow);
tweak?.current?.push(t);
} }
function filterNodes() { function filterNodes() {
@ -266,7 +267,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
return ( return (
<Dialog open={true} onOpenChange={setModalOpen}> <Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger> <DialogTrigger></DialogTrigger>
<DialogContent className="h-[80vh] lg:max-w-[80vw]"> <DialogContent className="h-[80vh] md:max-w-[80vw]">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<span className="pr-2">Code</span> <span className="pr-2">Code</span>
@ -382,10 +383,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
key={i} key={i}
className="h-10 dark:border-b-muted" className="h-10 dark:border-b-muted"
> >
<TableCell className="p-0 text-center text-sm text-gray-900"> <TableCell className="p-0 text-center text-sm text-foreground">
{n} {n}
</TableCell> </TableCell>
<TableCell className="p-0 text-center text-xs text-gray-900 dark:text-gray-300"> <TableCell className="p-0 text-xs text-foreground">
<div className="m-auto w-[250px]"> <div className="m-auto w-[250px]">
{t.data.node.template[n] {t.data.node.template[n]
.type === "str" && .type === "str" &&

View file

@ -1,5 +1,5 @@
import { Variable } from "lucide-react"; import { Variable } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useRef, useState } from "react";
import CodeAreaComponent from "../../components/codeAreaComponent"; import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent"; import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent"; import FloatComponent from "../../components/floatComponent";
@ -30,6 +30,7 @@ import {
TableRow, TableRow,
} from "../../components/ui/table"; } from "../../components/ui/table";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import { NodeDataType } from "../../types/flow"; import { NodeDataType } from "../../types/flow";
import { classNames, limitScrollFieldsModal } from "../../utils"; import { classNames, limitScrollFieldsModal } from "../../utils";
@ -54,7 +55,12 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
const { types } = useContext(typesContext); const { types } = useContext(typesContext);
const ref = useRef(); const ref = useRef();
const [enabled, setEnabled] = useState(null); const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === data.id) ??
false;
if (nodeLength == 0) { if (nodeLength == 0) {
closePopUp(); closePopUp();
} }
@ -66,18 +72,30 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
} }
} }
useEffect(() => {}, [closePopUp, data.node.template]); function changeAdvanced(node) {
Object.keys(data.node.template).map((n, i) => {
function changeAdvanced(node): void {
Object.keys(data.node.template).filter((n, i) => {
if (n === node.name) { if (n === node.name) {
data.node.template[n].advanced = !data.node.template[n].advanced; data.node.template[n].advanced = !data.node.template[n].advanced;
} }
return true; return n;
}); });
setNodeValue(!nodeValue); setNodeValue(!nodeValue);
} }
const handleOnNewValue = (newValue: any, name) => {
data.node.template[name].value = newValue;
// Set state to pending
setTabsState((prev) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
};
return ( return (
<Dialog open={true} onOpenChange={setModalOpen}> <Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger asChild></DialogTrigger> <DialogTrigger asChild></DialogTrigger>
@ -87,11 +105,13 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<span className="pr-2">{data.type}</span> <span className="pr-2">{data.type}</span>
<Badge variant="secondary">ID: {data.id}</Badge> <Badge variant="secondary">ID: {data.id}</Badge>
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription asChild>
{data.node?.description} <div>
<div className="flex pt-3"> {data.node?.description}
<Variable className="edit-node-modal-variable "></Variable> <div className="flex pt-3">
<span className="edit-node-modal-span">Parameters</span> <Variable className="edit-node-modal-variable "></Variable>
<span className="edit-node-modal-span">Parameters</span>
</div>
</div> </div>
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@ -145,7 +165,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
{data.node.template[n].list ? ( {data.node.template[n].list ? (
<InputListComponent <InputListComponent
editNode={true} editNode={true}
disabled={false} disabled={disabled}
value={ value={
!data.node.template[n].value || !data.node.template[n].value ||
data.node.template[n].value === "" data.node.template[n].value === ""
@ -153,28 +173,28 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
: data.node.template[n].value : data.node.template[n].value
} }
onChange={(t: string[]) => { onChange={(t: string[]) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
/> />
) : data.node.template[n].multiline ? ( ) : data.node.template[n].multiline ? (
<TextAreaComponent <TextAreaComponent
disabled={false} disabled={disabled}
editNode={true} editNode={true}
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
/> />
) : ( ) : (
<InputComponent <InputComponent
editNode={true} editNode={true}
disabled={false} disabled={disabled}
password={ password={
data.node.template[n].password ?? false data.node.template[n].password ?? false
} }
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
/> />
)} )}
@ -183,19 +203,18 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<div className="ml-auto"> <div className="ml-auto">
{" "} {" "}
<ToggleShadComponent <ToggleShadComponent
disabled={disabled}
enabled={data.node.template[n].value} enabled={data.node.template[n].value}
setEnabled={(e) => { setEnabled={(t) => {
data.node.template[n].value = e; handleOnNewValue(t, n);
setEnabled(e);
}} }}
size="small" size="small"
disabled={false}
/> />
</div> </div>
) : data.node.template[n].type === "float" ? ( ) : data.node.template[n].type === "float" ? (
<div className="mx-auto"> <div className="mx-auto">
<FloatComponent <FloatComponent
disabled={false} disabled={disabled}
editNode={true} editNode={true}
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t) => { onChange={(t) => {
@ -210,9 +229,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
numberOfOptions={nodeLength} numberOfOptions={nodeLength}
editNode={true} editNode={true}
options={data.node.template[n].options} options={data.node.template[n].options}
onSelect={(newValue) => onSelect={(t) => handleOnNewValue(t, n)}
(data.node.template[n].value = newValue)
}
value={ value={
data.node.template[n].value ?? data.node.template[n].value ??
"Choose an option" "Choose an option"
@ -222,11 +239,11 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
) : data.node.template[n].type === "int" ? ( ) : data.node.template[n].type === "int" ? (
<div className="mx-auto"> <div className="mx-auto">
<IntComponent <IntComponent
disabled={false} disabled={disabled}
editNode={true} editNode={true}
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
/> />
</div> </div>
@ -234,15 +251,15 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<div className="mx-auto"> <div className="mx-auto">
<InputFileComponent <InputFileComponent
editNode={true} editNode={true}
disabled={false} disabled={disabled}
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
fileTypes={data.node.template[n].fileTypes} fileTypes={data.node.template[n].fileTypes}
suffixes={data.node.template[n].suffixes} suffixes={data.node.template[n].suffixes}
onFileChange={(t: string) => { onFileChange={(t: string) => {
data.node.template[n].content = t; handleOnNewValue(t, n);
}} }}
></InputFileComponent> ></InputFileComponent>
</div> </div>
@ -251,10 +268,14 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<PromptAreaComponent <PromptAreaComponent
field_name={n} field_name={n}
editNode={true} editNode={true}
disabled={false} disabled={disabled}
nodeClass={data.node}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
/> />
</div> </div>
@ -272,7 +293,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
editNode={true} editNode={true}
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[n].value = t; handleOnNewValue(t, n);
}} }}
/> />
</div> </div>
@ -289,7 +310,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
setEnabled={(e) => setEnabled={(e) =>
changeAdvanced(data.node.template[n]) changeAdvanced(data.node.template[n])
} }
disabled={false} disabled={disabled}
size="small" size="small"
/> />
</div> </div>

View file

@ -52,7 +52,7 @@ export default function ModalField({
{display && ( {display && (
<div> <div>
<span className="mx-2">{title}</span> <span className="mx-2">{title}</span>
<span className="text-destructive">{required ? " *" : ""}</span> <span className="text-status-red">{required ? " *" : ""}</span>
</div> </div>
)} )}

View file

@ -54,7 +54,7 @@ function BaseModal({ open, setOpen, children }: BaseModalProps) {
); );
//UPDATE COLORS AND STYLE CLASSSES //UPDATE COLORS AND STYLE CLASSSES
return ( return (
<Dialog open={open} onOpenChange={setModalOpen}> <Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger className="hidden"></DialogTrigger> <DialogTrigger className="hidden"></DialogTrigger>
<DialogContent className="min-w-[80vw]"> <DialogContent className="min-w-[80vw]">
{headerChild} {headerChild}

View file

@ -1,157 +0,0 @@
import Convert from "ansi-to-html";
import DOMPurify from "dompurify";
import { MessageCircle, User2 } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
import AiIconStill from "../../../assets/froze-flow.png";
import { ChatMessageType } from "../../../types/chat";
import { classNames } from "../../../utils";
import FileCard from "../fileComponent";
import { CodeBlock } from "./codeBlock";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
}: {
chat: ChatMessageType;
lockChat: boolean;
lastMessage: boolean;
}) {
const convert = new Convert({ newline: true });
const [message, setMessage] = useState("");
const imgRef = useRef(null);
useEffect(() => {
setMessage(chat.message);
}, [chat.message]);
const [hidden, setHidden] = useState(true);
return (
<div
className={classNames(
"chat-message-modal",
chat.isSend ? "bg-background " : "bg-input"
)}
>
<div className={classNames("chat-message-modal-div")}>
{!chat.isSend && (
<div className="relative h-8 w-8">
<img
className={
"chat-message-modal-img " +
(lockChat ? "opacity-100" : "opacity-0")
}
src={lastMessage ? AiIcon : AiIconStill}
/>
<img
className={
"chat-message-modal-img " +
(lockChat ? "opacity-0" : "opacity-100")
}
src={AiIconStill}
/>
</div>
)}
{chat.isSend && <User2 className="-mb-1 h-6 w-6 text-primary " />}
</div>
{!chat.isSend ? (
<div className="chat-message-modal-display">
<div className="chat-message-modal-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="chat-message-modal-icon-div"
>
<MessageCircle className="h-5 w-5 animate-bounce " />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
onClick={() => setHidden((prev) => !prev)}
className=" chat-message-modal-thought"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(convert.toHtml(chat.thought)),
}}
></div>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="chat-message-modal-markdown">
<div className="w-full">
<div className="w-full">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose text-muted-foreground "
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == "▍") {
return (
<span className="chat-message-modal-markdown-span">
▍
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{message}
</ReactMarkdown>
</div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={"Generated File"}
fileType={file.data_type}
content={file.data}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div className="flex w-full items-center">
<div className="chat-message-modal-alert ">
{message.split("\n").map((line, index) => (
<span key={index} className="text-muted-foreground ">
{line}
<br />
</span>
))}
</div>
</div>
)}
</div>
);
}

View file

@ -1,415 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { Eraser, MessagesSquare, X } from "lucide-react";
import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { typesContext } from "../../contexts/typesContext";
import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat";
import { FlowType } from "../../types/flow";
import { validateNodes } from "../../utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import _ from "lodash";
import { getHealth } from "../../controllers/API";
export default function ChatModal({
flow,
open,
setOpen,
}: {
open: boolean;
setOpen: Function;
flow: FlowType;
}) {
const [chatValue, setChatValue] = useState("");
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext);
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef(null);
const id = useRef(flow.id);
useEffect(() => {
if (messagesRef.current) {
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
}
}, [chatHistory]);
useEffect(() => {
isOpen.current = open;
}, [open]);
useEffect(() => {
id.current = flow.id;
}, [flow.id]);
var isStream = false;
const addChatHistory = (
message: string,
isSend: boolean,
thought?: string,
files?: Array<any>
) => {
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if (files) {
newChat.push({ message, isSend, files, thought });
} else if (thought) {
newChat.push({ message, isSend, thought });
} else {
newChat.push({ message, isSend });
}
return newChat;
});
};
//add proper type signature for function
function updateLastMessage({
str,
thought,
end = false,
files,
}: {
str?: string;
thought?: string;
// end param default is false
end?: boolean;
files?: Array<any>;
}) {
setChatHistory((old) => {
let newChat = [...old];
if (str) {
if (end) {
newChat[newChat.length - 1].message = str;
} else {
newChat[newChat.length - 1].message =
newChat[newChat.length - 1].message + str;
}
}
if (thought) {
newChat[newChat.length - 1].thought = thought;
}
if (files) {
newChat[newChat.length - 1].files = files;
}
return newChat;
});
}
function handleOnClose(event: CloseEvent) {
if (isOpen.current) {
setErrorData({ title: event.reason });
setTimeout(() => {
connectWS();
setLockChat(false);
}, 1000);
}
}
function getWebSocketUrl(chatId, isDevelopment = false) {
const isSecureProtocol = window.location.protocol === "https:";
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
const host = isDevelopment ? "localhost:7860" : window.location.host;
const chatEndpoint = `/api/v1/chat/${chatId}`;
return `${
isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}`;
}
function handleWsMessage(data: any) {
if (Array.isArray(data)) {
//set chat history
setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = [];
data.forEach(
(chatItem: {
intermediate_steps?: "string";
is_bot: boolean;
message: string;
type: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
files: chatItem.files,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
}
);
}
}
);
return newChatHistory;
});
}
if (data.type === "start") {
addChatHistory("", false);
isStream = true;
}
if (data.type === "end") {
if (data.message) {
updateLastMessage({ str: data.message, end: true });
}
if (data.intermediate_steps) {
updateLastMessage({
str: data.message,
thought: data.intermediate_steps,
end: true,
});
}
if (data.files) {
updateLastMessage({
end: true,
files: data.files,
});
}
setLockChat(false);
isStream = false;
}
if (data.type === "stream" && isStream) {
updateLastMessage({ str: data.message });
}
}
function connectWS() {
try {
const urlWs = getWebSocketUrl(
id.current,
process.env.NODE_ENV === "development"
);
const newWs = new WebSocket(urlWs);
newWs.onopen = () => {
console.log("WebSocket connection established!");
};
newWs.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received data:", data);
handleWsMessage(data);
//get chat history
};
newWs.onclose = (event) => {
handleOnClose(event);
};
newWs.onerror = (ev) => {
getHealth()
.then((res) => {
if (res.status === 200) {
connectWS();
}
})
.catch((err) => {
setErrorData({
// message when the backend failed
title: "The backend is not responding. Please try again later.",
// possible solution list
list: [
"Check your internet connection.",
"Check if the backend is running.",
],
});
});
};
ws.current = newWs;
} catch (error) {
connectWS();
console.log(error);
}
}
useEffect(() => {
connectWS();
return () => {
console.log("unmount");
console.log(ws);
if (ws.current) {
ws.current.close();
}
};
}, []);
useEffect(() => {
if (
ws.current &&
(ws.current.readyState === ws.current.CLOSED ||
ws.current.readyState === ws.current.CLOSING)
) {
connectWS();
setLockChat(false);
}
}, [lockChat]);
async function sendAll(data: sendAllProps) {
try {
if (ws) {
ws.current.send(JSON.stringify(data));
}
} catch (error) {
setErrorData({
title: "There was an error sending the message",
list: [error.message],
});
setChatValue(data.message);
connectWS();
}
}
useEffect(() => {
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
const ref = useRef(null);
useEffect(() => {
if (open && ref.current) {
ref.current.focus();
}
}, [open]);
function sendMessage() {
if (chatValue !== "") {
let nodeValidationErrors = validateNodes(reactFlowInstance);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let message = chatValue;
setChatValue("");
addChatHistory(message, true);
sendAll({
...reactFlowInstance.toObject(),
message,
chatHistory,
name: flow.name,
description: flow.description,
});
} else {
setErrorData({
title: "Oops! Looks like you missed some required information:",
list: nodeValidationErrors,
});
}
} else {
setErrorData({
title: "Error sending message",
list: ["The message cannot be empty."],
});
}
}
function clearChat() {
setChatHistory([]);
ws.current.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
function setModalOpen(x: boolean) {
setOpen(x);
}
return (
<Transition.Root show={open} appear={open} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="send-message-modal-transition" />
</Transition.Child>
<div className="chat-modal-box">
<div className="chat-modal-box-div">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className=" chat-modal-dialog-panel">
<div className="chat-modal-dialog-panel-div">
<button
onClick={() => clearChat()}
className="chat-modal-dialog-trash-panel"
>
<Eraser className="h-4 w-4" />
</button>
<button
onClick={() => setModalOpen(false)}
className="chat-modal-dialog-x-panel"
>
<X className="h-5 w-5" />
</button>
</div>
<div ref={messagesRef} className="chat-modal-dialog-history">
{chatHistory.length > 0 ? (
chatHistory.map((c, i) => (
<ChatMessage
lockChat={lockChat}
chat={c}
lastMessage={chatHistory.length - 1 == i ? true : false}
key={i}
/>
))
) : (
<div className="chat-modal-dialog-span-box">
<span>
👋{" "}
<span className="text-lg text-muted-foreground">
Langflow Chat
</span>
</span>
<br />
<div className="chat-modal-dialog-desc">
<span className="text-base text-ring">
Start a conversation and click the agent’s thoughts{" "}
<span>
<MessagesSquare className="mx-1 inline h-5 w-5 animate-bounce " />
</span>{" "}
to inspect the chaining process.
</span>
</div>
</div>
)}
<div ref={ref}></div>
</div>
<div className="chat-modal-input-div">
<div className="chat-modal-input">
<ChatInput
chatValue={chatValue}
lockChat={lockChat}
sendMessage={sendMessage}
setChatValue={setChatValue}
inputRef={ref}
/>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -10,16 +10,9 @@ import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/ace"; import "ace-builds/src-noconflict/ace";
// import "ace-builds/webpack-resolver"; // import "ace-builds/webpack-resolver";
import { TerminalSquare } from "lucide-react"; import { TerminalSquare } from "lucide-react";
import { useContext, useState } from "react";
import AceEditor from "react-ace";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants"; import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
@ -45,9 +38,7 @@ export default function CodeAreaModal({
setNodeClass: (Class: APIClassType) => void; setNodeClass: (Class: APIClassType) => void;
dynamic?: boolean; dynamic?: boolean;
}) { }) {
const [open, setOpen] = useState(true);
const [code, setCode] = useState(value); const [code, setCode] = useState(value);
const [loading, setLoading] = useState(false);
const { dark } = useContext(darkContext); const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext); const { setErrorData, setSuccessData } = useContext(alertContext);
const [activeTab, setActiveTab] = useState("0"); const [activeTab, setActiveTab] = useState("0");
@ -55,14 +46,12 @@ export default function CodeAreaModal({
detail: { error: string; traceback: string }; detail: { error: string; traceback: string };
}>(null); }>(null);
const { closePopUp, setCloseEdit } = useContext(PopUpContext); const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef(); const { setErrorData, setSuccessData } = useContext(alertContext);
function setModalOpen(x: boolean) { function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) { if (x === false) {
setTimeout(() => { setCloseEdit("codearea");
setCloseEdit("editcode"); closePopUp();
closePopUp();
}, 300);
} }
} }
console.log(dynamic); console.log(dynamic);

View file

@ -71,10 +71,7 @@ export default function ExportModal() {
setChecked(event); setChecked(event);
}} }}
/> />
<label <label htmlFor="terms" className="export-modal-save-api text-sm">
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Save with my API keys Save with my API keys
</label> </label>
</div> </div>

View file

@ -1,4 +1,4 @@
import { Lock, LucideSend } from "lucide-react"; import { Lock, LucideSend, Sparkles } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { classNames } from "../../../utils"; import { classNames } from "../../../utils";
@ -8,6 +8,7 @@ export default function ChatInput({
sendMessage, sendMessage,
setChatValue, setChatValue,
inputRef, inputRef,
noInput,
}) { }) {
useEffect(() => { useEffect(() => {
if (!lockChat && inputRef.current) { if (!lockChat && inputRef.current) {
@ -32,7 +33,7 @@ export default function ChatInput({
}} }}
rows={1} rows={1}
ref={inputRef} ref={inputRef}
disabled={lockChat} disabled={lockChat || noInput}
style={{ style={{
resize: "none", resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`, bottom: `${inputRef?.current?.scrollHeight}px`,
@ -49,31 +50,38 @@ export default function ChatInput({
}} }}
className={classNames( className={classNames(
lockChat lockChat
? " bg-input text-black dark:bg-gray-700 dark:text-gray-300" ? " form-modal-lock-true bg-input"
: " bg-white-200 text-black dark:bg-gray-900 dark:text-gray-300", : noInput
"form-input block w-full rounded-md border-gray-300 p-4 pr-16 custom-scroll dark:border-gray-600 sm:text-sm" ? "form-modal-no-input bg-input"
: " form-modal-lock-false bg-background",
"form-modal-lockchat"
)} )}
placeholder={"Send a message..."} placeholder={
noInput
? "No chat input variables found. Click to run your flow."
: "Send a message..."
}
/> />
<div className="absolute bottom-2 right-4"> <div className="form-modal-send-icon-position">
<button <button
className={classNames( className={classNames(
"rounded-md p-2 px-1 transition-all duration-300", "form-modal-send-button",
chatValue == "" ? "text-primary" : " bg-indigo-600 text-background" noInput
? "bg-indigo-600 text-background"
: chatValue === ""
? "text-primary"
: "bg-emerald-600 text-background"
)} )}
disabled={lockChat} disabled={lockChat}
onClick={() => sendMessage()} onClick={() => sendMessage()}
> >
{lockChat ? ( {lockChat ? (
<Lock <Lock className="form-modal-lock-icon" aria-hidden="true" />
className="ml-1 mr-1 h-5 w-5 animate-pulse" ) : noInput ? (
aria-hidden="true" <Sparkles className="form-modal-play-icon" aria-hidden="true" />
/>
) : ( ) : (
<LucideSend <LucideSend className="form-modal-send-icon " aria-hidden="true" />
className="mr-2 h-5 w-5 rotate-[44deg] "
aria-hidden="true"
/>
)} )}
</button> </button>
</div> </div>

View file

@ -1,5 +1,5 @@
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react"; import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
import { FC, memo, useState } from "react"; import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { programmingLanguages } from "../../../../utils"; import { programmingLanguages } from "../../../../utils";
@ -9,7 +9,7 @@ interface Props {
value: string; value: string;
} }
export const CodeBlock: FC<Props> = memo(({ language, value }) => { export function CodeBlock({ language, value }) {
const [isCopied, setIsCopied] = useState<Boolean>(false); const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = () => { const copyToClipboard = () => {
@ -63,7 +63,7 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
</div> </div>
<SyntaxHighlighter <SyntaxHighlighter
className=" w-[47vw]" className="overflow-auto"
language={language} language={language}
style={oneDark} style={oneDark}
customStyle={{ margin: 0 }} customStyle={{ margin: 0 }}
@ -72,5 +72,5 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
</SyntaxHighlighter> </SyntaxHighlighter>
</div> </div>
); );
}); }
CodeBlock.displayName = "CodeBlock"; CodeBlock.displayName = "CodeBlock";

View file

@ -1,18 +1,18 @@
import Convert from "ansi-to-html"; import Convert from "ansi-to-html";
import { ChevronDown } from "lucide-react"; import { ChevronDown } from "lucide-react";
import { useState } from "react"; import { useMemo, useState } from "react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax"; import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import remarkMath from "remark-math"; import remarkMath from "remark-math";
import MaleTechnology from "../../../assets/male-technologist.png"; import MaleTechnology from "../../../assets/male-technologist.png";
import Robot from "../../../assets/robot.png"; import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import { THOUGHTS_ICON } from "../../../constants"; import { THOUGHTS_ICON } from "../../../constants";
import { ChatMessageType } from "../../../types/chat"; import { ChatMessageType } from "../../../types/chat";
import { classNames } from "../../../utils"; import { classNames } from "../../../utils";
import FileCard from "../fileComponent"; import FileCard from "../fileComponent";
import { CodeBlock } from "./codeBlock"; import { CodeBlock } from "./codeBlock";
export default function ChatMessage({ export default function ChatMessage({
chat, chat,
lockChat, lockChat,
@ -24,96 +24,111 @@ export default function ChatMessage({
}) { }) {
const convert = new Convert({ newline: true }); const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true); const [hidden, setHidden] = useState(true);
const [template, setTemplate] = useState(chat.template); const template = chat.template;
const [promptOpen, setPromptOpen] = useState(false); const [promptOpen, setPromptOpen] = useState(false);
return ( return (
<div <div
className={classNames( className={classNames("form-modal-chat-position", chat.isSend ? "" : " ")}
"flex w-full px-2 py-6 pl-4 pr-9",
chat.isSend ? "" : " "
)}
> >
<div className={classNames("mb-3 ml-3 mr-6 mt-1 ")}> <div className={classNames("form-modal-chatbot-icon ")}>
{!chat.isSend ? ( {!chat.isSend ? (
<div className="flex flex-col items-center gap-1"> <div className="form-modal-chat-image">
<div className="relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#afe6ef] p-5 text-2xl "> <div className="form-modal-chat-bot-icon ">
<img src={Robot} className="absolute scale-[60%]" /> <img
src={Robot}
className="form-modal-chat-icon-img"
alt="robot_image"
/>
</div> </div>
</div> </div>
) : ( ) : (
<div className="flex flex-col items-center gap-1"> <div className="form-modal-chat-image">
<div className="relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#aface9] p-5 text-2xl "> <div className="form-modal-chat-user-icon ">
<img src={MaleTechnology} className="absolute scale-[60%]" /> <img
src={MaleTechnology}
className="form-modal-chat-icon-img"
alt="male_technology"
/>
</div> </div>
</div> </div>
)} )}
</div> </div>
{!chat.isSend ? ( {!chat.isSend ? (
<div className="flex w-full flex-1 text-start"> <div className="form-modal-chat-text-position">
<div className="relative inline-block w-full text-start text-sm font-normal text-muted-foreground"> <div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && ( {hidden && chat.thought && chat.thought !== "" && (
<div <div
onClick={() => setHidden((prev) => !prev)} onClick={() => setHidden((prev) => !prev)}
className="absolute -left-8 -top-3 cursor-pointer" className="form-modal-chat-icon-div"
> >
<THOUGHTS_ICON className="h-4 w-4 animate-bounce dark:text-white" /> <THOUGHTS_ICON className="form-modal-chat-icon" />
</div> </div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && ( {chat.thought && chat.thought !== "" && !hidden && (
<div <SanitizedHTMLWrapper
className=" form-modal-chat-thought"
content={convert.toHtml(chat.thought)}
onClick={() => setHidden((prev) => !prev)} onClick={() => setHidden((prev) => !prev)}
className=" ml-3 inline-block h-full w-[95%] cursor-pointer overflow-scroll rounded-md border />
border-gray-300 bg-muted px-2 text-start text-primary scrollbar-hide dark:border-gray-500 dark:bg-gray-800"
dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought),
}}
></div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && <br></br>} {chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full"> <div className="w-full">
<div className="w-full dark:text-white"> <div className="w-full dark:text-white">
<div className="w-full"> <div className="w-full">
<ReactMarkdown {useMemo(
remarkPlugins={[remarkGfm, remarkMath]} () => (
rehypePlugins={[rehypeMathjax]} <ReactMarkdown
className="markdown prose inline-block break-words text-primary remarkPlugins={[remarkGfm, remarkMath]}
dark:prose-invert" rehypePlugins={[rehypeMathjax]}
components={{ className="markdown prose inline-block break-words text-primary
code({ node, inline, className, children, ...props }) { dark:prose-invert sm:max-w-[30vw] lg:max-w-[40vw]"
if (children.length) { components={{
if (children[0] == "▍") { code: ({
return ( node,
<span className="mt-1 animate-pulse cursor-default"> inline,
▍ className,
</span> children,
...props
}) => {
if (children.length) {
if (children[0] === "▍") {
return (
<span className="form-modal-markdown-span">
▍
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(
className || ""
); );
}
children[0] = (children[0] as string).replace( return !inline ? (
"`▍`", <CodeBlock
"▍" key={Math.random()}
); language={(match && match[1]) || ""}
} value={String(children).replace(/\n$/, "")}
{...props}
const match = /language-(\w+)/.exec(className || ""); />
) : (
return !inline ? ( <code className={className} {...props}>
<CodeBlock {children}
key={Math.random()} </code>
language={(match && match[1]) || ""} );
value={String(children).replace(/\n$/, "")} },
{...props} }}
/> >
) : ( {chat.message.toString()}
<code className={className} {...props}> </ReactMarkdown>
{children} ),
</code> [chat.message, chat.message.toString()]
); )}
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
</div> </div>
{chat.files && ( {chat.files && (
<div className="my-2 w-full"> <div className="my-2 w-full">
@ -136,51 +151,57 @@ export default function ChatMessage({
</div> </div>
) : ( ) : (
<div> <div>
<button {template ? (
className="mb-2 flex items-center gap-4 rounded-md border border-ring/60 bg-background px-4 py-2 text-base font-semibold" <>
onClick={() => { <button
setPromptOpen((old) => !old); className="form-modal-initial-prompt-btn"
}} onClick={() => {
> setPromptOpen((old) => !old);
Initial Prompt }}
<ChevronDown >
className={ Display Prompt
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "") <ChevronDown
} className={
/> "h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
</button> }
<span className="prose inline-block break-words text-primary dark:prose-invert"> />
{promptOpen </button>
? template.split("\n").map((line, index) => { <span className="prose inline-block break-words text-primary dark:prose-invert">
const regex = /{([^}]+)}/g; {promptOpen
let match; ? template?.split("\n")?.map((line, index) => {
let parts = []; const regex = /{([^}]+)}/g;
let lastIndex = 0; let match;
while ((match = regex.exec(line)) !== null) { let parts = [];
// Push text up to the match let lastIndex = 0;
if (match.index !== lastIndex) { while ((match = regex.exec(line)) !== null) {
parts.push(line.substring(lastIndex, match.index)); // Push text up to the match
} if (match.index !== lastIndex) {
// Push div with matched text parts.push(line.substring(lastIndex, match.index));
if (chat.message[match[1]]) { }
parts.push( // Push div with matched text
<span className="my-1 rounded-md bg-indigo-100"> if (chat.message[match[1]]) {
{chat.message[match[1]]} parts.push(
</span> <span className="chat-message-highlight">
); {chat.message[match[1]]}
} </span>
);
}
// Update last index // Update last index
lastIndex = regex.lastIndex; lastIndex = regex.lastIndex;
} }
// Push text after the last match // Push text after the last match
if (lastIndex !== line.length) { if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex)); parts.push(line.substring(lastIndex));
} }
return <p>{parts}</p>; return <p>{parts}</p>;
}) })
: chat.message[chat.chatKey]} : chat.message[chat.chatKey]}
</span> </span>
</>
) : (
<span>{chat.message[chat.chatKey]}</span>
)}
</div> </div>
)} )}
</div> </div>

View file

@ -50,7 +50,7 @@ export default function FormModal({
const handleKeys = formKeysData.handle_keys; const handleKeys = formKeysData.handle_keys;
const keyToUse = Object.keys(inputKeys).find( const keyToUse = Object.keys(inputKeys).find(
(k) => !handleKeys.some((j) => j === k) (k) => !handleKeys.some((j) => j === k) && inputKeys[k] === ""
); );
return inputKeys[keyToUse]; return inputKeys[keyToUse];
@ -63,15 +63,19 @@ export default function FormModal({
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]); const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const ws = useRef<WebSocket | null>(null); const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false); const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open); const isOpen = useRef(open);
const messagesRef = useRef(null); const messagesRef = useRef(null);
const id = useRef(flow.id); const id = useRef(flow.id);
const tabsStateFlowId = tabsState[flow.id];
const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData;
const [chatKey, setChatKey] = useState( const [chatKey, setChatKey] = useState(
Object.keys(tabsState[flow.id].formKeysData.input_keys).find( Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) => !tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k) (k) =>
!tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k) &&
tabsState[flow.id].formKeysData.input_keys[k] === ""
) )
); );
@ -86,7 +90,7 @@ export default function FormModal({
}, [open]); }, [open]);
useEffect(() => { useEffect(() => {
id.current = flow.id; id.current = flow.id;
}, [flow.id, tabsState[flow.id], tabsState[flow.id].formKeysData]); }, [flow.id, tabsStateFlowId, tabsStateFlowIdFormKeysData]);
var isStream = false; var isStream = false;
@ -290,6 +294,7 @@ export default function FormModal({
ws.current.close(); ws.current.close();
} }
}; };
// do not add connectWS on dependencies array
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -301,6 +306,7 @@ export default function FormModal({
connectWS(); connectWS();
setLockChat(false); setLockChat(false);
} }
// do not add connectWS on dependencies array
}, [lockChat]); }, [lockChat]);
async function sendAll(data: sendAllProps) { async function sendAll(data: sendAllProps) {
@ -331,41 +337,35 @@ export default function FormModal({
}, [open]); }, [open]);
function sendMessage() { function sendMessage() {
if (chatValue !== "") { let nodeValidationErrors = validateNodes(reactFlowInstance);
let nodeValidationErrors = validateNodes(reactFlowInstance); if (nodeValidationErrors.length === 0) {
if (nodeValidationErrors.length === 0) { setLockChat(true);
setLockChat(true); let inputs = tabsState[id.current].formKeysData.input_keys;
let inputs = tabsState[id.current].formKeysData.input_keys; setChatValue("");
setChatValue(""); const message = inputs;
const message = inputs; addChatHistory(
addChatHistory( message,
message, true,
true, chatKey,
chatKey, tabsState[flow.id].formKeysData.template
tabsState[flow.id].formKeysData.template );
); sendAll({
sendAll({ ...reactFlowInstance.toObject(),
...reactFlowInstance.toObject(), inputs: inputs,
inputs: inputs, chatHistory,
chatHistory, name: flow.name,
name: flow.name, description: flow.description,
description: flow.description, });
}); setTabsState((old) => {
setTabsState((old) => { if (!chatKey) return old;
let newTabsState = _.cloneDeep(old); let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[chatKey] = ""; newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
return newTabsState; return newTabsState;
}); });
} else {
setErrorData({
title: "Oops! Looks like you missed some required information:",
list: nodeValidationErrors,
});
}
} else { } else {
setErrorData({ setErrorData({
title: "Error sending message", title: "Oops! Looks like you missed some required information:",
list: ["The message cannot be empty."], list: nodeValidationErrors,
}); });
} }
} }
@ -383,6 +383,9 @@ export default function FormModal({
if (checked === true) { if (checked === true) {
setChatKey(i); setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]); setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
} else {
setChatKey(null);
setChatValue("");
} }
} }
return ( return (
@ -401,19 +404,19 @@ export default function FormModal({
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription> <DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="mt-2 flex h-[80vh] w-full "> <div className="form-modal-iv-box ">
<div className="mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide"> <div className="form-modal-iv-size">
<div className="flex items-center py-2"> <div className="file-component-arrangement">
<Variable className=" -ml-px mr-1 h-4 w-4 text-primary"></Variable> <Variable className=" file-component-variable"></Variable>
<span className="text-md font-semibold text-primary"> <span className="file-component-variables-span text-md">
Input Variables Input Variables
</span> </span>
</div> </div>
<div className="flex items-center justify-between pt-2"> <div className="file-component-variables-title">
<div className="mr-2.5 flex items-center"> <div className="file-component-variables-div">
<span className="text-sm font-medium text-primary">Name</span> <span className="text-sm font-medium text-primary">Name</span>
</div> </div>
<div className="mr-2.5 flex items-center"> <div className="file-component-variables-div">
<span className="text-sm font-medium text-primary"> <span className="text-sm font-medium text-primary">
Chat Input Chat Input
</span> </span>
@ -422,10 +425,10 @@ export default function FormModal({
<Accordion type="multiple" className="w-full"> <Accordion type="multiple" className="w-full">
{Object.keys(tabsState[id.current].formKeysData.input_keys).map( {Object.keys(tabsState[id.current].formKeysData.input_keys).map(
(i, k) => ( (i, k) => (
<div className="flex items-start gap-3" key={k}> <div className="file-component-accordion-div" key={k}>
<AccordionItem className="w-full" key={k} value={i}> <AccordionItem className="w-full" key={k} value={i}>
<AccordionTrigger className="flex gap-2"> <AccordionTrigger className="flex gap-2">
<div className="flex w-full items-center justify-between"> <div className="file-component-badge-div">
<Badge variant="gray" size="md"> <Badge variant="gray" size="md">
{i} {i}
</Badge> </Badge>
@ -450,7 +453,7 @@ export default function FormModal({
</div> </div>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent> <AccordionContent>
<div className="flex flex-col gap-2 p-1"> <div className="file-component-tab-column">
{tabsState[ {tabsState[
id.current id.current
].formKeysData.handle_keys.some((t) => t === i) && ( ].formKeysData.handle_keys.some((t) => t === i) && (
@ -482,21 +485,21 @@ export default function FormModal({
)} )}
{tabsState[id.current].formKeysData.memory_keys.map((i, k) => ( {tabsState[id.current].formKeysData.memory_keys.map((i, k) => (
<AccordionItem key={k} value={i}> <AccordionItem key={k} value={i}>
<div className="group flex flex-1 items-center justify-between py-4 text-sm font-normal text-muted-foreground transition-all"> <div className="tab-accordion-badge-div group">
<div className="group-hover:underline"> <div className="group-hover:underline">
<Badge size="md" variant="gray"> <Badge size="md" variant="gray">
{i} {i}
</Badge> </Badge>
</div> </div>
Used as Memory Key Used as memory key
</div> </div>
</AccordionItem> </AccordionItem>
))} ))}
</Accordion> </Accordion>
</div> </div>
<div className="flex w-full flex-1 flex-col"> <div className="eraser-column-arrangement">
<div className="relative flex h-full w-full flex-col rounded-md border bg-muted"> <div className="eraser-size">
<div className="absolute right-3 top-3 z-50"> <div className="eraser-position">
<button disabled={lockChat} onClick={() => clearChat()}> <button disabled={lockChat} onClick={() => clearChat()}>
<Eraser <Eraser
className={classNames( className={classNames(
@ -509,30 +512,29 @@ export default function FormModal({
/> />
</button> </button>
</div> </div>
<div <div ref={messagesRef} className="chat-message-div">
ref={messagesRef}
className="flex h-full w-full flex-col items-center overflow-scroll scrollbar-hide"
>
{chatHistory.length > 0 ? ( {chatHistory.length > 0 ? (
chatHistory.map((c, i) => ( chatHistory.map((c, i) => (
<ChatMessage <ChatMessage
lockChat={lockChat} lockChat={lockChat}
chat={c} chat={c}
lastMessage={chatHistory.length - 1 == i ? true : false} lastMessage={
chatHistory.length - 1 === i ? true : false
}
key={i} key={i}
/> />
)) ))
) : ( ) : (
<div className="flex h-full w-full flex-col items-center justify-center text-center align-middle"> <div className="chat-alert-box">
<span> <span>
👋{" "} 👋{" "}
<span className="text-lg text-gray-600 dark:text-gray-300"> <span className="langflow-chat-span">
LangFlow Chat LangFlow Chat
</span> </span>
</span> </span>
<br /> <br />
<div className="w-2/4 rounded-md border border-gray-200 bg-muted px-6 py-8 dark:border-gray-700 dark:bg-gray-900"> <div className="langflow-chat-desc">
<span className="text-base text-gray-500"> <span className="langflow-chat-desc-span">
Start a conversation and click the agent's thoughts{" "} Start a conversation and click the agent's thoughts{" "}
<span> <span>
<THOUGHTS_ICON className="mx-1 inline h-5 w-5 animate-bounce " /> <THOUGHTS_ICON className="mx-1 inline h-5 w-5 animate-bounce " />
@ -544,10 +546,11 @@ export default function FormModal({
)} )}
<div ref={ref}></div> <div ref={ref}></div>
</div> </div>
<div className="flex w-full flex-col items-center justify-between px-8 pb-6"> <div className="langflow-chat-input-div">
<div className="relative w-full rounded-md shadow-sm"> <div className="langflow-chat-input">
<ChatInput <ChatInput
chatValue={chatValue} chatValue={chatValue}
noInput={!chatKey}
lockChat={lockChat} lockChat={lockChat}
sendMessage={sendMessage} sendMessage={sendMessage}
setChatValue={(value) => { setChatValue={(value) => {

View file

@ -1,24 +1,12 @@
import DOMPurify from "dompurify";
import { FileText, Variable } from "lucide-react"; import { FileText, Variable } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { import { DialogTitle } from "../../components/ui/dialog";
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import { import { PROMPT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE } from "../../constants";
HIGHLIGH_CSS,
PROMPT_DIALOG_SUBTITLE,
TEXT_DIALOG_SUBTITLE,
} from "../../constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
@ -32,6 +20,7 @@ import {
regexHighlight, regexHighlight,
varHighlightHTML, varHighlightHTML,
} from "../../utils"; } from "../../utils";
import BaseModal from "../baseModal";
export default function GenericModal({ export default function GenericModal({
field_name = "", field_name = "",
@ -57,7 +46,6 @@ export default function GenericModal({
const [myModalType] = useState(type); const [myModalType] = useState(type);
const [inputValue, setInputValue] = useState(value); const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true); const [isEdit, setIsEdit] = useState(true);
const [wordsHighlightInvalid, setWordsHighlightInvalid] = useState([]);
const [wordsHighlight, setWordsHighlight] = useState([]); const [wordsHighlight, setWordsHighlight] = useState([]);
const { dark } = useContext(darkContext); const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData, setNoticeData } = const { setErrorData, setSuccessData, setNoticeData } =
@ -102,15 +90,14 @@ export default function GenericModal({
(word) => !invalid_chars.includes(word) (word) => !invalid_chars.includes(word)
); );
setWordsHighlightInvalid(invalid_chars);
setWordsHighlight(filteredWordsHighlight); setWordsHighlight(filteredWordsHighlight);
} }
useEffect(() => { useEffect(() => {
if (type == TypeModal.PROMPT && inputValue && inputValue != "") { if (type === TypeModal.PROMPT && inputValue && inputValue != "") {
checkVariables(inputValue); checkVariables(inputValue);
} }
}, []); }, [inputValue, type]);
const coloredContent = (inputValue || "") const coloredContent = (inputValue || "")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
@ -120,13 +107,13 @@ export default function GenericModal({
const TextAreaContentView = () => { const TextAreaContentView = () => {
return ( return (
<div <SanitizedHTMLWrapper
className={HIGHLIGH_CSS} className="code-highlight"
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(coloredContent) }} content={coloredContent}
suppressContentEditableWarning={true}
onClick={() => { onClick={() => {
setIsEdit(true); setIsEdit(true);
}} }}
suppressWarning={true}
/> />
); );
}; };
@ -135,10 +122,9 @@ export default function GenericModal({
postValidatePrompt(field_name, inputValue, nodeClass) postValidatePrompt(field_name, inputValue, nodeClass)
.then((apiReturn) => { .then((apiReturn) => {
if (apiReturn.data) { if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node); setNodeClass(apiReturn.data?.frontend_node);
let inputVariables = apiReturn.data.input_variables ?? [];
let inputVariables = apiReturn.data.input_variables; if (inputVariables && inputVariables.length === 0) {
if (inputVariables.length === 0) {
setIsEdit(true); setIsEdit(true);
setNoticeData({ setNoticeData({
title: "Your template does not have any variables.", title: "Your template does not have any variables.",
@ -159,143 +145,147 @@ export default function GenericModal({
} }
}) })
.catch((error) => { .catch((error) => {
console.log(error);
setIsEdit(true); setIsEdit(true);
return setErrorData({ return setErrorData({
title: "There is something wrong with this prompt, please review it", title: "There is something wrong with this prompt, please review it",
list: [error.response.data.detail], list: [error?.response?.data?.detail],
}); });
}); });
} }
return ( return (
<Dialog open={true} onOpenChange={setModalOpen}> <BaseModal open={true} setOpen={setModalOpen}>
<DialogTrigger></DialogTrigger> <BaseModal.Header
<DialogContent className="min-w-[80vw] gap-2"> description={(() => {
<DialogHeader> switch (myModalTitle) {
<DialogTitle className="flex items-center"> case "Edit Text":
<span className="pr-2">{myModalTitle}</span> return TEXT_DIALOG_SUBTITLE;
<FileText
strokeWidth={1.5}
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>
{(() => {
switch (myModalTitle) {
case "Edit Text":
return TEXT_DIALOG_SUBTITLE;
case "Edit Prompt": case "Edit Prompt":
return PROMPT_DIALOG_SUBTITLE; return PROMPT_DIALOG_SUBTITLE;
default: default:
return null; return null;
} }
})()} })()}
</DialogDescription> >
</DialogHeader> <DialogTitle className="flex items-center">
<span className="pr-2">{myModalTitle}</span>
<div <FileText
className={classNames( strokeWidth={1.5}
!isEdit ? "rounded-lg border" : "", className="h-6 w-6 pl-1 text-primary "
"flex h-[55vh] w-full" aria-hidden="true"
)} />
> </DialogTitle>
{type == TypeModal.PROMPT && isEdit ? ( </BaseModal.Header>
<Textarea <BaseModal.Content>
ref={ref} <div className="flex h-full flex-col">
className="form-input h-full w-full rounded-lg border-gray-300 custom-scroll focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white" <div
value={inputValue} className={classNames(
onBlur={() => { !isEdit ? "rounded-lg border" : "",
blur(); "flex h-full w-full"
setIsEdit(false); )}
}}
autoFocus
onChange={(e) => {
setInputValue(e.target.value);
checkVariables(e.target.value);
}}
placeholder="Type message here."
/>
) : type == TypeModal.PROMPT && !isEdit ? (
<TextAreaContentView />
) : type != TypeModal.PROMPT ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Type message here."
/>
) : (
<></>
)}
</div>
{type == TypeModal.PROMPT && (
<>
<div className="sm:6/6 mr-28 mt-3 h-[60px] overflow-y-auto custom-scroll">
<div className="flex flex-wrap items-center">
<Variable className=" -ml-px mr-1 flex h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary">
Input Variables:
</span>
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
content={word.replace(/[{}]/g, "")}
asChild={false}
>
<Badge
key={index}
variant="gray"
size="md"
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) + "..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
</div>
</>
)}
<DialogFooter>
<Button
className="mt-3"
onClick={() => {
switch (myModalType) {
case 1:
setValue(inputValue);
setModalOpen(false);
break;
case 2:
!inputValue || inputValue == ""
? setModalOpen(false)
: validatePrompt(false);
break;
default:
break;
}
}}
type="submit"
> >
{myButtonText} {type === TypeModal.PROMPT && isEdit ? (
</Button> <Textarea
</DialogFooter> ref={ref}
</DialogContent> className="form-input h-full w-full rounded-lg custom-scroll focus-visible:ring-1"
</Dialog> value={inputValue}
onBlur={() => {
setIsEdit(false);
}}
autoFocus
onChange={(e) => {
setInputValue(e.target.value);
checkVariables(e.target.value);
}}
placeholder="Type message here."
/>
) : type === TypeModal.PROMPT && !isEdit ? (
<TextAreaContentView />
) : type !== TypeModal.PROMPT ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Type message here."
/>
) : (
<></>
)}
</div>
<div className="mt-6 flex h-fit w-full items-end justify-between">
<div className="mb-auto flex-1">
{type === TypeModal.PROMPT && (
<div className=" mr-2">
<div className="max-h-20 overflow-y-auto custom-scroll">
<div className="flex flex-wrap items-center">
<Variable className=" -ml-px mr-1 flex h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary">
Prompt Variables:
</span>
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
content={word.replace(/[{}]/g, "")}
asChild={false}
>
<Badge
key={index}
variant="gray"
size="md"
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) +
"..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
</div>
<span className="mt-1 text-xs text-muted-foreground">
Prompt variables can be created with any chosen name inside
curly brackets, e.g. {"{variable_name}"}
</span>
</div>
)}
</div>
<Button
onClick={() => {
switch (myModalType) {
case 1:
setValue(inputValue);
setModalOpen(false);
break;
case 2:
!inputValue || inputValue === ""
? setModalOpen(false)
: validatePrompt(false);
break;
default:
break;
}
}}
type="submit"
>
{myButtonText}
</Button>
</div>
</div>
</BaseModal.Content>
</BaseModal>
); );
} }

View file

@ -1,151 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { DocumentTextIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { checkPrompt } from "../../controllers/API";
export default function PromptAreaModal({
value,
setValue,
}: {
setValue: (value: string) => void;
value: string;
}) {
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("prompt");
closePopUp();
}, 300);
}
}
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="node-modal-div" />
</Transition.Child>
<div className="node-modal-dialog-arrangement">
<div className="node-modal-dialog-div">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="node-modal-dialog-panel">
<div className=" node-modal-dialog-panel-div ">
<button
type="button"
className="node-modal-dialog-button"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="node-modal-dialog-icon-div">
<div className="node-modal-icon-arrangement">
<div className="prompt-modal-icon-box">
<DocumentTextIcon
className="prompt-modal-icon"
aria-hidden="true"
/>
</div>
<div className="node-modal-title-div ">
<Dialog.Title as="h3" className="node-modal-title">
Edit Prompt
</Dialog.Title>
</div>
</div>
<div className="prompt-modal-txtarea-arrangement">
<div className="flex-max-width h-full">
<div className="prompt-modal-txtarea-box">
<textarea
ref={ref}
className="prompt-modal-txtarea"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="node-modal-button-box">
<button
type="button"
className="node-modal-button"
onClick={() => {
checkPrompt(myValue)
.then((apiReturn) => {
if (apiReturn.data) {
let inputVariables =
apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setErrorData({
title:
"The template you are attempting to use does not contain any variables for data entry.",
});
} else {
setSuccessData({
title: "Prompt is ready",
});
setModalOpen(false);
setValue(myValue);
}
} else {
setErrorData({
title: "Something went wrong, please try again",
});
}
})
.catch((error) => {
return setErrorData({
title:
"There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
});
}}
>
Check & Save
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -1,121 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import {
ClipboardDocumentListIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
export default function TextAreaModal({
value,
setValue,
}: {
setValue: (value: string) => void;
value: string | string[];
}) {
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("textarea");
closePopUp();
}, 300);
}
}
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="node-modal-div" />
</Transition.Child>
<div className="node-modal-dialog-arrangement">
<div className="node-modal-dialog-div">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="node-modal-dialog-panel">
<div className=" node-modal-dialog-panel-div">
<button
type="button"
className="node-modal-dialog-button"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="node-modal-dialog-icon-div">
<div className="node-modal-icon-arrangement">
<div className="prompt-modal-icon-box">
<ClipboardDocumentListIcon
className="prompt-modal-icon"
aria-hidden="true"
/>
</div>
<div className="node-modal-title-div">
<Dialog.Title as="h3" className="node-modal-title">
Edit text
</Dialog.Title>
</div>
</div>
<div className="txtarea-modal-arrangement">
<div className="flex h-full w-full">
<div className="txtarea-modal-box">
<textarea
ref={ref}
className="txtarea-modal-input"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="node-modal-button-box">
<button
type="button"
className="node-modal-button"
onClick={() => {
setModalOpen(false);
}}
>
Finish editing
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -12,9 +12,8 @@ const ConnectionLineComponent = ({
<path <path
fill="none" fill="none"
// ! Replace hash # colors here // ! Replace hash # colors here
stroke="#222"
strokeWidth={1.5} strokeWidth={1.5}
className="animated " className="animated stroke-connection "
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`} d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
style={connectionLineStyle} style={connectionLineStyle}
/> />

View file

@ -184,11 +184,11 @@ export default function Page({ flow }: { flow: FlowType }) {
addEdge( addEdge(
{ {
...params, ...params,
style: { stroke: "inherit" }, style: { stroke: "#555" },
className: className:
params.targetHandle.split("|")[0] === "Text" (params.targetHandle.split("|")[0] === "Text"
? "stroke-foreground " ? "stroke-foreground "
: "stroke-foreground ", : "stroke-foreground ") + " stroke-connection",
animated: params.targetHandle.split("|")[0] === "Text", animated: params.targetHandle.split("|")[0] === "Text",
}, },
eds eds
@ -288,7 +288,7 @@ export default function Page({ flow }: { flow: FlowType }) {
} }
}, },
// Specify dependencies for useCallback // Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot] [getNodeId, reactFlowInstance, setNodes, takeSnapshot]
); );
useEffect(() => { useEffect(() => {
@ -322,7 +322,7 @@ export default function Page({ flow }: { flow: FlowType }) {
setEdges((els) => updateEdge(oldEdge, newConnection, els)); setEdges((els) => updateEdge(oldEdge, newConnection, els));
} }
}, },
[] [reactFlowInstance, setEdges]
); );
const onEdgeUpdateEnd = useCallback((_, edge) => { const onEdgeUpdateEnd = useCallback((_, edge) => {
@ -338,7 +338,8 @@ export default function Page({ flow }: { flow: FlowType }) {
const onSelectionEnd = useCallback(() => { const onSelectionEnd = useCallback(() => {
setSelectionEnded(true); setSelectionEnded(true);
}, []); }, []);
const onSelectionStart = useCallback(() => { const onSelectionStart = useCallback((event) => {
event.preventDefault();
setSelectionEnded(false); setSelectionEnded(false);
}, []); }, []);
@ -371,10 +372,11 @@ export default function Page({ flow }: { flow: FlowType }) {
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
onMove={() => { onMove={() => {
updateFlow({ if (reactFlowInstance)
...flow, updateFlow({
data: reactFlowInstance.toObject(), ...flow,
}); data: reactFlowInstance.toObject(),
});
}} }}
edges={edges} edges={edges}
onPaneClick={() => { onPaneClick={() => {
@ -409,7 +411,6 @@ export default function Page({ flow }: { flow: FlowType }) {
nodesDraggable={!disableCopyPaste} nodesDraggable={!disableCopyPaste}
panOnDrag={!disableCopyPaste} panOnDrag={!disableCopyPaste}
zoomOnDoubleClick={!disableCopyPaste} zoomOnDoubleClick={!disableCopyPaste}
selectNodesOnDrag={false}
className="theme-attribution" className="theme-attribution"
minZoom={0.01} minZoom={0.01}
maxZoom={8} maxZoom={8}

View file

@ -162,7 +162,9 @@ export default function ExtraSidebar() {
<div key={k} data-tooltip-id={t}> <div key={k} data-tooltip-id={t}>
<div <div
draggable draggable
className={"side-bar-components-border"} className={
"side-bar-components-border bg-background"
}
style={{ style={{
borderLeftColor: borderLeftColor:
nodeColors[d] ?? nodeColors.unknown, nodeColors[d] ?? nodeColors.unknown,

View file

@ -148,7 +148,7 @@ export type ShadToolTipType = {
asChild?: boolean; asChild?: boolean;
children?: ReactElement; children?: ReactElement;
delayDuration?: number; delayDuration?: number;
style?: string; styleClasses?: string;
}; };
export type TextHighlightType = { export type TextHighlightType = {

View file

@ -27,3 +27,11 @@ export type FlowStyleType = {
color: string; color: string;
flow_id: string; flow_id: string;
}; };
export type TweaksType = Array<
{
[key: string]: {
output_key?: string;
};
} & FlowStyleType
>;

View file

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { FlowType } from "../flow"; import { FlowType, TweaksType } from "../flow";
export type TabsContextType = { export type TabsContextType = {
saveFlow: (flow: FlowType) => Promise<void>; saveFlow: (flow: FlowType) => Promise<void>;
@ -32,8 +32,8 @@ export type TabsContextType = {
) => void; ) => void;
lastCopiedSelection: { nodes: any; edges: any }; lastCopiedSelection: { nodes: any; edges: any };
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void; setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
setTweak: (tweak: any) => void; setTweak: (tweak: TweaksType) => void;
getTweak: any; getTweak: TweaksType[];
}; };
export type TabsState = { export type TabsState = {

View file

@ -0,0 +1,10 @@
import { Edge } from "reactflow";
import { NodeType } from "../flow";
export type cleanEdgesType = {
flow: {
edges: Edge[];
nodes: NodeType[];
};
updateEdge: (edge: Edge[]) => void;
};

View file

@ -0,0 +1,46 @@
import _ from "lodash";
import { cleanEdgesType } from "./../types/utils/reactflowUtils";
export function cleanEdges({
flow: { edges, nodes },
updateEdge,
}: cleanEdgesType) {
let newEdges = _.cloneDeep(edges);
edges.forEach((edge) => {
// check if the source and target node still exists
const sourceNode = nodes.find((node) => node.id === edge.source);
const targetNode = nodes.find((node) => node.id === edge.target);
if (!sourceNode || !targetNode) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
// check if the source and target handle still exists
if (sourceNode && targetNode) {
const sourceHandle = edge.sourceHandle; //right
const targetHandle = edge.targetHandle; //left
if (targetHandle) {
const field = targetHandle.split("|")[1];
const id =
(targetNode.data.node.template[field]?.input_types?.join(";") ??
targetNode.data.node.template[field]?.type) +
"|" +
field +
"|" +
targetNode.data.id;
if (id !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
if (sourceHandle) {
const id = [
sourceNode.data.type,
sourceNode.data.id,
...sourceNode.data.node.base_classes,
].join("|");
if (id !== sourceHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
}
});
updateEdge(newEdges);
}

View file

@ -35,7 +35,7 @@ import { EvernoteIcon } from "./icons/Evernote";
import { FBIcon } from "./icons/FacebookMessenger"; import { FBIcon } from "./icons/FacebookMessenger";
import { GitBookIcon } from "./icons/GitBook"; import { GitBookIcon } from "./icons/GitBook";
import { GoogleIcon } from "./icons/Google"; import { GoogleIcon } from "./icons/Google";
import { HugginFaceIcon } from "./icons/HuggingFace"; import { HuggingFaceIcon } from "./icons/HuggingFace";
import { IFixIcon } from "./icons/IFixIt"; import { IFixIcon } from "./icons/IFixIt";
import { MetaIcon } from "./icons/Meta"; import { MetaIcon } from "./icons/Meta";
import { MidjourneyIcon } from "./icons/Midjorney"; import { MidjourneyIcon } from "./icons/Midjorney";
@ -206,10 +206,10 @@ export const nodeIconsLucide: {
HNLoader: HackerNewsIcon as React.ForwardRefExoticComponent< HNLoader: HackerNewsIcon as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>> ComponentType<SVGProps<SVGSVGElement>>
>, >,
HuggingFaceHub: HugginFaceIcon as React.ForwardRefExoticComponent< HuggingFaceHub: HuggingFaceIcon as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>> ComponentType<SVGProps<SVGSVGElement>>
>, >,
HuggingFaceEmbeddings: HugginFaceIcon as React.ForwardRefExoticComponent< HuggingFaceEmbeddings: HuggingFaceIcon as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>> ComponentType<SVGProps<SVGSVGElement>>
>, >,
IFixitLoader: IFixIcon as React.ForwardRefExoticComponent< IFixitLoader: IFixIcon as React.ForwardRefExoticComponent<
@ -608,7 +608,7 @@ export function isValidConnection(
) || ) ||
targetHandle.split("|")[0] === "str" targetHandle.split("|")[0] === "str"
) { ) {
let targetNode = reactFlowInstance.getNode(target).data.node; let targetNode = reactFlowInstance?.getNode(target)?.data?.node;
if (!targetNode) { if (!targetNode) {
if ( if (
!reactFlowInstance !reactFlowInstance
@ -853,7 +853,7 @@ export function groupByFamily(data, baseClasses, left, type) {
}); });
}); });
if (left == false) { if (left === false) {
let groupedBy = arrOfType.filter((object, index, self) => { let groupedBy = arrOfType.filter((object, index, self) => {
const foundIndex = self.findIndex( const foundIndex = self.findIndex(
(o) => o.family === object.family && o.type === object.type (o) => o.family === object.family && o.type === object.type
@ -876,7 +876,7 @@ export function groupByFamily(data, baseClasses, left, type) {
}); });
} }
if (left == false) { if (left === false) {
let resFil = result.filter((group) => group.family === parentOutput); let resFil = result.filter((group) => group.family === parentOutput);
result = resFil; result = resFil;
} }
@ -903,8 +903,8 @@ export function groupByFamily(data, baseClasses, left, type) {
} }
groupedArray.forEach((object, index, self) => { groupedArray.forEach((object, index, self) => {
const findObj = arrOfLength.find((x) => x.type == object.family); const findObj = arrOfLength.find((x) => x.type === object.family);
if (object.component.length == findObj.length) { if (object.component.length === findObj.length) {
self[index]["type"] = ""; self[index]["type"] = "";
} else { } else {
self[index]["type"] = object.component.join(", "); self[index]["type"] = object.component.join(", ");
@ -1048,10 +1048,6 @@ export const INVALID_CHARACTERS = [
export const regexHighlight = /\{([^}]+)\}/g; export const regexHighlight = /\{([^}]+)\}/g;
export const varHighlightHTML = ({ name }: IVarHighlightType) => { export const varHighlightHTML = ({ name }: IVarHighlightType) => {
const html = `<div class="inline-flex items-center justify-center rounded-md font-medium text-primary bg-muted pb-1"> const html = `<span class="font-semibold chat-message-highlight">{${name}}</span>`;
<span class='opacity-60 pl-1'>{</span>
<span>${name}</span>
<span class='opacity-60 pr-1'>}</span>
</div>`;
return html; return html;
}; };

View file

@ -43,6 +43,7 @@ module.exports = {
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
}, },
colors: { colors: {
connection: "var(--connection)",
"almost-dark-gray": "var(--almost-dark-gray)", "almost-dark-gray": "var(--almost-dark-gray)",
"almost-light-blue": "var(--almost-light-blue)", "almost-light-blue": "var(--almost-light-blue)",
"almost-medium-blue": "var(--almost-medium-blue)", "almost-medium-blue": "var(--almost-medium-blue)",
@ -82,6 +83,8 @@ module.exports = {
"status-yellow": "var(--status-yellow)", "status-yellow": "var(--status-yellow)",
"success-background": "var(--success-background)", "success-background": "var(--success-background)",
"success-foreground": "var(--success-foreground)", "success-foreground": "var(--success-foreground)",
"chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)",
white: "var(--white)", white: "var(--white)",
border: "hsl(var(--border))", border: "hsl(var(--border))",
@ -127,18 +130,18 @@ module.exports = {
sans: ["var(--font-sans)", ...fontFamily.sans], sans: ["var(--font-sans)", ...fontFamily.sans],
}, },
keyframes: { keyframes: {
"accordion-down": { slideDown: {
from: { height: 0 }, from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" }, to: { height: 100 },
}, },
"accordion-up": { slideUp: {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 }, to: { height: 0 },
}, },
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "slideDown 300ms ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "slideUp 300ms ease-in",
}, },
}, },
}, },

View file

@ -16,30 +16,94 @@ def test_zero_shot_agent(client: TestClient):
} }
template = zero_shot_agent["template"] template = zero_shot_agent["template"]
assert template["llm_chain"] == { assert template["tools"] == {
"required": True, "required": True,
"placeholder": "", "placeholder": "",
"show": True, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "llm_chain", "name": "tools",
"type": "LLMChain", "type": "BaseTool",
"list": True,
"advanced": False,
"info": "",
}
# Additional assertions for other template variables
assert template["callback_manager"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "callback_manager",
"type": "BaseCallbackManager",
"list": False, "list": False,
"advanced": False, "advanced": False,
"info": "", "info": "",
} }
assert template["allowed_tools"] == { assert template["llm"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": True, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "allowed_tools", "name": "llm",
"type": "Tool", "type": "BaseLanguageModel",
"list": False,
"advanced": False,
"info": "",
}
assert template["output_parser"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "output_parser",
"type": "AgentOutputParser",
"list": False,
"advanced": False,
"info": "",
}
assert template["input_variables"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "input_variables",
"type": "str",
"list": True, "list": True,
"advanced": False, "advanced": False,
"info": "", "info": "",
} }
assert template["prefix"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": True,
"value": "Answer the following questions as best you can. You have access to the following tools:",
"password": False,
"name": "prefix",
"type": "str",
"list": False,
"advanced": False,
"info": "",
}
assert template["suffix"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": True,
"value": "Begin!\n\nQuestion: {input}\nThought:{agent_scratchpad}",
"password": False,
"name": "suffix",
"type": "str",
"list": False,
"advanced": False,
"info": "",
}
def test_json_agent(client: TestClient): def test_json_agent(client: TestClient):

View file

@ -42,7 +42,7 @@ def test_conversation_chain(client: TestClient):
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": True, "show": False,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "verbose", "name": "verbose",
@ -128,7 +128,7 @@ def test_llm_chain(client: TestClient):
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": True, "show": False,
"multiline": False, "multiline": False,
"value": False, "value": False,
"password": False, "password": False,
@ -228,7 +228,7 @@ def test_llm_math_chain(client: TestClient):
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": True, "show": False,
"multiline": False, "multiline": False,
"value": False, "value": False,
"password": False, "password": False,