Merge branch 'dev' into python_custom_node_component
This commit is contained in:
commit
a6fe4091ac
78 changed files with 1415 additions and 1669 deletions
30
README.md
30
README.md
|
|
@ -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
|
||||||
[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
|
[](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
|
||||||
|
[](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
|
||||||
[](https://star-history.com/#logspace-ai/langflow&Date)
|
[](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
38
poetry.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
11
render.yaml
Normal 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
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)"""
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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%">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
21
src/frontend/src/components/SanitizedHTMLWrapper/index.tsx
Normal file
21
src/frontend/src/components/SanitizedHTMLWrapper/index.tsx
Normal 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;
|
||||||
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]";
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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" &&
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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, "<")
|
.replace(/</g, "<")
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
>;
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
10
src/frontend/src/types/utils/reactflowUtils.ts
Normal file
10
src/frontend/src/types/utils/reactflowUtils.ts
Normal 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;
|
||||||
|
};
|
||||||
46
src/frontend/src/util/reactflowUtils.ts
Normal file
46
src/frontend/src/util/reactflowUtils.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue