Merge dev into refactor/utils

This commit is contained in:
igorrCarvalho 2024-05-22 20:35:08 -03:00
commit b0fed4316d
165 changed files with 6452 additions and 1782 deletions

View file

@ -141,7 +141,7 @@ backend:
@echo 'Setting up the environment' @echo 'Setting up the environment'
@make setup_env @make setup_env
make install_backend make install_backend
@-kill -9 `lsof -t -i:7860` @-kill -9 $(lsof -t -i:7860)
ifdef login ifdef login
@echo "Running backend autologin is $(login)"; @echo "Running backend autologin is $(login)";
LANGFLOW_AUTO_LOGIN=$(login) poetry run uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --env-file .env --loop asyncio LANGFLOW_AUTO_LOGIN=$(login) poetry run uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --env-file .env --loop asyncio

View file

@ -16,7 +16,7 @@ You have a new document loader called **MyCustomDocumentLoader** and it would lo
6. Add the dependency to [/documentloaders/\_\_init\_\_.py](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/components/documentloaders/__init__.py) as `from .MyCustomDocumentLoader import MyCustomDocumentLoader`. 6. Add the dependency to [/documentloaders/\_\_init\_\_.py](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/components/documentloaders/__init__.py) as `from .MyCustomDocumentLoader import MyCustomDocumentLoader`.
7. Add any new dependencies to the outer [pyproject.toml](https://github.com/langflow-ai/langflow/blob/dev/pyproject.toml#L27) file. 7. Add any new dependencies to the outer [pyproject.toml](https://github.com/langflow-ai/langflow/blob/dev/pyproject.toml#L27) file.
8. Submit documentation for your component. For this example, you'd submit documentation to the [loaders page](https://github.com/langflow-ai/langflow/blob/dev/docs/docs/components/loaders.mdx). 8. Submit documentation for your component. For this example, you'd submit documentation to the [loaders page](https://github.com/langflow-ai/langflow/blob/dev/docs/docs/components/loaders.mdx).
8. Submit your changes as a pull request. The Langflow team will have a look, suggest changes, and add your component to Langflow. 9. Submit your changes as a pull request. The Langflow team will have a look, suggest changes, and add your component to Langflow.
## User Sharing ## User Sharing
@ -27,21 +27,19 @@ If so, you can share your component on the Langflow store.
1. [Register at the Langflow store](https://www.langflow.store/login/). 1. [Register at the Langflow store](https://www.langflow.store/login/).
2. Undergo pre-validation before receiving an API key. 2. Undergo pre-validation before receiving an API key.
3. To deploy your amazing component directly to the Langflow store, without it being merged into the main source code, navigate to your flow, and then click **Share**. 3. To deploy your amazing component directly to the Langflow store, without it being merged into the main source code, navigate to your flow, and then click **Share**.
The share window appears: The share window appears:
<ZoomableImage <ZoomableImage
alt="Docusaurus themed image" alt="Docusaurus themed image"
sources={{ sources={{
light: "img/add-component-to-store.png", light: "img/add-component-to-store.png",
dark: "img/add-component-to-store.png", dark: "img/add-component-to-store.png",
}} }}
style={{ width: "50%", margin: "20px auto" }} style={{ width: "50%", margin: "20px auto" }}
/> />
5. Choose whether you want to flow to be public or private. 5. Choose whether you want to flow to be public or private.
You can also **Export** your flow as a JSON file from this window. You can also **Export** your flow as a JSON file from this window.
When you're ready to share the flow, click **Share Flow**. When you're ready to share the flow, click **Share Flow**.
You should see a **Flow shared successfully** popup. You should see a **Flow shared successfully** popup.
6. To confirm, navigate to the **Langflow Store** and filter results by **Created By Me**. You should see your new flow on the **Langflow Store**. 6. To confirm, navigate to the **Langflow Store** and filter results by **Created By Me**. You should see your new flow on the **Langflow Store**.

File diff suppressed because one or more lines are too long

352
poetry.lock generated
View file

@ -156,30 +156,31 @@ vine = ">=5.0.0,<6.0.0"
[[package]] [[package]]
name = "annotated-types" name = "annotated-types"
version = "0.6.0" version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated" description = "Reusable constraint types to use with typing.Annotated"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
] ]
[[package]] [[package]]
name = "anthropic" name = "anthropic"
version = "0.26.0" version = "0.26.1"
description = "The official Python library for the anthropic API" description = "The official Python library for the anthropic API"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "anthropic-0.26.0-py3-none-any.whl", hash = "sha256:38fc415561d71dcf263b89da0cc6ecec498379b56256fc4242e9128bc707b283"}, {file = "anthropic-0.26.1-py3-none-any.whl", hash = "sha256:2812b9b250b551ed8a1f0a7e6ae3f005654098994f45ebca5b5808bd154c9628"},
{file = "anthropic-0.26.0.tar.gz", hash = "sha256:6aaffeb05d515cf9788eef57150a5f827f3786883628ccac71dbe5671ab6f44e"}, {file = "anthropic-0.26.1.tar.gz", hash = "sha256:26680ff781a6f678a30a1dccd0743631e602b23a47719439ffdef5335fa167d8"},
] ]
[package.dependencies] [package.dependencies]
anyio = ">=3.5.0,<5" anyio = ">=3.5.0,<5"
distro = ">=1.7.0,<2" distro = ">=1.7.0,<2"
httpx = ">=0.23.0,<1" httpx = ">=0.23.0,<1"
jiter = ">=0.1.0,<1"
pydantic = ">=1.9.0,<3" pydantic = ">=1.9.0,<3"
sniffio = "*" sniffio = "*"
tokenizers = ">=0.13.0" tokenizers = ">=0.13.0"
@ -469,17 +470,17 @@ files = [
[[package]] [[package]]
name = "boto3" name = "boto3"
version = "1.34.108" version = "1.34.110"
description = "The AWS SDK for Python" description = "The AWS SDK for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "boto3-1.34.108-py3-none-any.whl", hash = "sha256:3601267d76cac17f1d4595c3d8d968dc15be074b79bfa3985187a02b328a0a5f"}, {file = "boto3-1.34.110-py3-none-any.whl", hash = "sha256:2fc871b4a5090716c7a71af52c462e539529227f4d4888fd04896d5028f9cedc"},
{file = "boto3-1.34.108.tar.gz", hash = "sha256:677723295151d29ff9b363598a20c1997c4e2af7e50669d9e428b757fe586a10"}, {file = "boto3-1.34.110.tar.gz", hash = "sha256:83ffe2273da7bdfdb480d85b0705f04e95bd110e9741f23328b7c76c03e6d53c"},
] ]
[package.dependencies] [package.dependencies]
botocore = ">=1.34.108,<1.35.0" botocore = ">=1.34.110,<1.35.0"
jmespath = ">=0.7.1,<2.0.0" jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0" s3transfer = ">=0.10.0,<0.11.0"
@ -488,13 +489,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]] [[package]]
name = "botocore" name = "botocore"
version = "1.34.108" version = "1.34.110"
description = "Low-level, data-driven core of boto 3." description = "Low-level, data-driven core of boto 3."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "botocore-1.34.108-py3-none-any.whl", hash = "sha256:b1b9d00804267669c5fcc36489269f7e9c43580c30f0885fbf669cf73cec720b"}, {file = "botocore-1.34.110-py3-none-any.whl", hash = "sha256:1edf3a825ec0a5edf238b2d42ad23305de11d5a71bb27d6f9a58b7e8862df1b6"},
{file = "botocore-1.34.108.tar.gz", hash = "sha256:384c9408c447631475dc41fdc9bf2e0f30c29c420d96bfe8b468bdc2bace3e13"}, {file = "botocore-1.34.110.tar.gz", hash = "sha256:b2c98c40ecf0b1facb9e61ceb7dfa28e61ae2456490554a16c8dbf99f20d6a18"},
] ]
[package.dependencies] [package.dependencies]
@ -1847,17 +1848,20 @@ vectorstore-mmr = ["numpy (>=1)", "simsimd (>=3)"]
[[package]] [[package]]
name = "emoji" name = "emoji"
version = "2.11.1" version = "2.12.1"
description = "Emoji for Python" description = "Emoji for Python"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = ">=3.7"
files = [ files = [
{file = "emoji-2.11.1-py2.py3-none-any.whl", hash = "sha256:b7ba25299bbf520cc8727848ae66b986da32aee27dc2887eaea2bff07226ce49"}, {file = "emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235"},
{file = "emoji-2.11.1.tar.gz", hash = "sha256:062ff0b3154b6219143f8b9f4b3e5c64c35bc2b146e6e2349ab5f29e218ce1ee"}, {file = "emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb"},
] ]
[package.dependencies]
typing-extensions = ">=4.7.0"
[package.extras] [package.extras]
dev = ["coverage", "coveralls", "pytest"] dev = ["coverage", "pytest (>=7.4.4)"]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
@ -2581,13 +2585,13 @@ httplib2 = ">=0.19.0"
[[package]] [[package]]
name = "google-cloud-aiplatform" name = "google-cloud-aiplatform"
version = "1.51.0" version = "1.52.0"
description = "Vertex AI API client library" description = "Vertex AI API client library"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "google-cloud-aiplatform-1.51.0.tar.gz", hash = "sha256:901993b4d14392452699c14cf23d72c01c5488ee36a7e00b23195e64d7d2f5ec"}, {file = "google-cloud-aiplatform-1.52.0.tar.gz", hash = "sha256:932a56e3050b4bc9a2c0630e6af3c0bd52f0bcf72b5dc01c059874231099edd3"},
{file = "google_cloud_aiplatform-1.51.0-py2.py3-none-any.whl", hash = "sha256:f2d3ff231454fe397f02fce1358680dea76ed7c74fc42e6c7e1aa1acb1648df3"}, {file = "google_cloud_aiplatform-1.52.0-py2.py3-none-any.whl", hash = "sha256:8c62f5d0ec39e008737ebba4875105ed7563dd0958f591f95dc7816e4b30f92a"},
] ]
[package.dependencies] [package.dependencies]
@ -2610,7 +2614,7 @@ datasets = ["pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0de
endpoint = ["requests (>=2.28.1)"] endpoint = ["requests (>=2.28.1)"]
full = ["cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nest-asyncio (>=1.0.0,<1.6.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "requests (>=2.28.1)", "starlette (>=0.17.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)"] full = ["cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nest-asyncio (>=1.0.0,<1.6.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "requests (>=2.28.1)", "starlette (>=0.17.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)"]
langchain = ["langchain (>=0.1.16,<0.2)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)"] langchain = ["langchain (>=0.1.16,<0.2)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)"]
langchain-testing = ["absl-py", "cloudpickle (>=2.2.1,<3.0)", "langchain (>=0.1.16,<0.2)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist"] langchain-testing = ["absl-py", "cloudpickle (>=2.2.1,<4.0)", "langchain (>=0.1.16,<0.2)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist"]
lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0dev)"] lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0dev)"]
metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"] metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"]
pipelines = ["pyyaml (>=5.3.1,<7)"] pipelines = ["pyyaml (>=5.3.1,<7)"]
@ -2620,7 +2624,7 @@ private-endpoints = ["requests (>=2.28.1)", "urllib3 (>=1.21.1,<1.27)"]
rapid-evaluation = ["nest-asyncio (>=1.0.0,<1.6.0)", "pandas (>=1.0.0,<2.2.0)"] rapid-evaluation = ["nest-asyncio (>=1.0.0,<1.6.0)", "pandas (>=1.0.0,<2.2.0)"]
ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)"] ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)"]
ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "ray[train] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "scikit-learn", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"] ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "ray[train] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "scikit-learn", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"]
reasoningengine = ["cloudpickle (>=2.2.1,<3.0)", "pydantic (>=2.6.3,<3)"] reasoningengine = ["cloudpickle (>=2.2.1,<4.0)", "pydantic (>=2.6.3,<3)"]
tensorboard = ["tensorflow (>=2.3.0,<3.0.0dev)"] tensorboard = ["tensorflow (>=2.3.0,<3.0.0dev)"]
testing = ["bigframes", "cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nest-asyncio (>=1.0.0,<1.6.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyfakefs", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (==2.13.0)", "tensorflow (==2.16.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0)", "torch (>=2.2.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost"] testing = ["bigframes", "cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nest-asyncio (>=1.0.0,<1.6.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyfakefs", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (==2.13.0)", "tensorflow (==2.16.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0)", "torch (>=2.2.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost"]
vizier = ["google-vizier (>=0.1.6)"] vizier = ["google-vizier (>=0.1.6)"]
@ -2628,13 +2632,13 @@ xai = ["tensorflow (>=2.3.0,<3.0.0dev)"]
[[package]] [[package]]
name = "google-cloud-bigquery" name = "google-cloud-bigquery"
version = "3.23.0" version = "3.23.1"
description = "Google BigQuery API client library" description = "Google BigQuery API client library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "google-cloud-bigquery-3.23.0.tar.gz", hash = "sha256:7ecdb207727d513b1bce1f213dbb926ed2e1d4f0122778de00f0e56d19d47a01"}, {file = "google-cloud-bigquery-3.23.1.tar.gz", hash = "sha256:4b4597f9291b42102c9667d3b4528f801d4c8f24ef2b12dd1ecb881273330955"},
{file = "google_cloud_bigquery-3.23.0-py2.py3-none-any.whl", hash = "sha256:dc0a4a47ab541a34aa1dc1f48539d88c091adc0637da7744d7fab6f3bc8886d5"}, {file = "google_cloud_bigquery-3.23.1-py2.py3-none-any.whl", hash = "sha256:9fb72884fdbec9c4643cea6b7f21e1ecf3eb61d5305f87493d271dc801647a9e"},
] ]
[package.dependencies] [package.dependencies]
@ -3001,61 +3005,61 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4
[[package]] [[package]]
name = "grpcio" name = "grpcio"
version = "1.63.0" version = "1.64.0"
description = "HTTP/2-based RPC framework" description = "HTTP/2-based RPC framework"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "grpcio-1.63.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2e93aca840c29d4ab5db93f94ed0a0ca899e241f2e8aec6334ab3575dc46125c"}, {file = "grpcio-1.64.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:3b09c3d9de95461214a11d82cc0e6a46a6f4e1f91834b50782f932895215e5db"},
{file = "grpcio-1.63.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:91b73d3f1340fefa1e1716c8c1ec9930c676d6b10a3513ab6c26004cb02d8b3f"}, {file = "grpcio-1.64.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:7e013428ab472892830287dd082b7d129f4d8afef49227a28223a77337555eaa"},
{file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b3afbd9d6827fa6f475a4f91db55e441113f6d3eb9b7ebb8fb806e5bb6d6bd0d"}, {file = "grpcio-1.64.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:02cc9cc3f816d30f7993d0d408043b4a7d6a02346d251694d8ab1f78cc723e7e"},
{file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f3f6883ce54a7a5f47db43289a0a4c776487912de1a0e2cc83fdaec9685cc9f"}, {file = "grpcio-1.64.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f5de082d936e0208ce8db9095821361dfa97af8767a6607ae71425ac8ace15c"},
{file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf8dae9cc0412cb86c8de5a8f3be395c5119a370f3ce2e69c8b7d46bb9872c8d"}, {file = "grpcio-1.64.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b7bf346391dffa182fba42506adf3a84f4a718a05e445b37824136047686a1"},
{file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08e1559fd3b3b4468486b26b0af64a3904a8dbc78d8d936af9c1cf9636eb3e8b"}, {file = "grpcio-1.64.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b2cbdfba18408389a1371f8c2af1659119e1831e5ed24c240cae9e27b4abc38d"},
{file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c039ef01516039fa39da8a8a43a95b64e288f79f42a17e6c2904a02a319b357"}, {file = "grpcio-1.64.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca4f15427d2df592e0c8f3d38847e25135e4092d7f70f02452c0e90d6a02d6d"},
{file = "grpcio-1.63.0-cp310-cp310-win32.whl", hash = "sha256:ad2ac8903b2eae071055a927ef74121ed52d69468e91d9bcbd028bd0e554be6d"}, {file = "grpcio-1.64.0-cp310-cp310-win32.whl", hash = "sha256:7c1f5b2298244472bcda49b599be04579f26425af0fd80d3f2eb5fd8bc84d106"},
{file = "grpcio-1.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2e44f59316716532a993ca2966636df6fbe7be4ab6f099de6815570ebe4383a"}, {file = "grpcio-1.64.0-cp310-cp310-win_amd64.whl", hash = "sha256:73f84f9e5985a532e47880b3924867de16fa1aa513fff9b26106220c253c70c5"},
{file = "grpcio-1.63.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f28f8b2db7b86c77916829d64ab21ff49a9d8289ea1564a2b2a3a8ed9ffcccd3"}, {file = "grpcio-1.64.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2a18090371d138a57714ee9bffd6c9c9cb2e02ce42c681aac093ae1e7189ed21"},
{file = "grpcio-1.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:65bf975639a1f93bee63ca60d2e4951f1b543f498d581869922910a476ead2f5"}, {file = "grpcio-1.64.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59c68df3a934a586c3473d15956d23a618b8f05b5e7a3a904d40300e9c69cbf0"},
{file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5194775fec7dc3dbd6a935102bb156cd2c35efe1685b0a46c67b927c74f0cfb"}, {file = "grpcio-1.64.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b52e1ec7185512103dd47d41cf34ea78e7a7361ba460187ddd2416b480e0938c"},
{file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4cbb2100ee46d024c45920d16e888ee5d3cf47c66e316210bc236d5bebc42b3"}, {file = "grpcio-1.64.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d598b5d5e2c9115d7fb7e2cb5508d14286af506a75950762aa1372d60e41851"},
{file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff737cf29b5b801619f10e59b581869e32f400159e8b12d7a97e7e3bdeee6a2"}, {file = "grpcio-1.64.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01615bbcae6875eee8091e6b9414072f4e4b00d8b7e141f89635bdae7cf784e5"},
{file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd1e68776262dd44dedd7381b1a0ad09d9930ffb405f737d64f505eb7f77d6c7"}, {file = "grpcio-1.64.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0b2dfe6dcace264807d9123d483d4c43274e3f8c39f90ff51de538245d7a4145"},
{file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f45f27f516548e23e4ec3fbab21b060416007dbe768a111fc4611464cc773f"}, {file = "grpcio-1.64.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7f17572dc9acd5e6dfd3014d10c0b533e9f79cd9517fc10b0225746f4c24b58e"},
{file = "grpcio-1.63.0-cp311-cp311-win32.whl", hash = "sha256:878b1d88d0137df60e6b09b74cdb73db123f9579232c8456f53e9abc4f62eb3c"}, {file = "grpcio-1.64.0-cp311-cp311-win32.whl", hash = "sha256:6ec5ed15b4ffe56e2c6bc76af45e6b591c9be0224b3fb090adfb205c9012367d"},
{file = "grpcio-1.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:756fed02dacd24e8f488f295a913f250b56b98fb793f41d5b2de6c44fb762434"}, {file = "grpcio-1.64.0-cp311-cp311-win_amd64.whl", hash = "sha256:597191370951b477b7a1441e1aaa5cacebeb46a3b0bd240ec3bb2f28298c7553"},
{file = "grpcio-1.63.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:93a46794cc96c3a674cdfb59ef9ce84d46185fe9421baf2268ccb556f8f81f57"}, {file = "grpcio-1.64.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:1ce4cd5a61d4532651079e7aae0fedf9a80e613eed895d5b9743e66b52d15812"},
{file = "grpcio-1.63.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a7b19dfc74d0be7032ca1eda0ed545e582ee46cd65c162f9e9fc6b26ef827dc6"}, {file = "grpcio-1.64.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:650a8150a9b288f40d5b7c1d5400cc11724eae50bd1f501a66e1ea949173649b"},
{file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:8064d986d3a64ba21e498b9a376cbc5d6ab2e8ab0e288d39f266f0fca169b90d"}, {file = "grpcio-1.64.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:8de0399b983f8676a7ccfdd45e5b2caec74a7e3cc576c6b1eecf3b3680deda5e"},
{file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:219bb1848cd2c90348c79ed0a6b0ea51866bc7e72fa6e205e459fedab5770172"}, {file = "grpcio-1.64.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46b8b43ba6a2a8f3103f103f97996cad507bcfd72359af6516363c48793d5a7b"},
{file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d60cd1d58817bc5985fae6168d8b5655c4981d448d0f5b6194bbcc038090d2"}, {file = "grpcio-1.64.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a54362f03d4dcfae63be455d0a7d4c1403673498b92c6bfe22157d935b57c7a9"},
{file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e350cb096e5c67832e9b6e018cf8a0d2a53b2a958f6251615173165269a91b0"}, {file = "grpcio-1.64.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1f8ea18b928e539046bb5f9c124d717fbf00cc4b2d960ae0b8468562846f5aa1"},
{file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:56cdf96ff82e3cc90dbe8bac260352993f23e8e256e063c327b6cf9c88daf7a9"}, {file = "grpcio-1.64.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c56c91bd2923ddb6e7ed28ebb66d15633b03e0df22206f22dfcdde08047e0a48"},
{file = "grpcio-1.63.0-cp312-cp312-win32.whl", hash = "sha256:3a6d1f9ea965e750db7b4ee6f9fdef5fdf135abe8a249e75d84b0a3e0c668a1b"}, {file = "grpcio-1.64.0-cp312-cp312-win32.whl", hash = "sha256:874c741c8a66f0834f653a69e7e64b4e67fcd4a8d40296919b93bab2ccc780ba"},
{file = "grpcio-1.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:d2497769895bb03efe3187fb1888fc20e98a5f18b3d14b606167dacda5789434"}, {file = "grpcio-1.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:0da1d921f8e4bcee307aeef6c7095eb26e617c471f8cb1c454fd389c5c296d1e"},
{file = "grpcio-1.63.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fdf348ae69c6ff484402cfdb14e18c1b0054ac2420079d575c53a60b9b2853ae"}, {file = "grpcio-1.64.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:c46fb6bfca17bfc49f011eb53416e61472fa96caa0979b4329176bdd38cbbf2a"},
{file = "grpcio-1.63.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a3abfe0b0f6798dedd2e9e92e881d9acd0fdb62ae27dcbbfa7654a57e24060c0"}, {file = "grpcio-1.64.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3d2004e85cf5213995d09408501f82c8534700d2babeb81dfdba2a3bff0bb396"},
{file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:6ef0ad92873672a2a3767cb827b64741c363ebaa27e7f21659e4e31f4d750280"}, {file = "grpcio-1.64.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:6d5541eb460d73a07418524fb64dcfe0adfbcd32e2dac0f8f90ce5b9dd6c046c"},
{file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b416252ac5588d9dfb8a30a191451adbf534e9ce5f56bb02cd193f12d8845b7f"}, {file = "grpcio-1.64.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f279ad72dd7d64412e10f2443f9f34872a938c67387863c4cd2fb837f53e7d2"},
{file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b77eaefc74d7eb861d3ffbdf91b50a1bb1639514ebe764c47773b833fa2d91"}, {file = "grpcio-1.64.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fda90b81da25993aa47fae66cae747b921f8f6777550895fb62375b776a231"},
{file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b005292369d9c1f80bf70c1db1c17c6c342da7576f1c689e8eee4fb0c256af85"}, {file = "grpcio-1.64.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a053584079b793a54bece4a7d1d1b5c0645bdbee729215cd433703dc2532f72b"},
{file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdcda1156dcc41e042d1e899ba1f5c2e9f3cd7625b3d6ebfa619806a4c1aadda"}, {file = "grpcio-1.64.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:579dd9fb11bc73f0de061cab5f8b2def21480fd99eb3743ed041ad6a1913ee2f"},
{file = "grpcio-1.63.0-cp38-cp38-win32.whl", hash = "sha256:01799e8649f9e94ba7db1aeb3452188048b0019dc37696b0f5ce212c87c560c3"}, {file = "grpcio-1.64.0-cp38-cp38-win32.whl", hash = "sha256:23b6887bb21d77649d022fa1859e05853fdc2e60682fd86c3db652a555a282e0"},
{file = "grpcio-1.63.0-cp38-cp38-win_amd64.whl", hash = "sha256:6a1a3642d76f887aa4009d92f71eb37809abceb3b7b5a1eec9c554a246f20e3a"}, {file = "grpcio-1.64.0-cp38-cp38-win_amd64.whl", hash = "sha256:753cb58683ba0c545306f4e17dabf468d29cb6f6b11832e1e432160bb3f8403c"},
{file = "grpcio-1.63.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:75f701ff645858a2b16bc8c9fc68af215a8bb2d5a9b647448129de6e85d52bce"}, {file = "grpcio-1.64.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:2186d76a7e383e1466e0ea2b0febc343ffeae13928c63c6ec6826533c2d69590"},
{file = "grpcio-1.63.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cacdef0348a08e475a721967f48206a2254a1b26ee7637638d9e081761a5ba86"}, {file = "grpcio-1.64.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0f30596cdcbed3c98024fb4f1d91745146385b3f9fd10c9f2270cbfe2ed7ed91"},
{file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:0697563d1d84d6985e40ec5ec596ff41b52abb3fd91ec240e8cb44a63b895094"}, {file = "grpcio-1.64.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:d9171f025a196f5bcfec7e8e7ffb7c3535f7d60aecd3503f9e250296c7cfc150"},
{file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6426e1fb92d006e47476d42b8f240c1d916a6d4423c5258ccc5b105e43438f61"}, {file = "grpcio-1.64.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf4c8daed18ae2be2f1fc7d613a76ee2a2e28fdf2412d5c128be23144d28283d"},
{file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48cee31bc5f5a31fb2f3b573764bd563aaa5472342860edcc7039525b53e46a"}, {file = "grpcio-1.64.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3550493ac1d23198d46dc9c9b24b411cef613798dc31160c7138568ec26bc9b4"},
{file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:50344663068041b34a992c19c600236e7abb42d6ec32567916b87b4c8b8833b3"}, {file = "grpcio-1.64.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3161a8f8bb38077a6470508c1a7301cd54301c53b8a34bb83e3c9764874ecabd"},
{file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:259e11932230d70ef24a21b9fb5bb947eb4703f57865a404054400ee92f42f5d"}, {file = "grpcio-1.64.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e8fabe2cc57a369638ab1ad8e6043721014fdf9a13baa7c0e35995d3a4a7618"},
{file = "grpcio-1.63.0-cp39-cp39-win32.whl", hash = "sha256:a44624aad77bf8ca198c55af811fd28f2b3eaf0a50ec5b57b06c034416ef2d0a"}, {file = "grpcio-1.64.0-cp39-cp39-win32.whl", hash = "sha256:31890b24d47b62cc27da49a462efe3d02f3c120edb0e6c46dcc0025506acf004"},
{file = "grpcio-1.63.0-cp39-cp39-win_amd64.whl", hash = "sha256:166e5c460e5d7d4656ff9e63b13e1f6029b122104c1633d5f37eaea348d7356d"}, {file = "grpcio-1.64.0-cp39-cp39-win_amd64.whl", hash = "sha256:5a56797dea8c02e7d3a85dfea879f286175cf4d14fbd9ab3ef2477277b927baa"},
{file = "grpcio-1.63.0.tar.gz", hash = "sha256:f3023e14805c61bc439fb40ca545ac3d5740ce66120a678a3c6c2c55b70343d1"}, {file = "grpcio-1.64.0.tar.gz", hash = "sha256:257baf07f53a571c215eebe9679c3058a313fd1d1f7c4eede5a8660108c52d9c"},
] ]
[package.extras] [package.extras]
protobuf = ["grpcio-tools (>=1.63.0)"] protobuf = ["grpcio-tools (>=1.64.0)"]
[[package]] [[package]]
name = "grpcio-health-checking" name = "grpcio-health-checking"
@ -3716,6 +3720,76 @@ MarkupSafe = ">=2.0"
[package.extras] [package.extras]
i18n = ["Babel (>=2.7)"] i18n = ["Babel (>=2.7)"]
[[package]]
name = "jiter"
version = "0.1.0"
description = ""
optional = false
python-versions = ">=3.8"
files = [
{file = "jiter-0.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3aa466e89664cb94e69571df326f0c28e25e2e728f90fa4c3c235bbd35b40609"},
{file = "jiter-0.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:46eed20f7d9642787eed4143f7b25e16cf9915bb45656980cc9b966bb1e00f59"},
{file = "jiter-0.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51fcd4bdb23de3a26c2b64f7bd87e9e43c82f1171145ba13434a654d7c8e9aa9"},
{file = "jiter-0.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:657ca4cf8d99e2e899a5ef778daed5f42eff6de6f23403a6225b6d6bafb55f38"},
{file = "jiter-0.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5da72cf6582049d2b802e48dd647a096103994a21a7a762fe813b727565ac0ef"},
{file = "jiter-0.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:148ae1c97be312f1e969d76fbf507818d53e2867e90cf3c7f78941a199d5b84c"},
{file = "jiter-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12ce8243d1adb4657cfd9f23ec73fbd206bd5387bea0ebb5514c41fd268a1c1"},
{file = "jiter-0.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:067cc20889627a0afcaf6b465e942990b9f32d1ad88b0a083ece74becc3831b0"},
{file = "jiter-0.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce5866bb5ff7dc14d036fede7e7ddb86b3b67064dc66dde15de4771e2697e539"},
{file = "jiter-0.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f446f1f5e8466fc4dfe775f9c5d8b6c3f0b8b07dc24d4ce76d8de3468d7447a8"},
{file = "jiter-0.1.0-cp310-none-win32.whl", hash = "sha256:47c1e12bd0789bd4f76cc4973a04d512832568a2a4925cd0b52d0ed413aa5e8d"},
{file = "jiter-0.1.0-cp310-none-win_amd64.whl", hash = "sha256:0316fa82ee4dab455bac2ec05362f3ac19d77e3139225683289c366ce35605b9"},
{file = "jiter-0.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f47eb274aae20ee3b565886ab315c3f16f9831c0e4fd6722dc100a2dbc0923f9"},
{file = "jiter-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80d1bf437ea70f43c0976f96cd83fa4618aceb526ba3eaccf9f736d0c3185f5c"},
{file = "jiter-0.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215ca1178d30e7a652849b9ca145a4666e1ed0941aef0c61bbaf88a0cd084b66"},
{file = "jiter-0.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:08d7401e20fc660871a02ec05dda9dd93c95052a3c1588385230bca59d9d525b"},
{file = "jiter-0.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cd365396e9c50b1c458bad0b21452f4c33fea222413aea78826bca98097f487"},
{file = "jiter-0.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:050252cde3ae0b0a1eca028a30d953ce2d90e0150c1eef0e5ad75ce163d32484"},
{file = "jiter-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfcf0996949a9435a2ebba2455934ad72d9faa1de2069c65aeaeaa8c6219820d"},
{file = "jiter-0.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfce158151a3a7d0b8f8af549540e1d8328a9dce4ee61c2fb10b12f269d68b6d"},
{file = "jiter-0.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c22d684e663cc99f887c3133a7714c5ecba73524438bc3c93e6bb868c55a9097"},
{file = "jiter-0.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe94ab7e548e492dfd35118de7de613078b7e4ddc276976e8fa2f0f37029cad5"},
{file = "jiter-0.1.0-cp311-none-win32.whl", hash = "sha256:1c41463f82b67d2efa8f269f7cd150c6c16c5902a0508277f5b1c1569e93dc1d"},
{file = "jiter-0.1.0-cp311-none-win_amd64.whl", hash = "sha256:40d361aa7e728a186495b7b00a47f83f7153714a8b49d9d38dfc45f7b6630f99"},
{file = "jiter-0.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2a1e6335a4ce98dc64d0871ba3316f06d32728beefe336a621f9877b71a237be"},
{file = "jiter-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:832e1a91fea7819507b1d1215e1a82e02da423ea298231af842b35c41d8411db"},
{file = "jiter-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7f620a558d5952218fe924c7257ce3592835a23e651a140957ba66128675c0d"},
{file = "jiter-0.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2149878ed5c8d1909546d6bb259aaa3ca6b6f81487b03504ea618264f79f4e3b"},
{file = "jiter-0.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88ba8abb7025fa4e806a1fa03a2be23cb8584ec737bdf62554873ba2698e44d3"},
{file = "jiter-0.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b29b49fe73c7da72f29d922ce85ee5a74772678ecbc2542e99bc4935c68965"},
{file = "jiter-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b53bd047ee136f38c0b56779423bae99cab1b9a65b586f1c19e94a6f65013599"},
{file = "jiter-0.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92bd461af7766d7f091216942951b603d546f16c1818b9072f5ec7c89bb8a7d2"},
{file = "jiter-0.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:17a45f16e7253c23c81969086707229591645b192935cb2db226e01bd3abd148"},
{file = "jiter-0.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e6be9c021ca191c186275d5a9c70b7767cde0852454a8827c9edde995518c856"},
{file = "jiter-0.1.0-cp312-none-win32.whl", hash = "sha256:c6445c37eca8d79bedf3bd74683ad668137b05880c7af95f0b96222d62be2db9"},
{file = "jiter-0.1.0-cp312-none-win_amd64.whl", hash = "sha256:5cf60df741bc80439cb2d9b2923c7b712c6c82ac6848387f95d77c5723e01d0a"},
{file = "jiter-0.1.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:977d8b322f5f661f16903d2ff8e981e210ba0e057d2d70a1f7b59b8d478e6d45"},
{file = "jiter-0.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ceea8e8ac9af7b0f098660f3837bc3ec975716103788f36d228c543b1319c475"},
{file = "jiter-0.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3de99e3983c0697c449f45ec740096ac559656485aea48066c982530066dd8d"},
{file = "jiter-0.1.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb02700ad165a81b0993ad3c550f3b590f0952ff3ce10826fc62aeb064b47b6a"},
{file = "jiter-0.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb78293d40e38ee5c4370c547af06fb63c7e810f3895ecb76ddcc5fa413e9ccf"},
{file = "jiter-0.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:194fcbd242b35bda13196b501b116e50fa553c414e1cb0350dc1bc910bceb00d"},
{file = "jiter-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0f55e5cd7b2f649d934b1397bc104200043cdf35addf4892ed66e472e6fe05"},
{file = "jiter-0.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f6a5c6ae1587f60eb995724e34e7257291c919d163906edd030ced77af9f420b"},
{file = "jiter-0.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f9f521361cf3633b314edb2313e7abc4fce59dfa1d918561263474fb5c7e17b8"},
{file = "jiter-0.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c1c0e8b99b4e9fed57b7dbd5be63aa0fc36d45551dedc3c3697aa2694c9a0be3"},
{file = "jiter-0.1.0-cp38-none-win32.whl", hash = "sha256:631d92b82f228774e9f0b79927016fafed369521b8bf059fa8c0353ba4cd76b2"},
{file = "jiter-0.1.0-cp38-none-win_amd64.whl", hash = "sha256:c1e798a92daf8c6511907c3861c0cf500f23241c160d1c09cb0e9ba668fde667"},
{file = "jiter-0.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e04740a9ea37118fe6788754eb2ef043ad83809dd677bf3c5f331cc41f8ef70d"},
{file = "jiter-0.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d7272eefa5b390c6e450760959e224033b925b0ab76e3279dfdad7f5ec65db0"},
{file = "jiter-0.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae1f35e062a71deaad689fb2f51b202c1d55ac941733bdcd7587577e17b8a16"},
{file = "jiter-0.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:230fc4dd9c2c3f4b6608fda3f34891cabee2537eef7c7fd0cd68792def14bcc6"},
{file = "jiter-0.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f867893fb7b458c5cb302b33fbe769bb8c8e60594057f29e005fc8ad21b2a58"},
{file = "jiter-0.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b80b4b64aa5dd63e79bf5addc903855a9a5b7b2493a826cc2cbf9cc9ecfcd23a"},
{file = "jiter-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab154d2f877e66757202a71669142c1ba9e9b5c5d1cf81510924950d74f62a3"},
{file = "jiter-0.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b43b3fa25b828a2fc4ed7c42f788c261df17d82fb5ced129140ef8be2577ee2"},
{file = "jiter-0.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:eb3e9a062ddba709db2afde1ef2c72244dfbae09a27b4aa3701267e489ef7a30"},
{file = "jiter-0.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c98bddd0dd5cfe638f1a7e4341edfae0a1c97aed879207e594b221f8c5058aa6"},
{file = "jiter-0.1.0-cp39-none-win32.whl", hash = "sha256:9c6d24f8f1764b1c0917bc35131982bea5517cc7b12226f19c4c01215e1be208"},
{file = "jiter-0.1.0-cp39-none-win_amd64.whl", hash = "sha256:28f3d4f3e88313ef20e51e3330c22c6ce636ca2eb167b185c298a2ea1ab67b8c"},
{file = "jiter-0.1.0.tar.gz", hash = "sha256:d77da07222a42d2ae907dbd03bca708079e4268bb7e155006c2c6960281f7f1a"},
]
[[package]] [[package]]
name = "jmespath" name = "jmespath"
version = "1.0.1" version = "1.0.1"
@ -4233,7 +4307,7 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]] [[package]]
name = "langflow-base" name = "langflow-base"
version = "0.0.45" version = "0.0.46"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
optional = false optional = false
python-versions = ">=3.10,<3.13" python-versions = ">=3.10,<3.13"
@ -4288,13 +4362,13 @@ url = "src/backend/base"
[[package]] [[package]]
name = "langfuse" name = "langfuse"
version = "2.32.0" version = "2.33.0"
description = "A client library for accessing langfuse" description = "A client library for accessing langfuse"
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langfuse-2.32.0-py3-none-any.whl", hash = "sha256:ecdd06fae46637d635249dfaf8f0564ac8e8769519712b11e777d2905309e5d7"}, {file = "langfuse-2.33.0-py3-none-any.whl", hash = "sha256:362e3078c5a891df0b7ba3c9ce82f046d1f0274eab3d55337e443fff526f18ad"},
{file = "langfuse-2.32.0.tar.gz", hash = "sha256:07dcbb8fa9f754928d6af377dbea530d591680e3f50340d687018d8bcb83ba34"}, {file = "langfuse-2.33.0.tar.gz", hash = "sha256:3ca2ef8539a8f28cb80135f4b46b80d5585ce183f8e2035f318be296d09d7d88"},
] ]
[package.dependencies] [package.dependencies]
@ -4312,13 +4386,13 @@ openai = ["openai (>=0.27.8)"]
[[package]] [[package]]
name = "langsmith" name = "langsmith"
version = "0.1.59" version = "0.1.60"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langsmith-0.1.59-py3-none-any.whl", hash = "sha256:445e3bc1d3baa1e5340cd979907a19483b9763a2ed37b863a01113d406f69345"}, {file = "langsmith-0.1.60-py3-none-any.whl", hash = "sha256:3c3520ea473de0a984237b3e9d638fdf23ef3acc5aec89a42e693225e72d6120"},
{file = "langsmith-0.1.59.tar.gz", hash = "sha256:e748a89f4dd6aa441349143e49e546c03b5dfb43376a25bfef6a5ca792fe1437"}, {file = "langsmith-0.1.60.tar.gz", hash = "sha256:6a145b5454437f9e0f81525f23c4dcdbb8c07b1c91553b8f697456c418d6a599"},
] ]
[package.dependencies] [package.dependencies]
@ -4345,13 +4419,13 @@ regex = ["regex"]
[[package]] [[package]]
name = "litellm" name = "litellm"
version = "1.37.16" version = "1.37.19"
description = "Library to easily interface with LLM API providers" description = "Library to easily interface with LLM API providers"
optional = false optional = false
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
files = [ files = [
{file = "litellm-1.37.16-py3-none-any.whl", hash = "sha256:b3250832ff8578d906ee9230ebaf13b787f139de705d4d397f87a0ce3ee57392"}, {file = "litellm-1.37.19-py3-none-any.whl", hash = "sha256:5a45b99d6c16a91ba66db6c69d1f406098dbca566be1c2256df5c21c3eb4e4e9"},
{file = "litellm-1.37.16.tar.gz", hash = "sha256:c90c826a16d154c755f73a828b84b11cef9fc0891ff322023ea247b3c7fcdc1f"}, {file = "litellm-1.37.19.tar.gz", hash = "sha256:9ec9260edaf16476dcff6d91a405364fb994200afa725ed46e7be7c3e54d4515"},
] ]
[package.dependencies] [package.dependencies]
@ -4494,13 +4568,13 @@ query-tools = ["guidance (>=0.0.64,<0.0.65)", "jsonpath-ng (>=1.6.0,<2.0.0)", "l
[[package]] [[package]]
name = "llama-index-embeddings-openai" name = "llama-index-embeddings-openai"
version = "0.1.9" version = "0.1.10"
description = "llama-index embeddings openai integration" description = "llama-index embeddings openai integration"
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "llama_index_embeddings_openai-0.1.9-py3-none-any.whl", hash = "sha256:fbd16d6197b91f4dbdc6d0707e573cc224ac2b0a48d5b370c6232dd8a2282473"}, {file = "llama_index_embeddings_openai-0.1.10-py3-none-any.whl", hash = "sha256:c3cfa83b537ded34d035fc172a945dd444c87fb58a89b02dfbf785b675f9f681"},
{file = "llama_index_embeddings_openai-0.1.9.tar.gz", hash = "sha256:0fd292b2f9a0ad4534a790d6374726bc885853188087eb018167dcf239643924"}, {file = "llama_index_embeddings_openai-0.1.10.tar.gz", hash = "sha256:1bc1fc9b46773a12870c5d3097d3735d7ca33805f12462a8e35ae8a6e5ce1cf6"},
] ]
[package.dependencies] [package.dependencies]
@ -4562,13 +4636,13 @@ query-tools = ["guidance (>=0.0.64,<0.0.65)", "jsonpath-ng (>=1.6.0,<2.0.0)", "l
[[package]] [[package]]
name = "llama-index-llms-openai" name = "llama-index-llms-openai"
version = "0.1.19" version = "0.1.20"
description = "llama-index llms openai integration" description = "llama-index llms openai integration"
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "llama_index_llms_openai-0.1.19-py3-none-any.whl", hash = "sha256:2bd98ff3abbb4aa0daed1fbe01d8b69f8270ab86c53f8da51fc9f148a672264c"}, {file = "llama_index_llms_openai-0.1.20-py3-none-any.whl", hash = "sha256:f27401acdf9f65bf4d866a100615dcbd81987b890ae5fa9c513d544ba6d711e7"},
{file = "llama_index_llms_openai-0.1.19.tar.gz", hash = "sha256:f61b64a997892e424fb3cd547090d279c5b210ef15b614fc39de854d3ccaa7e7"}, {file = "llama_index_llms_openai-0.1.20.tar.gz", hash = "sha256:0282e4e252893487afd72383b46da5b28ddcd3fb73bace1caefce8a36e9cf492"},
] ]
[package.dependencies] [package.dependencies]
@ -5751,13 +5825,13 @@ files = [
[[package]] [[package]]
name = "nvidia-nvjitlink-cu12" name = "nvidia-nvjitlink-cu12"
version = "12.4.127" version = "12.5.40"
description = "Nvidia JIT LTO Library" description = "Nvidia JIT LTO Library"
optional = true optional = true
python-versions = ">=3" python-versions = ">=3"
files = [ files = [
{file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57"}, {file = "nvidia_nvjitlink_cu12-12.5.40-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d9714f27c1d0f0895cd8915c07a87a1d0029a0aa36acaf9156952ec2a8a12189"},
{file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:fd9020c501d27d135f983c6d3e244b197a7ccad769e34df53a42e276b0e25fa1"}, {file = "nvidia_nvjitlink_cu12-12.5.40-py3-none-win_amd64.whl", hash = "sha256:c3401dc8543b52d3a8158007a0c1ab4e9c768fcbd24153a48c86972102197ddd"},
] ]
[[package]] [[package]]
@ -8032,13 +8106,13 @@ files = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.31.0" version = "2.32.2"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
] ]
[package.dependencies] [package.dependencies]
@ -8301,45 +8375,48 @@ torch = ["safetensors[numpy]", "torch (>=1.10)"]
[[package]] [[package]]
name = "scikit-learn" name = "scikit-learn"
version = "1.4.2" version = "1.5.0"
description = "A set of python modules for machine learning and data mining" description = "A set of python modules for machine learning and data mining"
optional = true optional = true
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "scikit-learn-1.4.2.tar.gz", hash = "sha256:daa1c471d95bad080c6e44b4946c9390a4842adc3082572c20e4f8884e39e959"}, {file = "scikit_learn-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12e40ac48555e6b551f0a0a5743cc94cc5a765c9513fe708e01f0aa001da2801"},
{file = "scikit_learn-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8539a41b3d6d1af82eb629f9c57f37428ff1481c1e34dddb3b9d7af8ede67ac5"}, {file = "scikit_learn-1.5.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f405c4dae288f5f6553b10c4ac9ea7754d5180ec11e296464adb5d6ac68b6ef5"},
{file = "scikit_learn-1.4.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:68b8404841f944a4a1459b07198fa2edd41a82f189b44f3e1d55c104dbc2e40c"}, {file = "scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df8ccabbf583315f13160a4bb06037bde99ea7d8211a69787a6b7c5d4ebb6fc3"},
{file = "scikit_learn-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81bf5d8bbe87643103334032dd82f7419bc8c8d02a763643a6b9a5c7288c5054"}, {file = "scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c75ea812cd83b1385bbfa94ae971f0d80adb338a9523f6bbcb5e0b0381151d4"},
{file = "scikit_learn-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f0ea5d0f693cb247a073d21a4123bdf4172e470e6d163c12b74cbb1536cf38"}, {file = "scikit_learn-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a90c5da84829a0b9b4bf00daf62754b2be741e66b5946911f5bdfaa869fcedd6"},
{file = "scikit_learn-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:87440e2e188c87db80ea4023440923dccbd56fbc2d557b18ced00fef79da0727"}, {file = "scikit_learn-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a65af2d8a6cce4e163a7951a4cfbfa7fceb2d5c013a4b593686c7f16445cf9d"},
{file = "scikit_learn-1.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45dee87ac5309bb82e3ea633955030df9bbcb8d2cdb30383c6cd483691c546cc"}, {file = "scikit_learn-1.5.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:4c0c56c3005f2ec1db3787aeaabefa96256580678cec783986836fc64f8ff622"},
{file = "scikit_learn-1.4.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1d0b25d9c651fd050555aadd57431b53d4cf664e749069da77f3d52c5ad14b3b"}, {file = "scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f77547165c00625551e5c250cefa3f03f2fc92c5e18668abd90bfc4be2e0bff"},
{file = "scikit_learn-1.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0203c368058ab92efc6168a1507d388d41469c873e96ec220ca8e74079bf62e"}, {file = "scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:118a8d229a41158c9f90093e46b3737120a165181a1b58c03461447aa4657415"},
{file = "scikit_learn-1.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44c62f2b124848a28fd695db5bc4da019287abf390bfce602ddc8aa1ec186aae"}, {file = "scikit_learn-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:a03b09f9f7f09ffe8c5efffe2e9de1196c696d811be6798ad5eddf323c6f4d40"},
{file = "scikit_learn-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:5cd7b524115499b18b63f0c96f4224eb885564937a0b3477531b2b63ce331904"}, {file = "scikit_learn-1.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:460806030c666addee1f074788b3978329a5bfdc9b7d63e7aad3f6d45c67a210"},
{file = "scikit_learn-1.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90378e1747949f90c8f385898fff35d73193dfcaec3dd75d6b542f90c4e89755"}, {file = "scikit_learn-1.5.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1b94d6440603752b27842eda97f6395f570941857456c606eb1d638efdb38184"},
{file = "scikit_learn-1.4.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ff4effe5a1d4e8fed260a83a163f7dbf4f6087b54528d8880bab1d1377bd78be"}, {file = "scikit_learn-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d82c2e573f0f2f2f0be897e7a31fcf4e73869247738ab8c3ce7245549af58ab8"},
{file = "scikit_learn-1.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:671e2f0c3f2c15409dae4f282a3a619601fa824d2c820e5b608d9d775f91780c"}, {file = "scikit_learn-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3a10e1d9e834e84d05e468ec501a356226338778769317ee0b84043c0d8fb06"},
{file = "scikit_learn-1.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d36d0bc983336bbc1be22f9b686b50c964f593c8a9a913a792442af9bf4f5e68"}, {file = "scikit_learn-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:855fc5fa8ed9e4f08291203af3d3e5fbdc4737bd617a371559aaa2088166046e"},
{file = "scikit_learn-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:d762070980c17ba3e9a4a1e043ba0518ce4c55152032f1af0ca6f39b376b5928"}, {file = "scikit_learn-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40fb7d4a9a2db07e6e0cae4dc7bdbb8fada17043bac24104d8165e10e4cff1a2"},
{file = "scikit_learn-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9993d5e78a8148b1d0fdf5b15ed92452af5581734129998c26f481c46586d68"}, {file = "scikit_learn-1.5.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:47132440050b1c5beb95f8ba0b2402bbd9057ce96ec0ba86f2f445dd4f34df67"},
{file = "scikit_learn-1.4.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:426d258fddac674fdf33f3cb2d54d26f49406e2599dbf9a32b4d1696091d4256"}, {file = "scikit_learn-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174beb56e3e881c90424e21f576fa69c4ffcf5174632a79ab4461c4c960315ac"},
{file = "scikit_learn-1.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5460a1a5b043ae5ae4596b3126a4ec33ccba1b51e7ca2c5d36dac2169f62ab1d"}, {file = "scikit_learn-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261fe334ca48f09ed64b8fae13f9b46cc43ac5f580c4a605cbb0a517456c8f71"},
{file = "scikit_learn-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d64ef6cb8c093d883e5a36c4766548d974898d378e395ba41a806d0e824db8"}, {file = "scikit_learn-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:057b991ac64b3e75c9c04b5f9395eaf19a6179244c089afdebaad98264bff37c"},
{file = "scikit_learn-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:c97a50b05c194be9146d61fe87dbf8eac62b203d9e87a3ccc6ae9aed2dfaf361"}, {file = "scikit_learn-1.5.0.tar.gz", hash = "sha256:789e3db01c750ed6d496fa2db7d50637857b451e57bcae863bff707c1247bef7"},
] ]
[package.dependencies] [package.dependencies]
joblib = ">=1.2.0" joblib = ">=1.2.0"
numpy = ">=1.19.5" numpy = ">=1.19.5"
scipy = ">=1.6.0" scipy = ">=1.6.0"
threadpoolctl = ">=2.0.0" threadpoolctl = ">=3.1.0"
[package.extras] [package.extras]
benchmark = ["matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "pandas (>=1.1.5)"] benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"]
docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.15.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] build = ["cython (>=3.0.10)", "meson-python (>=0.15.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"]
docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.15.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"]
examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"]
tests = ["black (>=23.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.19.12)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.17.2)"] install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"]
maintenance = ["conda-lock (==2.5.6)"]
tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"]
[[package]] [[package]]
name = "scipy" name = "scipy"
@ -8409,19 +8486,18 @@ dev = ["pre-commit", "pytest", "ruff (>=0.3.0)"]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "69.5.1" version = "70.0.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"},
{file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"},
] ]
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]] [[package]]
name = "shapely" name = "shapely"
@ -9296,13 +9372,13 @@ files = [
[[package]] [[package]]
name = "types-pillow" name = "types-pillow"
version = "10.2.0.20240511" version = "10.2.0.20240520"
description = "Typing stubs for Pillow" description = "Typing stubs for Pillow"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-Pillow-10.2.0.20240511.tar.gz", hash = "sha256:b2fcc27b8e15ae3741941e43b4f39eba6fce6bcb152af90bbb07b387d2585783"}, {file = "types-Pillow-10.2.0.20240520.tar.gz", hash = "sha256:130b979195465fa1e1676d8e81c9c7c30319e8e95b12fae945e8f0d525213107"},
{file = "types_Pillow-10.2.0.20240511-py3-none-any.whl", hash = "sha256:ef87a19ea0a02a89c784cbc1b99dfff6c00dd0d5796a8ac868cf7ec69c5f88ff"}, {file = "types_Pillow-10.2.0.20240520-py3-none-any.whl", hash = "sha256:33c36494b380e2a269bb742181bea5d9b00820367822dbd3760f07210a1da23d"},
] ]
[[package]] [[package]]
@ -9395,13 +9471,13 @@ types-pyOpenSSL = "*"
[[package]] [[package]]
name = "types-requests" name = "types-requests"
version = "2.31.0.20240406" version = "2.32.0.20240521"
description = "Typing stubs for requests" description = "Typing stubs for requests"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, {file = "types-requests-2.32.0.20240521.tar.gz", hash = "sha256:c5c4a0ae95aad51f1bf6dae9eed04a78f7f2575d4b171da37b622e08b93eb5d3"},
{file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, {file = "types_requests-2.32.0.20240521-py3-none-any.whl", hash = "sha256:ab728ba43ffb073db31f21202ecb97db8753ded4a9dc49cb480d8a5350c5c421"},
] ]
[package.dependencies] [package.dependencies]
@ -9799,13 +9875,13 @@ files = [
[[package]] [[package]]
name = "weaviate-client" name = "weaviate-client"
version = "4.6.2" version = "4.6.3"
description = "A python native Weaviate client" description = "A python native Weaviate client"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "weaviate_client-4.6.2-py3-none-any.whl", hash = "sha256:dfe1981230100a202f94510447b0136357d77d5663fb4fc37a0894412eb2a207"}, {file = "weaviate_client-4.6.3-py3-none-any.whl", hash = "sha256:b2921f9aea84a4eccb1c75d55dd2857a87241e5536540fb96ffdf4737ed4fe8a"},
{file = "weaviate_client-4.6.2.tar.gz", hash = "sha256:6f66319bb2d76501c8c3262c08470873c716578048241373f627095aa3fb6cc1"}, {file = "weaviate_client-4.6.3.tar.gz", hash = "sha256:a6e638f746f91c310fe6680cffa77949718f17d8b40b966f7037028cacfd94e0"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "1.0.0a34" version = "1.0.0a36"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

View file

@ -17,8 +17,10 @@ from rich import print as rprint
from rich.console import Console from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
from rich.table import Table from rich.table import Table
from sqlmodel import select
from langflow.main import setup_app from langflow.main import setup_app
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
from langflow.services.database.utils import session_getter from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service from langflow.services.deps import get_db_service
from langflow.services.utils import initialize_services from langflow.services.utils import initialize_services
@ -432,11 +434,16 @@ def superuser(
# Verify that the superuser was created # Verify that the superuser was created
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
user: User = session.query(User).filter(User.username == username).first() user: User = session.exec(select(User).where(User.username == username)).first()
if user is None or not user.is_superuser: if user is None or not user.is_superuser:
typer.echo("Superuser creation failed.") typer.echo("Superuser creation failed.")
return return
# Now create the first folder for the user
result = create_default_folder_if_it_doesnt_exist(session, user.id)
if result:
typer.echo("Default folder created successfully.")
else:
raise RuntimeError("Could not create default folder.")
typer.echo("Superuser created successfully.") typer.echo("Superuser created successfully.")
else: else:

View file

@ -0,0 +1,78 @@
"""Add Folder table
Revision ID: 012fb73ac359
Revises: c153816fd85f
Create Date: 2024-05-07 12:52:16.954691
"""
from typing import Sequence, Union
import sqlalchemy as sa
import sqlmodel
from alembic import op
from sqlalchemy.engine.reflection import Inspector
# revision identifiers, used by Alembic.
revision: str = "012fb73ac359"
down_revision: Union[str, None] = "c153816fd85f"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "folder" not in table_names:
op.create_table(
"folder",
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("parent_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["parent_id"],
["folder.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["user.id"],
),
sa.PrimaryKeyConstraint("id"),
)
indexes = inspector.get_indexes("folder")
if "ix_folder_name" not in [index["name"] for index in indexes]:
with op.batch_alter_table("folder", schema=None) as batch_op:
batch_op.create_index(batch_op.f("ix_folder_name"), ["name"], unique=False)
if "folder_id" not in inspector.get_columns("flow"):
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("folder_id", sqlmodel.sql.sqltypes.GUID(), nullable=True))
batch_op.create_foreign_key("flow_folder_id_fkey", "folder", ["folder_id"], ["id"])
batch_op.drop_column("folder")
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "folder_id" in inspector.get_columns("flow"):
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("folder", sa.VARCHAR(), nullable=True))
batch_op.drop_constraint("flow_folder_id_fkey", type_="foreignkey")
batch_op.drop_column("folder_id")
indexes = inspector.get_indexes("folder")
if "ix_folder_name" in [index["name"] for index in indexes]:
with op.batch_alter_table("folder", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_folder_name"))
if "folder" in table_names:
op.drop_table("folder")
# ### end Alembic commands ###

View file

@ -0,0 +1,43 @@
"""Add missing index
Revision ID: 29fe8f1f806b
Revises: 012fb73ac359
Create Date: 2024-05-21 09:23:48.772367
"""
from typing import Sequence, Union
from alembic import op
from sqlalchemy.engine.reflection import Inspector
revision: str = "29fe8f1f806b"
down_revision: Union[str, None] = "012fb73ac359"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
# ### commands auto generated by Alembic - please adjust! ###
indexes = inspector.get_indexes("flow")
with op.batch_alter_table("flow", schema=None) as batch_op:
indexes_names = [index["name"] for index in indexes]
if "ix_flow_folder_id" not in indexes_names:
batch_op.create_index(batch_op.f("ix_flow_folder_id"), ["folder_id"], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
# ### commands auto generated by Alembic - please adjust! ###
indexes = inspector.get_indexes("flow")
with op.batch_alter_table("flow", schema=None) as batch_op:
indexes_names = [index["name"] for index in indexes]
if "ix_flow_folder_id" in indexes_names:
batch_op.drop_index(batch_op.f("ix_flow_folder_id"))
# ### end Alembic commands ###

View file

@ -13,6 +13,7 @@ from langflow.api.v1 import (
users_router, users_router,
validate_router, validate_router,
variables_router, variables_router,
folders_router,
) )
router = APIRouter( router = APIRouter(
@ -29,3 +30,4 @@ router.include_router(login_router)
router.include_router(variables_router) router.include_router(variables_router)
router.include_router(files_router) router.include_router(files_router)
router.include_router(monitor_router) router.include_router(monitor_router)
router.include_router(folders_router)

View file

@ -9,6 +9,7 @@ from langflow.api.v1.store import router as store_router
from langflow.api.v1.users import router as users_router from langflow.api.v1.users import router as users_router
from langflow.api.v1.validate import router as validate_router from langflow.api.v1.validate import router as validate_router
from langflow.api.v1.variable import router as variables_router from langflow.api.v1.variable import router as variables_router
from langflow.api.v1.folders import router as folders_router
__all__ = [ __all__ = [
"chat_router", "chat_router",
@ -22,4 +23,5 @@ __all__ = [
"variables_router", "variables_router",
"monitor_router", "monitor_router",
"files_router", "files_router",
"folders_router",
] ]

View file

@ -29,7 +29,7 @@ from langflow.services.deps import get_chat_service, get_session, get_session_se
from langflow.services.monitor.utils import log_vertex_build from langflow.services.monitor.utils import log_vertex_build
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.graph.vertex.types import ChatVertex from langflow.graph.vertex.types import InterfaceVertex
from langflow.services.session.service import SessionService from langflow.services.session.service import SessionService
router = APIRouter(tags=["Chat"]) router = APIRouter(tags=["Chat"])
@ -288,7 +288,7 @@ async def build_vertex_stream(
if not graph: if not graph:
raise ValueError(f"No graph found for {flow_id}.") raise ValueError(f"No graph found for {flow_id}.")
vertex: "ChatVertex" = graph.get_vertex(vertex_id) vertex: "InterfaceVertex" = graph.get_vertex(vertex_id)
if not hasattr(vertex, "stream"): if not hasattr(vertex, "stream"):
raise ValueError(f"Vertex {vertex_id} does not support streaming") raise ValueError(f"Vertex {vertex_id} does not support streaming")
if isinstance(vertex._built_result, str) and vertex._built_result: if isinstance(vertex._built_result, str) and vertex._built_result:

View file

@ -4,7 +4,7 @@ from typing import Annotated, List, Optional, Union
import sqlalchemy as sa import sqlalchemy as sa
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
from loguru import logger from loguru import logger
from sqlmodel import Session, select from sqlmodel import Session, col, select
from langflow.api.utils import update_frontend_node_with_template_values from langflow.api.utils import update_frontend_node_with_template_values
from langflow.api.v1.schemas import ( from langflow.api.v1.schemas import (

View file

@ -2,17 +2,19 @@ from datetime import datetime, timezone
from typing import List from typing import List
from uuid import UUID from uuid import UUID
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
import orjson import orjson
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from loguru import logger from loguru import logger
from sqlmodel import Session, select from sqlmodel import Session, col, select
from langflow.api.utils import remove_api_keys, validate_is_component from langflow.api.utils import remove_api_keys, validate_is_component
from langflow.api.v1.schemas import FlowListCreate, FlowListRead from langflow.api.v1.schemas import FlowListCreate, FlowListIds, FlowListRead
from langflow.initial_setup.setup import STARTER_FOLDER_NAME from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_current_active_user from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
from langflow.services.database.models.folder.model import Folder
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
from langflow.services.settings.service import SettingsService from langflow.services.settings.service import SettingsService
@ -35,6 +37,11 @@ def create_flow(
db_flow = Flow.model_validate(flow, from_attributes=True) db_flow = Flow.model_validate(flow, from_attributes=True)
db_flow.updated_at = datetime.now(timezone.utc) db_flow.updated_at = datetime.now(timezone.utc)
if db_flow.folder_id is None:
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if default_folder:
db_flow.folder_id = default_folder.id
session.add(db_flow) session.add(db_flow)
session.commit() session.commit()
session.refresh(db_flow) session.refresh(db_flow)
@ -67,7 +74,7 @@ def read_flows(
example_flows = session.exec( example_flows = session.exec(
select(Flow).where( select(Flow).where(
Flow.user_id == None, # noqa Flow.user_id == None, # noqa
Flow.folder == STARTER_FOLDER_NAME, Flow.folder.has(Folder.name == STARTER_FOLDER_NAME),
) )
).all() ).all()
for example_flow in example_flows: for example_flow in example_flows:
@ -129,6 +136,10 @@ def update_flow(
if value is not None: if value is not None:
setattr(db_flow, key, value) setattr(db_flow, key, value)
db_flow.updated_at = datetime.now(timezone.utc) db_flow.updated_at = datetime.now(timezone.utc)
if db_flow.folder_id is None:
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if default_folder:
db_flow.folder_id = default_folder.id
session.add(db_flow) session.add(db_flow)
session.commit() session.commit()
session.refresh(db_flow) session.refresh(db_flow)
@ -208,3 +219,31 @@ async def download_file(
"""Download all flows as a file.""" """Download all flows as a file."""
flows = read_flows(current_user=current_user, session=session, settings_service=settings_service) flows = read_flows(current_user=current_user, session=session, settings_service=settings_service)
return FlowListRead(flows=flows) return FlowListRead(flows=flows)
@router.post("/multiple_delete/")
async def delete_multiple_flows(
flow_ids: FlowListIds, user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
):
"""
Delete multiple flows by their IDs.
Args:
flow_ids (List[str]): The list of flow IDs to delete.
user (User, optional): The user making the request. Defaults to the current active user.
Returns:
dict: A dictionary containing the number of flows deleted.
"""
try:
deleted_flows = db.exec(
select(Flow).where(col(Flow.id).in_(flow_ids.flow_ids)).where(Flow.user_id == user.id)
).all()
for flow in deleted_flows:
db.delete(flow)
db.commit()
return {"deleted": len(deleted_flows)}
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc

View file

@ -0,0 +1,243 @@
from typing import List
from uuid import UUID
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
from langflow.api.v1.flows import create_flows
from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
import orjson
from sqlalchemy import update
from sqlmodel import Session, select
from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.folder.model import (
Folder,
FolderCreate,
FolderRead,
FolderReadWithFlows,
FolderUpdate,
)
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session
router = APIRouter(prefix="/folders", tags=["Folders"])
@router.post("/", response_model=FolderRead, status_code=201)
def create_folder(
*,
session: Session = Depends(get_session),
folder: FolderCreate,
current_user: User = Depends(get_current_active_user),
):
try:
new_folder = Folder.model_validate(folder, from_attributes=True)
new_folder.user_id = current_user.id
session.add(new_folder)
session.commit()
session.refresh(new_folder)
if folder.components_list.__len__() > 0:
update_statement_components = (
update(Flow).where(Flow.id.in_(folder.components_list)).values(folder_id=new_folder.id)
)
session.exec(update_statement_components)
session.commit()
if folder.flows_list.__len__() > 0:
update_statement_flows = update(Flow).where(Flow.id.in_(folder.flows_list)).values(folder_id=new_folder.id)
session.exec(update_statement_flows)
session.commit()
return new_folder
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/", response_model=List[FolderRead], status_code=200)
def read_folders(
*,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user),
):
try:
folders = session.exec(select(Folder).where(Folder.user_id == current_user.id)).all()
return folders
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/starter-projects", response_model=FolderReadWithFlows, status_code=200)
def read_starter_folders(*, session: Session = Depends(get_session)):
try:
folders = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
return folders
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{folder_id}", response_model=FolderReadWithFlows, status_code=200)
def read_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
folder.flows = session.exec(select(Flow).where(Flow.folder_id == folder_id)).all()
return folder
except Exception as e:
if "No result found" in str(e):
raise HTTPException(status_code=404, detail="Folder not found")
raise HTTPException(status_code=500, detail=str(e))
@router.patch("/{folder_id}", response_model=FolderRead, status_code=200)
def update_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
folder: FolderUpdate, # Assuming FolderUpdate is a Pydantic model defining updatable fields
current_user: User = Depends(get_current_active_user),
):
try:
existing_folder = session.exec(
select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)
).first()
if not existing_folder:
raise HTTPException(status_code=404, detail="Folder not found")
folder_data = folder.model_dump(exclude_unset=True)
for key, value in folder_data.items():
if key != "components" and key != "flows":
setattr(existing_folder, key, value)
session.add(existing_folder)
session.commit()
session.refresh(existing_folder)
concat_folder_components = folder.components + folder.flows
flows_ids = session.exec(select(Flow.id).where(Flow.folder_id == existing_folder.id)).all()
excluded_flows = list(set(flows_ids) - set(concat_folder_components))
my_collection_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if my_collection_folder:
update_statement_my_collection = (
update(Flow).where(Flow.id.in_(excluded_flows)).values(folder_id=my_collection_folder.id)
)
session.exec(update_statement_my_collection)
session.commit()
if concat_folder_components.__len__() > 0:
update_statement_components = (
update(Flow).where(Flow.id.in_(concat_folder_components)).values(folder_id=existing_folder.id)
)
session.exec(update_statement_components)
session.commit()
return existing_folder
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{folder_id}", status_code=204)
def delete_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
session.delete(folder)
session.commit()
flows = session.exec(select(Flow).where(Flow.folder_id == folder_id, Folder.user_id == current_user.id)).all()
for flow in flows:
session.delete(flow)
session.commit()
return Response(status_code=status.HTTP_204_NO_CONTENT)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/download/{folder_id}", response_model=FlowListReadWithFolderName, status_code=200)
async def download_file(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
"""Download all flows from folder."""
try:
flows = session.exec(
select(Flow).distinct().join(Folder).where(Flow.folder_id == folder_id, Folder.user_id == current_user.id)
).all()
folder_name = (
session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first().name
)
folder_description = (
session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id))
.first()
.description
)
if not flows:
flows = []
return FlowListReadWithFolderName(flows=flows, folder_name=folder_name, folder_description=folder_description)
except Exception as e:
if "No result found" in str(e):
raise HTTPException(status_code=404, detail="Folder not found")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/upload/", response_model=List[FlowRead], status_code=201)
async def upload_file(
*,
session: Session = Depends(get_session),
file: UploadFile = File(...),
current_user: User = Depends(get_current_active_user),
):
"""Upload flows from a file."""
contents = await file.read()
data = orjson.loads(contents)
if data.__len__() == 0:
raise HTTPException(status_code=400, detail="No flows found in the file")
folder_results = session.exec(
select(Folder).where(Folder.name.like(f"{data['folder_name']}%"), Folder.user_id == current_user.id)
)
existing_folder_names = [folder.name for folder in folder_results]
if existing_folder_names.__len__() > 0:
data["folder_name"] = f"{data['folder_name']} ({existing_folder_names.__len__() + 1})"
folder = FolderCreate(name=data["folder_name"], description=data["folder_description"])
new_folder = Folder.model_validate(folder, from_attributes=True)
new_folder.id = None
new_folder.user_id = current_user.id
session.add(new_folder)
session.commit()
session.refresh(new_folder)
del data["folder_name"]
del data["folder_description"]
if "flows" in data:
flow_list = FlowListCreate(flows=[FlowCreate(**flow) for flow in data["flows"]])
else:
raise HTTPException(status_code=400, detail="No flows found in the data")
# Now we set the user_id for all flows
for flow in flow_list.flows:
flow.user_id = current_user.id
flow.folder_id = new_folder.id
return create_flows(session=session, flow_list=flow_list, current_user=current_user)

View file

@ -1,5 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session
from langflow.api.v1.schemas import Token from langflow.api.v1.schemas import Token
from langflow.services.auth.utils import ( from langflow.services.auth.utils import (
authenticate_user, authenticate_user,
@ -7,14 +9,10 @@ from langflow.services.auth.utils import (
create_user_longterm_token, create_user_longterm_token,
create_user_tokens, create_user_tokens,
) )
from langflow.services.deps import ( from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
get_session, from langflow.services.deps import get_session, get_settings_service, get_variable_service
get_settings_service,
get_variable_service,
)
from langflow.services.settings.manager import SettingsService from langflow.services.settings.manager import SettingsService
from langflow.services.variable.service import VariableService from langflow.services.variable.service import VariableService
from sqlmodel import Session
router = APIRouter(tags=["Login"]) router = APIRouter(tags=["Login"])
@ -58,6 +56,8 @@ async def login_to_get_access_token(
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS, expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
) )
variable_service.initialize_user_variables(user.id, db) variable_service.initialize_user_variables(user.id, db)
# Create default folder for user if it doesn't exist
create_default_folder_if_it_doesnt_exist(db, user.id)
return tokens return tokens
else: else:
raise HTTPException( raise HTTPException(
@ -86,6 +86,7 @@ async def auto_login(
expires=None, # Set to None to make it a session cookie expires=None, # Set to None to make it a session cookie
) )
variable_service.initialize_user_variables(user_id, db) variable_service.initialize_user_variables(user_id, db)
create_default_folder_if_it_doesnt_exist(db, user_id)
return tokens return tokens
raise HTTPException( raise HTTPException(
@ -139,4 +140,3 @@ async def logout(response: Response):
response.delete_cookie("refresh_token_lf") response.delete_cookie("refresh_token_lf")
response.delete_cookie("access_token_lf") response.delete_cookie("access_token_lf")
return {"message": "Logout successful"} return {"message": "Logout successful"}
return {"message": "Logout successful"}

View file

@ -1,10 +1,13 @@
from typing import Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from langflow.services.deps import get_monitor_service from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import VertexBuildMapModel from langflow.services.monitor.schema import (
MessageModelResponse,
TransactionModelResponse,
VertexBuildMapModel,
)
from langflow.services.monitor.service import MonitorService from langflow.services.monitor.service import MonitorService
router = APIRouter(prefix="/monitor", tags=["Monitor"]) router = APIRouter(prefix="/monitor", tags=["Monitor"])
@ -40,8 +43,9 @@ async def delete_vertex_builds(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/messages") @router.get("/messages", response_model=List[MessageModelResponse])
async def get_messages( async def get_messages(
flow_id: Optional[str] = Query(None),
session_id: Optional[str] = Query(None), session_id: Optional[str] = Query(None),
sender: Optional[str] = Query(None), sender: Optional[str] = Query(None),
sender_name: Optional[str] = Query(None), sender_name: Optional[str] = Query(None),
@ -49,25 +53,32 @@ async def get_messages(
monitor_service: MonitorService = Depends(get_monitor_service), monitor_service: MonitorService = Depends(get_monitor_service),
): ):
try: try:
return monitor_service.get_messages( df = monitor_service.get_messages(
flow_id=flow_id,
sender=sender, sender=sender,
sender_name=sender_name, sender_name=sender_name,
session_id=session_id, session_id=session_id,
order_by=order_by, order_by=order_by,
) )
dicts = df.to_dict(orient="records")
return [MessageModelResponse(**d) for d in dicts]
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/transactions") @router.get("/transactions", response_model=List[TransactionModelResponse])
async def get_transactions( async def get_transactions(
source: Optional[str] = Query(None), source: Optional[str] = Query(None),
target: Optional[str] = Query(None), target: Optional[str] = Query(None),
status: Optional[str] = Query(None), status: Optional[str] = Query(None),
order_by: Optional[str] = Query("timestamp"), order_by: Optional[str] = Query("timestamp"),
flow_id: Optional[str] = Query(None),
monitor_service: MonitorService = Depends(get_monitor_service), monitor_service: MonitorService = Depends(get_monitor_service),
): ):
try: try:
return monitor_service.get_transactions(source=source, target=target, status=status, order_by=order_by) dicts = monitor_service.get_transactions(
source=source, target=target, status=status, order_by=order_by, flow_id=flow_id
)
return [TransactionModelResponse(**d) for d in dicts]
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View file

@ -139,10 +139,20 @@ class FlowListCreate(BaseModel):
flows: List[FlowCreate] flows: List[FlowCreate]
class FlowListIds(BaseModel):
flow_ids: List[str]
class FlowListRead(BaseModel): class FlowListRead(BaseModel):
flows: List[FlowRead] flows: List[FlowRead]
class FlowListReadWithFolderName(BaseModel):
flows: List[FlowRead]
folder_name: str
folder_description: str
class InitResponse(BaseModel): class InitResponse(BaseModel):
flowId: str flowId: str

View file

@ -13,6 +13,7 @@ from langflow.services.auth.utils import (
get_password_hash, get_password_hash,
verify_password, verify_password,
) )
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
from langflow.services.database.models.user import User, UserCreate, UserRead, UserUpdate from langflow.services.database.models.user import User, UserCreate, UserRead, UserUpdate
from langflow.services.database.models.user.crud import get_user_by_id, update_user from langflow.services.database.models.user.crud import get_user_by_id, update_user
from langflow.services.deps import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
@ -36,6 +37,9 @@ def add_user(
session.add(new_user) session.add(new_user)
session.commit() session.commit()
session.refresh(new_user) session.refresh(new_user)
folder = create_default_folder_if_it_doesnt_exist(session, new_user.id)
if not folder:
raise HTTPException(status_code=500, detail="Error creating default folder")
except IntegrityError as e: except IntegrityError as e:
session.rollback() session.rollback()
raise HTTPException(status_code=400, detail="This username is unavailable.") from e raise HTTPException(status_code=400, detail="This username is unavailable.") from e

View file

@ -54,6 +54,7 @@ class ChatComponent(CustomComponent):
session_id=session_id, session_id=session_id,
sender=sender, sender=sender,
sender_name=sender_name, sender_name=sender_name,
flow_id=self.graph.flow_id,
) )
self.status = records self.status = records

View file

@ -1,3 +1,6 @@
from copy import deepcopy
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.schema import Record from langflow.schema import Record
@ -27,19 +30,20 @@ def dict_values_to_string(d: dict) -> dict:
dict: The dictionary with values converted to strings. dict: The dictionary with values converted to strings.
""" """
# Do something similar to the above # Do something similar to the above
for key, value in d.items(): d_copy = deepcopy(d)
for key, value in d_copy.items():
# it could be a list of records or documents or strings # it could be a list of records or documents or strings
if isinstance(value, list): if isinstance(value, list):
for i, item in enumerate(value): for i, item in enumerate(value):
if isinstance(item, Record): if isinstance(item, Record):
d[key][i] = record_to_string(item) d_copy[key][i] = record_to_string(item)
elif isinstance(item, Document): elif isinstance(item, Document):
d[key][i] = document_to_string(item) d_copy[key][i] = document_to_string(item)
elif isinstance(value, Record): elif isinstance(value, Record):
d[key] = record_to_string(value) d_copy[key] = record_to_string(value)
elif isinstance(value, Document): elif isinstance(value, Document):
d[key] = document_to_string(value) d_copy[key] = document_to_string(value)
return d return d_copy
def document_to_string(document: Document) -> str: def document_to_string(document: Document) -> str:

View file

@ -8,7 +8,7 @@ from langchain.prompts import SystemMessagePromptTemplate
from langchain.prompts.chat import MessagesPlaceholder from langchain.prompts.chat import MessagesPlaceholder
from langchain.schema.memory import BaseMemory from langchain.schema.memory import BaseMemory
from langchain.tools import Tool from langchain.tools import Tool
from langchain_community.chat_models import ChatOpenAI from langchain_openai import ChatOpenAI
from langflow.field_typing.range_spec import RangeSpec from langflow.field_typing.range_spec import RangeSpec
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent

View file

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from langchain_community.chat_models.openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langflow.base.models.openai_constants import MODEL_NAMES from langflow.base.models.openai_constants import MODEL_NAMES
from langflow.field_typing import BaseLanguageModel, NestedDict from langflow.field_typing import BaseLanguageModel, NestedDict

View file

@ -0,0 +1,10 @@
from langflow.base.io.text import TextComponent
from langflow.schema import Record
class RecordsOutput(TextComponent):
display_name = "Records Output"
description = "Display Records as a Table"
def build(self, input_value: Record) -> Record:
return input_value

View file

@ -3,9 +3,8 @@ from typing import TYPE_CHECKING, Any, List, Optional
from loguru import logger from loguru import logger
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from langflow.graph.edge.utils import build_clean_params from langflow.graph.vertex.utils import log_transaction
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.utils import log_message from langflow.services.monitor.utils import log_message
if TYPE_CHECKING: if TYPE_CHECKING:
@ -157,26 +156,9 @@ class ContractEdge(Edge):
message=target.params.get(INPUT_FIELD_NAME, {}), message=target.params.get(INPUT_FIELD_NAME, {}),
session_id=target.params.get("session_id", ""), session_id=target.params.get("session_id", ""),
artifacts=target.artifacts, artifacts=target.artifacts,
flow_id=target.graph.flow_id,
) )
return self.result return self.result
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}" return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)
data = {
"source": source.vertex_type,
"target": target.vertex_type,
"target_args": clean_params,
"timestamp": monitor_service.get_timestamp(),
"status": status,
"error": error,
}
monitor_service.add_row(table_name="transactions", data=data)
except Exception as e:
logger.error(f"Error logging transaction: {e}")
logger.error(f"Error logging transaction: {e}")

View file

@ -1,19 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
def build_clean_params(target: "Vertex") -> dict:
"""
Cleans the parameters of the target vertex.
"""
# Removes all keys that the values aren't python types like str, int, bool, etc.
params = {
key: value for key, value in target.params.items() if isinstance(value, (str, int, bool, float, list, dict))
}
# if it is a list we need to check if the contents are python types
for key, value in params.items():
if isinstance(value, list):
params[key] = [item for item in value if isinstance(item, (str, int, bool, float, list, dict))]
return params

View file

@ -14,7 +14,7 @@ from langflow.graph.graph.state_manager import GraphStateManager
from langflow.graph.graph.utils import process_flow from langflow.graph.graph.utils import process_flow
from langflow.graph.schema import InterfaceComponentTypes, RunOutputs from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ChatVertex, FileToolVertex, LLMVertex, StateVertex, ToolkitVertex from langflow.graph.vertex.types import FileToolVertex, InterfaceVertex, LLMVertex, StateVertex, ToolkitVertex
from langflow.interface.tools.constants import FILE_TOOLS from langflow.interface.tools.constants import FILE_TOOLS
from langflow.schema import Record from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME, InputType from langflow.schema.schema import INPUT_FIELD_NAME, InputType
@ -994,8 +994,8 @@ class Graph:
"""Returns the node class based on the node type.""" """Returns the node class based on the node type."""
# First we check for the node_base_type # First we check for the node_base_type
node_name = node_id.split("-")[0] node_name = node_id.split("-")[0]
if node_name in ["ChatOutput", "ChatInput"]: if node_name in InterfaceComponentTypes:
return ChatVertex return InterfaceVertex
elif node_name in ["SharedState", "Notify", "Listen"]: elif node_name in ["SharedState", "Notify", "Listen"]:
return StateVertex return StateVertex
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP: elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:

View file

@ -1,3 +1,4 @@
from langflow.graph.schema import CHAT_COMPONENTS
from langflow.graph.vertex import types from langflow.graph.vertex import types
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.custom.base import custom_component_creator from langflow.interface.custom.base import custom_component_creator
@ -13,8 +14,6 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils.lazy_load import LazyLoadDictBase from langflow.utils.lazy_load import LazyLoadDictBase
CHAT_COMPONENTS = ["ChatInput", "ChatOutput", "TextInput", "SessionID"]
class VertexTypesDict(LazyLoadDictBase): class VertexTypesDict(LazyLoadDictBase):
def __init__(self): def __init__(self):
@ -47,7 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()}, **{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()}, **{t: types.RetrieverVertex for t in retriever_creator.to_list()},
**{t: types.ChatVertex for t in CHAT_COMPONENTS}, **{t: types.InterfaceVertex for t in CHAT_COMPONENTS},
} }
def get_custom_component_vertex_type(self): def get_custom_component_vertex_type(self):

View file

@ -30,6 +30,7 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
ChatOutput = "ChatOutput" ChatOutput = "ChatOutput"
TextInput = "TextInput" TextInput = "TextInput"
TextOutput = "TextOutput" TextOutput = "TextOutput"
RecordsOutput = "RecordsOutput"
def __contains__(cls, item): def __contains__(cls, item):
try: try:
@ -40,6 +41,8 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
return True return True
CHAT_COMPONENTS = [InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatOutput]
RECORDS_COMPONENTS = [InterfaceComponentTypes.RecordsOutput]
INPUT_COMPONENTS = [ INPUT_COMPONENTS = [
InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatInput,
InterfaceComponentTypes.TextInput, InterfaceComponentTypes.TextInput,

View file

@ -10,7 +10,7 @@ from loguru import logger
from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData
from langflow.graph.utils import UnbuiltObject, UnbuiltResult from langflow.graph.utils import UnbuiltObject, UnbuiltResult
from langflow.graph.vertex.utils import generate_result from langflow.graph.vertex.utils import generate_result, log_transaction
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
from langflow.interface.listing import lazy_load_dict from langflow.interface.listing import lazy_load_dict
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
@ -440,7 +440,11 @@ class Vertex:
# to the frontend # to the frontend
self.set_artifacts() self.set_artifacts()
artifacts = self.artifacts artifacts = self.artifacts
messages = self.extract_messages_from_artifacts(artifacts) if isinstance(artifacts, dict):
messages = self.extract_messages_from_artifacts(artifacts)
else:
messages = []
result_dict = ResultData( result_dict = ResultData(
results=result_dict, results=result_dict,
artifacts=artifacts, artifacts=artifacts,
@ -508,7 +512,7 @@ class Vertex:
if not self._is_vertex(value): if not self._is_vertex(value):
self.params[key][sub_key] = value self.params[key][sub_key] = value
else: else:
result = await value.get_result() result = await value.get_result(self)
self.params[key][sub_key] = result self.params[key][sub_key] = result
def _is_vertex(self, value): def _is_vertex(self, value):
@ -523,9 +527,7 @@ class Vertex:
""" """
return all(self._is_vertex(vertex) for vertex in value) return all(self._is_vertex(vertex) for vertex in value)
async def get_result( async def get_result(self, requester: "Vertex") -> Any:
self,
) -> Any:
""" """
Retrieves the result of the vertex. Retrieves the result of the vertex.
@ -535,9 +537,9 @@ class Vertex:
The result of the vertex. The result of the vertex.
""" """
async with self._lock: async with self._lock:
return await self._get_result() return await self._get_result(requester)
async def _get_result(self) -> Any: async def _get_result(self, requester: "Vertex") -> Any:
""" """
Retrieves the result of the built component. Retrieves the result of the built component.
@ -547,15 +549,19 @@ class Vertex:
The built result if use_result is True, else the built object. The built result if use_result is True, else the built object.
""" """
if not self._built: if not self._built:
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="error")
raise ValueError(f"Component {self.display_name} has not been built yet") raise ValueError(f"Component {self.display_name} has not been built yet")
return self._built_result if self.use_result else self._built_object
result = self._built_result if self.use_result else self._built_object
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="success")
return result
async def _build_vertex_and_update_params(self, key, vertex: "Vertex"): async def _build_vertex_and_update_params(self, key, vertex: "Vertex"):
""" """
Builds a given vertex and updates the params dictionary accordingly. Builds a given vertex and updates the params dictionary accordingly.
""" """
result = await vertex.get_result() result = await vertex.get_result(self)
self._handle_func(key, result) self._handle_func(key, result)
if isinstance(result, list): if isinstance(result, list):
self._extend_params_list_with_result(key, result) self._extend_params_list_with_result(key, result)
@ -571,7 +577,7 @@ class Vertex:
""" """
self.params[key] = [] self.params[key] = []
for vertex in vertices: for vertex in vertices:
result = await vertex.get_result() result = await vertex.get_result(self)
# Weird check to see if the params[key] is a list # Weird check to see if the params[key] is a list
# because sometimes it is a Record and breaks the code # because sometimes it is a Record and breaks the code
if not isinstance(self.params[key], list): if not isinstance(self.params[key], list):

View file

@ -6,14 +6,14 @@ import yaml
from langchain_core.messages import AIMessage from langchain_core.messages import AIMessage
from loguru import logger from loguru import logger
from langflow.graph.schema import InterfaceComponentTypes from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes
from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.schema import Record from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.monitor.utils import log_vertex_build from langflow.services.monitor.utils import log_vertex_build
from langflow.utils.schemas import ChatOutputResponse from langflow.utils.schemas import ChatOutputResponse, RecordOutputResponse
from langflow.utils.util import unescape_string from langflow.utils.util import unescape_string
@ -309,7 +309,7 @@ class CustomComponentVertex(Vertex):
return self.artifacts["repr"] or super()._built_object_repr() return self.artifacts["repr"] or super()._built_object_repr()
class ChatVertex(Vertex): class InterfaceVertex(Vertex):
def __init__(self, data: Dict, graph): def __init__(self, data: Dict, graph):
super().__init__(data, graph=graph, base_type="custom_components", is_task=True) super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
self.steps = [self._build, self._run] self.steps = [self._build, self._run]
@ -325,56 +325,131 @@ class ChatVertex(Vertex):
return f"Task {self.task_id} is not running" return f"Task {self.task_id} is not running"
if self.artifacts: if self.artifacts:
# dump as a yaml string # dump as a yaml string
artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None} if isinstance(self.artifacts, dict):
_artifacts = [self.artifacts]
elif hasattr(self.artifacts, "records"):
_artifacts = self.artifacts.records
else:
_artifacts = self.artifacts
artifacts = []
for artifact in _artifacts:
# artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None}
artifact = {k.title().replace("_", " "): v for k, v in artifact.items() if v is not None}
artifacts.append(artifact)
yaml_str = yaml.dump(artifacts, default_flow_style=False, allow_unicode=True) yaml_str = yaml.dump(artifacts, default_flow_style=False, allow_unicode=True)
return yaml_str return yaml_str
return super()._built_object_repr() return super()._built_object_repr()
def _process_chat_component(self):
"""
Process the chat component and return the message.
This method processes the chat component by extracting the necessary parameters
such as sender, sender_name, and message from the `params` dictionary. It then
performs additional operations based on the type of the `_built_object` attribute.
If `_built_object` is an instance of `AIMessage`, it creates a `ChatOutputResponse`
object using the `from_message` method. If `_built_object` is not an instance of
`UnbuiltObject`, it checks the type of `_built_object` and performs specific
operations accordingly. If `_built_object` is a dictionary, it converts it into a
code block. If `_built_object` is an instance of `Record`, it assigns the `text`
attribute to the `message` variable. If `message` is an instance of `AsyncIterator`
or `Iterator`, it builds a stream URL and sets `message` to an empty string. If
`_built_object` is not a string, it converts it to a string. If `message` is a
generator or iterator, it assigns it to the `message` variable. Finally, it creates
a `ChatOutputResponse` object using the extracted parameters and assigns it to the
`artifacts` attribute. If `artifacts` is not None, it calls the `model_dump` method
on it and assigns the result to the `artifacts` attribute. It then returns the
`message` variable.
Returns:
str: The processed message.
"""
artifacts = None
sender = self.params.get("sender", None)
sender_name = self.params.get("sender_name", None)
message = self.params.get(INPUT_FIELD_NAME, None)
if isinstance(message, str):
message = unescape_string(message)
stream_url = None
if isinstance(self._built_object, AIMessage):
artifacts = ChatOutputResponse.from_message(
self._built_object,
sender=sender,
sender_name=sender_name,
)
elif not isinstance(self._built_object, UnbuiltObject):
if isinstance(self._built_object, dict):
# Turn the dict into a pleasing to
# read JSON inside a code block
message = dict_to_codeblock(self._built_object)
elif isinstance(self._built_object, Record):
message = self._built_object.text
elif isinstance(message, (AsyncIterator, Iterator)):
stream_url = self.build_stream_url()
message = ""
elif not isinstance(self._built_object, str):
message = str(self._built_object)
# if the message is a generator or iterator
# it means that it is a stream of messages
else:
message = self._built_object
artifacts = ChatOutputResponse(
message=message,
sender=sender,
sender_name=sender_name,
stream_url=stream_url,
)
self.will_stream = stream_url is not None
if artifacts:
self.artifacts = artifacts.model_dump(exclude_none=True)
return message
def _process_record_component(self):
"""
Process the record component of the vertex.
If the built object is an instance of `Record`, it calls the `model_dump` method
and assigns the result to the `artifacts` attribute.
If the built object is a list, it iterates over each element and checks if it is
an instance of `Record`. If it is, it calls the `model_dump` method and appends
the result to the `artifacts` list. If it is not, it raises a `ValueError` if the
`ignore_errors` parameter is set to `False`, or logs an error message if it is set
to `True`.
Returns:
The built object.
Raises:
ValueError: If an element in the list is not an instance of `Record` and
`ignore_errors` is set to `False`.
"""
if isinstance(self._built_object, Record):
artifacts = [self._built_object.data]
elif isinstance(self._built_object, list):
artifacts = []
ignore_errors = self.params.get("ignore_errors", False)
for record in self._built_object:
if isinstance(record, Record):
artifacts.append(record.data)
elif ignore_errors:
logger.error(f"Record expected, but got {record} of type {type(record)}")
else:
raise ValueError(f"Record expected, but got {record} of type {type(record)}")
self.artifacts = RecordOutputResponse(records=artifacts)
return self._built_object
async def _run(self, *args, **kwargs): async def _run(self, *args, **kwargs):
if self.is_interface_component: if self.is_interface_component:
if self.vertex_type in ["ChatOutput", "ChatInput"]: if self.vertex_type in CHAT_COMPONENTS:
artifacts = None message = self._process_chat_component()
sender = self.params.get("sender", None) elif self.vertex_type in RECORDS_COMPONENTS:
sender_name = self.params.get("sender_name", None) message = self._process_record_component()
message = self.params.get(INPUT_FIELD_NAME, None)
if isinstance(message, str):
message = unescape_string(message)
stream_url = None
if isinstance(self._built_object, AIMessage):
artifacts = ChatOutputResponse.from_message(
self._built_object,
sender=sender,
sender_name=sender_name,
)
elif not isinstance(self._built_object, UnbuiltObject):
if isinstance(self._built_object, dict):
# Turn the dict into a pleasing to
# read JSON inside a code block
message = dict_to_codeblock(self._built_object)
elif isinstance(self._built_object, Record):
message = self._built_object.text
elif isinstance(message, (AsyncIterator, Iterator)):
stream_url = self.build_stream_url()
message = ""
elif not isinstance(self._built_object, str):
message = str(self._built_object)
# if the message is a generator or iterator
# it means that it is a stream of messages
else:
message = self._built_object
artifacts = ChatOutputResponse(
message=message,
sender=sender,
sender_name=sender_name,
stream_url=stream_url,
)
self.will_stream = stream_url is not None
if artifacts:
self.artifacts = artifacts.model_dump(exclude_none=True)
if isinstance(self._built_object, (AsyncIterator, Iterator)): if isinstance(self._built_object, (AsyncIterator, Iterator)):
if self.params["return_record"]: if self.params.get("return_record", False):
self._built_object = Record(text=message, data=self.artifacts) self._built_object = Record(text=message, data=self.artifacts)
else: else:
self._built_object = message self._built_object = message

View file

@ -1,11 +1,15 @@
from typing import Any, Optional, Union from typing import Any, Optional, Union, TYPE_CHECKING
from langchain_core.messages import BaseMessage from langchain_core.messages import BaseMessage
from langchain_core.runnables import Runnable from langchain_core.runnables import Runnable
from loguru import logger from loguru import logger
from langflow.services.deps import get_monitor_service
from langflow.utils.constants import PYTHON_BASIC_TYPES from langflow.utils.constants import PYTHON_BASIC_TYPES
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
def is_basic_type(obj): def is_basic_type(obj):
return type(obj) in PYTHON_BASIC_TYPES return type(obj) in PYTHON_BASIC_TYPES
@ -63,3 +67,49 @@ async def generate_result(built_object: Any, inputs: dict, has_external_output:
else: else:
result = built_object result = built_object
return result return result
def build_clean_params(target: "Vertex") -> dict:
"""
Cleans the parameters of the target vertex.
"""
# Removes all keys that the values aren't python types like str, int, bool, etc.
params = {
key: value for key, value in target.params.items() if isinstance(value, (str, int, bool, float, list, dict))
}
# if it is a list we need to check if the contents are python types
for key, value in params.items():
if isinstance(value, list):
params[key] = [item for item in value if isinstance(item, (str, int, bool, float, list, dict))]
return params
def log_transaction(source: "Vertex", target: "Vertex", flow_id, status, error=None):
"""
Logs a transaction between two vertices.
Args:
source (Vertex): The source vertex of the transaction.
target (Vertex): The target vertex of the transaction.
status: The status of the transaction.
error (Optional): Any error associated with the transaction.
Raises:
Exception: If there is an error while logging the transaction.
"""
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)
data = {
"source": source.vertex_type,
"target": target.vertex_type,
"target_args": clean_params,
"timestamp": monitor_service.get_timestamp(),
"status": status,
"error": error,
"flow_id": flow_id,
}
monitor_service.add_row(table_name="transactions", data=data)
except Exception as e:
logger.error(f"Error logging transaction: {e}")

View file

@ -11,10 +11,11 @@ from sqlmodel import select
from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES
from langflow.interface.types import get_all_components from langflow.interface.types import get_all_components
from langflow.services.database.models.flow.model import Flow, FlowCreate from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.folder.model import Folder, FolderCreate
from langflow.services.deps import get_settings_service, session_scope from langflow.services.deps import get_settings_service, session_scope
STARTER_FOLDER_NAME = "Starter Projects" STARTER_FOLDER_NAME = "Starter Projects"
STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow."
# In the folder ./starter_projects we have a few JSON files that represent # In the folder ./starter_projects we have a few JSON files that represent
# starter projects. We want to load these into the database so that users # starter projects. We want to load these into the database so that users
@ -158,6 +159,7 @@ def create_new_project(
project_data, project_data,
project_icon, project_icon,
project_icon_bg_color, project_icon_bg_color,
new_folder_id
): ):
logger.debug(f"Creating starter project {project_name}") logger.debug(f"Creating starter project {project_name}")
new_project = FlowCreate( new_project = FlowCreate(
@ -168,33 +170,41 @@ def create_new_project(
data=project_data, data=project_data,
is_component=project_is_component, is_component=project_is_component,
updated_at=updated_at_datetime, updated_at=updated_at_datetime,
folder=STARTER_FOLDER_NAME, folder_id=new_folder_id,
) )
db_flow = Flow.model_validate(new_project, from_attributes=True) db_flow = Flow.model_validate(new_project, from_attributes=True)
session.add(db_flow) session.add(db_flow)
def get_all_flows_similar_to_project(session, project_name): def get_all_flows_similar_to_project(session, folder_id):
flows = session.exec( flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
select(Flow).where(
Flow.name == project_name,
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
return flows return flows
def delete_start_projects(session): def delete_start_projects(session, folder_id):
flows = session.exec( flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
select(Flow).where(
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
for flow in flows: for flow in flows:
session.delete(flow) session.delete(flow)
session.commit() session.commit()
def folder_exists(session, folder_name):
folder = session.exec(select(Folder).where(Folder.name == folder_name)).first()
return folder is not None
def create_starter_folder(session):
if not folder_exists(session, STARTER_FOLDER_NAME):
new_folder = FolderCreate(name=STARTER_FOLDER_NAME, description=STARTER_FOLDER_DESCRIPTION)
db_folder = Folder.model_validate(new_folder, from_attributes=True)
session.add(db_folder)
session.commit()
session.refresh(db_folder)
return db_folder
else:
return session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
def create_or_update_starter_projects(): def create_or_update_starter_projects():
components_paths = get_settings_service().settings.COMPONENTS_PATH components_paths = get_settings_service().settings.COMPONENTS_PATH
try: try:
@ -203,8 +213,9 @@ def create_or_update_starter_projects():
logger.exception(f"Error loading components: {e}") logger.exception(f"Error loading components: {e}")
raise e raise e
with session_scope() as session: with session_scope() as session:
new_folder = create_starter_folder(session)
starter_projects = load_starter_projects() starter_projects = load_starter_projects()
delete_start_projects(session) delete_start_projects(session, new_folder.id)
for project_path, project in starter_projects: for project_path, project in starter_projects:
( (
project_name, project_name,
@ -224,7 +235,7 @@ def create_or_update_starter_projects():
update_project_file(project_path, project, updated_project_data) update_project_file(project_path, project, updated_project_data)
if project_name and project_data: if project_name and project_data:
for existing_project in get_all_flows_similar_to_project(session, project_name): for existing_project in get_all_flows_similar_to_project(session, new_folder.id):
session.delete(existing_project) session.delete(existing_project)
create_new_project( create_new_project(
@ -236,4 +247,5 @@ def create_or_update_starter_projects():
project_data, project_data,
project_icon, project_icon,
project_icon_bg_color, project_icon_bg_color,
new_folder.id
) )

View file

@ -1,5 +1,5 @@
import warnings import warnings
from typing import Optional, Union from typing import List, Optional, Union
from loguru import logger from loguru import logger
@ -60,7 +60,7 @@ def get_messages(
return records return records
def add_messages(records: Union[list[Record], Record]): def add_messages(records: Union[list[Record], Record], flow_id: Optional[str] = None):
""" """
Add a message to the monitor service. Add a message to the monitor service.
""" """
@ -76,7 +76,8 @@ def add_messages(records: Union[list[Record], Record]):
messages: list[MessageModel] = [] messages: list[MessageModel] = []
for record in records: for record in records:
messages.append(MessageModel.from_record(record)) record.timestamp = monitor_service.get_timestamp()
messages.append(MessageModel.from_record(record, flow_id=flow_id))
for message in messages: for message in messages:
try: try:
@ -107,7 +108,24 @@ def store_message(
session_id: Optional[str] = None, session_id: Optional[str] = None,
sender: Optional[str] = None, sender: Optional[str] = None,
sender_name: Optional[str] = None, sender_name: Optional[str] = None,
) -> list[Record]: flow_id: Optional[str] = None,
) -> List[Record]:
"""
Stores a message in the memory.
Args:
message (Union[str, Record]): The message to be stored. It can be either a string or a Record object.
session_id (Optional[str]): The session ID associated with the message.
sender (Optional[str]): The sender ID associated with the message.
sender_name (Optional[str]): The name of the sender associated with the message.
flow_id (Optional[str]): The flow ID associated with the message. When running from the CustomComponent you can access this using `self.graph.flow_id`.
Returns:
List[Record]: A list of records containing the stored message.
Raises:
ValueError: If any of the required parameters (session_id, sender, sender_name) is not provided.
"""
if not message: if not message:
warnings.warn("No message provided.") warnings.warn("No message provided.")
return [] return []
@ -134,4 +152,4 @@ def store_message(
}, },
) )
return add_messages([record]) return add_messages([record], flow_id=flow_id)

View file

@ -1,4 +1,5 @@
import copy import copy
import json
from typing import Literal, Optional, cast from typing import Literal, Optional, cast
from langchain_core.documents import Document from langchain_core.documents import Document
@ -162,23 +163,15 @@ class Record(BaseModel):
# Create a new Record object with a deep copy of the data dictionary # Create a new Record object with a deep copy of the data dictionary
return Record(data=copy.deepcopy(self.data, memo), text_key=self.text_key, default_value=self.default_value) return Record(data=copy.deepcopy(self.data, memo), text_key=self.text_key, default_value=self.default_value)
def __str__(self) -> str:
"""
Returns a string representation of the Record, including text and data.
"""
# Assuming a method to dump model data as JSON string exists.
# If it doesn't, you might need to implement it or use json.dumps() directly.
# build the string considering all keys in the data dictionary
prefix = "Record("
suffix = ")"
text = f"text_key={self.text_key}, "
text += ", ".join([f"{k}={v}" for k, v in self.data.items()])
return prefix + text + suffix
# check which attributes the Record has by checking the keys in the data dictionary # check which attributes the Record has by checking the keys in the data dictionary
def __dir__(self): def __dir__(self):
return super().__dir__() + list(self.data.keys()) return super().__dir__() + list(self.data.keys())
def __str__(self) -> str:
# return a JSON string representation of the Record atributes
return json.dumps(self.data)
INPUT_FIELD_NAME = "input_value" INPUT_FIELD_NAME = "input_value"

View file

@ -1,6 +1,7 @@
from .api_key import ApiKey from .api_key import ApiKey
from .flow import Flow from .flow import Flow
from .folder import Folder
from .user import User from .user import User
from .variable import Variable from .variable import Variable
__all__ = ["Flow", "User", "ApiKey", "Variable"] __all__ = ["Flow", "User", "ApiKey", "Variable", "Folder"]

View file

@ -13,6 +13,7 @@ from sqlmodel import JSON, Column, Field, Relationship, SQLModel
from langflow.schema.schema import Record from langflow.schema.schema import Record
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.database.models.folder import Folder
from langflow.services.database.models.user import User from langflow.services.database.models.user import User
@ -24,7 +25,7 @@ class FlowBase(SQLModel):
data: Optional[Dict] = Field(default=None, nullable=True) data: Optional[Dict] = Field(default=None, nullable=True)
is_component: Optional[bool] = Field(default=False, nullable=True) is_component: Optional[bool] = Field(default=False, nullable=True)
updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc), nullable=True) updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc), nullable=True)
folder: Optional[str] = Field(default=None, nullable=True) folder_id: Optional[UUID] = Field(default=None, nullable=True)
@field_validator("icon_bg_color") @field_validator("icon_bg_color")
def validate_icon_bg_color(cls, v): def validate_icon_bg_color(cls, v):
@ -112,6 +113,8 @@ class Flow(FlowBase, table=True):
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON)) data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
user_id: Optional[UUID] = Field(index=True, foreign_key="user.id", nullable=True) user_id: Optional[UUID] = Field(index=True, foreign_key="user.id", nullable=True)
user: "User" = Relationship(back_populates="flows") user: "User" = Relationship(back_populates="flows")
folder_id: Optional[UUID] = Field(default=None, foreign_key="folder.id", nullable=True, index=True)
folder: Optional["Folder"] = Relationship(back_populates="flows")
def to_record(self): def to_record(self):
serialized = self.model_dump() serialized = self.model_dump()
@ -128,14 +131,17 @@ class Flow(FlowBase, table=True):
class FlowCreate(FlowBase): class FlowCreate(FlowBase):
user_id: Optional[UUID] = None user_id: Optional[UUID] = None
folder_id: Optional[UUID] = None
class FlowRead(FlowBase): class FlowRead(FlowBase):
id: UUID id: UUID
user_id: Optional[UUID] = Field() user_id: Optional[UUID] = Field()
folder_id: Optional[UUID] = Field()
class FlowUpdate(SQLModel): class FlowUpdate(SQLModel):
name: Optional[str] = None name: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
data: Optional[Dict] = None data: Optional[Dict] = None
folder_id: Optional[UUID] = None

View file

@ -0,0 +1,3 @@
from .model import Folder, FolderCreate, FolderRead, FolderUpdate
__all__ = ["Folder", "FolderCreate", "FolderRead", "FolderUpdate"]

View file

@ -0,0 +1,2 @@
DEFAULT_FOLDER_DESCRIPTION = "Manage your personal projects. Download and upload entire collections."
DEFAULT_FOLDER_NAME = "My Projects"

View file

@ -0,0 +1,55 @@
from typing import TYPE_CHECKING, List, Optional
from uuid import UUID, uuid4
from sqlmodel import Field, Relationship, SQLModel
from langflow.services.database.models.flow.model import FlowRead
if TYPE_CHECKING:
from langflow.services.database.models.flow.model import Flow
from langflow.services.database.models.user.model import User
class FolderBase(SQLModel):
name: str = Field(index=True)
description: Optional[str] = Field(default=None)
class Folder(FolderBase, table=True):
id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True)
parent_id: Optional[UUID] = Field(default=None, foreign_key="folder.id")
parent: Optional["Folder"] = Relationship(
back_populates="children",
sa_relationship_kwargs=dict(remote_side="Folder.id"),
)
children: List["Folder"] = Relationship(back_populates="parent")
user_id: Optional[UUID] = Field(default=None, foreign_key="user.id")
user: "User" = Relationship(back_populates="folders")
flows: List["Flow"] = Relationship(
back_populates="folder", sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"}
)
class FolderCreate(FolderBase):
components_list: Optional[List[UUID]] = None
flows_list: Optional[List[UUID]] = None
class FolderRead(FolderBase):
id: UUID
parent_id: Optional[UUID] = Field()
class FolderReadWithFlows(FolderBase):
id: UUID
parent_id: Optional[UUID] = Field()
flows: List["FlowRead"] = Field(default=[])
class FolderUpdate(SQLModel):
name: Optional[str] = None
description: Optional[str] = None
parent_id: Optional[UUID] = None
components: Optional[List[UUID]] = None
flows: Optional[List[UUID]] = None

View file

@ -0,0 +1,26 @@
from typing import TYPE_CHECKING
from uuid import UUID
from langflow.services.database.models.flow.model import Flow
from sqlmodel import Session, select, update
from .constants import DEFAULT_FOLDER_DESCRIPTION, DEFAULT_FOLDER_NAME
from .model import Folder
if TYPE_CHECKING:
from langflow.services.database.models.user.model import User
def create_default_folder_if_it_doesnt_exist(session: Session, user_id: UUID):
if not session.exec(select(Folder).where(Folder.user_id == user_id)).first():
folder = Folder(name=DEFAULT_FOLDER_NAME, user_id=user_id, description=DEFAULT_FOLDER_DESCRIPTION)
session.add(folder)
session.commit()
session.refresh(folder)
session.exec(
update(Flow)
.where((Flow.folder_id == None) & (Flow.user_id == user_id))
.values(folder_id=folder.id)
)
session.commit()
return None

View file

@ -8,6 +8,7 @@ if TYPE_CHECKING:
from langflow.services.database.models.api_key import ApiKey from langflow.services.database.models.api_key import ApiKey
from langflow.services.database.models.variable import Variable from langflow.services.database.models.variable import Variable
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.services.database.models.folder import Folder
class User(SQLModel, table=True): class User(SQLModel, table=True):
@ -30,6 +31,10 @@ class User(SQLModel, table=True):
back_populates="user", back_populates="user",
sa_relationship_kwargs={"cascade": "delete"}, sa_relationship_kwargs={"cascade": "delete"},
) )
folders: list["Folder"] = Relationship(
back_populates="user",
sa_relationship_kwargs={"cascade": "delete"},
)
class UserCreate(SQLModel): class UserCreate(SQLModel):

View file

@ -2,15 +2,16 @@ import json
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Any, Optional
from pydantic import BaseModel, Field, field_serializer, validator from pydantic import BaseModel, Field, field_serializer, field_validator
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.schema import Record from langflow.schema import Record
class TransactionModel(BaseModel): class TransactionModel(BaseModel):
id: Optional[int] = Field(default=None, alias="id") index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp") timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
flow_id: str
source: str source: str
target: str target: str
target_args: dict target_args: dict
@ -22,15 +23,53 @@ class TransactionModel(BaseModel):
populate_by_name = True populate_by_name = True
# validate target_args in case it is a JSON # validate target_args in case it is a JSON
@validator("target_args", pre=True) @field_validator("target_args", mode="before")
def validate_target_args(cls, v): def validate_target_args(cls, v):
if isinstance(v, str): if isinstance(v, str):
return json.loads(v) return json.loads(v)
return v return v
@field_serializer("target_args")
def serialize_target_args(v):
if isinstance(v, dict):
return json.dumps(v)
return v
class TransactionModelResponse(BaseModel):
index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
flow_id: str
source: str
target: str
target_args: dict
status: str
error: Optional[str] = None
class Config:
from_attributes = True
populate_by_name = True
# validate target_args in case it is a JSON
@field_validator("target_args", mode="before")
def validate_target_args(cls, v):
if isinstance(v, str):
return json.loads(v)
return v
@field_validator("index", mode="before")
def validate_id(cls, v):
if isinstance(v, float):
try:
return int(v)
except ValueError:
return None
return v
class MessageModel(BaseModel): class MessageModel(BaseModel):
id: Optional[int] = Field(default=None, alias="id") index: Optional[int] = Field(default=None)
flow_id: Optional[str] = Field(default=None, alias="flow_id")
timestamp: datetime = Field(default_factory=datetime.now) timestamp: datetime = Field(default_factory=datetime.now)
sender: str sender: str
sender_name: str sender_name: str
@ -42,14 +81,14 @@ class MessageModel(BaseModel):
from_attributes = True from_attributes = True
populate_by_name = True populate_by_name = True
@validator("artifacts", pre=True) @field_validator("artifacts", mode="before")
def validate_target_args(cls, v): def validate_target_args(cls, v):
if isinstance(v, str): if isinstance(v, str):
return json.loads(v) return json.loads(v)
return v return v
@classmethod @classmethod
def from_record(cls, record: "Record"): def from_record(cls, record: "Record", flow_id: Optional[str] = None):
# first check if the record has all the required fields # first check if the record has all the required fields
if not record.data or ("sender" not in record.data and "sender_name" not in record.data): if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.") raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
@ -59,9 +98,30 @@ class MessageModel(BaseModel):
message=record.text, message=record.text,
session_id=record.session_id, session_id=record.session_id,
artifacts=record.artifacts or {}, artifacts=record.artifacts or {},
timestamp=record.timestamp,
flow_id=flow_id,
) )
class MessageModelResponse(MessageModel):
index: Optional[int] = Field(default=None)
@field_validator("artifacts", mode="before")
def serialize_artifacts(v):
if isinstance(v, str):
return json.loads(v)
return v
@field_validator("index", mode="before")
def validate_id(cls, v):
if isinstance(v, float):
try:
return int(v)
except ValueError:
return None
return v
class VertexBuildModel(BaseModel): class VertexBuildModel(BaseModel):
index: Optional[int] = Field(default=None, alias="index", exclude=True) index: Optional[int] = Field(default=None, alias="index", exclude=True)
id: Optional[str] = Field(default=None, alias="id") id: Optional[str] = Field(default=None, alias="id")
@ -86,9 +146,11 @@ class VertexBuildModel(BaseModel):
elif isinstance(value, list) and all(isinstance(i, BaseModel) for i in value): elif isinstance(value, list) and all(isinstance(i, BaseModel) for i in value):
v[key] = [i.model_dump() for i in value] v[key] = [i.model_dump() for i in value]
return json.dumps(v) return json.dumps(v)
elif isinstance(v, BaseModel):
return v.model_dump_json()
return v return v
@validator("params", pre=True) @field_validator("params", mode="before")
def validate_params(cls, v): def validate_params(cls, v):
if isinstance(v, str): if isinstance(v, str):
try: try:
@ -103,16 +165,18 @@ class VertexBuildModel(BaseModel):
return json.dumps([i.model_dump() for i in v]) return json.dumps([i.model_dump() for i in v])
return v return v
@validator("data", pre=True) @field_validator("data", mode="before")
def validate_data(cls, v): def validate_data(cls, v):
if isinstance(v, str): if isinstance(v, str):
return json.loads(v) return json.loads(v)
return v return v
@validator("artifacts", pre=True) @field_validator("artifacts", mode="before")
def validate_artifacts(cls, v): def validate_artifacts(cls, v):
if isinstance(v, str): if isinstance(v, str):
return json.loads(v) return json.loads(v)
elif isinstance(v, BaseModel):
return v.model_dump()
return v return v

View file

@ -69,7 +69,7 @@ class MonitorService(Service):
valid: Optional[bool] = None, valid: Optional[bool] = None,
order_by: Optional[str] = "timestamp", order_by: Optional[str] = "timestamp",
): ):
query = "SELECT id, flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds" query = "SELECT index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
conditions = [] conditions = []
if flow_id: if flow_id:
conditions.append(f"flow_id = '{flow_id}'") conditions.append(f"flow_id = '{flow_id}'")
@ -109,6 +109,7 @@ class MonitorService(Service):
def get_messages( def get_messages(
self, self,
flow_id: Optional[str] = None,
sender: Optional[str] = None, sender: Optional[str] = None,
sender_name: Optional[str] = None, sender_name: Optional[str] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
@ -116,7 +117,7 @@ class MonitorService(Service):
order: Optional[str] = "DESC", order: Optional[str] = "DESC",
limit: Optional[int] = None, limit: Optional[int] = None,
): ):
query = "SELECT sender_name, sender, session_id, message, artifacts, timestamp FROM messages" query = "SELECT index, flow_id, sender_name, sender, session_id, message, artifacts, timestamp FROM messages"
conditions = [] conditions = []
if sender: if sender:
conditions.append(f"sender = '{sender}'") conditions.append(f"sender = '{sender}'")
@ -124,6 +125,8 @@ class MonitorService(Service):
conditions.append(f"sender_name = '{sender_name}'") conditions.append(f"sender_name = '{sender_name}'")
if session_id: if session_id:
conditions.append(f"session_id = '{session_id}'") conditions.append(f"session_id = '{session_id}'")
if flow_id:
conditions.append(f"flow_id = '{flow_id}'")
if conditions: if conditions:
query += " WHERE " + " AND ".join(conditions) query += " WHERE " + " AND ".join(conditions)
@ -146,8 +149,9 @@ class MonitorService(Service):
target: Optional[str] = None, target: Optional[str] = None,
status: Optional[str] = None, status: Optional[str] = None,
order_by: Optional[str] = "timestamp", order_by: Optional[str] = "timestamp",
flow_id: Optional[str] = None,
): ):
query = "SELECT source, target, target_args, status, error, timestamp FROM transactions" query = "SELECT index,flow_id, source, target, target_args, status, error, timestamp FROM transactions"
conditions = [] conditions = []
if source: if source:
conditions.append(f"source = '{source}'") conditions.append(f"source = '{source}'")
@ -155,6 +159,8 @@ class MonitorService(Service):
conditions.append(f"target = '{target}'") conditions.append(f"target = '{target}'")
if status: if status:
conditions.append(f"status = '{status}'") conditions.append(f"status = '{status}'")
if flow_id:
conditions.append(f"flow_id = '{flow_id}'")
if conditions: if conditions:
query += " WHERE " + " AND ".join(conditions) query += " WHERE " + " AND ".join(conditions)

View file

@ -61,7 +61,7 @@ def drop_and_create_table_if_schema_mismatch(db_path: str, table_name: str, mode
if current_schema != desired_schema: if current_schema != desired_schema:
# If they don't match, drop the existing table and create a new one # If they don't match, drop the existing table and create a new one
conn.execute(f"DROP TABLE IF EXISTS {table_name}") conn.execute(f"DROP TABLE IF EXISTS {table_name}")
if "id" in desired_schema.keys(): if INDEX_KEY in desired_schema.keys():
# Create a sequence for the id column # Create a sequence for the id column
try: try:
conn.execute(f"CREATE SEQUENCE seq_{table_name} START 1;") conn.execute(f"CREATE SEQUENCE seq_{table_name} START 1;")
@ -91,7 +91,7 @@ def add_row_to_table(
columns = ", ".join(keys) columns = ", ".join(keys)
values_placeholders = ", ".join(["?" for _ in keys]) values_placeholders = ", ".join(["?" for _ in keys])
values = list(validated_dict.values()) values = [validated_dict[key] for key in keys]
# Create the insert statement # Create the insert statement
insert_sql = f"INSERT INTO {table_name} ({columns}) VALUES ({values_placeholders})" insert_sql = f"INSERT INTO {table_name} ({columns}) VALUES ({values_placeholders})"
@ -104,7 +104,7 @@ def add_row_to_table(
column_error_message = "" column_error_message = ""
for key, value in validated_dict.items(): for key, value in validated_dict.items():
logger.error(f"{key}: {type(value)}") logger.error(f"{key}: {type(value)}")
if value in str(e): if str(value) in str(e):
column_error_message = f"Column: {key} Value: {value} Error: {e}" column_error_message = f"Column: {key} Value: {value} Error: {e}"
if column_error_message: if column_error_message:
@ -119,6 +119,7 @@ async def log_message(
message: str, message: str,
session_id: str, session_id: str,
artifacts: Optional[dict] = None, artifacts: Optional[dict] = None,
flow_id: Optional[str] = None,
): ):
try: try:
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
@ -134,6 +135,7 @@ async def log_message(
"artifacts": artifacts or {}, "artifacts": artifacts or {},
"session_id": session_id, "session_id": session_id,
"timestamp": monitor_service.get_timestamp(), "timestamp": monitor_service.get_timestamp(),
"flow_id": flow_id,
} }
monitor_service.add_row(table_name="messages", data=row) monitor_service.add_row(table_name="messages", data=row)
except Exception as e: except Exception as e:

View file

@ -45,6 +45,12 @@ class ChatOutputResponse(BaseModel):
return self return self
class RecordOutputResponse(BaseModel):
"""Record output response schema."""
records: List[Optional[Dict]]
class ContainsEnumMeta(enum.EnumMeta): class ContainsEnumMeta(enum.EnumMeta):
def __contains__(cls, item): def __contains__(cls, item):
try: try:

View file

@ -131,13 +131,13 @@ tz = ["backports.zoneinfo"]
[[package]] [[package]]
name = "annotated-types" name = "annotated-types"
version = "0.6.0" version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated" description = "Reusable constraint types to use with typing.Annotated"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
] ]
[[package]] [[package]]
@ -632,17 +632,20 @@ gmpy2 = ["gmpy2"]
[[package]] [[package]]
name = "emoji" name = "emoji"
version = "2.11.1" version = "2.12.1"
description = "Emoji for Python" description = "Emoji for Python"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = ">=3.7"
files = [ files = [
{file = "emoji-2.11.1-py2.py3-none-any.whl", hash = "sha256:b7ba25299bbf520cc8727848ae66b986da32aee27dc2887eaea2bff07226ce49"}, {file = "emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235"},
{file = "emoji-2.11.1.tar.gz", hash = "sha256:062ff0b3154b6219143f8b9f4b3e5c64c35bc2b146e6e2349ab5f29e218ce1ee"}, {file = "emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb"},
] ]
[package.dependencies]
typing-extensions = ">=4.7.0"
[package.extras] [package.extras]
dev = ["coverage", "coveralls", "pytest"] dev = ["coverage", "pytest (>=7.4.4)"]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
@ -1169,13 +1172,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]] [[package]]
name = "langsmith" name = "langsmith"
version = "0.1.59" version = "0.1.60"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langsmith-0.1.59-py3-none-any.whl", hash = "sha256:445e3bc1d3baa1e5340cd979907a19483b9763a2ed37b863a01113d406f69345"}, {file = "langsmith-0.1.60-py3-none-any.whl", hash = "sha256:3c3520ea473de0a984237b3e9d638fdf23ef3acc5aec89a42e693225e72d6120"},
{file = "langsmith-0.1.59.tar.gz", hash = "sha256:e748a89f4dd6aa441349143e49e546c03b5dfb43376a25bfef6a5ca792fe1437"}, {file = "langsmith-0.1.60.tar.gz", hash = "sha256:6a145b5454437f9e0f81525f23c4dcdbb8c07b1c91553b8f697456c418d6a599"},
] ]
[package.dependencies] [package.dependencies]
@ -2329,13 +2332,13 @@ files = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.31.0" version = "2.32.2"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
] ]
[package.dependencies] [package.dependencies]
@ -2583,13 +2586,13 @@ typing-extensions = ">=3.7.4.3"
[[package]] [[package]]
name = "types-requests" name = "types-requests"
version = "2.31.0.20240406" version = "2.32.0.20240521"
description = "Typing stubs for requests" description = "Typing stubs for requests"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, {file = "types-requests-2.32.0.20240521.tar.gz", hash = "sha256:c5c4a0ae95aad51f1bf6dae9eed04a78f7f2575d4b171da37b622e08b93eb5d3"},
{file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, {file = "types_requests-2.32.0.20240521-py3-none-any.whl", hash = "sha256:ab728ba43ffb073db31f21202ecb97db8753ded4a9dc49cb480d8a5350c5c421"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow-base" name = "langflow-base"
version = "0.0.45" version = "0.0.47"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

View file

@ -9,6 +9,7 @@
"version": "0.1.2", "version": "0.1.2",
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.17", "@headlessui/react": "^1.7.17",
"@hookform/resolvers": "^3.3.4",
"@million/lint": "^0.0.73", "@million/lint": "^0.0.73",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
@ -55,6 +56,7 @@
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "^18.2.21", "react-dom": "^18.2.21",
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
"react-hook-form": "^7.51.4",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
@ -73,10 +75,11 @@
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vite-plugin-svgr": "^3.2.0", "vite-plugin-svgr": "^3.2.0",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"zod": "^3.23.7",
"zustand": "^4.4.7" "zustand": "^4.4.7"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.43.1", "@playwright/test": "^1.44.0",
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80", "@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
@ -150,6 +153,26 @@
"nun": "bin/nun.mjs" "nun": "bin/nun.mjs"
} }
}, },
"node_modules/@axiomhq/js": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/@axiomhq/js/-/js-1.0.0-rc.3.tgz",
"integrity": "sha512-Zm10TczcMLounWqC42nMkXQ7XKLqjzLrd5ia022oBKDUZqAFVg2y9d1quQVNV4FlXyg9MKDdfMjpKQRmzEGaog==",
"dependencies": {
"fetch-retry": "^6.0.0",
"uuid": "^8.3.2"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@axiomhq/js/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.24.2", "version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@ -429,6 +452,40 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@clack/core": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz",
"integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==",
"dependencies": {
"picocolors": "^1.0.0",
"sisteransi": "^1.0.5"
}
},
"node_modules/@clack/prompts": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz",
"integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==",
"bundleDependencies": [
"is-unicode-supported"
],
"dependencies": {
"@clack/core": "^0.3.3",
"is-unicode-supported": "*",
"picocolors": "^1.0.0",
"sisteransi": "^1.0.5"
}
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@ -868,9 +925,9 @@
} }
}, },
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.6.1", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.1.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz",
"integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==", "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==",
"dependencies": { "dependencies": {
"@floating-ui/utils": "^0.2.0" "@floating-ui/utils": "^0.2.0"
} }
@ -885,9 +942,9 @@
} }
}, },
"node_modules/@floating-ui/react-dom": { "node_modules/@floating-ui/react-dom": {
"version": "2.0.9", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz",
"integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==", "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.0.0" "@floating-ui/dom": "^1.0.0"
}, },
@ -917,6 +974,14 @@
"react-dom": "^16 || ^17 || ^18" "react-dom": "^16 || ^17 || ^18"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.4.2.tgz",
"integrity": "sha512-1m9uAVIO8wVf7VCDAGsuGA0t6Z3m6jVGAN50HkV9vYLl0yixKK/Z1lr01vaRvYCkIKGoy1noVRxMzQYb4y/j1Q==",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@ -999,6 +1064,17 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1" "url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": { "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -1034,6 +1110,22 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1" "url": "https://github.com/chalk/strip-ansi?sponsor=1"
} }
}, },
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@ -1109,6 +1201,25 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@million/install": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@million/install/-/install-0.0.3.tgz",
"integrity": "sha512-yK8NgP+73+Tby/bmQ12B96bJ7RjsLazLtFgBed1Fg1WfTCCpHTILq79yQMSD/OWgm1tt1NYV4ELaTRM6wZOeAg==",
"dependencies": {
"@antfu/ni": "^0.21.12",
"@axiomhq/js": "1.0.0-rc.3",
"@babel/core": "^7.24.5",
"@babel/types": "^7.23.6",
"@clack/prompts": "^0.7.0",
"cli-high": "^0.4.1",
"diff": "^5.1.0",
"posthog-node": "^3.6.3",
"xycolors": "^0.1.1"
},
"bin": {
"install": "bin/index.js"
}
},
"node_modules/@million/lint": { "node_modules/@million/lint": {
"version": "0.0.73", "version": "0.0.73",
"resolved": "https://registry.npmjs.org/@million/lint/-/lint-0.0.73.tgz", "resolved": "https://registry.npmjs.org/@million/lint/-/lint-0.0.73.tgz",
@ -3232,14 +3343,14 @@
} }
}, },
"node_modules/@swc/core": { "node_modules/@swc/core": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.7.tgz",
"integrity": "sha512-0UC0NkgWoqd9fkHPn1NTkTsQucW8iaA1fujK2OLGp40Zg5Vr7nrwBlqruX9expVMggS4rv/3vZSAGzRm80VQ/g==", "integrity": "sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@swc/counter": "^0.1.2", "@swc/counter": "^0.1.2",
"@swc/types": "^0.1.5" "@swc/types": "0.1.7"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@ -3249,16 +3360,16 @@
"url": "https://opencollective.com/swc" "url": "https://opencollective.com/swc"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-darwin-arm64": "1.5.6", "@swc/core-darwin-arm64": "1.5.7",
"@swc/core-darwin-x64": "1.5.6", "@swc/core-darwin-x64": "1.5.7",
"@swc/core-linux-arm-gnueabihf": "1.5.6", "@swc/core-linux-arm-gnueabihf": "1.5.7",
"@swc/core-linux-arm64-gnu": "1.5.6", "@swc/core-linux-arm64-gnu": "1.5.7",
"@swc/core-linux-arm64-musl": "1.5.6", "@swc/core-linux-arm64-musl": "1.5.7",
"@swc/core-linux-x64-gnu": "1.5.6", "@swc/core-linux-x64-gnu": "1.5.7",
"@swc/core-linux-x64-musl": "1.5.6", "@swc/core-linux-x64-musl": "1.5.7",
"@swc/core-win32-arm64-msvc": "1.5.6", "@swc/core-win32-arm64-msvc": "1.5.7",
"@swc/core-win32-ia32-msvc": "1.5.6", "@swc/core-win32-ia32-msvc": "1.5.7",
"@swc/core-win32-x64-msvc": "1.5.6" "@swc/core-win32-x64-msvc": "1.5.7"
}, },
"peerDependencies": { "peerDependencies": {
"@swc/helpers": "^0.5.0" "@swc/helpers": "^0.5.0"
@ -3270,9 +3381,9 @@
} }
}, },
"node_modules/@swc/core-darwin-arm64": { "node_modules/@swc/core-darwin-arm64": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.7.tgz",
"integrity": "sha512-U4szqU03cvZOTXug5o+HQbbg16ZGwANtr7LELjw4hmWBqSTcbBVXgocyBMm1L4ngFIsqJc33D+urnnaae/NfMg==", "integrity": "sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3286,9 +3397,9 @@
} }
}, },
"node_modules/@swc/core-darwin-x64": { "node_modules/@swc/core-darwin-x64": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.7.tgz",
"integrity": "sha512-k4jymBHYkbfGw8DT4bLtVsAckpp2dblyImQHRPScpvDyS4jUo4174mgy/dEnFmZVLTbuZAY876zBQyH+eJ3p4A==", "integrity": "sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3302,9 +3413,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm-gnueabihf": { "node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.7.tgz",
"integrity": "sha512-ijwGEdP18vS8YmvHUIfKYDFQ5mQ1GtCxhJp+IcJlrBJE+/eSJVvEVF5WwXFQ+Hzj6tr/OOub8UcRNUaQokjh3A==", "integrity": "sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3318,9 +3429,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-gnu": { "node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.7.tgz",
"integrity": "sha512-6YD942jsDm64wgGNew4Q1Yh8CRI4QKWTqjzCkvlZCGV6aOw5B663WDSnUEKIoN4egeLcvSiqYYGlKMfbkkfNMw==", "integrity": "sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3334,9 +3445,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-musl": { "node_modules/@swc/core-linux-arm64-musl": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.7.tgz",
"integrity": "sha512-Jntdd/HQAgRSQFUpmXjiU00BG08Yl5G1pgYDBXG2mPfkndGGgapRT8JPsnzfQujh9nOdWMIQDnCGU+mB4NY2Dg==", "integrity": "sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3350,9 +3461,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-gnu": { "node_modules/@swc/core-linux-x64-gnu": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.7.tgz",
"integrity": "sha512-cdaAyAZJvYCx0u9uadUVe4VVJOEC6GeVO5uTGo/vybCsKFn2Z8WVjGVTeHbxdp7pH2II9MRTySIv1eCCq70SOQ==", "integrity": "sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3366,9 +3477,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-musl": { "node_modules/@swc/core-linux-x64-musl": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.7.tgz",
"integrity": "sha512-mGIs4d6/Hv/5EbP2d+2YNmIj9U5U/6CiPZsTyahQcEl+vJjNsmwDJoLex36LhxoGIUmVbbgk6cHj5DoHWNl0bQ==", "integrity": "sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3382,9 +3493,9 @@
} }
}, },
"node_modules/@swc/core-win32-arm64-msvc": { "node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.7.tgz",
"integrity": "sha512-ViiS2pUXy/J9RZWJXQJMuDKid0lqH9fu2piFi3IHnMWyyWPla5qHYijkeODwrdBsjc30NAHjV6jfka5SZduqiw==", "integrity": "sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3398,9 +3509,9 @@
} }
}, },
"node_modules/@swc/core-win32-ia32-msvc": { "node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.7.tgz",
"integrity": "sha512-Ju5z65Nda8oYjt2Z4CGbuBLxM+98TcewLogutI69rBo5S70xunadp4ANIHcr9fZlTu/IX9mxZmUpOaHtb+/TfA==", "integrity": "sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -3414,9 +3525,9 @@
} }
}, },
"node_modules/@swc/core-win32-x64-msvc": { "node_modules/@swc/core-win32-x64-msvc": {
"version": "1.5.6", "version": "1.5.7",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.6.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz",
"integrity": "sha512-pkqJsdrgxyFYJA+g9a3zbl+AoxYoBtv+Rji1Fw4+Sq7CsSMWboayWNN5fKRiyi3u3nG8kxqyGi51PsKy2G4Vpg==", "integrity": "sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3436,9 +3547,9 @@
"dev": true "dev": true
}, },
"node_modules/@swc/types": { "node_modules/@swc/types": {
"version": "0.1.6", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.7.tgz",
"integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", "integrity": "sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@swc/counter": "^0.1.3" "@swc/counter": "^0.1.3"
@ -4230,9 +4341,9 @@
} }
}, },
"node_modules/@types/lodash": { "node_modules/@types/lodash": {
"version": "4.17.1", "version": "4.17.4",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==", "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==",
"dev": true "dev": true
}, },
"node_modules/@types/mathjax": { "node_modules/@types/mathjax": {
@ -4318,12 +4429,12 @@
"dev": true "dev": true
}, },
"node_modules/@vitejs/plugin-react-swc": { "node_modules/@vitejs/plugin-react-swc": {
"version": "3.6.0", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.6.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.0.tgz",
"integrity": "sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==", "integrity": "sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@swc/core": "^1.3.107" "@swc/core": "^1.5.7"
}, },
"peerDependencies": { "peerDependencies": {
"vite": "^4 || ^5" "vite": "^4 || ^5"
@ -4342,9 +4453,9 @@
"optional": true "optional": true
}, },
"node_modules/ace-builds": { "node_modules/ace-builds": {
"version": "1.33.2", "version": "1.34.0",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.33.2.tgz", "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.34.0.tgz",
"integrity": "sha512-uDqCe+XDIdnADaDrA8o+x+qAfbM6uqyDQ43QcE6qC7zBPTvQSMOSPcXW+HvjZhEc2YbVYSaxXJX1qQKPgYqi5w==" "integrity": "sha512-ZQqoV76wl4guDE5zvEnxCDrvy9gzLAwu7eD4ikYj/Gdlb4+qRLbb+aOFVnweZZRsHh089V0WVaw2NMNuiiEdTw=="
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.11.3", "version": "8.11.3",
@ -4384,16 +4495,16 @@
} }
}, },
"node_modules/ag-grid-community": { "node_modules/ag-grid-community": {
"version": "31.3.1", "version": "31.3.2",
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.3.1.tgz", "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.3.2.tgz",
"integrity": "sha512-kKnNxY8UaVoF0aUSdtzK7oGr48Wj+VrdDY5l2p9+HdF0cAo/jBEasuUYR85QbkumNyilI6UbFpO6IyCrjNQ6Iw==" "integrity": "sha512-GxqFRD0OcjaVRE1gwLgoP0oERNPH8Lk8wKJ1txulsxysEQ5dZWHhiIoXXSiHjvOCVMkK/F5qzY6HNrn6VeDMTQ=="
}, },
"node_modules/ag-grid-react": { "node_modules/ag-grid-react": {
"version": "31.3.1", "version": "31.3.2",
"resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.3.1.tgz", "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.3.2.tgz",
"integrity": "sha512-MP5PhFeRhe1gWNx866Sr1C+IrJcrJ0yj8pM/ls+Be/GNmSyIcmGb0Gwbl19bOSdfL5btTkNmGY/RbwnIO4GoMQ==", "integrity": "sha512-SFHN05bsXp901rIT00Fa6iQLCtyavoJiKaXEDUtAU5LMu+GTkjs/FPQBQ8754omgdDFr4NsS3Ri6QbqBne3rug==",
"dependencies": { "dependencies": {
"ag-grid-community": "31.3.1", "ag-grid-community": "31.3.2",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"peerDependencies": { "peerDependencies": {
@ -4508,6 +4619,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"delegates": "^1.0.0", "delegates": "^1.0.0",
@ -4621,9 +4733,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.8", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -4810,11 +4922,11 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -4963,9 +5075,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001618", "version": "1.0.30001621",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz",
"integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==", "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -5118,6 +5230,24 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/cli-high": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cli-high/-/cli-high-0.4.1.tgz",
"integrity": "sha512-fPbkILfCVG6mfxbsJbJZQsKE5JmfgJNlfBxap52XHXjLwPDhs/OMFVFrNR7bqiavZt2r5A80+wiRHzJyzWqwEQ==",
"hasInstallScript": true,
"dependencies": {
"@clack/prompts": "^0.7.0",
"sugar-high": "^0.6.1",
"xycolors": "^0.1.1",
"yargs": "^17.7.2"
},
"bin": {
"cli-high": "bin/index.js"
},
"funding": {
"url": "https://github.com/sponsors/xinyao27"
}
},
"node_modules/cli-spinners": { "node_modules/cli-spinners": {
"version": "2.9.2", "version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
@ -5134,6 +5264,19 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
}, },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/clone": { "node_modules/clone": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@ -5787,9 +5930,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.3.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.4.tgz",
"integrity": "sha512-5sOWYSNPaxz6o2MUPvtyxTTqR4D3L77pr5rUQoWgD5ROQtVIZQgJkXbo1DLlK3vj11YGw5+LnF4SYti4gZmwng==" "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww=="
}, },
"node_modules/dot-case": { "node_modules/dot-case": {
"version": "3.0.4", "version": "3.0.4",
@ -5817,9 +5960,9 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.767", "version": "1.4.778",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.767.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.778.tgz",
"integrity": "sha512-nzzHfmQqBss7CE3apQHkHjXW77+8w3ubGCIoEijKCJebPufREaFETgGXWTkh32t259F3Kcq+R8MZdFdOJROgYw==" "integrity": "sha512-C6q/xcUJf/2yODRxAVCfIk4j3y3LMsD0ehiE2RQNV2cxc8XU62gR6vvYh3+etSUzlgTfil+qDHI1vubpdf0TOA=="
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
@ -6554,6 +6697,11 @@
"node": "^12.20 || >= 14.13" "node": "^12.20 || >= 14.13"
} }
}, },
"node_modules/fetch-retry": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz",
"integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag=="
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -6618,9 +6766,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -6779,9 +6927,9 @@
} }
}, },
"node_modules/framer-motion": { "node_modules/framer-motion": {
"version": "11.2.0", "version": "11.2.6",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.0.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.6.tgz",
"integrity": "sha512-LRfLVPEwtO9IXJCAsWvtj3XZxrdZDcTxNNkZEq30aQ8p7/wimfUkDy67TDWdtzPiyKDkqOHDhaQC6XVrQ4Fh7A==", "integrity": "sha512-XUrjjBt57e5YoHQtjwc3eNchFBuHvIgN/cS8SC4oIaAn2J/0+bLanUxXizidJKZVeHJam/JrmMnPRjYMglVn5g==",
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
@ -6885,6 +7033,7 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"aproba": "^1.0.3 || ^2.0.0", "aproba": "^1.0.3 || ^2.0.0",
@ -6909,6 +7058,14 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
@ -7885,9 +8042,9 @@
} }
}, },
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "2.3.6", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
}, },
@ -9309,11 +9466,11 @@
] ]
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.5", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dependencies": { "dependencies": {
"braces": "^3.0.2", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
@ -9321,12 +9478,13 @@
} }
}, },
"node_modules/million": { "node_modules/million": {
"version": "3.0.6", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/million/-/million-3.0.6.tgz", "resolved": "https://registry.npmjs.org/million/-/million-3.1.1.tgz",
"integrity": "sha512-OLjRVASGOZdyZw2ctBSSOu5kb9PaxafqkueqVvw0iQtUUnTLVRk1EmtqcNAtJWCIm8wn+WGRpDbnp+5Hi8//Kg==", "integrity": "sha512-vmI3lyA3IN4QKiB0/M3uDef3lZZvgVUokWtLkc5NvxEnykY+TdSR6xatMMNDJmzMHTneayIOpc/eQu4d9Z/r2w==",
"dependencies": { "dependencies": {
"@babel/core": "^7.23.7", "@babel/core": "^7.23.7",
"@babel/types": "^7.23.6", "@babel/types": "^7.23.6",
"@million/install": "^0.0.3",
"@rollup/pluginutils": "^5.1.0", "@rollup/pluginutils": "^5.1.0",
"kleur": "^4.1.5", "kleur": "^4.1.5",
"undici": "^6.3.0", "undici": "^6.3.0",
@ -9650,6 +9808,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"are-we-there-yet": "^2.0.0", "are-we-there-yet": "^2.0.0",
@ -10268,9 +10427,9 @@
} }
}, },
"node_modules/postcss-nested/node_modules/postcss-selector-parser": { "node_modules/postcss-nested/node_modules/postcss-selector-parser": {
"version": "6.0.16", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@ -10810,6 +10969,21 @@
"react": ">=16.13.1" "react": ">=16.13.1"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.51.5",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz",
"integrity": "sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==",
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/react-icons": { "node_modules/react-icons": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz",
@ -11256,6 +11430,14 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/requires-port": { "node_modules/requires-port": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@ -12130,12 +12312,12 @@
} }
}, },
"node_modules/sucrase/node_modules/glob": { "node_modules/sucrase/node_modules/glob": {
"version": "10.3.15", "version": "10.3.16",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz",
"integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==",
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^2.3.6", "jackspeak": "^3.1.2",
"minimatch": "^9.0.1", "minimatch": "^9.0.1",
"minipass": "^7.0.4", "minipass": "^7.0.4",
"path-scurry": "^1.11.0" "path-scurry": "^1.11.0"
@ -12158,6 +12340,11 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/sugar-high": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/sugar-high/-/sugar-high-0.6.1.tgz",
"integrity": "sha512-kg1qMW7WwJcueXIlHkChL/p2EWY3gf8rQmP6n5nUq2TWVqatqDTMLvViS9WgAjgyTKH5/3/b8sRwWPOOAo1zMA=="
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -12253,9 +12440,9 @@
} }
}, },
"node_modules/tailwindcss/node_modules/postcss-selector-parser": { "node_modules/tailwindcss/node_modules/postcss-selector-parser": {
"version": "6.0.16", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@ -12520,9 +12707,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.16.1", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.16.1.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.18.1.tgz",
"integrity": "sha512-NeNiTT7ixpeiL1qOIU/xTVpHpVP0svmI6PwoCKaMGaI5AsHOaRdwqU/f7Fi9eyU4u03nd5U/BC8wmRMnS9nqoA==", "integrity": "sha512-/0BWqR8rJNRysS5lqVmfc7eeOErcOP4tZpATVjJOojjHZ71gSYVAtFhEmadcIjwMIUehh5NFyKGsXCnXIajtbA==",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"
} }
@ -13462,16 +13649,16 @@
} }
}, },
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "8.1.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": { "dependencies": {
"ansi-styles": "^6.1.0", "ansi-styles": "^4.0.0",
"string-width": "^5.0.1", "string-width": "^4.1.0",
"strip-ansi": "^7.0.1" "strip-ansi": "^6.0.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=10"
}, },
"funding": { "funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1" "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@ -13524,62 +13711,35 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
}, },
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": { "node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": { "engines": {
"node": ">=12" "node": ">=8"
}, },
"funding": { "funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/wrap-ansi/node_modules/emoji-regex": { "node_modules/wrap-ansi/node_modules/color-convert": {
"version": "9.2.2", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dependencies": { "dependencies": {
"eastasianwidth": "^0.2.0", "color-name": "~1.1.4"
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=7.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/wrap-ansi/node_modules/strip-ansi": { "node_modules/wrap-ansi/node_modules/color-name": {
"version": "7.1.0", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
}, },
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
@ -13636,6 +13796,23 @@
"node": ">=0.4" "node": ">=0.4"
} }
}, },
"node_modules/xycolors": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/xycolors/-/xycolors-0.1.1.tgz",
"integrity": "sha512-BbRKWpz/87nNH4lXp6TbBFUT0QipzmJI7ksQpSpBb3ny8mGJgkiKk36bIr8VqfyTEhasEBsfbp/Cum37fIHnjA==",
"hasInstallScript": true,
"funding": {
"url": "https://github.com/sponsors/xinyao27"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@ -13652,6 +13829,31 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -1,134 +1,137 @@
{ {
"name": "langflow", "name": "langflow",
"version": "0.1.2", "version": "0.1.2",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.17", "@headlessui/react": "^1.7.17",
"@million/lint": "^0.0.73", "@hookform/resolvers": "^3.3.4",
"@radix-ui/react-accordion": "^1.1.2", "@million/lint": "^0.0.73",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-form": "^0.0.3", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-menubar": "^1.0.3", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.6", "@radix-ui/react-tabs": "^1.0.4",
"@tabler/icons-react": "^2.32.0", "@radix-ui/react-tooltip": "^1.0.6",
"@tailwindcss/forms": "^0.5.6", "@tabler/icons-react": "^2.32.0",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/forms": "^0.5.6",
"@types/axios": "^0.14.0", "@tailwindcss/line-clamp": "^0.4.4",
"ace-builds": "^1.24.1", "@types/axios": "^0.14.0",
"ag-grid-community": "^31.2.1", "ace-builds": "^1.24.1",
"ag-grid-react": "^31.2.1", "ag-grid-community": "^31.2.1",
"ansi-to-html": "^0.7.2", "ag-grid-react": "^31.2.1",
"axios": "^1.5.0", "ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1", "axios": "^1.5.0",
"class-variance-authority": "^0.6.1", "base64-js": "^1.5.1",
"clsx": "^1.2.1", "class-variance-authority": "^0.6.1",
"cmdk": "^1.0.0", "clsx": "^1.2.1",
"dompurify": "^3.0.5", "cmdk": "^1.0.0",
"dotenv": "^16.4.5", "dompurify": "^3.0.5",
"esbuild": "^0.17.19", "dotenv": "^16.4.5",
"file-saver": "^2.0.5", "esbuild": "^0.17.19",
"framer-motion": "^11.0.6", "file-saver": "^2.0.5",
"lodash": "^4.17.21", "framer-motion": "^11.0.6",
"lucide-react": "^0.331.0", "lodash": "^4.17.21",
"million": "^3.0.6", "lucide-react": "^0.331.0",
"moment": "^2.29.4", "million": "^3.0.6",
"openseadragon": "^4.1.1", "moment": "^2.29.4",
"playwright": "^1.42.0", "openseadragon": "^4.1.1",
"react": "^18.2.21", "playwright": "^1.42.0",
"react-ace": "^10.1.0", "react": "^18.2.21",
"react-cookie": "^4.1.1", "react-ace": "^10.1.0",
"react-dom": "^18.2.21", "react-cookie": "^4.1.1",
"react-error-boundary": "^4.0.11", "react-dom": "^18.2.21",
"react-icons": "^5.0.1", "react-error-boundary": "^4.0.11",
"react-laag": "^2.0.5", "react-hook-form": "^7.51.4",
"react-markdown": "^8.0.7", "react-icons": "^5.0.1",
"react-pdf": "^7.7.1", "react-laag": "^2.0.5",
"react-router-dom": "^6.15.0", "react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0", "react-pdf": "^7.7.1",
"react18-json-view": "^0.2.3", "react-router-dom": "^6.15.0",
"reactflow": "^11.9.2", "react-syntax-highlighter": "^15.5.0",
"rehype-mathjax": "^4.0.3", "react18-json-view": "^0.2.3",
"remark-gfm": "^3.0.1", "reactflow": "^11.9.2",
"remark-math": "^5.1.1", "rehype-mathjax": "^4.0.3",
"shadcn-ui": "^0.2.3", "remark-gfm": "^3.0.1",
"short-unique-id": "^4.4.4", "remark-math": "^5.1.1",
"tailwind-merge": "^1.14.0", "shadcn-ui": "^0.2.3",
"tailwindcss-animate": "^1.0.7", "short-unique-id": "^4.4.4",
"uuid": "^9.0.0", "tailwind-merge": "^1.14.0",
"vite-plugin-svgr": "^3.2.0", "tailwindcss-animate": "^1.0.7",
"web-vitals": "^2.1.4", "uuid": "^9.0.0",
"zustand": "^4.4.7" "vite-plugin-svgr": "^3.2.0",
}, "web-vitals": "^2.1.4",
"scripts": { "zod": "^3.23.7",
"dev:docker": "vite --host 0.0.0.0", "zustand": "^4.4.7"
"start": "vite", },
"build": "vite build", "scripts": {
"serve": "vite preview", "dev:docker": "vite --host 0.0.0.0",
"format": "npx prettier --write \"{docs,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore", "start": "vite",
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite" "build": "vite build",
}, "serve": "vite preview",
"simple-git-hooks": { "format": "npx prettier --write \"{docs,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore",
"pre-commit": "npx pretty-quick --staged" "type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
}, },
"eslintConfig": { "simple-git-hooks": {
"extends": [ "pre-commit": "npx pretty-quick --staged"
"react-app", },
"react-app/jest" "eslintConfig": {
] "extends": [
}, "react-app",
"browserslist": { "react-app/jest"
"production": [ ]
">0.2%", },
"not dead", "browserslist": {
"not op_mini all" "production": [
], ">0.2%",
"development": [ "not dead",
"last 1 chrome version", "not op_mini all"
"last 1 firefox version", ],
"last 1 safari version" "development": [
] "last 1 chrome version",
}, "last 1 firefox version",
"proxy": "http://127.0.0.1:7860", "last 1 safari version"
"devDependencies": { ]
"@playwright/test": "^1.43.1", },
"@swc/cli": "^0.1.62", "proxy": "http://127.0.0.1:7860",
"@swc/core": "^1.3.80", "devDependencies": {
"@tailwindcss/typography": "^0.5.9", "@playwright/test": "^1.44.0",
"@testing-library/jest-dom": "^5.17.0", "@swc/cli": "^0.1.62",
"@testing-library/react": "^13.4.0", "@swc/core": "^1.3.80",
"@testing-library/user-event": "^13.5.0", "@tailwindcss/typography": "^0.5.9",
"@types/jest": "^27.5.2", "@testing-library/jest-dom": "^5.17.0",
"@types/lodash": "^4.14.197", "@testing-library/react": "^13.4.0",
"@types/node": "^16.18.46", "@testing-library/user-event": "^13.5.0",
"@types/react": "^18.2.21", "@types/jest": "^27.5.2",
"@types/react-dom": "^18.2.7", "@types/lodash": "^4.14.197",
"@types/uuid": "^9.0.2", "@types/node": "^16.18.46",
"@vitejs/plugin-react-swc": "^3.3.2", "@types/react": "^18.2.21",
"autoprefixer": "^10.4.15", "@types/react-dom": "^18.2.7",
"daisyui": "^4.0.4", "@types/uuid": "^9.0.2",
"eslint": "^8.57.0", "@vitejs/plugin-react-swc": "^3.3.2",
"eslint-plugin-node": "^11.1.0", "autoprefixer": "^10.4.15",
"postcss": "^8.4.29", "daisyui": "^4.0.4",
"prettier": "^2.8.8", "eslint": "^8.57.0",
"prettier-plugin-organize-imports": "^3.2.3", "eslint-plugin-node": "^11.1.0",
"prettier-plugin-tailwindcss": "^0.3.0", "postcss": "^8.4.29",
"pretty-quick": "^3.1.3", "prettier": "^2.8.8",
"simple-git-hooks": "^2.11.1", "prettier-plugin-organize-imports": "^3.2.3",
"tailwindcss": "^3.3.3", "prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss-dotted-background": "^1.1.0", "pretty-quick": "^3.1.3",
"typescript": "^5.2.2", "simple-git-hooks": "^2.11.1",
"ua-parser-js": "^1.0.37", "tailwindcss": "^3.3.3",
"vite": "^4.5.2" "tailwindcss-dotted-background": "^1.1.0",
} "typescript": "^5.2.2",
"ua-parser-js": "^1.0.37",
"vite": "^4.5.2"
}
} }

View file

@ -11,6 +11,13 @@ body {
text-align: center; text-align: center;
} }
.label {
user-select: none;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none;
}
.react-flow__node { .react-flow__node {
width: auto; width: auto;
height: auto; height: auto;
@ -76,6 +83,25 @@ body {
height: 8px !important; height: 8px !important;
border-radius: 10px; border-radius: 10px;
} }
*::-webkit-scrollbar {
width: 8px !important;
height: 8px !important;
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: #f1f1f1 !important;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background-color: #ccc !important;
border-radius: 999px !important;
}
::-webkit-scrollbar-thumb:hover {
background-color: #bbb !important;
}
.jv-indent::-webkit-scrollbar-track { .jv-indent::-webkit-scrollbar-track {
background-color: #f1f1f1 !important; background-color: #f1f1f1 !important;
@ -111,3 +137,30 @@ body {
.json-view-flow .json-view { .json-view-flow .json-view {
background-color: #bbb !important; background-color: #bbb !important;
} }
.ag-body-horizontal-scroll-viewport,
.ag-body-vertical-scroll-viewport {
cursor: auto;
}
.ag-body-horizontal-scroll-viewport::-webkit-scrollbar,
.ag-body-vertical-scroll-viewport::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-track,
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb,
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 999px;
}
.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb:hover,
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
background-color: #bbb;
}

View file

@ -19,6 +19,7 @@ import Router from "./routes";
import useAlertStore from "./stores/alertStore"; import useAlertStore from "./stores/alertStore";
import { useDarkStore } from "./stores/darkStore"; import { useDarkStore } from "./stores/darkStore";
import useFlowsManagerStore from "./stores/flowsManagerStore"; import useFlowsManagerStore from "./stores/flowsManagerStore";
import { useFolderStore } from "./stores/foldersStore";
import { useGlobalVariablesStore } from "./stores/globalVariablesStore/globalVariables"; import { useGlobalVariablesStore } from "./stores/globalVariablesStore/globalVariables";
import { useStoreStore } from "./stores/storeStore"; import { useStoreStore } from "./stores/storeStore";
import { useTypesStore } from "./stores/typesStore"; import { useTypesStore } from "./stores/typesStore";
@ -47,13 +48,13 @@ export default function App() {
const setGlobalVariables = useGlobalVariablesStore( const setGlobalVariables = useGlobalVariablesStore(
(state) => state.setGlobalVariables, (state) => state.setGlobalVariables,
); );
const setUnavailableFields = useGlobalVariablesStore(
(state) => state.setUnavaliableFields,
);
const checkHasStore = useStoreStore((state) => state.checkHasStore); const checkHasStore = useStoreStore((state) => state.checkHasStore);
const navigate = useNavigate(); const navigate = useNavigate();
const dark = useDarkStore((state) => state.dark); const dark = useDarkStore((state) => state.dark);
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
const loadingFolders = useFolderStore((state) => state.loading);
const [isLoadingHealth, setIsLoadingHealth] = useState(false); const [isLoadingHealth, setIsLoadingHealth] = useState(false);
useEffect(() => { useEffect(() => {
@ -76,7 +77,7 @@ export default function App() {
setUserData(user); setUserData(user);
setAutoLogin(true); setAutoLogin(true);
setLoading(false); setLoading(false);
await Promise.all([refreshStars(), refreshVersion(), fetchData()]); fetchAllData();
} }
}) })
.catch(async (error) => { .catch(async (error) => {
@ -84,7 +85,7 @@ export default function App() {
setAutoLogin(false); setAutoLogin(false);
if (isAuthenticated && !isLoginPage) { if (isAuthenticated && !isLoginPage) {
getUser(); getUser();
await Promise.all([refreshStars(), refreshVersion(), fetchData()]); fetchAllData();
} else { } else {
setLoading(false); setLoading(false);
useFlowsManagerStore.setState({ isLoading: false }); useFlowsManagerStore.setState({ isLoading: false });
@ -100,6 +101,13 @@ export default function App() {
return () => abortController.abort(); return () => abortController.abort();
}, []); }, []);
const fetchAllData = async () => {
setTimeout(async () => {
await Promise.all([refreshStars(), refreshVersion(), fetchData()]);
getFoldersApi();
}, 1000);
};
const fetchData = async () => { const fetchData = async () => {
return new Promise<void>(async (resolve, reject) => { return new Promise<void>(async (resolve, reject) => {
if (isAuthenticated) { if (isAuthenticated) {
@ -157,7 +165,7 @@ export default function App() {
setFetchError(false); setFetchError(false);
//This condition is necessary to avoid infinite loop on starter page when the application is not healthy //This condition is necessary to avoid infinite loop on starter page when the application is not healthy
if (isLoading === true && window.location.pathname === "/") { if (isLoading === true && window.location.pathname === "/") {
navigate("/flows"); navigate("/all");
window.location.reload(); window.location.reload();
} }
}; };
@ -184,7 +192,7 @@ export default function App() {
></FetchErrorComponent> ></FetchErrorComponent>
} }
{isLoading ? ( {isLoading || loadingFolders ? (
<div className="loading-page-panel"> <div className="loading-page-panel">
<LoadingComponent remSize={50} /> <LoadingComponent remSize={50} />
</div> </div>

View file

@ -0,0 +1,72 @@
import { useState } from "react";
import IconComponent from "../../../../components/genericIconComponent";
import { AccordionComponentType } from "../../../../types/components";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "../../../ui/custom-accordion";
export default function FolderAccordionComponent({
trigger,
open = [],
keyValue,
options,
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion(),
);
function getOpenAccordion(): string {
let value = "";
open.forEach((el) => {
if (el == trigger) {
value = trigger;
}
});
return value;
}
function handleClick(): void {
value === "" ? setValue(keyValue!) : setValue("");
}
return (
<>
<Accordion
type="single"
className="w-full"
value={value}
onValueChange={setValue}
>
<AccordionItem value={keyValue!} className="">
<AccordionTrigger
onClick={() => {
handleClick();
}}
className="px-2"
>
{trigger}
</AccordionTrigger>
<AccordionContent>
{options!.map((option, index) => (
<div
key={index}
className="flex cursor-pointer px-2 py-1 hover:bg-muted-foreground/10"
>
<IconComponent
name={option.icon}
className="relative top-[1.5px] mr-2 h-4 w-4"
aria-hidden="true"
/>
<span className="truncate">{option.title}</span>
</div>
))}
</AccordionContent>
</AccordionItem>
</Accordion>
</>
);
}

View file

@ -130,8 +130,8 @@ export default function AddNewVariableButton({ children }): JSX.Element {
<InputComponent <InputComponent
setSelectedOptions={(value) => setFields(value)} setSelectedOptions={(value) => setFields(value)}
selectedOptions={fields} selectedOptions={fields}
password={false}
options={availableFields()} options={availableFields()}
password={false}
placeholder="Choose a field for the variable..." placeholder="Choose a field for the variable..."
id={"apply-to-fields"} id={"apply-to-fields"}
></InputComponent> ></InputComponent>

View file

@ -0,0 +1,12 @@
export default function ArrayReader({ array }: { array: any[] }): JSX.Element {
//TODO check array type
return (
<div>
<ul>
{array.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}

View file

@ -0,0 +1,39 @@
import { storeComponent } from "../../../../types/store";
import { cn } from "../../../../utils/utils";
import ForwardedIconComponent from "../../../genericIconComponent";
import ShadTooltip from "../../../shadTooltipComponent";
import { Card, CardHeader, CardTitle } from "../../../ui/card";
export default function DragCardComponent({ data }: { data: storeComponent }) {
return (
<>
<Card
draggable
//TODO check color schema
className={cn(
"group relative flex flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
)}
>
<div>
<CardHeader>
<div>
<CardTitle className="flex w-full items-start justify-between gap-3 text-xl">
<ForwardedIconComponent
className={cn(
"visible flex-shrink-0",
data.is_component
? "mx-0.5 h-6 w-6 text-component-icon"
: "h-7 w-7 flex-shrink-0 text-flow-icon",
)}
name={data.is_component ? "ToyBrick" : "Group"}
/>
<div className="w-full truncate pr-3">{data.name}</div>
</CardTitle>
</div>
</CardHeader>
</div>
</Card>
</>
);
}

View file

@ -1,4 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import { Control } from "react-hook-form";
import { getComponent, postLikeComponent } from "../../controllers/API"; import { getComponent, postLikeComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal"; import IOModal from "../../modals/IOModal";
import DeleteConfirmationModal from "../../modals/deleteConfirmationModal"; import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
@ -22,8 +24,11 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "../ui/card"; } from "../ui/card";
import { Checkbox } from "../ui/checkbox";
import { FormControl, FormField } from "../ui/form";
import Loading from "../ui/loading"; import Loading from "../ui/loading";
import { convertTestName } from "./utils/convert-test-name"; import { convertTestName } from "./utils/convert-test-name";
import DragCardComponent from "./components/dragCardComponent";
export default function CollectionCardComponent({ export default function CollectionCardComponent({
data, data,
@ -33,6 +38,8 @@ export default function CollectionCardComponent({
onClick, onClick,
onDelete, onDelete,
playground, playground,
control,
is_component,
}: { }: {
data: storeComponent; data: storeComponent;
authorized?: boolean; authorized?: boolean;
@ -41,6 +48,8 @@ export default function CollectionCardComponent({
button?: JSX.Element; button?: JSX.Element;
playground?: boolean; playground?: boolean;
onDelete?: () => void; onDelete?: () => void;
control?: Control<any, any>;
is_component?: boolean;
}) { }) {
const addFlow = useFlowsManagerStore((state) => state.addFlow); const addFlow = useFlowsManagerStore((state) => state.addFlow);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -70,6 +79,10 @@ export default function CollectionCardComponent({
); );
const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false);
const selectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.selectedFlowsComponentsCards,
);
const name = data.is_component ? "Component" : "Flow"; const name = data.is_component ? "Component" : "Flow";
async function getFlowData() { async function getFlowData() {
@ -83,7 +96,6 @@ export default function CollectionCardComponent({
return false; return false;
} }
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []); const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
console.log(inputs, outputs);
return inputs.length > 0 || outputs.length > 0; return inputs.length > 0 || outputs.length > 0;
} }
@ -178,36 +190,59 @@ export default function CollectionCardComponent({
} }
} }
const isSelectedCard =
selectedFlowsComponentsCards?.includes(data?.id) ?? false;
function onDragStart(event: React.DragEvent<any>) {
let image: JSX.Element = <DragCardComponent data={data} />; // <== whatever you want here
var ghost = document.createElement("div");
ghost.style.transform = "translate(-10000px, -10000px)";
ghost.style.position = "absolute";
document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 0, 0);
const root = createRoot(ghost);
root.render(image);
const flow = getFlowById(data.id);
if (flow) {
event.dataTransfer.setData("flow", JSON.stringify(data));
}
}
return ( return (
<> <>
<Card <Card
onDragStart={onDragStart}
draggable
data-testid={`card-${convertTestName(data.name)}`} data-testid={`card-${convertTestName(data.name)}`}
//TODO check color schema //TODO check color schema
className={cn( className={cn(
"group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]", "group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
disabled ? "pointer-events-none opacity-50" : "", disabled ? "pointer-events-none opacity-50" : "",
onClick ? "cursor-pointer" : "", onClick ? "cursor-pointer" : "",
isSelectedCard ? "border border-selected" : "",
)} )}
onClick={onClick} onClick={onClick}
> >
<div> <div>
<CardHeader> <CardHeader>
<div> <div>
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl"> <CardTitle className="flex w-full items-start justify-between gap-3 text-xl">
<IconComponent <IconComponent
className={cn( className={cn(
"flex-shrink-0", "visible flex-shrink-0",
data.is_component data.is_component
? "mx-0.5 h-6 w-6 text-component-icon" ? "mx-0.5 h-6 w-6 text-component-icon"
: "h-7 w-7 flex-shrink-0 text-flow-icon", : "h-7 w-7 flex-shrink-0 text-flow-icon",
)} )}
name={data.is_component ? "ToyBrick" : "Group"} name={data.is_component ? "ToyBrick" : "Group"}
/> />
<ShadTooltip content={data.name}> <ShadTooltip content={data.name}>
<div className="w-full truncate">{data.name}</div> <div className="w-full truncate pr-3">{data.name}</div>
</ShadTooltip> </ShadTooltip>
{data?.metadata !== undefined && ( {data?.metadata !== undefined && (
<div className="flex gap-3"> <div className="flex items-center gap-3">
{data.private && ( {data.private && (
<ShadTooltip content="Private"> <ShadTooltip content="Private">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground"> <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
@ -250,19 +285,29 @@ export default function CollectionCardComponent({
</div> </div>
)} )}
{onDelete && data?.metadata === undefined && ( {control && (
<button <div
className="z-50" className="flex"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setOpenDelete(true);
}} }}
> >
<IconComponent <FormField
name="Trash2" control={control}
className="h-5 w-5 text-primary opacity-0 transition-all hover:text-destructive group-hover:opacity-100" name={`${data.id}`}
defaultValue={false}
render={({ field }) => (
<FormControl>
<Checkbox
aria-label="checkbox-component"
checked={field.value}
onCheckedChange={field.onChange}
className="h-5 w-5 border border-ring data-[state=checked]:border-selected data-[state=checked]:bg-selected"
/>
</FormControl>
)}
/> />
</button> </div>
)} )}
</CardTitle> </CardTitle>
</div> </div>
@ -369,11 +414,7 @@ export default function CollectionCardComponent({
authorized ? "Delete" : "Please review your API key." authorized ? "Delete" : "Please review your API key."
} }
> >
<DeleteConfirmationModal <DeleteConfirmationModal onConfirm={onDelete}>
onConfirm={() => {
onDelete();
}}
>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@ -541,6 +582,7 @@ export default function CollectionCardComponent({
onConfirm={() => { onConfirm={() => {
if (onDelete) onDelete(); if (onDelete) onDelete();
}} }}
description={` ${is_component ? "component" : "flow"}`}
> >
<></> <></>
</DeleteConfirmationModal> </DeleteConfirmationModal>

View file

@ -1,5 +1,6 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { cn } from "../../utils/utils";
export default function CardsWrapComponent({ export default function CardsWrapComponent({
onFileDrop, onFileDrop,
@ -11,6 +12,23 @@ export default function CardsWrapComponent({
dragMessage?: string; dragMessage?: string;
}) { }) {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
useEffect(() => {
// Function to handle visibility change
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
// Reset hover state or perform any necessary actions when the tab becomes visible again
setIsDragging(false);
}
};
// Add event listener for visibility change
document.addEventListener("visibilitychange", handleVisibilityChange);
// Cleanup event listener on component unmount
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
const dragOver = (e) => { const dragOver = (e) => {
e.preventDefault(); e.preventDefault();
@ -43,12 +61,12 @@ export default function CardsWrapComponent({
onDragEnter={dragEnter} onDragEnter={dragEnter}
onDragLeave={dragLeave} onDragLeave={dragLeave}
onDrop={onDrop} onDrop={onDrop}
className={ className={cn(
"h-full w-full " + "h-full w-full",
(isDragging isDragging
? "mb-24 flex flex-col items-center justify-center gap-4 text-2xl font-light" ? "mb-36 flex flex-col items-center justify-center gap-4 text-2xl font-light"
: "") : "",
} )}
> >
{isDragging ? ( {isDragging ? (
<> <>

View file

@ -15,8 +15,8 @@ export default function FlowToolbar(): JSX.Element {
const hasIO = useFlowStore((state) => state.hasIO); const hasIO = useFlowStore((state) => state.hasIO);
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
const validApiKey = useStoreStore((state) => state.validApiKey); const validApiKey = useStoreStore((state) => state.validApiKey);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const hasApiKey = useStoreStore((state) => state.hasApiKey); const hasApiKey = useStoreStore((state) => state.hasApiKey);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {

View file

@ -1,7 +1,6 @@
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
import "ag-grid-community/styles/ag-theme-balham.css"; // Optional Theme applied to the grid import "ag-grid-community/styles/ag-theme-balham.css"; // Optional Theme applied to the grid
import { AgGridReact } from "ag-grid-react"; import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { import {
CSVError, CSVError,
CSVNoDataError, CSVNoDataError,
@ -11,6 +10,7 @@ import { useDarkStore } from "../../stores/darkStore";
import { FlowPoolObjectType } from "../../types/chat"; import { FlowPoolObjectType } from "../../types/chat";
import { NodeType } from "../../types/flow"; import { NodeType } from "../../types/flow";
import ForwardedIconComponent from "../genericIconComponent"; import ForwardedIconComponent from "../genericIconComponent";
import TableComponent from "../tableComponent";
import Loading from "../ui/loading"; import Loading from "../ui/loading";
import { convertCSVToData } from "./helpers/convert-data-function"; import { convertCSVToData } from "./helpers/convert-data-function";
@ -54,8 +54,6 @@ function CsvOutputComponent({
const [colDefs, setColDefs] = useState([]); const [colDefs, setColDefs] = useState([]);
const [status, setStatus] = useState("loading"); const [status, setStatus] = useState("loading");
var currentRowHeight: number;
var minRowHeight = 25;
const defaultColDef = useMemo(() => { const defaultColDef = useMemo(() => {
return { return {
width: 200, width: 200,
@ -82,48 +80,6 @@ function CsvOutputComponent({
} }
}, [separator]); }, [separator]);
const getRowHeight = useCallback(() => {
return currentRowHeight;
}, []);
const onGridReady = useCallback((params: any) => {
minRowHeight = params.api.getSizesForCurrentTheme().rowHeight;
currentRowHeight = minRowHeight;
}, []);
const updateRowHeight = (params: { api: any }) => {
const bodyViewport = document.querySelector(".ag-body-viewport");
if (!bodyViewport) {
return;
}
var gridHeight = bodyViewport.clientHeight;
var renderedRowCount = params.api.getDisplayedRowCount();
if (renderedRowCount * minRowHeight >= gridHeight) {
if (currentRowHeight !== minRowHeight) {
currentRowHeight = minRowHeight;
params.api.resetRowHeights();
}
} else {
currentRowHeight = Math.floor(gridHeight / renderedRowCount);
params.api.resetRowHeights();
}
};
const onFirstDataRendered = useCallback(
(params: any) => {
updateRowHeight(params);
},
[updateRowHeight],
);
const onGridSizeChanged = useCallback(
(params: any) => {
updateRowHeight(params);
},
[updateRowHeight],
);
return ( return (
<div className=" h-full rounded-md border bg-muted"> <div className=" h-full rounded-md border bg-muted">
{status === "nodata" && ( {status === "nodata" && (
@ -158,14 +114,10 @@ function CsvOutputComponent({
className={`${dark ? "ag-theme-balham-dark" : "ag-theme-balham"}`} className={`${dark ? "ag-theme-balham-dark" : "ag-theme-balham"}`}
style={{ height: "100%", width: "100%" }} style={{ height: "100%", width: "100%" }}
> >
<AgGridReact <TableComponent
rowData={rowData} rowData={rowData}
columnDefs={colDefs} columnDefs={colDefs}
defaultColDef={defaultColDef} defaultColDef={defaultColDef}
getRowHeight={getRowHeight}
onGridReady={onGridReady}
onFirstDataRendered={onFirstDataRendered}
onGridSizeChanged={onGridSizeChanged}
scrollbarWidth={8} scrollbarWidth={8}
overlayNoRowsTemplate="No data available" overlayNoRowsTemplate="No data available"
/> />

View file

@ -0,0 +1,21 @@
export default function DateReader({
date: dateString,
}: {
date: string;
}): JSX.Element {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are 0-indexed in JavaScript
const day = String(date.getDate()).padStart(2, "0");
const hours = date.getHours();
const minutes = String(date.getMinutes()).padStart(2, "0");
const ampm = hours >= 12 ? "PM" : "AM";
const hours12 = hours > 12 ? hours - 12 : hours === 0 ? 12 : hours; // Convert to 12-hour format
const formattedDate = `${year}-${month}-${day} ${hours12}:${minutes} ${ampm}`;
return <span>{formattedDate}</span>;
}

View file

@ -7,7 +7,7 @@ import Loading from "../ui/loading";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
const ForwardedIconComponent = memo( export const ForwardedIconComponent = memo(
forwardRef( forwardRef(
( (
{ {
@ -18,7 +18,7 @@ const ForwardedIconComponent = memo(
strokeWidth, strokeWidth,
id = "", id = "",
}: IconComponentProps, }: IconComponentProps,
ref ref,
) => { ) => {
const [showFallback, setShowFallback] = useState(false); const [showFallback, setShowFallback] = useState(false);
@ -65,8 +65,8 @@ const ForwardedIconComponent = memo(
/> />
</Suspense> </Suspense>
); );
} },
) ),
); );
export default ForwardedIconComponent; export default ForwardedIconComponent;

View file

@ -12,6 +12,7 @@ import { Node } from "reactflow";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants"; import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import { SAVED_HOVER } from "../../../../constants/constants"; import { SAVED_HOVER } from "../../../../constants/constants";
import ExportModal from "../../../../modals/exportModal"; import ExportModal from "../../../../modals/exportModal";
import FlowLogsModal from "../../../../modals/flowLogsModal";
import FlowSettingsModal from "../../../../modals/flowSettingsModal"; import FlowSettingsModal from "../../../../modals/flowSettingsModal";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
@ -34,6 +35,7 @@ export const MenuBar = ({
const redo = useFlowsManagerStore((state) => state.redo); const redo = useFlowsManagerStore((state) => state.redo);
const saveLoading = useFlowsManagerStore((state) => state.saveLoading); const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
const [openSettings, setOpenSettings] = useState(false); const [openSettings, setOpenSettings] = useState(false);
const [openLogs, setOpenLogs] = useState(false);
const nodes = useFlowStore((state) => state.nodes); const nodes = useFlowStore((state) => state.nodes);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const navigate = useNavigate(); const navigate = useNavigate();
@ -123,6 +125,18 @@ export const MenuBar = ({
/> />
Settings Settings
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setOpenLogs(true);
}}
className="cursor-pointer"
>
<IconComponent
name="ScrollText"
className="header-menu-options "
/>
Logs
</DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
className="cursor-pointer" className="cursor-pointer"
onClick={() => { onClick={() => {
@ -132,7 +146,7 @@ export const MenuBar = ({
title: UPLOAD_ERROR_ALERT, title: UPLOAD_ERROR_ALERT,
list: [error], list: [error],
}); });
} },
); );
}} }}
> >
@ -194,6 +208,7 @@ export const MenuBar = ({
open={openSettings} open={openSettings}
setOpen={setOpenSettings} setOpen={setOpenSettings}
></FlowSettingsModal> ></FlowSettingsModal>
<FlowLogsModal open={openLogs} setOpen={setOpenLogs}></FlowLogsModal>
</div> </div>
{(currentFlow.updated_at || saveLoading) && ( {(currentFlow.updated_at || saveLoading) && (
<ShadTooltip <ShadTooltip
@ -213,7 +228,7 @@ export const MenuBar = ({
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"} name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
className={cn( className={cn(
"h-4 w-4", "h-4 w-4",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle" isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
)} )}
/> />
{printByBuildStatus()} {printByBuildStatus()}

View file

@ -59,7 +59,7 @@ export default function Header(): JSX.Element {
<Button <Button
className="gap-2" className="gap-2"
variant={ variant={
location.pathname === "/flows" || location.pathname === "/all" ||
location.pathname === "/components" location.pathname === "/components"
? "primary" ? "primary"
: "secondary" : "secondary"
@ -73,18 +73,7 @@ export default function Header(): JSX.Element {
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div> <div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
</Button> </Button>
</Link> </Link>
{/* <Link to="/community">
<Button
className="gap-2"
variant={
location.pathname === "/community" ? "primary" : "secondary"
}
size="sm"
>
<IconComponent name="Users2" className="h-4 w-4" />
<div className="flex-1">Community Examples</div>
</Button>
</Link> */}
{hasStore && ( {hasStore && (
<Link to="/store"> <Link to="/store">
<Button <Button

View file

@ -0,0 +1,61 @@
import { useEffect, useRef, useState } from "react";
export default function HorizontalScrollFadeComponent({
children,
isFolder = true,
}) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const fadeContainerRef = useRef<HTMLDivElement>(null);
const [divWidth, setDivWidth] = useState<number>(0);
useEffect(() => {
const handleResize = () => {
if (scrollContainerRef.current) {
setDivWidth(scrollContainerRef.current.clientWidth);
}
};
window.addEventListener("resize", handleResize);
handleResize(); // call the function at start to get the initial width
return () => window.removeEventListener("resize", handleResize);
}, []);
useEffect(() => {
const handleScroll = () => {
if (!scrollContainerRef.current || !fadeContainerRef.current) return;
const { scrollLeft, scrollWidth, clientWidth } =
scrollContainerRef.current;
const atStart = scrollLeft === 0;
const atEnd = scrollLeft === scrollWidth - clientWidth;
const isScrollable = scrollWidth > clientWidth;
fadeContainerRef.current.classList.toggle(
"fade-left",
isScrollable && !atStart,
);
fadeContainerRef.current.classList.toggle(
"fade-right",
isScrollable && !atEnd,
);
};
const scrollContainer = scrollContainerRef.current;
if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll);
// Delay the initial scroll event dispatch to ensure correct calculation
scrollContainer.dispatchEvent(new Event("scroll"));
return () => scrollContainer.removeEventListener("scroll", handleScroll);
}
}, [divWidth, children]); // Depend on divWidth
return isFolder ? (
<div className="hidden w-full flex-col gap-2 lg:flex">{children}</div>
) : (
<div ref={fadeContainerRef} className="fade-container flex">
<div ref={scrollContainerRef} className="scroll-container flex gap-2">
{children}
</div>
</div>
);
}

View file

@ -0,0 +1,192 @@
import { PopoverAnchor } from "@radix-ui/react-popover";
import useAlertStore from "../../../../stores/alertStore";
import { classNames, cn } from "../../../../utils/utils";
import ForwardedIconComponent from "../../../genericIconComponent";
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../../../ui/command";
import { Input } from "../../../ui/input";
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
const CustomInputPopover = ({
id,
refInput,
onInputLostFocus,
selectedOption,
setSelectedOption,
selectedOptions,
setSelectedOptions,
value,
autoFocus,
disabled,
setShowOptions,
required,
className,
password,
pwdVisible,
editNode,
placeholder,
onChange,
blurOnEnter,
options,
optionsPlaceholder,
optionButton,
optionsButton,
handleKeyDown,
showOptions,
}) => {
const setErrorData = useAlertStore.getState().setErrorData;
const handleInputChange = (e) => {
if (password) {
if (
e.target.value.split("").every((char) => char === "•") &&
e.target.value !== ""
) {
setErrorData({
title: `Invalid characters: ${e.target.value}`,
list: [
"It seems you are trying to paste a password. Make sure the value is visible before copying from another field.",
],
});
}
}
onChange && onChange(e.target.value);
};
return (
<Popover modal open={showOptions} onOpenChange={setShowOptions}>
<PopoverAnchor>
<Input
id={id}
ref={refInput}
type="text"
onBlur={onInputLostFocus}
value={
(selectedOption !== "" || !onChange) && setSelectedOption
? selectedOption
: (selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions
? selectedOptions?.join(", ")
: value
}
autoFocus={autoFocus}
disabled={disabled}
onClick={() => {
(((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)) &&
setShowOptions(true);
}}
required={required}
className={classNames(
password &&
(!setSelectedOption || selectedOption === "") &&
!pwdVisible &&
value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && (setSelectedOption || setSelectedOptions)
? "pr-[62.9px]"
: "",
(!password && (setSelectedOption || setSelectedOptions)) ||
(password && !(setSelectedOption || setSelectedOptions))
? "pr-8"
: "",
className!,
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={handleInputChange}
onKeyDown={(e) => {
handleKeyDown(e);
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
data-testid={editNode ? id + "-edit" : id}
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"
>
<Command
filter={(value, search) => {
if (
value.toLowerCase().includes(search.toLowerCase()) ||
value.includes("doNotFilter-")
)
return 1;
return 0;
}}
>
<CommandInput placeholder={optionsPlaceholder} />
<CommandList>
<CommandGroup defaultChecked={false}>
{options.map((option, id) => (
<CommandItem
className="group"
key={option + id}
value={option}
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption ? "" : currentValue,
);
setSelectedOptions &&
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter(
(item) => item !== currentValue,
)
: [...selectedOptions, currentValue],
);
!setSelectedOptions && setShowOptions(false);
}}
>
<div className="group flex w-full items-center justify-between">
<div className="flex items-center">
<div
className={cn(
"relative mr-2 h-4 w-4",
selectedOption === option ||
selectedOptions?.includes(option)
? "opacity-100"
: "opacity-0",
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
<ForwardedIconComponent
name="Check"
className="mr-2 h-4 w-4 text-primary"
aria-hidden="true"
/>
</div>
<div className="absolute opacity-0 transition-all group-hover:opacity-100">
<ForwardedIconComponent
name="X"
className="mr-2 h-4 w-4 text-status-red"
aria-hidden="true"
/>
</div>
</div>
{option}
</div>
{optionButton && optionButton(option)}
</div>
</CommandItem>
))}
{optionsButton && optionsButton}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</Popover>
);
};
export default CustomInputPopover;

View file

@ -0,0 +1,167 @@
import { PopoverAnchor } from "@radix-ui/react-popover";
import { classNames, cn } from "../../../../utils/utils";
import ForwardedIconComponent from "../../../genericIconComponent";
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../../../ui/command";
import { Input } from "../../../ui/input";
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
const CustomInputPopoverObject = ({
id,
refInput,
onInputLostFocus,
selectedOption,
setSelectedOption,
selectedOptions,
setSelectedOptions,
value,
autoFocus,
disabled,
setShowOptions,
required,
className,
placeholder,
onChange,
blurOnEnter,
options,
optionsPlaceholder,
optionButton,
optionsButton,
handleKeyDown,
showOptions,
}) => {
const handleInputChange = (e) => {
onChange && onChange(e.target.value);
};
return (
<Popover modal open={showOptions} onOpenChange={setShowOptions}>
<PopoverAnchor>
<Input
id={id}
ref={refInput}
type="text"
onBlur={onInputLostFocus}
value={
(selectedOption !== "" || !onChange) && setSelectedOption
? options.find((option) => option.id === selectedOption)?.name ||
""
: (selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions
? selectedOptions
.map(
(optionId) =>
options.find((option) => option.id === optionId)?.name,
)
.join(", ")
: value
}
autoFocus={autoFocus}
disabled={disabled}
onClick={() => {
(((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)) &&
setShowOptions(true);
}}
required={required}
className={classNames(className!)}
placeholder={placeholder}
onChange={handleInputChange}
onKeyDown={(e) => {
handleKeyDown(e);
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
data-testid={id}
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"
>
<Command
filter={(value, search) => {
if (
value.toLowerCase().includes(search.toLowerCase()) ||
value.includes("doNotFilter-")
)
return 1;
return 0;
}}
>
<CommandInput placeholder={optionsPlaceholder} />
<CommandList>
<CommandGroup defaultChecked={false}>
{options.map((option, index) => (
<CommandItem
className="group"
key={option.id}
value={option.id}
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption ? "" : currentValue,
);
setSelectedOptions &&
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter(
(item) => item !== currentValue,
)
: [...selectedOptions, currentValue],
);
!setSelectedOptions && setShowOptions(false);
}}
>
<div className="group flex w-full items-center justify-between">
<div className="flex items-center">
<div
className={cn(
"relative mr-2 h-4 w-4",
selectedOption === option.id ||
selectedOptions?.includes(option.id)
? "opacity-100"
: "opacity-0",
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
<ForwardedIconComponent
name="Check"
className="mr-2 h-4 w-4 text-primary"
aria-hidden="true"
/>
</div>
<div className="absolute opacity-0 transition-all group-hover:opacity-100">
<ForwardedIconComponent
name="X"
className="mr-2 h-4 w-4 text-status-red"
aria-hidden="true"
/>
</div>
</div>
<span data-testid={`option-${index}`}>
{option.name}{" "}
</span>
{/* Display the name property of the option */}
</div>
{optionButton && optionButton(option)}
</div>
</CommandItem>
))}
{optionsButton && optionsButton}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</Popover>
);
};
export default CustomInputPopoverObject;

View file

@ -1,20 +1,12 @@
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { PopoverAnchor } from "@radix-ui/react-popover";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import useAlertStore from "../../stores/alertStore";
import { InputComponentType } from "../../types/components"; import { InputComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils"; import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames, cn } from "../../utils/utils"; import { classNames, cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent"; import ForwardedIconComponent from "../genericIconComponent";
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../ui/command";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { Popover, PopoverContentWithoutPortal } from "../ui/popover"; import CustomInputPopover from "./components/popover";
import CustomInputPopoverObject from "./components/popoverObject";
export default function InputComponent({ export default function InputComponent({
autoFocus = false, autoFocus = false,
@ -39,13 +31,13 @@ export default function InputComponent({
optionsPlaceholder = "Search options...", optionsPlaceholder = "Search options...",
optionsButton, optionsButton,
optionButton, optionButton,
objectOptions,
isObjectOption = false,
}: InputComponentType): JSX.Element { }: InputComponentType): JSX.Element {
const setErrorData = useAlertStore.getState().setErrorData;
const [pwdVisible, setPwdVisible] = useState(false); const [pwdVisible, setPwdVisible] = useState(false);
const refInput = useRef<HTMLInputElement>(null); const refInput = useRef<HTMLInputElement>(null);
const [showOptions, setShowOptions] = useState<boolean>(false); const [showOptions, setShowOptions] = useState<boolean>(false);
// Clear component state
useEffect(() => { useEffect(() => {
if (disabled && value && onChange && value !== "") { if (disabled && value && onChange && value !== "") {
onChange("", true); onChange("", true);
@ -93,182 +85,61 @@ export default function InputComponent({
</Form.Control> </Form.Control>
) : ( ) : (
<> <>
<Popover modal open={showOptions} onOpenChange={setShowOptions}> {isObjectOption ? (
<PopoverAnchor> // Content to render when isObjectOption is true
<Input <CustomInputPopoverObject
id={id} refInput={refInput}
ref={refInput} handleKeyDown={handleKeyDown}
type="text" optionButton={optionButton}
onBlur={onInputLostFocus} optionsButton={optionsButton}
value={ showOptions={showOptions}
(selectedOption !== "" || !onChange) && setSelectedOption onChange={onChange}
? selectedOption id={`object-${id}`}
: (selectedOptions?.length !== 0 || !onChange) && onInputLostFocus={onInputLostFocus}
setSelectedOptions selectedOption={selectedOption}
? selectedOptions?.join(", ") setSelectedOption={setSelectedOption}
: value selectedOptions={selectedOptions}
} setSelectedOptions={setSelectedOptions}
autoFocus={autoFocus} options={objectOptions}
disabled={disabled} value={value}
onClick={() => { autoFocus={autoFocus}
(((selectedOption !== "" || !onChange) && disabled={disabled}
setSelectedOption) || setShowOptions={setShowOptions}
((selectedOptions?.length !== 0 || !onChange) && required={required}
setSelectedOptions)) && placeholder={placeholder}
setShowOptions(true); blurOnEnter={blurOnEnter}
}} optionsPlaceholder={optionsPlaceholder}
required={required} className={className}
className={classNames( />
password && ) : (
(!setSelectedOption || selectedOption === "") && <CustomInputPopover
!pwdVisible && refInput={refInput}
value !== "" handleKeyDown={handleKeyDown}
? " text-clip password " optionButton={optionButton}
: "", optionsButton={optionsButton}
editNode ? " input-edit-node " : "", showOptions={showOptions}
password && (setSelectedOption || setSelectedOptions) onChange={onChange}
? "pr-[62.9px]" id={`popover-anchor-${id}`}
: "", onInputLostFocus={onInputLostFocus}
(!password && (setSelectedOption || setSelectedOptions)) || selectedOption={selectedOption}
(password && !(setSelectedOption || setSelectedOptions)) setSelectedOption={setSelectedOption}
? "pr-8" selectedOptions={selectedOptions}
: "", setSelectedOptions={setSelectedOptions}
value={value}
className!, autoFocus={autoFocus}
)} disabled={disabled}
placeholder={password && editNode ? "Key" : placeholder} setShowOptions={setShowOptions}
onChange={(e) => { required={required}
// if the user copies a password from another input password={password}
// it might come as ••••••••••• it causes errors pwdVisible={pwdVisible}
// in ascii encoding, so we need to handle it editNode={editNode}
if (password) { placeholder={placeholder}
// check if all chars are • blurOnEnter={blurOnEnter}
if ( options={options}
e.target.value.split("").every((char) => char === "•") && optionsPlaceholder={optionsPlaceholder}
e.target.value !== "" className={className}
) { />
setErrorData({ )}
title: `Invalid characters: ${e.target.value}`,
list: [
"It seems you are trying to paste a password. Make sure the value is visible before copying from another field.",
],
});
}
}
onChange && onChange(e.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter")
refInput.current?.blur();
}}
data-testid={editNode ? id + "-edit" : id}
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
avoidCollisions={false}
align="center"
>
<Command
filter={(value, search) => {
if (
value.toLowerCase().includes(search.toLowerCase()) ||
value.includes("doNotFilter-")
)
return 1; // ensures items arent filtered
return 0;
}}
>
<CommandInput placeholder={optionsPlaceholder} />
<CommandList>
<CommandGroup defaultChecked={false}>
{options.map((option, id) => (
<CommandItem
className="group"
key={option + id}
value={option}
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption
? ""
: currentValue,
);
setSelectedOptions &&
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter(
(item) => item !== currentValue,
)
: [...selectedOptions, currentValue],
);
!setSelectedOptions && setShowOptions(false);
}}
>
<div className="group flex w-full items-center justify-between">
<div className="flex items-center">
<div
className={cn(
"relative mr-2 h-4 w-4",
selectedOption === option ||
selectedOptions?.includes(option)
? "opacity-100"
: "opacity-0",
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
<ForwardedIconComponent
name="Check"
className="mr-2 h-4 w-4 text-primary"
aria-hidden="true"
/>
</div>
<div className="absolute opacity-0 transition-all group-hover:opacity-100">
<ForwardedIconComponent
name="X"
className="mr-2 h-4 w-4 text-status-red"
aria-hidden="true"
/>
</div>
</div>
{option}
</div>
{optionButton && optionButton(option)}
</div>
</CommandItem>
))}
{optionsButton && optionsButton}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</Popover>
<div
data-testid={"popover-anchor-" + id}
className={cn(
"pointer-events-auto absolute inset-y-0 h-full w-full cursor-pointer",
((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)
? ""
: "hidden",
)}
onClick={
((selectedOption !== "" || !onChange) && setSelectedOption) ||
((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions)
? (e) => {
setShowOptions((old) => !old);
e.preventDefault();
e.stopPropagation();
}
: () => {}
}
></div>
</> </>
)} )}
@ -280,8 +151,10 @@ export default function InputComponent({
)} )}
> >
<button <button
onClick={() => { onClick={(e) => {
setShowOptions(!showOptions); setShowOptions(!showOptions);
e.preventDefault();
e.stopPropagation();
}} }}
className={cn( className={cn(
selectedOption !== "" selectedOption !== ""

View file

@ -0,0 +1,7 @@
export default function NumberReader({
number,
}: {
number: number;
}): JSX.Element {
return <span>{number}</span>;
}

View file

@ -0,0 +1,13 @@
import DictAreaModal from "../../modals/dictAreaModal";
export default function ObjectRender({ object }: { object: any }): JSX.Element {
//TODO check object type
return (
<DictAreaModal value={object}>
<div className="flex h-full w-full items-center align-middle transition-all hover:scale-105">
<div className="truncate">{JSON.stringify(object)}</div>
</div>
</DictAreaModal>
);
}

View file

@ -0,0 +1,34 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
import "ag-grid-community/styles/ag-theme-balham.css"; // Optional Theme applied to the grid
import { FlowPoolObjectType } from "../../types/chat";
import TableComponent from "../tableComponent";
import { extractColumnsFromRows } from "../../utils/utils";
function RecordsOutputComponent({
flowPool,
pagination,
}: {
flowPool: FlowPoolObjectType;
pagination: boolean;
}) {
const rows = flowPool?.data?.artifacts?.records ?? [];
const columns = extractColumnsFromRows(rows, "union");
const columnDefs = columns.map((col, idx) => ({
...col,
resizable: idx !== columns.length - 1,
flex: idx !== columns.length - 1 ? 1 : 2,
})) as (ColDef<any> | ColGroupDef<any>)[];
return (
<TableComponent
overlayNoRowsTemplate="No data available"
suppressRowClickSelection={true}
pagination={pagination}
columnDefs={columnDefs}
rowData={rows}
/>
);
}
export default RecordsOutputComponent;

View file

@ -0,0 +1,38 @@
import { Link } from "react-router-dom";
import { cn } from "../../../../utils/utils";
import { buttonVariants } from "../../../ui/button";
type SideBarButtonsComponentProps = {
items: {
href?: string;
title: string;
icon: React.ReactNode;
}[];
pathname: string;
handleOpenNewFolderModal?: () => void;
};
const SideBarButtonsComponent = ({
items,
handleOpenNewFolderModal,
}: SideBarButtonsComponentProps) => {
return (
<>
{items.map((item) => (
<Link to={item.href!}>
<div
key={item.title}
data-testid={`sidebar-nav-${item.title}`}
className={cn(
buttonVariants({ variant: "ghost" }),
"!w-[200px] cursor-pointer justify-start gap-2 border border-transparent hover:border-border hover:bg-transparent",
)}
onClick={handleOpenNewFolderModal}
>
{item.title}
</div>
</Link>
))}
</>
);
};
export default SideBarButtonsComponent;

View file

@ -0,0 +1,170 @@
import { useLocation } from "react-router-dom";
import { FolderType } from "../../../../pages/MainPage/entities";
import { useFolderStore } from "../../../../stores/foldersStore";
import { cn } from "../../../../utils/utils";
import DropdownButton from "../../../dropdownButtonComponent";
import IconComponent, {
ForwardedIconComponent,
} from "../../../genericIconComponent";
import { Button, buttonVariants } from "../../../ui/button";
import useFileDrop from "../../hooks/use-on-file-drop";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { handleDownloadFolderFn } from "../../../../pages/MainPage/utils/handle-download-folder";
import useAlertStore from "../../../../stores/alertStore";
type SideBarFoldersButtonsComponentProps = {
folders: FolderType[];
pathname: string;
handleChangeFolder?: (id: string) => void;
handleEditFolder?: (item: FolderType) => void;
handleDeleteFolder?: (item: FolderType) => void;
handleAddFolder?: () => void;
};
const SideBarFoldersButtonsComponent = ({
folders,
pathname,
handleAddFolder,
handleChangeFolder,
handleEditFolder,
handleDeleteFolder,
}: SideBarFoldersButtonsComponentProps) => {
const uploadFolder = useFolderStore((state) => state.uploadFolder);
const currentFolder = pathname.split("/");
const urlWithoutPath = pathname.split("/").length < 4;
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const allFlows = useFlowsManagerStore((state) => state.allFlows);
const setErrorData = useAlertStore((state) => state.setErrorData);
const checkPathName = (itemId: string) => {
if (urlWithoutPath && itemId === myCollectionId) {
return true;
}
return currentFolder.includes(itemId);
};
const location = useLocation();
const folderId = location?.state?.folderId ?? myCollectionId;
const getFolderById = useFolderStore((state) => state.getFolderById);
const handleFolderChange = (folderId: string) => {
getFolderById(folderId);
};
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(
folderId,
handleFolderChange,
);
const handleUploadFlowsToFolder = () => {
uploadFolder(folderId);
};
const handleDownloadFolder = (id: string) => {
handleDownloadFolderFn(id);
};
return (
<>
<div className="flex shrink-0 items-center justify-between">
<DropdownButton
firstButtonName="New Folder"
onFirstBtnClick={handleAddFolder!}
options={[]}
plusButton={true}
dropdownOptions={false}
/>
<Button
variant="primary"
onClick={handleUploadFlowsToFolder}
className="px-7"
>
<ForwardedIconComponent
name="Upload"
className="main-page-nav-button"
/>
Upload
</Button>
</div>
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
<>
{folders.map((item, index) => (
<div
onDragOver={dragOver}
onDragEnter={dragEnter}
onDragLeave={dragLeave}
onDrop={(e) => onDrop(e, item.id!)}
key={item.id}
data-testid={`sidebar-nav-${item.name}`}
className={cn(
buttonVariants({ variant: "ghost" }),
checkPathName(item.id!)
? "border border-border bg-muted hover:bg-muted"
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
)}
onClick={() => handleChangeFolder!(item.id!)}
>
<div className="flex w-full items-center gap-2">
<IconComponent
name={"folder"}
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
/>
<span className="block max-w-full truncate opacity-100">
{item.name}
</span>
<div className="flex-1" />
{index > 0 && (
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
handleDeleteFolder!(item);
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
>
<IconComponent
name={"trash"}
className=" w-4 stroke-[1.5]"
/>
</Button>
)}
{index > 0 && (
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
handleEditFolder!(item);
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
>
<IconComponent
name={"pencil"}
className=" w-4 stroke-[1.5] text-white "
/>
</Button>
)}
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
handleDownloadFolder(item.id!);
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
>
<IconComponent
name={"Download"}
className=" w-4 stroke-[1.5] text-white "
/>
</Button>
</div>
</div>
))}
</>
</div>
</>
);
};
export default SideBarFoldersButtonsComponent;

View file

@ -0,0 +1,134 @@
import {
UPLOAD_ALERT_LIST,
WRONG_FILE_ERROR_ALERT,
} from "../../../constants/alerts_constants";
import { updateFlowInDatabase } from "../../../controllers/API";
import { uploadFlowsFromFolders } from "../../../pages/MainPage/services";
import useAlertStore from "../../../stores/alertStore";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../stores/foldersStore";
import { FlowType } from "../../../types/flow";
const useFileDrop = (folderId, folderChangeCallback) => {
const setFolderDragging = useFolderStore((state) => state.setFolderDragging);
const setErrorData = useAlertStore((state) => state.setErrorData);
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows);
const flows = useFlowsManagerStore((state) => state.flows);
const triggerFolderChange = (folderId) => {
if (folderChangeCallback) {
folderChangeCallback(folderId);
}
};
const handleFileDrop = async (e) => {
if (e.dataTransfer.types.some((type) => type === "Files")) {
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const firstFile = e.dataTransfer.files[0];
if (firstFile.type === "application/json") {
uploadFormData(firstFile);
} else {
setErrorData({
title: WRONG_FILE_ERROR_ALERT,
list: [UPLOAD_ALERT_LIST],
});
}
}
}
};
const dragOver = (
e:
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
) => {
e.preventDefault();
if (e.dataTransfer.types.some((types) => types === "Files")) {
setFolderDragging(true);
}
};
const dragEnter = (
e:
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
) => {
if (e.dataTransfer.types.some((types) => types === "Files")) {
setFolderDragging(true);
}
e.preventDefault();
};
const dragLeave = (
e:
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
) => {
e.preventDefault();
if (e.target === e.currentTarget) {
setFolderDragging(false);
}
};
const onDrop = (
e:
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
folderId: string,
) => {
if (e?.dataTransfer?.getData("flow")) {
const data = JSON.parse(e?.dataTransfer?.getData("flow"));
if (data) {
uploadFromDragCard(data.id, folderId);
return;
}
}
e.preventDefault();
handleFileDrop(e);
setFolderDragging(false);
};
const uploadFromDragCard = (flowId, folderId) => {
const selectedFlow = flows.find((flow) => flow.id === flowId);
if (!selectedFlow) {
throw new Error("Flow not found");
}
const updatedFlow: FlowType = {
...selectedFlow,
folder_id: folderId,
};
updateFlowInDatabase(updatedFlow).then(() => {
getFoldersApi(true);
triggerFolderChange(folderId);
});
};
const uploadFormData = (data) => {
const formData = new FormData();
formData.append("file", data);
uploadFlowsFromFolders(formData).then(() => {
getFoldersApi(true);
triggerFolderChange(folderId);
refreshFlows();
});
};
return {
dragOver,
dragEnter,
dragLeave,
onDrop,
};
};
export default useFileDrop;

View file

@ -1,48 +1,61 @@
import { Link, useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { FolderType } from "../../pages/MainPage/entities";
import { useFolderStore } from "../../stores/foldersStore";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import { buttonVariants } from "../ui/button"; import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
import SideBarButtonsComponent from "./components/sideBarButtons";
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> { type SidebarNavProps = {
items: { items: {
href: string; href?: string;
title: string; title: string;
icon: React.ReactNode; icon: React.ReactNode;
}[]; }[];
} handleOpenNewFolderModal?: () => void;
handleChangeFolder?: (id: string) => void;
handleEditFolder?: (item: FolderType) => void;
handleDeleteFolder?: (item: FolderType) => void;
className?: string;
};
export default function SidebarNav({ export default function SidebarNav({
className, className,
items, items,
handleOpenNewFolderModal,
handleChangeFolder,
handleEditFolder,
handleDeleteFolder,
...props ...props
}: SidebarNavProps) { }: SidebarNavProps) {
const location = useLocation(); const location = useLocation();
const pathname = location.pathname; const pathname = location.pathname;
const loadingFolders = useFolderStore((state) => state.loading);
const folders = useFolderStore((state) => state.folders);
const pathValues = ["folder", "components", "flows", "all"];
const isFolderPath = pathValues.some((value) => pathname.includes(value));
return ( return (
<nav <nav className={cn(className)} {...props}>
className={cn( <HorizontalScrollFadeComponent>
"flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1", <SideBarButtonsComponent
className items={items}
)} pathname={pathname}
{...props} handleOpenNewFolderModal={handleOpenNewFolderModal}
> />
{items.map((item) => (
<Link {!loadingFolders && folders?.length > 0 && isFolderPath && (
data-testid={`sidebar-nav-${item.title}`} <SideBarFoldersButtonsComponent
key={item.href} folders={folders}
to={item.href} pathname={pathname}
className={cn( handleChangeFolder={handleChangeFolder}
buttonVariants({ variant: "ghost" }), handleEditFolder={handleEditFolder}
pathname === item.href handleDeleteFolder={handleDeleteFolder}
? "border border-border bg-muted hover:bg-muted" handleAddFolder={handleOpenNewFolderModal}
: "border border-transparent hover:border-border hover:bg-transparent", />
"justify-start gap-2" )}
)} </HorizontalScrollFadeComponent>
>
{item.icon}
{item.title}
</Link>
))}
</nav> </nav>
); );
} }

View file

@ -5,7 +5,7 @@ export const StoreGuard = ({ children }) => {
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
if (!hasStore) { if (!hasStore) {
return <Navigate to="/flows" replace />; return <Navigate to="/all" replace />;
} }
return children; return children;

View file

@ -0,0 +1,7 @@
export default function StringReader({
string,
}: {
string: string;
}): JSX.Element {
return <span className="truncate">{string}</span>;
}

View file

@ -0,0 +1,60 @@
import { CustomCellRendererProps } from "ag-grid-react";
import { cn, isTimeStampString } from "../../utils/utils";
import ArrayReader from "../arrayReaderComponent";
import DateReader from "../dateReaderComponent";
import NumberReader from "../numberReader";
import ObjectRender from "../objectRender";
import StringReader from "../stringReaderComponent";
import { Label } from "../ui/label";
import { Badge } from "../ui/badge";
export default function TableAutoCellRender({
value,
}: CustomCellRendererProps) {
function getCellType() {
switch (typeof value) {
case "object":
if (value === null) {
return String(value);
} else if (Array.isArray(value)) {
return <ArrayReader array={value} />;
} else if (value.definitions) {
// use a custom render defined by the sender
} else {
return <ObjectRender object={value} />;
}
break;
case "string":
if (isTimeStampString(value)) {
return <DateReader date={value} />;
}
//TODO: REFACTOR FOR ANY LABEL NOT HARDCODED
else if (value === "success") {
return (
<Badge
variant="outline"
size="sq"
className={cn(
"min-w-min bg-success-background text-success-foreground hover:bg-success-background",
)}
>
{value}
</Badge>
);
} else {
return <StringReader string={value} />;
}
break;
case "number":
return <NumberReader number={value} />;
default:
return String(value);
}
}
return (
<div className="group flex h-full w-full items-center align-middle">
{getCellType()}
</div>
);
}

View file

@ -1,27 +1,119 @@
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid
import { AgGridReact } from "ag-grid-react"; import { AgGridReact, AgGridReactProps } from "ag-grid-react";
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react"; import { ElementRef, forwardRef, useCallback } from "react";
import {
DEFAULT_TABLE_ALERT_MSG,
DEFAULT_TABLE_ALERT_TITLE,
} from "../../constants/constants";
import { useDarkStore } from "../../stores/darkStore"; import { useDarkStore } from "../../stores/darkStore";
import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
interface TableComponentProps extends AgGridReactProps {
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;
rowData: NonNullable<AgGridReactProps["rowData"]>;
alertTitle?: string;
alertDescription?: string;
}
const TableComponent = forwardRef< const TableComponent = forwardRef<
ElementRef<typeof AgGridReact>, ElementRef<typeof AgGridReact>,
ComponentPropsWithoutRef<typeof AgGridReact> TableComponentProps
>(({ ...props }, ref) => { >(
const dark = useDarkStore((state) => state.dark); (
{
alertTitle = DEFAULT_TABLE_ALERT_TITLE,
alertDescription = DEFAULT_TABLE_ALERT_MSG,
...props
},
ref,
) => {
const dark = useDarkStore((state) => state.dark);
var currentRowHeight: number;
var minRowHeight = 25;
return ( const getRowHeight = useCallback(() => {
<div return currentRowHeight;
className={cn( }, []);
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
"ag-theme-shadcn flex h-full flex-col", const onGridReady = useCallback((params: any) => {
)} // applying the grid theme minRowHeight = params.api.getSizesForCurrentTheme().rowHeight;
> currentRowHeight = minRowHeight;
<AgGridReact ref={ref} {...props} /> }, []);
</div>
); const updateRowHeight = (params: { api: any }) => {
}); const bodyViewport = document.querySelector(".ag-body-viewport");
if (!bodyViewport) {
return;
}
var gridHeight = bodyViewport.clientHeight;
var renderedRowCount = params.api.getDisplayedRowCount();
if (renderedRowCount * minRowHeight >= gridHeight) {
if (currentRowHeight !== minRowHeight) {
currentRowHeight = minRowHeight;
params.api.resetRowHeights();
}
} else {
currentRowHeight = Math.floor(gridHeight / renderedRowCount);
params.api.resetRowHeights();
}
};
const onFirstDataRendered = useCallback(
(params: any) => {
updateRowHeight(params);
},
[updateRowHeight],
);
const onGridSizeChanged = useCallback(
(params: any) => {
updateRowHeight(params);
},
[updateRowHeight],
);
if (props.rowData.length === 0) {
return (
<div className="flex h-full w-full items-center justify-center rounded-md border">
<Alert variant={"default"} className="w-fit">
<ForwardedIconComponent
name="AlertCircle"
className="h-5 w-5 text-primary"
/>
<AlertTitle>{alertTitle}</AlertTitle>
<AlertDescription>{alertDescription}</AlertDescription>
</Alert>
</div>
);
}
return (
<div
className={cn(
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
"ag-theme-shadcn flex h-full flex-col",
)} // applying the grid theme
>
<AgGridReact
{...props}
className={cn(props.className, "custom-scroll")}
getRowHeight={getRowHeight}
onGridReady={onGridReady}
onFirstDataRendered={onFirstDataRendered}
onGridSizeChanged={onGridSizeChanged}
defaultColDef={{
minWidth: 100,
}}
ref={ref}
/>
</div>
);
},
);
export default TableComponent; export default TableComponent;

View file

@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
import { Badge } from "../ui/badge"; import { Badge } from "../ui/badge";
export function TagsSelector({ export function TagsSelector({
@ -24,87 +24,37 @@ export function TagsSelector({
setSelectedTags(newArray); setSelectedTags(newArray);
}; };
const scrollContainerRef = useRef<HTMLDivElement>(null);
const fadeContainerRef = useRef<HTMLDivElement>(null);
const [divWidth, setDivWidth] = useState<number>(0);
useEffect(() => {
const handleResize = () => {
if (scrollContainerRef.current) {
setDivWidth(scrollContainerRef.current.clientWidth);
}
};
window.addEventListener("resize", handleResize);
handleResize(); // call the function at start to get the initial width
return () => window.removeEventListener("resize", handleResize);
}, []);
useEffect(() => {
const handleScroll = () => {
if (!scrollContainerRef.current || !fadeContainerRef.current) return;
const { scrollLeft, scrollWidth, clientWidth } =
scrollContainerRef.current;
const atStart = scrollLeft === 0;
const atEnd = scrollLeft === scrollWidth - clientWidth;
const isScrollable = scrollWidth > clientWidth;
fadeContainerRef.current.classList.toggle(
"fade-left",
isScrollable && !atStart
);
fadeContainerRef.current.classList.toggle(
"fade-right",
isScrollable && !atEnd
);
};
const scrollContainer = scrollContainerRef.current;
if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll);
// Delay the initial scroll event dispatch to ensure correct calculation
scrollContainer.dispatchEvent(new Event("scroll"));
return () => scrollContainer.removeEventListener("scroll", handleScroll);
}
}, [divWidth, loadingTags]); // Depend on divWidth
return ( return (
<div ref={fadeContainerRef} className="fade-container"> <HorizontalScrollFadeComponent isFolder={false}>
<div {!loadingTags &&
ref={scrollContainerRef} tags.map((tag, idx) => (
className="scroll-container flex min-w-min gap-2" <button
> disabled={disabled}
{!loadingTags && className={
tags.map((tag, idx) => ( disabled
<button ? "cursor-not-allowed"
disabled={disabled} : " overflow-hidden whitespace-nowrap"
className={ }
disabled onClick={() => {
? "cursor-not-allowed" updateTags(tag.name);
: " overflow-hidden whitespace-nowrap" }}
} key={idx}
onClick={() => { data-testid={`tag-selector-${tag.name}`}
updateTags(tag.name); >
}} <Badge
key={idx} key={idx}
data-testid={`tag-selector-${tag.name}`} variant="outline"
size="sq"
className={cn(
selectedTags.some((category) => category === tag.name)
? "min-w-min bg-beta-foreground text-background hover:bg-beta-foreground"
: "",
)}
> >
<Badge {tag.name}
key={idx} </Badge>
variant="outline" </button>
size="sq" ))}
className={cn( </HorizontalScrollFadeComponent>
selectedTags.some((category) => category === tag.name)
? "min-w-min bg-beta-foreground text-background hover:bg-beta-foreground"
: ""
)}
>
{tag.name}
</Badge>
</button>
))}
</div>
</div>
); );
} }

View file

@ -0,0 +1,58 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../utils/utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
},
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription };

View file

@ -13,14 +13,14 @@ const Checkbox = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className className,
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")} className={cn("flex items-center justify-center text-current")}
> >
<IconComponent name="Check" className="h-4 w-4" /> <IconComponent name="Check" className="h-4 w-4 stroke-2" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
)); ));
@ -37,7 +37,7 @@ const CheckBoxDiv = ({
className={cn( className={cn(
className, className,
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
checked ? "bg-primary text-primary-foreground" : "" checked ? "bg-primary text-primary-foreground" : "",
)} )}
> >
{checked && ( {checked && (

View file

@ -0,0 +1,55 @@
"use client";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { cn } from "../../utils/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className={cn("", className)} {...props} />
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
<div
className={cn(
" flex flex-1 cursor-pointer items-center justify-between border-[1px] py-2 text-sm font-medium data-[state=closed]:rounded-md data-[state=open]:rounded-t-md data-[state=open]:border-b-0 data-[state=open]:bg-muted [&[data-state=open]>svg]:rotate-180",
className,
)}
>
{children}
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden border-[1px] text-sm data-[state=open]:rounded-b-md data-[state=open]:border-t-0 data-[state=open]:bg-muted",
className,
)}
{...props}
>
<div className="pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };

View file

@ -0,0 +1,176 @@
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import * as React from "react";
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form";
import { cn } from "../../utils/utils";
import { Label } from "./label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = "FormMessage";
export {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
useFormField,
};

View file

@ -95,6 +95,12 @@ export const EXPORT_DIALOG_SUBTITLE = "Export flow as JSON file.";
*/ */
export const SETTINGS_DIALOG_SUBTITLE = "Edit details about your project."; export const SETTINGS_DIALOG_SUBTITLE = "Edit details about your project.";
/**
* The base text for subtitle of Flow Logs (Menubar)
* @constant
*/
export const LOGS_DIALOG_SUBTITLE = "Check out information about your flow.";
/** /**
* The base text for subtitle of Code Dialog (Toolbar) * The base text for subtitle of Code Dialog (Toolbar)
* @constant * @constant
@ -125,7 +131,6 @@ export const CODE_PROMPT_DIALOG_SUBTITLE =
export const CODE_DICT_DIALOG_SUBTITLE = export const CODE_DICT_DIALOG_SUBTITLE =
"Edit your dictionary. This dialog allows you to create your own customized dictionary. You can add as many key-value pairs as you want. While in edit mode, you can enter ({}) or ([]), and this will result in adding a new object or array."; "Edit your dictionary. This dialog allows you to create your own customized dictionary. You can add as many key-value pairs as you want. While in edit mode, you can enter ({}) or ([]), and this will result in adding a new object or array.";
/** /**
* The base text for subtitle of Prompt Dialog * The base text for subtitle of Prompt Dialog
* @constant * @constant
@ -533,6 +538,8 @@ export const NOUNS: string[] = [
*/ */
export const USER_PROJECTS_HEADER = "My Collection"; export const USER_PROJECTS_HEADER = "My Collection";
export const DEFAULT_FOLDER = "My Projects";
/** /**
* Header text for admin page * Header text for admin page
* @constant * @constant
@ -727,6 +734,8 @@ export const OUTPUT_TYPES = new Set([
"JsonOutput", "JsonOutput",
"KeyPairOutput", "KeyPairOutput",
"StringListOutput", "StringListOutput",
"RecordsOutput",
"TableOutput",
]); ]);
export const CHAT_FIRST_INITIAL_TEXT = export const CHAT_FIRST_INITIAL_TEXT =
@ -746,8 +755,8 @@ export const EDIT_TEXT_MODAL_TITLE = "Edit Text";
export const EDIT_TEXT_PLACEHOLDER = "Type message here."; export const EDIT_TEXT_PLACEHOLDER = "Type message here.";
export const INPUT_HANDLER_HOVER = "Avaliable input components:"; export const INPUT_HANDLER_HOVER = "Avaliable input components:";
export const OUTPUT_HANDLER_HOVER = "Avaliable output components:"; export const OUTPUT_HANDLER_HOVER = "Avaliable output components:";
export const TEXT_INPUT_MODAL_TITLE = "Text Inputs"; export const TEXT_INPUT_MODAL_TITLE = "Inputs";
export const OUTPUTS_MODAL_TITLE = "Text Outputs"; export const OUTPUTS_MODAL_TITLE = "Outputs";
export const LANGFLOW_CHAT_TITLE = "Langflow Chat"; export const LANGFLOW_CHAT_TITLE = "Langflow Chat";
export const CHAT_INPUT_PLACEHOLDER = export const CHAT_INPUT_PLACEHOLDER =
"No chat input variables found. Click to run your flow."; "No chat input variables found. Click to run your flow.";
@ -792,3 +801,7 @@ export const NATIVE_CATEGORIES = [
]; ];
export const SAVE_DEBOUNCE_TIME = 300; export const SAVE_DEBOUNCE_TIME = 300;
export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to display right now. Please check back later.`;
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";

View file

@ -22,7 +22,10 @@ function ApiInterceptor() {
const interceptor = api.interceptors.response.use( const interceptor = api.interceptors.response.use(
(response) => response, (response) => response,
async (error: AxiosError) => { async (error: AxiosError) => {
if (error.response?.status === 403 || error.response?.status === 401) { if (
error?.response?.status === 403 ||
error?.response?.status === 401
) {
if (!autoLogin) { if (!autoLogin) {
if (error?.config?.url?.includes("github")) { if (error?.config?.url?.includes("github")) {
return Promise.reject(error); return Promise.reject(error);
@ -44,7 +47,7 @@ function ApiInterceptor() {
} }
await clearBuildVerticesState(error); await clearBuildVerticesState(error);
return Promise.reject(error); return Promise.reject(error);
} },
); );
const isAuthorizedURL = (url) => { const isAuthorizedURL = (url) => {
@ -61,10 +64,10 @@ function ApiInterceptor() {
const parsedURL = new URL(url); const parsedURL = new URL(url);
const isDomainAllowed = authorizedDomains.some( const isDomainAllowed = authorizedDomains.some(
(domain) => parsedURL.origin === new URL(domain).origin (domain) => parsedURL.origin === new URL(domain).origin,
); );
const isEndpointAllowed = authorizedEndpoints.some((endpoint) => const isEndpointAllowed = authorizedEndpoints.some((endpoint) =>
parsedURL.pathname.includes(endpoint) parsedURL.pathname.includes(endpoint),
); );
return isDomainAllowed || isEndpointAllowed; return isDomainAllowed || isEndpointAllowed;
@ -77,6 +80,18 @@ function ApiInterceptor() {
// Request interceptor to add access token to every request // Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use( const requestInterceptor = api.interceptors.request.use(
(config) => { (config) => {
const lastUrl = localStorage.getItem("lastUrlCalled");
if (
config?.url === lastUrl &&
config?.url !== "/health" &&
config?.method === "get"
) {
return Promise.reject("Duplicate request");
}
localStorage.setItem("lastUrlCalled", config.url ?? "");
const accessToken = cookies.get("access_token_lf"); const accessToken = cookies.get("access_token_lf");
if (accessToken && !isAuthorizedURL(config?.url)) { if (accessToken && !isAuthorizedURL(config?.url)) {
config.headers["Authorization"] = `Bearer ${accessToken}`; config.headers["Authorization"] = `Bearer ${accessToken}`;
@ -86,7 +101,7 @@ function ApiInterceptor() {
}, },
(error) => { (error) => {
return Promise.reject(error); return Promise.reject(error);
} },
); );
return () => { return () => {
@ -118,7 +133,7 @@ function ApiInterceptor() {
if (error?.config?.headers) { if (error?.config?.headers) {
delete error.config.headers["Authorization"]; delete error.config.headers["Authorization"];
error.config.headers["Authorization"] = `Bearer ${cookies.get( error.config.headers["Authorization"] = `Bearer ${cookies.get(
"access_token_lf" "access_token_lf",
)}`; )}`;
const response = await axios.request(error.config); const response = await axios.request(error.config);
return response; return response;

View file

@ -1,3 +1,4 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import { AxiosRequestConfig, AxiosResponse } from "axios"; import { AxiosRequestConfig, AxiosResponse } from "axios";
import { Edge, Node, ReactFlowJsonObject } from "reactflow"; import { Edge, Node, ReactFlowJsonObject } from "reactflow";
import { BASE_URL_API } from "../../constants/constants"; import { BASE_URL_API } from "../../constants/constants";
@ -18,6 +19,7 @@ import { UserInputType } from "../../types/components";
import { FlowStyleType, FlowType } from "../../types/flow"; import { FlowStyleType, FlowType } from "../../types/flow";
import { StoreComponentResponse } from "../../types/store"; import { StoreComponentResponse } from "../../types/store";
import { FlowPoolType } from "../../types/zustand/flow"; import { FlowPoolType } from "../../types/zustand/flow";
import { extractColumnsFromRows } from "../../utils/utils";
import { import {
APIClassType, APIClassType,
BuildStatusTypeAPI, BuildStatusTypeAPI,
@ -119,6 +121,7 @@ export async function saveFlowToDatabase(newFlow: {
description: string; description: string;
style?: FlowStyleType; style?: FlowStyleType;
is_component?: boolean; is_component?: boolean;
folder_id?: string;
}): Promise<FlowType> { }): Promise<FlowType> {
try { try {
const response = await api.post(`${BASE_URL_API}flows/`, { const response = await api.post(`${BASE_URL_API}flows/`, {
@ -126,6 +129,7 @@ export async function saveFlowToDatabase(newFlow: {
data: newFlow.data, data: newFlow.data,
description: newFlow.description, description: newFlow.description,
is_component: newFlow.is_component, is_component: newFlow.is_component,
folder_id: newFlow.folder_id === "" ? null : newFlow.folder_id,
}); });
if (response.status !== 201) { if (response.status !== 201) {
@ -152,6 +156,7 @@ export async function updateFlowInDatabase(
name: updatedFlow.name, name: updatedFlow.name,
data: updatedFlow.data, data: updatedFlow.data,
description: updatedFlow.description, description: updatedFlow.description,
folder_id: updatedFlow.folder_id === "" ? null : updatedFlow.folder_id,
}); });
if (response?.status !== 200) { if (response?.status !== 200) {
@ -992,3 +997,41 @@ export async function deleteFlowPool(
config["params"] = { flow_id: flowId }; config["params"] = { flow_id: flowId };
return await api.delete(`${BASE_URL_API}monitor/builds`, config); return await api.delete(`${BASE_URL_API}monitor/builds`, config);
} }
export async function multipleDeleteFlowsComponents(
flowIds: string[],
): Promise<AxiosResponse<any>> {
return await api.post(`${BASE_URL_API}flows/multiple_delete/`, {
flow_ids: flowIds,
});
}
export async function getTransactionTable(
id: string,
mode: "intersection" | "union",
params = {},
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
const config = {};
config["params"] = { flow_id: id };
if (params) {
config["params"] = { ...config["params"], ...params };
}
const rows = await api.get(`${BASE_URL_API}monitor/transactions`, config);
const columns = extractColumnsFromRows(rows.data, mode);
return { rows: rows.data, columns };
}
export async function getMessagesTable(
id: string,
mode: "intersection" | "union",
params = {},
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
const config = {};
config["params"] = { flow_id: id };
if (params) {
config["params"] = { ...config["params"], ...params };
}
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
const columns = extractColumnsFromRows(rows.data, mode);
return { rows: rows.data, columns };
}

View file

@ -0,0 +1,140 @@
import { useEffect, useState } from "react";
import InputComponent from "../../../components/inputComponent";
import {
FormControl,
FormField,
FormItem,
FormMessage,
} from "../../../components/ui/form";
import { Input } from "../../../components/ui/input";
import { Label } from "../../../components/ui/label";
import { Textarea } from "../../../components/ui/textarea";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
type FolderFormsProps = {
control: any;
setValue: any;
folderToEdit: any;
};
export const FolderForms = ({
control,
setValue,
folderToEdit,
}: FolderFormsProps): JSX.Element => {
const flows = useFlowsManagerStore((state) => state.flows);
const [selectedComponents, setSelectedComponents] = useState<string[]>([]);
const [selectedFlows, setSelectedFlows] = useState<string[]>([]);
const componentsList = flows
.filter((flow) => flow.is_component && flow.folder_id !== null)
.map((flow) => ({ id: flow.id, name: flow.name }));
const flowsList = flows
.filter((flow) => !flow.is_component && flow.folder_id !== null)
.map((flow) => ({ id: flow.id, name: flow.name }));
useEffect(() => {
setValue("components", selectedComponents);
setValue("flows", selectedFlows);
}, [selectedComponents, selectedFlows]);
useEffect(() => {
if (folderToEdit) {
setValue("name", folderToEdit.name);
setValue("description", folderToEdit.description);
console.log(folderToEdit);
// setSelectedComponents(folderToEdit.components);
// setSelectedFlows(folderToEdit.flows);
return;
}
setValue("name", "");
setValue("description", "");
setSelectedComponents([]);
setSelectedFlows([]);
}, [folderToEdit]);
return (
<>
<div className="flex h-full w-full flex-col gap-4 align-middle">
<Label>Folder Name</Label>
<FormField
control={control}
name="name"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
value={field.value}
onChange={field.onChange}
placeholder="Insert a name for the folder..."
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Label>Description (optional) </Label>
<FormField
control={control}
name="description"
render={({ field }) => (
<FormControl>
<Textarea
value={field.value}
onChange={field.onChange}
placeholder="Insert a description for the folder..."
></Textarea>
</FormControl>
)}
/>
<Label>Add Flows</Label>
<FormField
control={control}
name="flows"
render={() => (
<FormControl>
<InputComponent
isObjectOption
password={false}
objectOptions={flowsList}
placeholder="Choose a flow to add..."
id="input-flow"
setSelectedOptions={(value: any) => setSelectedFlows(value)}
selectedOptions={selectedFlows}
></InputComponent>
</FormControl>
)}
/>
<Label>Add Components</Label>
<FormField
control={control}
name="components"
render={() => (
<FormControl>
<InputComponent
isObjectOption
password={false}
objectOptions={componentsList}
placeholder="Choose a component to add..."
id="input-component"
setSelectedOptions={(value) => setSelectedComponents(value)}
selectedOptions={selectedComponents}
></InputComponent>
</FormControl>
)}
/>
</div>
</>
);
};
export default FolderForms;

View file

@ -0,0 +1,10 @@
import { z } from "zod";
export const FolderFormsSchema = z.object({
name: z.string().min(1, {
message: "Name must be at least 1 characters.",
}),
description: z.string().optional(),
components: z.array(z.string()),
flows: z.array(z.string()),
});

View file

@ -0,0 +1,53 @@
import { addFolder, updateFolder } from "../../../pages/MainPage/services";
import useAlertStore from "../../../stores/alertStore";
import { useFolderStore } from "../../../stores/foldersStore";
const useFolderSubmit = (setOpen, folderToEdit) => {
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
const onSubmit = (data) => {
if (folderToEdit) {
updateFolder(data, folderToEdit?.id!).then(
() => {
setSuccessData({
title: "Folder updated successfully.",
});
getFoldersApi(true);
setOpen(false);
},
(reason) => {
if (reason) {
setErrorData({
title: `Error updating folder.`,
});
console.error(reason);
} else {
getFoldersApi(true);
setOpen(false);
}
},
);
} else {
addFolder(data).then(
() => {
setSuccessData({
title: "Folder created successfully.",
});
getFoldersApi(true);
setOpen(false);
},
() => {
setErrorData({
title: `Error creating folder.`,
});
},
);
}
};
return { onSubmit, open, setOpen };
};
export default useFolderSubmit;

View file

@ -0,0 +1,79 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import ForwardedIconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Form } from "../../components/ui/form";
import { useFolderStore } from "../../stores/foldersStore";
import BaseModal from "../baseModal";
import FolderForms from "./component";
import { FolderFormsSchema } from "./entities";
import useFolderSubmit from "./hooks/submit-folder";
type FoldersModalProps = {
open: boolean;
setOpen: (open: boolean) => void;
};
export default function FoldersModal({
open,
setOpen,
}: FoldersModalProps): JSX.Element {
const form = useForm<z.infer<typeof FolderFormsSchema>>({
resolver: zodResolver(FolderFormsSchema),
defaultValues: {
name: "",
description: "",
components: [],
flows: [],
},
mode: "all",
});
const folderToEdit = useFolderStore((state) => state.folderToEdit);
const { onSubmit: onSubmitFolder } = useFolderSubmit(setOpen, folderToEdit);
const onSubmit = (data) => {
onSubmitFolder(data);
};
return (
<>
<BaseModal size="x-small" open={open} setOpen={setOpen}>
<BaseModal.Header
description={`${folderToEdit ? "Edit a folder" : "Add a new folder"}`}
>
<span className="pr-2" data-testid="modal-title">
{folderToEdit ? "Edit" : "New"} Folder
</span>
<ForwardedIconComponent
name="Plus"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FolderForms
folderToEdit={folderToEdit}
control={form.control}
setValue={form.setValue}
/>
<Button
className="float-right mt-6"
type="submit"
disabled={!form.formState.isValid}
>
{folderToEdit ? "Edit" : "Save"} Folder
</Button>
</form>
</Form>
</div>
</BaseModal.Content>
</BaseModal>
</>
);
}

View file

@ -4,6 +4,7 @@ import ImageViewer from "../../../../components/ImageViewer";
import CsvOutputComponent from "../../../../components/csvOutputComponent"; import CsvOutputComponent from "../../../../components/csvOutputComponent";
import InputListComponent from "../../../../components/inputListComponent"; import InputListComponent from "../../../../components/inputListComponent";
import PdfViewer from "../../../../components/pdfViewer"; import PdfViewer from "../../../../components/pdfViewer";
import RecordsOutputComponent from "../../../../components/recordsOutputComponent";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -47,6 +48,7 @@ export default function IOFieldView({
} }
} }
}; };
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
function handleOutputType() { function handleOutputType() {
@ -204,7 +206,7 @@ export default function IOFieldView({
<SelectItem key={separator} value={separator}> <SelectItem key={separator} value={separator}>
{separator} {separator}
</SelectItem> </SelectItem>
) ),
)} )}
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
@ -280,6 +282,15 @@ export default function IOFieldView({
/> />
</> </>
); );
case "RecordsOutput":
return (
<div className={left ? "h-56" : "h-full"}>
<RecordsOutputComponent
flowPool={flowPoolNode}
pagination={!left}
/>
</div>
);
default: default:
return ( return (

View file

@ -25,15 +25,21 @@ type TriggerProps = {
children: ReactNode; children: ReactNode;
asChild?: boolean; asChild?: boolean;
disable?: boolean; disable?: boolean;
className?: string;
}; };
const Content: React.FC<ContentProps> = ({ children }) => { const Content: React.FC<ContentProps> = ({ children }) => {
return <div className="flex h-full w-full flex-col">{children}</div>; return <div className="flex h-full w-full flex-col">{children}</div>;
}; };
const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => { const Trigger: React.FC<TriggerProps> = ({
children,
asChild,
disable,
className,
}) => {
return ( return (
<DialogTrigger <DialogTrigger
className={asChild ? "" : "w-full"} className={asChild ? "" : cn("w-full", className)}
hidden={children ? false : true} hidden={children ? false : true}
disabled={disable} disabled={disable}
asChild={asChild} asChild={asChild}
@ -113,7 +119,7 @@ function BaseModal({
switch (size) { switch (size) {
case "x-small": case "x-small":
minWidth = "min-w-[20vw]"; minWidth = "min-w-[20vw]";
height = " "; height = "h-full";
break; break;
case "smaller": case "smaller":
minWidth = "min-w-[40vw]"; minWidth = "min-w-[40vw]";
@ -129,6 +135,7 @@ function BaseModal({
break; break;
case "small-h-full": case "small-h-full":
minWidth = "min-w-[40vw]"; minWidth = "min-w-[40vw]";
height = "h-full";
break; break;
case "medium": case "medium":
minWidth = "min-w-[60vw]"; minWidth = "min-w-[60vw]";
@ -136,6 +143,8 @@ function BaseModal({
break; break;
case "medium-h-full": case "medium-h-full":
minWidth = "min-w-[60vw]"; minWidth = "min-w-[60vw]";
height = "h-full";
break; break;
case "large": case "large":
minWidth = "min-w-[85vw]"; minWidth = "min-w-[85vw]";
@ -162,6 +171,7 @@ function BaseModal({
case "large-h-full": case "large-h-full":
minWidth = "min-w-[80vw]"; minWidth = "min-w-[80vw]";
height = "h-full";
break; break;
default: default:
minWidth = "min-w-[80vw]"; minWidth = "min-w-[80vw]";
@ -186,7 +196,7 @@ function BaseModal({
{headerChild} {headerChild}
</div> </div>
<div <div
className={`flex flex-col ${height!} w-full transition-all duration-300`} className={`flex flex-col ${height} w-full transition-all duration-300`}
> >
{ContentChild} {ContentChild}
</div> </div>
@ -203,7 +213,7 @@ function BaseModal({
{headerChild} {headerChild}
</div> </div>
<div <div
className={`flex flex-col ${height!} w-full transition-all duration-300`} className={`flex flex-col ${height} w-full transition-all duration-300`}
> >
{ContentChild} {ContentChild}
</div> </div>

View file

@ -17,6 +17,7 @@ export default function DeleteConfirmationModal({
asChild, asChild,
open, open,
setOpen, setOpen,
note = "",
}: { }: {
children: JSX.Element; children: JSX.Element;
onConfirm: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; onConfirm: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
@ -24,6 +25,7 @@ export default function DeleteConfirmationModal({
asChild?: boolean; asChild?: boolean;
open?: boolean; open?: boolean;
setOpen?: (open: boolean) => void; setOpen?: (open: boolean) => void;
note?: string;
}) { }) {
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
@ -43,7 +45,14 @@ export default function DeleteConfirmationModal({
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<span> <span>
Confirm deletion of {description ?? "component"}?<br></br> Are you sure you want to delete the selected{" "}
{description ?? "component"}?<br></br>
{note && (
<>
{note}
<br></br>
</>
)}
Note: This action is irreversible. Note: This action is irreversible.
</span> </span>
<DialogFooter> <DialogFooter>

View file

@ -18,6 +18,10 @@ export default function DictAreaModal({
children, children,
onChange, onChange,
value, value,
}: {
children: JSX.Element;
onChange?: (value: Object) => void;
value: Object;
}): JSX.Element { }): JSX.Element {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const isDark = useDarkStore((state) => state.dark); const isDark = useDarkStore((state) => state.dark);
@ -29,9 +33,13 @@ export default function DictAreaModal({
return ( return (
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}> <BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger className="h-full">{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_DICT_DIALOG_SUBTITLE}> <BaseModal.Header
<span className="pr-2">Edit Dictionary</span> description={onChange ? CODE_DICT_DIALOG_SUBTITLE : null}
>
<span className="pr-2">
{onChange ? "Edit Dictionary" : "View Dictionary"}
</span>
<IconComponent <IconComponent
name="BookMarked" name="BookMarked"
className="h-6 w-6 pl-1 text-primary " className="h-6 w-6 pl-1 text-primary "
@ -44,7 +52,7 @@ export default function DictAreaModal({
theme="vscode" theme="vscode"
dark={isDark} dark={isDark}
className={!isDark ? "json-view-white" : "json-view-dark"} className={!isDark ? "json-view-white" : "json-view-dark"}
editable editable={!!onChange}
enableClipboard enableClipboard
onEdit={(edit) => { onEdit={(edit) => {
ref.current = edit["src"]; ref.current = edit["src"];
@ -54,19 +62,21 @@ export default function DictAreaModal({
}} }}
src={ref.current} src={ref.current}
/> />
<div className="flex h-fit w-full justify-end"> {onChange && (
<Button <div className="flex h-fit w-full justify-end">
data-testid="save-dict-button" <Button
className="mt-3" data-testid="save-dict-button"
type="submit" className="mt-3"
onClick={() => { type="submit"
onChange(ref.current); onClick={() => {
setOpen(false); onChange(ref.current);
}} setOpen(false);
> }}
Save >
</Button> Save
</div> </Button>
</div>
)}
</div> </div>
</BaseModal.Content> </BaseModal.Content>
</BaseModal> </BaseModal>

View file

@ -0,0 +1,104 @@
import { useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { Tabs, TabsList, TabsTrigger } from "../../components/ui/tabs";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowSettingsPropsType } from "../../types/components";
import { FlowType } from "../../types/flow";
import BaseModal from "../baseModal";
import TableComponent from "../../components/tableComponent";
import { getMessagesTable, getTransactionTable } from "../../controllers/API";
import {
ColDef,
ColGroupDef,
SizeColumnsToFitGridStrategy,
} from "ag-grid-community";
export default function FlowLogsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const flows = useFlowsManagerStore((state) => state.flows);
useEffect(() => {
setName(currentFlow!.name);
setDescription(currentFlow!.description);
}, [currentFlow!.name, currentFlow!.description, open]);
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
const [columns, setColumns] = useState<Array<ColDef | ColGroupDef>>([]);
const [rows, setRows] = useState<any>([]);
const [activeTab, setActiveTab] = useState("Executions");
function handleClick(): void {
currentFlow!.name = name;
currentFlow!.description = description;
saveFlow(currentFlow!);
setOpen(false);
}
useEffect(() => {
if (activeTab === "Executions") {
getTransactionTable(currentFlowId, "union").then((data) => {
const { columns, rows } = data;
setColumns(columns.map((col) => ({ ...col, editable: true })));
setRows(rows);
});
} else if (activeTab === "Messages") {
getMessagesTable(currentFlowId, "union").then((data) => {
const { columns, rows } = data;
setColumns(columns.map((col) => ({ ...col, editable: true })));
setRows(rows);
});
}
}, [open, activeTab]);
const [nameLists, setNameList] = useState<string[]>([]);
useEffect(() => {
const tempNameList: string[] = [];
flows.forEach((flow: FlowType) => {
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
}, [flows]);
return (
<BaseModal open={open} setOpen={setOpen} size="large">
<BaseModal.Header description="Inspect component executions and monitor sent messages in the playground.">
<div className="flex w-full justify-between">
<div className="flex h-fit w-32 items-center">
<span className="pr-2">Logs</span>
<IconComponent name="ScrollText" className="mr-2 h-4 w-4 " />
</div>
<div className="flex h-fit w-32 items-center"></div>
</div>
</BaseModal.Header>
<BaseModal.Content>
<Tabs
value={activeTab}
onValueChange={setActiveTab}
className={
"text-center; inset-0 m-0 mb-2 flex flex-col self-center overflow-hidden rounded-md border bg-muted pb-1"
}
>
<TabsList>
<TabsTrigger value={"Executions"}>Executions</TabsTrigger>
<TabsTrigger value={"Messages"}>Messages</TabsTrigger>
</TabsList>
</Tabs>
<TableComponent
readOnlyEdit
className="h-max-full h-full w-full"
pagination={rows.length === 0 ? false : true}
columnDefs={columns}
autoSizeStrategy={{ type: "fitGridWidth" }}
rowData={rows}
headerHeight={rows.length === 0 ? 0 : undefined}
></TableComponent>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -0,0 +1,148 @@
import { useEffect, useState } from "react";
import InputComponent from "../../../components/inputComponent";
import {
FormControl,
FormField,
FormItem,
FormMessage,
} from "../../../components/ui/form";
import { Input } from "../../../components/ui/input";
import { Label } from "../../../components/ui/label";
import { Textarea } from "../../../components/ui/textarea";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
type FolderFormsProps = {
control: any;
setValue: any;
folderToEdit: any;
};
export const FolderForms = ({
control,
setValue,
folderToEdit,
}: FolderFormsProps): JSX.Element => {
const flows = useFlowsManagerStore((state) => state.flows);
const [selectedComponents, setSelectedComponents] = useState<string[]>([]);
const [selectedFlows, setSelectedFlows] = useState<string[]>([]);
const allFlows = useFlowsManagerStore((state) => state.allFlows);
const componentsList = flows
.filter((flow) => flow.is_component && flow.folder_id !== null)
.map((flow) => ({ id: flow.id, name: flow.name }));
const flowsList = flows
.filter((flow) => !flow.is_component && flow.folder_id !== null)
.map((flow) => ({ id: flow.id, name: flow.name }));
const componentsOnFolder = allFlows
.filter((flow) => flow.is_component && flow.folder_id === folderToEdit?.id)
.map((flow) => flow.id);
const flowsOnFolder = allFlows
.filter((flow) => !flow.is_component && flow.folder_id === folderToEdit?.id)
.map((flow) => flow.id);
useEffect(() => {
setValue("components", selectedComponents);
setValue("flows", selectedFlows);
}, [selectedComponents, selectedFlows]);
useEffect(() => {
if (folderToEdit) {
setValue("name", folderToEdit.name);
setValue("description", folderToEdit.description);
setSelectedComponents(componentsOnFolder);
setSelectedFlows(flowsOnFolder);
return;
}
setValue("name", "");
setValue("description", "");
setSelectedComponents([]);
setSelectedFlows([]);
}, [folderToEdit]);
return (
<>
<div className="flex h-full w-full flex-col gap-4 align-middle">
<Label>Folder Name</Label>
<FormField
control={control}
name="name"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
value={field.value}
onChange={field.onChange}
placeholder="Insert a name for the folder..."
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Label>Description (optional) </Label>
<FormField
control={control}
name="description"
render={({ field }) => (
<FormControl>
<Textarea
value={field.value}
onChange={field.onChange}
placeholder="Insert a description for the folder..."
></Textarea>
</FormControl>
)}
/>
{/*
<Label>Add Flows</Label>
<FormField
control={control}
name="flows"
render={() => (
<FormControl>
<InputComponent
isObjectOption
password={false}
objectOptions={flowsList}
placeholder="Choose a flow to add..."
id="input-flow"
setSelectedOptions={(value: any) => setSelectedFlows(value)}
selectedOptions={selectedFlows}
></InputComponent>
</FormControl>
)}
/>
<Label>Add Components</Label>
<FormField
control={control}
name="components"
render={() => (
<FormControl>
<InputComponent
isObjectOption
password={false}
objectOptions={componentsList}
placeholder="Choose a component to add..."
id="input-component"
setSelectedOptions={(value) => setSelectedComponents(value)}
selectedOptions={selectedComponents}
></InputComponent>
</FormControl>
)}
/> */}
</div>
</>
);
};
export default FolderForms;

View file

@ -0,0 +1,10 @@
import { z } from "zod";
export const FolderFormsSchema = z.object({
name: z.string().min(1, {
message: "Name must be at least 1 characters.",
}),
description: z.string().optional(),
components: z.array(z.string()),
flows: z.array(z.string()),
});

View file

@ -0,0 +1,60 @@
import { useNavigate } from "react-router-dom";
import { addFolder, updateFolder } from "../../../pages/MainPage/services";
import useAlertStore from "../../../stores/alertStore";
import { useFolderStore } from "../../../stores/foldersStore";
const useFolderSubmit = (setOpen, folderToEdit) => {
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
const navigate = useNavigate();
const onSubmit = (data) => {
if (folderToEdit) {
updateFolder(data, folderToEdit?.id!).then(
() => {
setSuccessData({
title: "Folder updated successfully.",
});
getFoldersApi(true);
setOpen(false);
navigate(`all/folder/${folderToEdit.id}`, {
state: { folderId: folderToEdit.id },
});
},
//TODO: LOOK THIS ERRO MORE CAREFULLY
(reason) => {
if (reason) {
setErrorData({
title: `Error updating folder.`,
});
console.error(reason);
} else {
getFoldersApi(true);
setOpen(false);
}
},
);
} else {
addFolder(data).then(
(res) => {
setSuccessData({
title: "Folder created successfully.",
});
getFoldersApi(true);
setOpen(false);
navigate(`all/folder/${res.id}`, { state: { folderId: res.id } });
},
() => {
setErrorData({
title: `Error creating folder.`,
});
},
);
}
};
return { onSubmit, open, setOpen };
};
export default useFolderSubmit;

View file

@ -0,0 +1,79 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import ForwardedIconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Form } from "../../components/ui/form";
import { useFolderStore } from "../../stores/foldersStore";
import BaseModal from "../baseModal";
import FolderForms from "./component";
import { FolderFormsSchema } from "./entities";
import useFolderSubmit from "./hooks/submit-folder";
type FoldersModalProps = {
open: boolean;
setOpen: (open: boolean) => void;
};
export default function FoldersModal({
open,
setOpen,
}: FoldersModalProps): JSX.Element {
const form = useForm<z.infer<typeof FolderFormsSchema>>({
resolver: zodResolver(FolderFormsSchema),
defaultValues: {
name: "",
description: "",
components: [],
flows: [],
},
mode: "all",
});
const folderToEdit = useFolderStore((state) => state.folderToEdit);
const { onSubmit: onSubmitFolder } = useFolderSubmit(setOpen, folderToEdit);
const onSubmit = (data) => {
onSubmitFolder(data);
};
return (
<>
<BaseModal size="x-small" open={open} setOpen={setOpen}>
<BaseModal.Header
description={`${folderToEdit ? "Edit a folder" : "Add a new folder"}`}
>
<span className="pr-2" data-testid="modal-title">
{folderToEdit ? "Edit" : "New"} Folder
</span>
<ForwardedIconComponent
name={folderToEdit ? "Pencil" : "Plus"}
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FolderForms
folderToEdit={folderToEdit}
control={form.control}
setValue={form.setValue}
/>
<Button
className="float-right mt-6"
type="submit"
disabled={!form.formState.isValid}
>
{folderToEdit ? "Edit" : "Save"} Folder
</Button>
</form>
</Form>
</div>
</BaseModal.Content>
</BaseModal>
</>
);
}

View file

@ -1,4 +1,4 @@
import { useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { import {
Card, Card,
CardContent, CardContent,
@ -6,15 +6,21 @@ import {
CardTitle, CardTitle,
} from "../../../../components/ui/card"; } from "../../../../components/ui/card";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../../stores/foldersStore";
export default function NewFlowCardComponent() { export default function NewFlowCardComponent() {
const addFlow = useFlowsManagerStore((state) => state.addFlow); const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const folderId = location?.state?.folderId;
const setFolderUrl = useFolderStore((state) => state.setFolderUrl);
return ( return (
<Card <Card
onClick={() => { onClick={() => {
addFlow(true).then((id) => { addFlow(true).then((id) => {
navigate("/flow/" + id); setFolderUrl(folderId ?? "");
navigate(`/flow/${id}${folderId ? `/folder/${folderId}` : ""}`);
}); });
}} }}
className="h-64 w-80 cursor-pointer bg-background pt-4" className="h-64 w-80 cursor-pointer bg-background pt-4"

Some files were not shown because too many files have changed in this diff Show more