Merge remote-tracking branch 'origin/dev' into login_improvements

This commit is contained in:
anovazzi1 2023-09-04 16:19:13 -03:00
commit 2112f6152e
49 changed files with 1016 additions and 594 deletions

0
.githooks/pre-commit Normal file → Executable file
View file

View file

@ -27,7 +27,7 @@ format:
cd src/frontend && npm run format cd src/frontend && npm run format
lint: lint:
poetry run mypy --exclude .venv . poetry run mypy src/backend/langflow
poetry run black . --check poetry run black . --check
poetry run ruff . --fix poetry run ruff . --fix

226
poetry.lock generated
View file

@ -165,13 +165,13 @@ tz = ["python-dateutil"]
[[package]] [[package]]
name = "anthropic" name = "anthropic"
version = "0.3.10" version = "0.3.11"
description = "Client library for the anthropic API" description = "Client library for the anthropic API"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
{file = "anthropic-0.3.10-py3-none-any.whl", hash = "sha256:95cd73168296a4f91a5899a28660991e044322cf94442d07b99901d4ca74acd6"}, {file = "anthropic-0.3.11-py3-none-any.whl", hash = "sha256:5c81105cd9ee7388bff3fdb739aaddedc83bbae9b95d51c2d50c13b1ad106138"},
{file = "anthropic-0.3.10.tar.gz", hash = "sha256:d1f66efc541fbff0ecfd37fd4d3690f9daaa748fc42d9ded5863a10815a5d97b"}, {file = "anthropic-0.3.11.tar.gz", hash = "sha256:2e0fa5351c9b368cbed0bbd7217deaa9409b82b56afaf244e2196e99eb4fe20e"},
] ]
[package.dependencies] [package.dependencies]
@ -969,13 +969,13 @@ test-randomorder = ["pytest-randomly"]
[[package]] [[package]]
name = "ctransformers" name = "ctransformers"
version = "0.2.24" version = "0.2.25"
description = "Python bindings for the Transformer models implemented in C/C++ using GGML library." description = "Python bindings for the Transformer models implemented in C/C++ using GGML library."
optional = true optional = true
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "ctransformers-0.2.24-py3-none-any.whl", hash = "sha256:fa2ad7a38726c3ad6e57d1aff696f6e89fe3c0de5df2109b579cb6bc6c2ef599"}, {file = "ctransformers-0.2.25-py3-none-any.whl", hash = "sha256:a97405373c61c8d8f0279b88f96543caec638773a4fe82ff2ed8d993844830f1"},
{file = "ctransformers-0.2.24.tar.gz", hash = "sha256:bb463204f557d00d533e1dc50346e0b57870cea68965ec135d3fa8db1c76ed2e"}, {file = "ctransformers-0.2.25.tar.gz", hash = "sha256:59d654a4be8a51f0c5b7976d8d39a1e410f5e808f8cdabad02da4957001053d8"},
] ]
[package.dependencies] [package.dependencies]
@ -1449,18 +1449,21 @@ zstandard = ["zstandard"]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.12.2" version = "3.12.3"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, {file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"},
{file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, {file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"},
] ]
[package.dependencies]
typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"]
[[package]] [[package]]
name = "filetype" name = "filetype"
@ -2848,13 +2851,13 @@ files = [
[[package]] [[package]]
name = "jupyter-client" name = "jupyter-client"
version = "8.3.0" version = "8.3.1"
description = "Jupyter protocol implementation and client libraries" description = "Jupyter protocol implementation and client libraries"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "jupyter_client-8.3.0-py3-none-any.whl", hash = "sha256:7441af0c0672edc5d28035e92ba5e32fadcfa8a4e608a434c228836a89df6158"}, {file = "jupyter_client-8.3.1-py3-none-any.whl", hash = "sha256:5eb9f55eb0650e81de6b7e34308d8b92d04fe4ec41cd8193a913979e33d8e1a5"},
{file = "jupyter_client-8.3.0.tar.gz", hash = "sha256:3af69921fe99617be1670399a0b857ad67275eefcfa291e2c81a160b7b650f5f"}, {file = "jupyter_client-8.3.1.tar.gz", hash = "sha256:60294b2d5b869356c893f57b1a877ea6510d60d45cf4b38057f1672d85699ac9"},
] ]
[package.dependencies] [package.dependencies]
@ -2991,13 +2994,13 @@ test = ["psutil", "pytest", "pytest-asyncio"]
[[package]] [[package]]
name = "langsmith" name = "langsmith"
version = "0.0.27" version = "0.0.29"
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 = ">=3.8.1,<4.0" python-versions = ">=3.8.1,<4.0"
files = [ files = [
{file = "langsmith-0.0.27-py3-none-any.whl", hash = "sha256:f61b07f093ba377b9af53c3d6f68fd1245f8f28605d4fc88433208aca93a5a23"}, {file = "langsmith-0.0.29-py3-none-any.whl", hash = "sha256:1b8fa032bf1671d80fed027545aa822ef8f39fec0381b799a9c511120e8e8258"},
{file = "langsmith-0.0.27.tar.gz", hash = "sha256:c4df680ee8bf88d37f56ba196048341847c48b50ae561719c5542ef6488170e5"}, {file = "langsmith-0.0.29.tar.gz", hash = "sha256:d389a0199937cb219d7cb90d4adbed7f3902818b22b604bb1ced5b74c571cfe5"},
] ]
[package.dependencies] [package.dependencies]
@ -3026,12 +3029,12 @@ test = ["coverage", "pytest", "pytest-cov"]
[[package]] [[package]]
name = "llama-cpp-python" name = "llama-cpp-python"
version = "0.1.81" version = "0.1.83"
description = "A Python wrapper for llama.cpp" description = "A Python wrapper for llama.cpp"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "llama_cpp_python-0.1.81.tar.gz", hash = "sha256:8b8fa42e41c6334efe056571b5f19056ffd9776b94ee152530e1fb9fe81deda2"}, {file = "llama_cpp_python-0.1.83.tar.gz", hash = "sha256:9f40656e46a85a3c3427790246e03490bb90202c37cb99732a095ffcb99efe54"},
] ]
[package.dependencies] [package.dependencies]
@ -3812,13 +3815,13 @@ sympy = "*"
[[package]] [[package]]
name = "openai" name = "openai"
version = "0.27.9" version = "0.27.10"
description = "Python client library for the OpenAI API" description = "Python client library for the OpenAI API"
optional = false optional = false
python-versions = ">=3.7.1" python-versions = ">=3.7.1"
files = [ files = [
{file = "openai-0.27.9-py3-none-any.whl", hash = "sha256:6a3cf8e276d1a6262b50562fbc0cba7967cfebb78ed827d375986b48fdad6475"}, {file = "openai-0.27.10-py3-none-any.whl", hash = "sha256:beabd1757e3286fa166dde3b70ebb5ad8081af046876b47c14c41e203ed22a14"},
{file = "openai-0.27.9.tar.gz", hash = "sha256:b687761c82f5ebb6f61efc791b2083d2d068277b94802d4d1369efe39851813d"}, {file = "openai-0.27.10.tar.gz", hash = "sha256:60e09edf7100080283688748c6803b7b3b52d5a55d21890f3815292a0552d83b"},
] ]
[package.dependencies] [package.dependencies]
@ -4199,69 +4202,61 @@ files = [
[[package]] [[package]]
name = "pandas" name = "pandas"
version = "2.0.3" version = "2.1.0"
description = "Powerful data structures for data analysis, time series, and statistics" description = "Powerful data structures for data analysis, time series, and statistics"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, {file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"},
{file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, {file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"},
{file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, {file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"},
{file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, {file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"},
{file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, {file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"},
{file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, {file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"},
{file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, {file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"},
{file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, {file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"},
{file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, {file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"},
{file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, {file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"},
{file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, {file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"},
{file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, {file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"},
{file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, {file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"},
{file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, {file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"},
{file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, {file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"},
{file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, {file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"},
{file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, {file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"},
{file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, {file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"},
{file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, {file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"},
{file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"},
{file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"},
{file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"},
{file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"},
{file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"},
{file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"},
] ]
[package.dependencies] [package.dependencies]
numpy = [ numpy = {version = ">=1.22.4", markers = "python_version < \"3.11\""}
{version = ">=1.20.3", markers = "python_version < \"3.10\""},
{version = ">=1.21.0", markers = "python_version >= \"3.10\""},
]
python-dateutil = ">=2.8.2" python-dateutil = ">=2.8.2"
pytz = ">=2020.1" pytz = ">=2020.1"
tzdata = ">=2022.1" tzdata = ">=2022.1"
[package.extras] [package.extras]
all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
aws = ["s3fs (>=2021.08.0)"] aws = ["s3fs (>=2022.05.0)"]
clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] compression = ["zstandard (>=0.17.0)"]
computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"]
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"]
feather = ["pyarrow (>=7.0.0)"] feather = ["pyarrow (>=7.0.0)"]
fss = ["fsspec (>=2021.07.0)"] fss = ["fsspec (>=2022.05.0)"]
gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"]
hdf5 = ["tables (>=3.6.1)"] hdf5 = ["tables (>=3.7.0)"]
html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"]
mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"]
output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"]
parquet = ["pyarrow (>=7.0.0)"] parquet = ["pyarrow (>=7.0.0)"]
performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"]
plot = ["matplotlib (>=3.6.1)"] plot = ["matplotlib (>=3.6.1)"]
postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
spss = ["pyreadstat (>=1.1.2)"] spss = ["pyreadstat (>=1.1.5)"]
sql-other = ["SQLAlchemy (>=1.4.16)"] sql-other = ["SQLAlchemy (>=1.4.36)"]
test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
xml = ["lxml (>=4.6.3)"] xml = ["lxml (>=4.8.0)"]
[[package]] [[package]]
name = "pandas-stubs" name = "pandas-stubs"
@ -4845,41 +4840,41 @@ files = [
[[package]] [[package]]
name = "pulsar-client" name = "pulsar-client"
version = "3.2.0" version = "3.3.0"
description = "Apache Pulsar Python client library" description = "Apache Pulsar Python client library"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "pulsar_client-3.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:da53bbe1903026ca1253d36a67bde0ae88513497091658aee8c5514c3e567483"}, {file = "pulsar_client-3.3.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:c31afd3e67a044ff93177df89e08febf214cc965e95ede097d9fe8755af00e01"},
{file = "pulsar_client-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec595a71b7a25f1a72a1350efd6680a511b53253c3cac1911ba3d6c4d71fa64c"}, {file = "pulsar_client-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f66982284571674b215324cc26b5c2f7c56c7043113c47a7084cb70d67a8afb"},
{file = "pulsar_client-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3557c65463d74ec8d2864752389beb06761ab591dd134a164e0b1303c66719b"}, {file = "pulsar_client-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fe50a06f81c48a75a9b95c27a6446260039adca71d9face273740de96b2efca"},
{file = "pulsar_client-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d51dc76fec48217489bde95754ad58288c9389361de42f5a27d64e19840d27fb"}, {file = "pulsar_client-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d4c46a4b96a6e9919cfe220156d69a2ede8053d9ea1add4ada108abcf2ba9775"},
{file = "pulsar_client-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9ef2baf85311e0fe1b98342fdafbb93a1818a08ef999eaa524234fedf6f3b941"}, {file = "pulsar_client-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1e4b5d44b992c9b036286b483f3588c10b89c6047fb59d80c7474445997f4e10"},
{file = "pulsar_client-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:0928b02beda0c98e77178f4e30e962ddb8ee8c3320e4c7304a78b0796e976523"}, {file = "pulsar_client-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:497a59ac6b650835a3b2c502f53477e5c98e5226998ca3f17c0b0a3eb4d67d08"},
{file = "pulsar_client-3.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:584f44b03474a69906be711a597a4d516263a55be31e49fc07be503dc8406821"}, {file = "pulsar_client-3.3.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:386e78ff52058d881780bae1f6e84ac9434ae0b01a8581755ca8cc0dc844a332"},
{file = "pulsar_client-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a637b9a3b30860c61e68a7b8ea650e0987d89e82f73b6a3df1ab662a6438fdda"}, {file = "pulsar_client-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e4ecb780df58bcfd3918590bd3ff31ed79bccfbef3a1a60370642eb1e14a9d2"},
{file = "pulsar_client-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4a187fdc5febcf16f725179dcf2c476f31eeebd8353794d91754a3202dd5072"}, {file = "pulsar_client-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ce1e215c252f22a6f26ca5e9076826041a04d88dc213b92c86b524be2774a64"},
{file = "pulsar_client-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5ff879f868cf1fd29db99f39fdb22b3ec3e749c648aca28526689756d922d1c5"}, {file = "pulsar_client-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b0fd5be73a4103986b9dbe3a66468cf8829371e34af87ff8f216e3980f4cbe"},
{file = "pulsar_client-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a5f85d0cc414f739a5b51d843f213b54b2cd768c3a34f7c27cca410712b1f81"}, {file = "pulsar_client-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33656450536d83eed1563ff09692c2c415fb199d88e9ed97d701ca446a119e1b"},
{file = "pulsar_client-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:4fe748283848d829a80c0323558faeebea4c240d69fa58314ac90344f6999d17"}, {file = "pulsar_client-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:ce33de700b06583df8777e139d68cb4b4b3d0a2eac168d74278d8935f357fb10"},
{file = "pulsar_client-3.2.0-cp37-cp37m-macosx_10_15_universal2.whl", hash = "sha256:06b91c26def86dbbc35be15257999fd8a2afbadf32983916ea3eef44f4d4cab4"}, {file = "pulsar_client-3.3.0-cp37-cp37m-macosx_10_15_universal2.whl", hash = "sha256:7b5dd25cf778d6c980d36c53081e843ea272afe7af4f0ad6394ae9513f94641b"},
{file = "pulsar_client-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39ec897bc8d232e6b118793378fc662a844334b829a28a1b4ad1c5fe8d019135"}, {file = "pulsar_client-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33c4e6865fda62a2e460f823dce4d49ac2973a4459b8ff99eda5fdd6aaaebf46"},
{file = "pulsar_client-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa37c96c25c1b5aff3bad0fd0194b385ec190b2c67a2f439ac91577f81ae18d3"}, {file = "pulsar_client-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1810ddc623c8de2675d17405ce47057a9a2b92298e708ce4d9564847f5ad904"},
{file = "pulsar_client-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d49cdd4d1b7fc2e80d100acf14e6fd3898f6e099e403fc56ed22a690245b2fec"}, {file = "pulsar_client-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8259c3b856eb6deaa1f93dce893ab18d99d36d102da5612c8e97a4fb41b70ab1"},
{file = "pulsar_client-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0058ca3191fd24528ccf94dba6f12e4093831454a2597166f96900d0717271bf"}, {file = "pulsar_client-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e7a48b2e505cde758fd51a601b5da0671fa98c9baee38362aaaa3ab2b930c28"},
{file = "pulsar_client-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:cb69b0411008e0b56df51de0aab20aa1c1a12aef3019b9ceba89afbae1f07fe2"}, {file = "pulsar_client-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ede264385d47257b2f2b08ecde9181ec5338bea5639cc543d1856f01736778d2"},
{file = "pulsar_client-3.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:f7d33e99602352df7a30707eab4e5781654602212fb618928bffb5523f2bcf35"}, {file = "pulsar_client-3.3.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:0f64c62746ccd5b65a0c505f5f40b9af1f147eb1fa2d8f9c90cd5c8b92dd8597"},
{file = "pulsar_client-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad1ac15a175ca90555c681a4d0134568771c6346b97a172f3ef14006556a50ae"}, {file = "pulsar_client-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b84a20c9012e3c4ef1b7085acd7467197118c090b378dec27d773fb79d91556"},
{file = "pulsar_client-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369e08ef1d5cb196dd9271039928800f90b4701a9c9df90bc068b44260d2fb11"}, {file = "pulsar_client-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e15fa696e275ccb66d0791fdc19c4dea0420d81349c8055e485b134125e14f"},
{file = "pulsar_client-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a52ba2b6736a2ebeed31b590e75d417dda149e333461655860efa84d898a3eb4"}, {file = "pulsar_client-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:72cbb1bdcba2dd1265296b5ba65331622ee89c16db75edaad46dd7b90c6dd447"},
{file = "pulsar_client-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c801334b3b569b23976481a2922bcea0c6dd990fc26544658dd9e9c8f78ca36"}, {file = "pulsar_client-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d54dd12955bf587dd46d9184444af5e853d9da2a14bbfb739ed2c7c3b78ce280"},
{file = "pulsar_client-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:cd01fd419280e9013d1655bc53662248be2656b623b1506480e1a985aa7dadd2"}, {file = "pulsar_client-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:43f98afdf0334b2b957a4d96f97a1fe8a7f7fd1e2631d40c3f00b4162f396485"},
{file = "pulsar_client-3.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:0abe54d84db76435a6cd88ce27610352cabc7efae9fa3e7f874e032ec2ca0b3f"}, {file = "pulsar_client-3.3.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:efe7c1e6a96daccc522c3567b6847ffa54c13e0f510d9a427b4aeff9fbebe54b"},
{file = "pulsar_client-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a1b6a806eb4819d8cbab1c4ae44ebf2110a94204a46c365f5757e1455252f2"}, {file = "pulsar_client-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f28e94420090fceeb38e23fc744f3edf8710e48314ef5927d2b674a1d1e43ee0"},
{file = "pulsar_client-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ea2a6b75ae0e303d522e5b57c75a4ff03dc18b9bfc14151fb14dfaf5866f17"}, {file = "pulsar_client-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c8f3eaa98e2351805ecb6efb6d5fedf47a314a3ce6af0e05ea1449ea7244ed"},
{file = "pulsar_client-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be6d3a9b2e1db3b6d1a7db5e13f7b4ed420674cf072cdb520fb004c4cd54c0af"}, {file = "pulsar_client-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5e69750f8ae57e55fddf97b459ce0d8b38b2bb85f464a71e871ee6a86d893be7"},
{file = "pulsar_client-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6b733e6239ffb505f7084df0175baf9d0215f14d0a02e9bbd1fdf71a2d6ea17"}, {file = "pulsar_client-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7e147e5ba460c1818bc05254279a885b4e552bcafb8961d40e31f98d5ff46628"},
{file = "pulsar_client-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:edc2135d02b4793efb086edca0ffaa6e8ac9133961c2cdc17ae487e0a53da481"}, {file = "pulsar_client-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:694530af1d6c75fb81456fb509778c1868adee31e997ddece6e21678200182ea"},
] ]
[package.dependencies] [package.dependencies]
@ -6855,13 +6850,13 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
[[package]] [[package]]
name = "transformers" name = "transformers"
version = "4.32.0" version = "4.32.1"
description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow"
optional = true optional = true
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
files = [ files = [
{file = "transformers-4.32.0-py3-none-any.whl", hash = "sha256:32d8adf0ed76285508e7fd66657b4448ec1f882599ae6bf6f9c36bd7bf798402"}, {file = "transformers-4.32.1-py3-none-any.whl", hash = "sha256:b930d3dbd907a3f300cf49e54d63a56f8a0ab16b01a2c2a61ecff37c6de1da08"},
{file = "transformers-4.32.0.tar.gz", hash = "sha256:ca510f9688d2fe7347abbbfbd13f2f6dcd3c8349870c8d0ed98beed5f579b354"}, {file = "transformers-4.32.1.tar.gz", hash = "sha256:1edc8ae1de357d97c3d36b04412aa63d55e6fc0c4b39b419a7d380ed947d2252"},
] ]
[package.dependencies] [package.dependencies]
@ -7046,6 +7041,17 @@ files = [
{file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"}, {file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"},
] ]
[[package]]
name = "types-pywin32"
version = "306.0.0.4"
description = "Typing stubs for pywin32"
optional = false
python-versions = "*"
files = [
{file = "types-pywin32-306.0.0.4.tar.gz", hash = "sha256:ae4bbec80d535053236d4bebedf55f58dee89cf5883d277f0fa89e857f3ff337"},
{file = "types_pywin32-306.0.0.4-py3-none-any.whl", hash = "sha256:f76a343ed6933008af85e158063963f923e54f2f461e697b2929b4178c7b77a1"},
]
[[package]] [[package]]
name = "types-pyyaml" name = "types-pyyaml"
version = "6.0.12.11" version = "6.0.12.11"
@ -7334,13 +7340,13 @@ files = [
[[package]] [[package]]
name = "weaviate-client" name = "weaviate-client"
version = "3.23.1" version = "3.23.2"
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-3.23.1.tar.gz", hash = "sha256:035f395fb8b17008224dc8a9ca4459b7ef4a2b0449209ac0c8d0f2e3b9a77f59"}, {file = "weaviate-client-3.23.2.tar.gz", hash = "sha256:1c8c94df032dd2fa5a4ea615fc69ccb983ffad5cc02974f78c793839e61ac150"},
{file = "weaviate_client-3.23.1-py3-none-any.whl", hash = "sha256:826b28237f7143ee4c51b988c5c37494760b4377a8536acc772a2194eb33f30c"}, {file = "weaviate_client-3.23.2-py3-none-any.whl", hash = "sha256:88ffc38cca07806d64726cc74bc194c7da50b222aa4e2cd129f4c1f5e53e9b61"},
] ]
[package.dependencies] [package.dependencies]
@ -7778,4 +7784,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "505fe04c51514ef25dd955b377a00185c4a2581770af3cc84db4c47477760048" content-hash = "a3a506d483c2db7169a9790090095d1764aa5be223d135c6fc3fc2768dfef36c"

View file

@ -83,6 +83,7 @@ bcrypt = "^4.0.1"
python-jose = "^3.3.0" python-jose = "^3.3.0"
metaphor-python = "^0.1.11" metaphor-python = "^0.1.11"
markupsafe = "^2.1.3" markupsafe = "^2.1.3"
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"
@ -102,6 +103,7 @@ types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13" types-passlib = "^1.7.7.13"
pytest-mock = "^3.11.1" pytest-mock = "^3.11.1"
pytest-xdist = "^3.3.1" pytest-xdist = "^3.3.1"
types-pywin32 = "^306.0.0.4"
[tool.poetry.extras] [tool.poetry.extras]

View file

@ -356,7 +356,7 @@ def superuser(
with session_getter(db_manager) as session: with session_getter(db_manager) as session:
from langflow.services.auth.utils import create_super_user from langflow.services.auth.utils import create_super_user
if create_super_user(session, username, password): if create_super_user(db=session, username=username, password=password):
# Verify that the superuser was created # Verify that the superuser was created
from langflow.services.database.models.user.user import User from langflow.services.database.models.user.user import User

View file

@ -30,10 +30,10 @@ def upgrade() -> None:
# and other related indices # and other related indices
if "flowstyle" in existing_tables: if "flowstyle" in existing_tables:
op.drop_table("flowstyle") op.drop_table("flowstyle")
if "ix_flowstyle_flow_id" in [ if "ix_flowstyle_flow_id" in [
index["name"] for index in inspector.get_indexes("flowstyle") index["name"] for index in inspector.get_indexes("flowstyle")
]: ]:
op.drop_index("ix_flowstyle_flow_id", table_name="flowstyle") op.drop_index("ix_flowstyle_flow_id", table_name="flowstyle")
existing_indices_flow = [] existing_indices_flow = []
existing_fks_flow = [] existing_fks_flow = []

View file

@ -21,8 +21,8 @@ class FrontendNodeRequest(FrontendNode):
class ValidatePromptRequest(BaseModel): class ValidatePromptRequest(BaseModel):
name: str name: str
template: str template: str
#optional for tweak call # optional for tweak call
frontend_node: Optional[FrontendNodeRequest] frontend_node: Optional[FrontendNodeRequest] = None
# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}} # Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
@ -41,8 +41,8 @@ class CodeValidationResponse(BaseModel):
class PromptValidationResponse(BaseModel): class PromptValidationResponse(BaseModel):
input_variables: list input_variables: list
#object return for tweak call # object return for tweak call
frontend_node: FrontendNodeRequest | object frontend_node: Optional[FrontendNodeRequest] = None
INVALID_CHARACTERS = { INVALID_CHARACTERS = {

View file

@ -11,13 +11,14 @@ from fastapi.responses import StreamingResponse
from langflow.api.utils import build_input_keys_response from langflow.api.utils import build_input_keys_response
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
from langflow.services import service_manager, ServiceType
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
from langflow.services.auth.utils import get_current_active_user, get_current_user from langflow.services.auth.utils import get_current_active_user, get_current_user
from langflow.services.utils import get_session from langflow.services.utils import get_chat_manager, get_session
from langflow.utils.logger import logger from langflow.utils.logger import logger
from cachetools import LRUCache from cachetools import LRUCache
from sqlmodel import Session from sqlmodel import Session
from langflow.services.chat.manager import ChatManager
router = APIRouter(tags=["Chat"]) router = APIRouter(tags=["Chat"])
@ -30,29 +31,48 @@ async def chat(
websocket: WebSocket, websocket: WebSocket,
token: str = Query(...), token: str = Query(...),
db: Session = Depends(get_session), db: Session = Depends(get_session),
chat_manager: "ChatManager" = Depends(get_chat_manager),
): ):
"""Websocket endpoint for chat.""" """Websocket endpoint for chat."""
try: try:
await websocket.accept()
user = await get_current_user(token, db) user = await get_current_user(token, db)
if not user:
await websocket.close(
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
)
if not user.is_active: if not user.is_active:
raise HTTPException(status_code=401, detail="Invalid token") await websocket.close(
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER) code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
)
if client_id in chat_manager.in_memory_cache: if client_id in chat_manager.in_memory_cache:
await chat_manager.handle_websocket(client_id, websocket) await chat_manager.handle_websocket(client_id, websocket)
else: else:
# We accept the connection but close it immediately # We accept the connection but close it immediately
# if the flow is not built yet # if the flow is not built yet
await websocket.accept()
message = "Please, build the flow before sending messages" message = "Please, build the flow before sending messages"
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=message) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=message)
except WebSocketException as exc: except WebSocketException as exc:
logger.error(f"Websocket error: {exc}") logger.error(f"Websocket error: {exc}")
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc)) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
except Exception as exc:
logger.error(f"Error in chat websocket: {exc}")
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
if "Could not validate credentials" in str(exc):
await websocket.close(
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
)
else:
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
@router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201) @router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201)
async def init_build( async def init_build(
graph_data: dict, flow_id: str, current_user=Depends(get_current_active_user) graph_data: dict,
flow_id: str,
current_user=Depends(get_current_active_user),
chat_manager: "ChatManager" = Depends(get_chat_manager),
): ):
"""Initialize the build by storing graph data and returning a unique session ID.""" """Initialize the build by storing graph data and returning a unique session ID."""
@ -67,7 +87,6 @@ async def init_build(
return InitResponse(flowId=flow_id) return InitResponse(flowId=flow_id)
# Delete from cache if already exists # Delete from cache if already exists
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
if flow_id in chat_manager.in_memory_cache: if flow_id in chat_manager.in_memory_cache:
with chat_manager.in_memory_cache._lock: with chat_manager.in_memory_cache._lock:
chat_manager.in_memory_cache.delete(flow_id) chat_manager.in_memory_cache.delete(flow_id)
@ -103,7 +122,9 @@ async def build_status(flow_id: str):
@router.get("/build/stream/{flow_id}", response_class=StreamingResponse) @router.get("/build/stream/{flow_id}", response_class=StreamingResponse)
async def stream_build(flow_id: str): async def stream_build(
flow_id: str, chat_manager: "ChatManager" = Depends(get_chat_manager)
):
"""Stream the build process based on stored flow data.""" """Stream the build process based on stored flow data."""
async def event_stream(flow_id): async def event_stream(flow_id):
@ -182,7 +203,6 @@ async def stream_build(flow_id: str):
"handle_keys": [], "handle_keys": [],
} }
yield str(StreamData(event="message", data=input_keys_response)) yield str(StreamData(event="message", data=input_keys_response))
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
chat_manager.set_cache(flow_id, langchain_object) chat_manager.set_cache(flow_id, langchain_object)
# We need to reset the chat history # We need to reset the chat history
chat_manager.chat_history.empty_history(flow_id) chat_manager.chat_history.empty_history(flow_id)

View file

@ -34,14 +34,15 @@ from sqlmodel import Session
router = APIRouter(tags=["Base"]) router = APIRouter(tags=["Base"])
@router.get("/all") @router.get("/all", dependencies=[Depends(get_current_active_user)])
def get_all(current_user: User = Depends(get_current_active_user)): def get_all(
settings_manager=Depends(get_settings_manager),
):
logger.debug("Building langchain types dict") logger.debug("Building langchain types dict")
native_components = build_langchain_types_dict() native_components = build_langchain_types_dict()
# custom_components is a list of dicts # custom_components is a list of dicts
# need to merge all the keys into one dict # need to merge all the keys into one dict
custom_components_from_file: dict[str, Any] = {} custom_components_from_file: dict[str, Any] = {}
settings_manager = get_settings_manager()
if settings_manager.settings.COMPONENTS_PATH: if settings_manager.settings.COMPONENTS_PATH:
logger.info( logger.info(
f"Building custom components from {settings_manager.settings.COMPONENTS_PATH}" f"Building custom components from {settings_manager.settings.COMPONENTS_PATH}"

View file

@ -83,6 +83,7 @@ def update_flow(
flow_id: UUID, flow_id: UUID,
flow: FlowUpdate, flow: FlowUpdate,
current_user: User = Depends(get_current_active_user), current_user: User = Depends(get_current_active_user),
settings_manager=Depends(get_settings_manager),
): ):
"""Update a flow.""" """Update a flow."""
@ -90,7 +91,6 @@ def update_flow(
if not db_flow: if not db_flow:
raise HTTPException(status_code=404, detail="Flow not found") raise HTTPException(status_code=404, detail="Flow not found")
flow_data = flow.dict(exclude_unset=True) flow_data = flow.dict(exclude_unset=True)
settings_manager = get_settings_manager()
if settings_manager.settings.REMOVE_API_KEYS: if settings_manager.settings.REMOVE_API_KEYS:
flow_data = remove_api_keys(flow_data) flow_data = remove_api_keys(flow_data)
for key, value in flow_data.items(): for key, value in flow_data.items():

View file

@ -34,9 +34,9 @@ async def login_to_get_access_token(
@router.get("/auto_login") @router.get("/auto_login")
async def auto_login(db: Session = Depends(get_session)): async def auto_login(
settings_manager = get_settings_manager() db: Session = Depends(get_session), settings_manager=Depends(get_settings_manager)
):
if settings_manager.auth_settings.AUTO_LOGIN: if settings_manager.auth_settings.AUTO_LOGIN:
return create_user_longterm_token(db) return create_user_longterm_token(db)

View file

@ -29,7 +29,7 @@ router = APIRouter(tags=["Users"])
@router.post("/user", response_model=UserRead, status_code=201) @router.post("/user", response_model=UserRead, status_code=201)
def add_user( def add_user(
user: UserCreate, user: UserCreate,
db: Session = Depends(get_session), session: Session = Depends(get_session),
) -> User: ) -> User:
""" """
Add a new user to the database. Add a new user to the database.
@ -38,12 +38,14 @@ def add_user(
try: try:
new_user.password = get_password_hash(user.password) new_user.password = get_password_hash(user.password)
db.add(new_user) session.add(new_user)
db.commit() session.commit()
db.refresh(new_user) session.refresh(new_user)
except IntegrityError as e: except IntegrityError as e:
db.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
return new_user return new_user
@ -63,16 +65,16 @@ def read_all_users(
skip: int = 0, skip: int = 0,
limit: int = 10, limit: int = 10,
current_user: Session = Depends(get_current_active_superuser), current_user: Session = Depends(get_current_active_superuser),
db: Session = Depends(get_session), session: Session = Depends(get_session),
) -> UsersResponse: ) -> UsersResponse:
""" """
Retrieve a list of users from the database with pagination. Retrieve a list of users from the database with pagination.
""" """
query = select(User).offset(skip).limit(limit) query = select(User).offset(skip).limit(limit)
users = db.execute(query).fetchall() users = session.execute(query).fetchall()
count_query = select(func.count()).select_from(User) # type: ignore count_query = select(func.count()).select_from(User) # type: ignore
total_count = db.execute(count_query).scalar() total_count = session.execute(count_query).scalar()
return UsersResponse( return UsersResponse(
total_count=total_count, # type: ignore total_count=total_count, # type: ignore
@ -85,19 +87,19 @@ def patch_user(
user_id: UUID, user_id: UUID,
user: UserUpdate, user: UserUpdate,
_: Session = Depends(get_current_active_user), _: Session = Depends(get_current_active_user),
db: Session = Depends(get_session), session: Session = Depends(get_session),
) -> User: ) -> User:
""" """
Update an existing user's data. Update an existing user's data.
""" """
return update_user(user_id, user, db) return update_user(user_id, user, session)
@router.delete("/user/{user_id}") @router.delete("/user/{user_id}")
def delete_user( def delete_user(
user_id: UUID, user_id: UUID,
current_user: User = Depends(get_current_active_superuser), current_user: User = Depends(get_current_active_superuser),
db: Session = Depends(get_session), session: Session = Depends(get_session),
) -> dict: ) -> dict:
""" """
Delete a user from the database. Delete a user from the database.
@ -111,12 +113,12 @@ def delete_user(
status_code=403, detail="You don't have the permission to delete this user" status_code=403, detail="You don't have the permission to delete this user"
) )
user_db = db.query(User).filter(User.id == user_id).first() user_db = session.query(User).filter(User.id == user_id).first()
if not user_db: if not user_db:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
db.delete(user_db) session.delete(user_db)
db.commit() session.commit()
return {"detail": "User deleted"} return {"detail": "User deleted"}
@ -124,7 +126,7 @@ def delete_user(
# TODO: REMOVE - Just for testing purposes # TODO: REMOVE - Just for testing purposes
@router.post("/super_user", response_model=User) @router.post("/super_user", response_model=User)
def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
db: Session = Depends(get_session), session: Session = Depends(get_session),
) -> User: ) -> User:
""" """
Add a superuser for testing purposes. Add a superuser for testing purposes.
@ -139,11 +141,11 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
) )
try: try:
db.add(new_user) session.add(new_user)
db.commit() session.commit()
db.refresh(new_user) session.refresh(new_user)
except IntegrityError as e: except IntegrityError as e:
db.rollback() session.rollback()
raise HTTPException(status_code=400, detail="User exists") from e raise HTTPException(status_code=400, detail="User exists") from e
return new_user return new_user

View file

@ -35,7 +35,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
if prompt_request.frontend_node is None: if prompt_request.frontend_node is None:
return PromptValidationResponse( return PromptValidationResponse(
input_variables=input_variables, input_variables=input_variables,
frontend_node={}, frontend_node=None,
) )
old_custom_fields = get_old_custom_fields(prompt_request) old_custom_fields = get_old_custom_fields(prompt_request)

View file

@ -10,7 +10,7 @@ from langflow.api import router
from langflow.interface.utils import setup_llm_caching from langflow.interface.utils import setup_llm_caching
from langflow.services.database.utils import initialize_database from langflow.services.database.utils import initialize_database
from langflow.services.manager import initialize_services from langflow.services.manager import initialize_services, teardown_services
from langflow.utils.logger import configure from langflow.utils.logger import configure
@ -40,6 +40,7 @@ def create_app():
app.on_event("startup")(initialize_services) app.on_event("startup")(initialize_services)
app.on_event("startup")(initialize_database) app.on_event("startup")(initialize_database)
app.on_event("startup")(setup_llm_caching) app.on_event("startup")(setup_llm_caching)
app.on_event("shutdown")(teardown_services)
return app return app

View file

@ -37,7 +37,12 @@ async def api_key_security(
result: Optional[Union[ApiKey, User]] = None result: Optional[Union[ApiKey, User]] = None
if settings_manager.auth_settings.AUTO_LOGIN: if settings_manager.auth_settings.AUTO_LOGIN:
# Get the first user # Get the first user
settings_manager.auth_settings.FIRST_SUPERUSER if not settings_manager.auth_settings.FIRST_SUPERUSER:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing first superuser credentials",
)
result = get_user_by_username( result = get_user_by_username(
db, settings_manager.auth_settings.FIRST_SUPERUSER db, settings_manager.auth_settings.FIRST_SUPERUSER
) )
@ -80,6 +85,9 @@ async def get_current_user(
if isinstance(token, Coroutine): if isinstance(token, Coroutine):
token = await token token = await token
if settings_manager.auth_settings.SECRET_KEY is None:
raise credentials_exception
try: try:
payload = jwt.decode( payload = jwt.decode(
token, token,
@ -88,6 +96,11 @@ async def get_current_user(
) )
user_id: UUID = payload.get("sub") # type: ignore user_id: UUID = payload.get("sub") # type: ignore
token_type: str = payload.get("type") # type: ignore token_type: str = payload.get("type") # type: ignore
if expires := payload.get("exp", None):
expires_datetime = datetime.fromtimestamp(expires, timezone.utc)
# TypeError: can't compare offset-naive and offset-aware datetimes
if datetime.now(timezone.utc) > expires_datetime:
raise credentials_exception
if user_id is None or token_type: if user_id is None or token_type:
raise credentials_exception raise credentials_exception
@ -145,22 +158,16 @@ def create_token(data: dict, expires_delta: timedelta):
def create_super_user( def create_super_user(
username: str,
password: str,
db: Session = Depends(get_session), db: Session = Depends(get_session),
username: Optional[str] = None,
password: Optional[str] = None,
) -> User: ) -> User:
settings_manager = get_settings_manager() super_user = get_user_by_username(db, username)
super_user = get_user_by_username(
db, username or settings_manager.auth_settings.FIRST_SUPERUSER
)
if not super_user: if not super_user:
super_user = User( super_user = User(
username=username or settings_manager.auth_settings.FIRST_SUPERUSER, username=username,
password=get_password_hash( password=get_password_hash(password),
password or settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD
),
is_superuser=True, is_superuser=True,
is_active=True, is_active=True,
last_login_at=None, last_login_at=None,
@ -174,7 +181,15 @@ def create_super_user(
def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: def create_user_longterm_token(db: Session = Depends(get_session)) -> dict:
super_user = create_super_user(db) settings_manager = get_settings_manager()
username = settings_manager.auth_settings.FIRST_SUPERUSER
password = settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD
if not username or not password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing first superuser credentials",
)
super_user = create_super_user(db=db, username=username, password=password)
access_token_expires_longterm = timedelta(days=365) access_token_expires_longterm = timedelta(days=365)
access_token = create_token( access_token = create_token(

View file

@ -1,2 +1,8 @@
class Service: from abc import ABC
class Service(ABC):
name: str name: str
def teardown(self):
pass

View file

@ -92,7 +92,6 @@ class ChatManager(Service):
) )
async def connect(self, client_id: str, websocket: WebSocket): async def connect(self, client_id: str, websocket: WebSocket):
await websocket.accept()
self.active_connections[client_id] = websocket self.active_connections[client_id] = websocket
def disconnect(self, client_id: str): def disconnect(self, client_id: str):

View file

@ -1,6 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from langflow.services.base import Service from langflow.services.base import Service
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults from langflow.services.database.utils import Result, TableResults
from langflow.services.utils import get_settings_manager from langflow.services.utils import get_settings_manager
from sqlalchemy import inspect from sqlalchemy import inspect
@ -159,3 +160,23 @@ class DatabaseManager(Service):
) )
logger.debug("Database and tables created successfully") logger.debug("Database and tables created successfully")
def teardown(self):
logger.debug("Tearing down database")
try:
settings_manager = get_settings_manager()
# remove the default superuser if auto_login is enabled
# using the FIRST_SUPERUSER to get the user
if settings_manager.auth_settings.AUTO_LOGIN:
logger.debug("Removing default superuser")
username = settings_manager.auth_settings.FIRST_SUPERUSER
with Session(self.engine) as session:
user = get_user_by_username(session, username)
session.delete(user)
session.commit()
logger.debug("Default superuser removed")
except Exception as exc:
logger.error(f"Error tearing down database: {exc}")
self.engine.dispose()

View file

@ -1,5 +1,6 @@
from langflow.services.schema import ServiceType from langflow.services.schema import ServiceType
from typing import TYPE_CHECKING, List, Optional from typing import TYPE_CHECKING, List, Optional
from langflow.utils.logger import logger
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.factory import ServiceFactory from langflow.services.factory import ServiceFactory
@ -42,6 +43,7 @@ class ServiceManager:
""" """
Create a new service given its name, handling dependencies. Create a new service given its name, handling dependencies.
""" """
logger.debug(f"Create service {service_name}")
self._validate_service_creation(service_name) self._validate_service_creation(service_name)
# Create dependencies first # Create dependencies first
@ -74,9 +76,21 @@ class ServiceManager:
Update a service by its name. Update a service by its name.
""" """
if service_name in self.services: if service_name in self.services:
logger.debug(f"Update service {service_name}")
self.services.pop(service_name, None) self.services.pop(service_name, None)
self.get(service_name) self.get(service_name)
def teardown(self):
"""
Teardown all the services.
"""
for service in self.services.values():
logger.debug(f"Teardown service {service.name}")
service.teardown()
self.services = {}
self.factories = {}
self.dependencies = {}
service_manager = ServiceManager() service_manager = ServiceManager()
@ -121,7 +135,7 @@ def initialize_session_manager():
""" """
Initialize the session manager. Initialize the session manager.
""" """
from langflow.services.session import factory as session_manager_factory from langflow.services.session import factory as session_manager_factory # type: ignore
from langflow.services.cache import factory as cache_factory from langflow.services.cache import factory as cache_factory
initialize_settings_manager() initialize_settings_manager()
@ -134,3 +148,10 @@ def initialize_session_manager():
session_manager_factory.SessionManagerFactory(), session_manager_factory.SessionManagerFactory(),
dependencies=[ServiceType.CACHE_MANAGER], dependencies=[ServiceType.CACHE_MANAGER],
) )
def teardown_services():
"""
Teardown all the services.
"""
service_manager.teardown()

View file

@ -1,13 +1,22 @@
from pathlib import Path
from typing import Optional from typing import Optional
import secrets import secrets
from langflow.services.settings.utils import read_secret_from_file, write_secret_to_file
from pydantic import BaseSettings from pydantic import BaseSettings, Field, validator
from passlib.context import CryptContext from passlib.context import CryptContext
from langflow.utils.logger import logger
class AuthSettings(BaseSettings): class AuthSettings(BaseSettings):
# Login settings # Login settings
SECRET_KEY: str = secrets.token_hex(32) CONFIG_DIR: str
SECRET_KEY: str = Field(
default="",
description="Secret key for JWT. If not provided, a random one will be generated.",
env="LANGFLOW_SECRET_KEY",
allow_mutation=False,
)
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70
@ -31,3 +40,33 @@ class AuthSettings(BaseSettings):
validate_assignment = True validate_assignment = True
extra = "ignore" extra = "ignore"
env_prefix = "LANGFLOW_" env_prefix = "LANGFLOW_"
@validator("SECRET_KEY", pre=True)
def get_secret_key(cls, value, values):
config_dir = values.get("CONFIG_DIR")
if not config_dir:
logger.debug("No CONFIG_DIR provided, not saving secret key")
return value or secrets.token_urlsafe(32)
secret_key_path = Path(config_dir) / "secret_key"
if value:
logger.debug("Secret key provided")
write_secret_to_file(secret_key_path, value)
else:
logger.debug("No secret key provided, generating a random one")
if secret_key_path.exists():
value = read_secret_from_file(secret_key_path)
logger.debug("Loaded secret key")
if not value:
value = secrets.token_urlsafe(32)
write_secret_to_file(secret_key_path, value)
logger.debug("Saved secret key")
else:
value = secrets.token_urlsafe(32)
write_secret_to_file(secret_key_path, value)
logger.debug("Saved secret key")
return value

View file

@ -35,5 +35,10 @@ class SettingsManager(Service):
) )
settings = Settings(**settings_dict) settings = Settings(**settings_dict)
auth_settings = AuthSettings() if not settings.CONFIG_DIR:
raise ValueError("CONFIG_DIR must be set in settings")
auth_settings = AuthSettings(
CONFIG_DIR=settings.CONFIG_DIR,
)
return cls(settings, auth_settings) return cls(settings, auth_settings)

View file

@ -0,0 +1,47 @@
import os
from pathlib import Path
import platform
from langflow.utils.logger import logger
def set_secure_permissions(file_path):
if platform.system() in ["Linux", "Darwin"]: # Unix/Linux/Mac
os.chmod(file_path, 0o600)
elif platform.system() == "Windows":
import win32api
import win32con
import win32security
user, domain, _ = win32security.LookupAccountName("", win32api.GetUserName())
sd = win32security.GetFileSecurity(
file_path, win32security.DACL_SECURITY_INFORMATION
)
dacl = win32security.ACL()
# Set the new DACL for the file: read and write access for the owner, no access for everyone else
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
win32con.GENERIC_READ | win32con.GENERIC_WRITE,
user,
)
sd.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(
file_path, win32security.DACL_SECURITY_INFORMATION, sd
)
else:
print("Unsupported OS")
def write_secret_to_file(path: Path, value: str) -> None:
with path.open("wb") as f:
f.write(value.encode("utf-8"))
try:
set_secure_permissions(path)
except Exception:
logger.error("Failed to set secure permissions on secret key")
def read_secret_from_file(path: Path) -> str:
with path.open("r") as f:
return f.read()

View file

@ -1,10 +1,12 @@
from langflow.services import ServiceType, service_manager from langflow.services import ServiceType, service_manager
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Generator
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.database.manager import DatabaseManager from langflow.services.database.manager import DatabaseManager
from langflow.services.settings.manager import SettingsManager from langflow.services.settings.manager import SettingsManager
from langflow.services.chat.manager import ChatManager
from sqlmodel import Session
def get_settings_manager() -> "SettingsManager": def get_settings_manager() -> "SettingsManager":
@ -15,6 +17,10 @@ def get_db_manager() -> "DatabaseManager":
return service_manager.get(ServiceType.DATABASE_MANAGER) return service_manager.get(ServiceType.DATABASE_MANAGER)
def get_session(): def get_session() -> Generator["Session", None, None]:
db_manager = service_manager.get(ServiceType.DATABASE_MANAGER) db_manager = service_manager.get(ServiceType.DATABASE_MANAGER)
yield from db_manager.get_session() yield from db_manager.get_session()
def get_chat_manager() -> "ChatManager":
return service_manager.get(ServiceType.CHAT_MANAGER)

View file

@ -8,11 +8,11 @@
"name": "langflow", "name": "langflow",
"version": "0.1.2", "version": "0.1.2",
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.10", "@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.18",
"@mui/material": "^5.11.9", "@mui/material": "^5.14.7",
"@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",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
@ -29,20 +29,20 @@
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.6", "@radix-ui/react-tooltip": "^1.0.6",
"@tabler/icons-react": "^2.18.0", "@tabler/icons-react": "^2.32.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.6",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"accordion": "^3.0.2", "accordion": "^3.0.2",
"ace-builds": "^1.16.0", "ace-builds": "^1.24.1",
"add": "^2.0.6", "add": "^2.0.6",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"axios": "^1.3.2", "axios": "^1.5.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0", "class-variance-authority": "^0.6.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"dompurify": "^3.0.4", "dompurify": "^3.0.5",
"esbuild": "^0.17.18", "esbuild": "^0.17.19",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.233.0",
"moment": "^2.29.4", "moment": "^2.29.4",
@ -50,51 +50,51 @@
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-error-boundary": "^4.0.2", "react-error-boundary": "^4.0.11",
"react-icons": "^4.8.0", "react-icons": "^4.10.1",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.15.0",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.0", "react-tabs": "^6.0.2",
"react-tooltip": "^5.13.1", "react-tooltip": "^5.21.1",
"reactflow": "^11.8.3", "reactflow": "^11.8.3",
"rehype-mathjax": "^4.0.2", "rehype-mathjax": "^4.0.3",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"shadcn-ui": "^0.2.2", "shadcn-ui": "^0.2.3",
"short-unique-id": "^4.4.4", "short-unique-id": "^4.4.4",
"switch": "^0.0.0", "switch": "^0.0.0",
"table": "^6.8.1", "table": "^6.8.1",
"tailwind-merge": "^1.13.0", "tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.5", "tailwindcss-animate": "^1.0.7",
"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"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62", "@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.14.197",
"@types/node": "^16.18.12", "@types/node": "^16.18.46",
"@types/react": "^18.0.28", "@types/react": "^18.2.21",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.2.7",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.2",
"@vitejs/plugin-react-swc": "^3.0.0", "@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.15",
"daisyui": "^3.1.1", "daisyui": "^3.6.3",
"postcss": "^8.4.23", "postcss": "^8.4.29",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2", "prettier-plugin-organize-imports": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.3.0", "prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.3",
"typescript": "^5.0.2", "typescript": "^5.2.2",
"vite": "^4.3.9" "vite": "^4.4.9"
} }
}, },
"node_modules/@adobe/css-tools": { "node_modules/@adobe/css-tools": {

View file

@ -3,11 +3,11 @@
"version": "0.1.2", "version": "0.1.2",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.10", "@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.18",
"@mui/material": "^5.11.9", "@mui/material": "^5.14.7",
"@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",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
@ -24,20 +24,20 @@
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.6", "@radix-ui/react-tooltip": "^1.0.6",
"@tabler/icons-react": "^2.18.0", "@tabler/icons-react": "^2.32.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.6",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"accordion": "^3.0.2", "accordion": "^3.0.2",
"ace-builds": "^1.16.0", "ace-builds": "^1.24.1",
"add": "^2.0.6", "add": "^2.0.6",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"axios": "^1.3.2", "axios": "^1.5.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0", "class-variance-authority": "^0.6.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"dompurify": "^3.0.4", "dompurify": "^3.0.5",
"esbuild": "^0.17.18", "esbuild": "^0.17.19",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.233.0",
"moment": "^2.29.4", "moment": "^2.29.4",
@ -45,24 +45,24 @@
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-error-boundary": "^4.0.2", "react-error-boundary": "^4.0.11",
"react-icons": "^4.8.0", "react-icons": "^4.10.1",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.15.0",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.0", "react-tabs": "^6.0.2",
"react-tooltip": "^5.13.1", "react-tooltip": "^5.21.1",
"reactflow": "^11.8.3", "reactflow": "^11.8.3",
"rehype-mathjax": "^4.0.2", "rehype-mathjax": "^4.0.3",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"shadcn-ui": "^0.2.2", "shadcn-ui": "^0.2.3",
"short-unique-id": "^4.4.4", "short-unique-id": "^4.4.4",
"switch": "^0.0.0", "switch": "^0.0.0",
"table": "^6.8.1", "table": "^6.8.1",
"tailwind-merge": "^1.13.0", "tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.5", "tailwindcss-animate": "^1.0.7",
"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"
@ -96,26 +96,26 @@
"proxy": "http://127.0.0.1:7860", "proxy": "http://127.0.0.1:7860",
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62", "@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.14.197",
"@types/node": "^16.18.12", "@types/node": "^16.18.46",
"@types/react": "^18.0.28", "@types/react": "^18.2.21",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.2.7",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.2",
"@vitejs/plugin-react-swc": "^3.0.0", "@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.15",
"daisyui": "^3.1.1", "daisyui": "^3.6.3",
"postcss": "^8.4.23", "postcss": "^8.4.29",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2", "prettier-plugin-organize-imports": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.3.0", "prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.3",
"typescript": "^5.0.2", "typescript": "^5.2.2",
"vite": "^4.3.9" "vite": "^4.4.9"
} }
} }

View file

@ -0,0 +1,65 @@
import { useState } from "react";
import { dropdownButtonPropsType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
export default function DropdownButton({
firstButtonName,
onFirstBtnClick,
options,
}: dropdownButtonPropsType): JSX.Element {
const [showOptions, setShowOptions] = useState<boolean>(false);
return (
<div>
<DropdownMenu open={showOptions}>
<DropdownMenuTrigger asChild>
<Button
variant="primary"
className="relative pr-10"
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
onFirstBtnClick();
}}
>
{firstButtonName}
<div
className="absolute right-2 items-center text-muted-foreground"
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
setShowOptions(!showOptions);
}}
>
{!showOptions ? (
<IconComponent name="ChevronDown" />
) : (
<IconComponent name="ChevronUp" />
)}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
onPointerDownOutside={(event) => {
event.stopPropagation();
event.preventDefault();
setShowOptions(!showOptions);
}}
>
{options.map(({ name, onBtnClick }, index) => (
<DropdownMenuItem onClick={onBtnClick} key={index}>
{name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}

View file

@ -28,7 +28,6 @@ import {
TabsList, TabsList,
TabsTrigger, TabsTrigger,
} from "../../components/ui/tabs"; } from "../../components/ui/tabs";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import { codeTabsPropsType } from "../../types/components"; import { codeTabsPropsType } from "../../types/components";
@ -57,7 +56,7 @@ export default function CodeTabsComponent({
}, [flow]); }, [flow]);
useEffect(() => { useEffect(() => {
if(tweaks){ if (tweaks) {
unselectAllNodes({ unselectAllNodes({
data, data,
updateNodes: (nodes) => { updateNodes: (nodes) => {

View file

@ -7,8 +7,17 @@ import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { gradients } from "../../utils/styleUtils";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar"; import MenuBar from "./components/menuBar";
@ -17,7 +26,7 @@ export default function Header(): JSX.Element {
const { dark, setDark } = useContext(darkContext); const { dark, setDark } = useContext(darkContext);
const { notificationCenter } = useContext(alertContext); const { notificationCenter } = useContext(alertContext);
const location = useLocation(); const location = useLocation();
const { logout, autoLogin, isAdmin } = useContext(AuthContext); const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
const { stars } = useContext(darkContext); const { stars } = useContext(darkContext);
const navigate = useNavigate(); const navigate = useNavigate();
@ -31,31 +40,6 @@ export default function Header(): JSX.Element {
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar flows={flows} tabId={tabId} /> <MenuBar flows={flows} tabId={tabId} />
)} )}
{!autoLogin && location.pathname !== `/flow/${tabId}` && (
<Button
variant="outline"
onClick={() => {
logout();
navigate("/login");
}}
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer mx-5"
>
Sign out
</Button>
)}
{isAdmin &&
!autoLogin &&
location.pathname !== "/admin" &&
location.pathname !== `/flow/${tabId}` && (
<Button
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer"
variant="outline"
onClick={() => navigate("/admin")}
>
Admin page
</Button>
)}
</div> </div>
<div className="round-button-div"> <div className="round-button-div">
<Link to="/"> <Link to="/">
@ -147,6 +131,44 @@ export default function Header(): JSX.Element {
/> />
</button> </button>
)} )}
{!autoLogin && (
<>
<Separator orientation="vertical" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
gradients[
parseInt(userData?.id ?? "", 10) % gradients.length
]
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
{isAdmin && (
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/admin")}
>
Admin Page
</DropdownMenuItem>
)}
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
logout();
navigate("/login");
}}
>
Sign Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -30,6 +30,7 @@ export default function InputComponent({
{isForm ? ( {isForm ? (
<Form.Control asChild> <Form.Control asChild>
<Input <Input
type={password && !pwdVisible ? "password" : "text"}
value={value} value={value}
disabled={disabled} disabled={disabled}
required={required} required={required}
@ -53,6 +54,7 @@ export default function InputComponent({
</Form.Control> </Form.Control>
) : ( ) : (
<Input <Input
type={password && !pwdVisible ? "password" : "text"}
value={value} value={value}
disabled={disabled} disabled={disabled}
required={required} required={required}
@ -76,6 +78,8 @@ export default function InputComponent({
)} )}
{password && ( {password && (
<button <button
type="button"
tabIndex={-1}
className={classNames( className={classNames(
editNode editNode
? "input-component-true-button" ? "input-component-true-button"

View file

@ -0,0 +1,16 @@
import { Skeleton } from "../ui/skeleton";
export const SkeletonCardComponent = (): JSX.Element => {
return (
<div className="skeleton-card">
<div className="skeleton-card-wrapper">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-4 w-[120px]" />
</div>
<div className="skeleton-card-text">
<Skeleton className="h-3 w-[250px]" />
<Skeleton className="h-3 w-[200px]" />
</div>
</div>
);
};

View file

@ -0,0 +1,15 @@
import { cn } from "../../utils/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-border", className)}
{...props}
/>
);
}
export { Skeleton };

View file

@ -25,7 +25,7 @@ const initialValue: alertContextType = {
notificationList: [], notificationList: [],
pushNotificationList: () => {}, pushNotificationList: () => {},
clearNotificationList: () => {}, clearNotificationList: () => {},
removeFromNotificationList: () => {} removeFromNotificationList: () => {},
}; };
export const alertContext = createContext<alertContextType>(initialValue); export const alertContext = createContext<alertContextType>(initialValue);

View file

@ -39,6 +39,7 @@ const TabsContextInitialValue: TabsContextType = {
save: () => {}, save: () => {},
tabId: "", tabId: "",
setTabId: (index: string) => {}, setTabId: (index: string) => {},
isLoading: true,
flows: [], flows: [],
removeFlow: (id: string) => {}, removeFlow: (id: string) => {},
addFlow: async (flowData?: any) => "", addFlow: async (flowData?: any) => "",
@ -47,7 +48,7 @@ const TabsContextInitialValue: TabsContextType = {
downloadFlow: (flow: FlowType) => {}, downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {}, downloadFlows: () => {},
uploadFlows: () => {}, uploadFlows: () => {},
uploadFlow: () => {}, uploadFlow: async () => "",
isBuilt: false, isBuilt: false,
setIsBuilt: (state: boolean) => {}, setIsBuilt: (state: boolean) => {},
hardReset: () => {}, hardReset: () => {},
@ -72,10 +73,12 @@ export const TabsContext = createContext<TabsContextType>(
export function TabsProvider({ children }: { children: ReactNode }) { export function TabsProvider({ children }: { children: ReactNode }) {
const { setErrorData, setNoticeData, setSuccessData } = const { setErrorData, setNoticeData, setSuccessData } =
useContext(alertContext); useContext(alertContext);
const { getAuthentication } = useContext(AuthContext); const { getAuthentication, isAuthenticated } = useContext(AuthContext);
const [tabId, setTabId] = useState(""); const [tabId, setTabId] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [flows, setFlows] = useState<Array<FlowType>>([]); const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uid()); const [id, setId] = useState(uid());
const { templates, reactFlowInstance } = useContext(typesContext); const { templates, reactFlowInstance } = useContext(typesContext);
@ -86,6 +89,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const [tabsState, setTabsState] = useState<TabsState>({}); const [tabsState, setTabsState] = useState<TabsState>({});
const [getTweak, setTweak] = useState<tweakType>([]); const [getTweak, setTweak] = useState<tweakType>([]);
useEffect(() => {
if (!isAuthenticated) {
hardReset();
}
}, [isAuthenticated]);
const newNodeId = useRef(uid()); const newNodeId = useRef(uid());
function incrementNodeId() { function incrementNodeId() {
newNodeId.current = uid(); newNodeId.current = uid();
@ -116,11 +125,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
} }
function refreshFlows() { function refreshFlows() {
setIsLoading(true);
getTabsDataFromDB().then((DbData) => { getTabsDataFromDB().then((DbData) => {
if (DbData && Object.keys(templates).length > 0) { if (DbData && Object.keys(templates).length > 0) {
try { try {
processDBData(DbData); processDBData(DbData);
updateStateWithDbData(DbData); updateStateWithDbData(DbData);
setIsLoading(false);
} catch (e) {} } catch (e) {}
} }
}); });
@ -229,6 +240,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
setTabId(""); setTabId("");
setFlows([]); setFlows([]);
setIsLoading(true);
setId(uid()); setId(uid());
} }
@ -286,39 +298,40 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* If the file type is application/json, the file is read and parsed into a JSON object. * If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function. * The resulting JSON object is passed to the addFlow function.
*/ */
function uploadFlow(newProject?: boolean, file?: File) { async function uploadFlow(
newProject?: boolean,
file?: File
): Promise<String | undefined> {
let id;
if (file) { if (file) {
file.text().then((text) => { let text = await file.text();
// parse the text into a JSON object // parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject); id = await addFlow(flow, newProject);
});
} else { } else {
// create a file input // create a file input
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.accept = ".json"; input.accept = ".json";
// add a change event listener to the file input // add a change event listener to the file input
input.onchange = (e: Event) => { id = await new Promise((resolve) => {
// check if the file type is application/json input.onchange = async (e: Event) => {
if ( if (
(e.target as HTMLInputElement).files![0].type === "application/json" (e.target as HTMLInputElement).files![0].type === "application/json"
) { ) {
// get the file from the file input const currentfile = (e.target as HTMLInputElement).files![0];
const currentfile = (e.target as HTMLInputElement).files![0]; let text = await currentfile.text();
// read the file as text
currentfile.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
const flowId = await addFlow(flow, newProject);
addFlow(flow, newProject); resolve(flowId);
}); }
} };
}; // trigger the file input click event to open the file dialog
// trigger the file input click event to open the file dialog input.click();
input.click(); });
} }
return id;
} }
function uploadFlows() { function uploadFlows() {
@ -641,6 +654,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
paste, paste,
getTweak, getTweak,
setTweak, setTweak,
isLoading,
}} }}
> >
{children} {children}

View file

@ -33,7 +33,7 @@ function ApiInterceptor() {
} }
const res = await renewAccessToken(refreshToken); const res = await renewAccessToken(refreshToken);
login(res.data.access_token, res.data.refresh_token); login(res?.data?.access_token, res?.data?.refresh_token);
try { try {
if (error?.config?.headers) { if (error?.config?.headers) {
delete error.config.headers["Authorization"]; delete error.config.headers["Authorization"];

View file

@ -312,7 +312,7 @@ export async function getHealth() {
*/ */
export async function getBuildStatus( export async function getBuildStatus(
flowId: string flowId: string
): Promise<BuildStatusTypeAPI> { ): Promise<AxiosResponse<BuildStatusTypeAPI>> {
return await api.get(`${BASE_URL_API}build/${flowId}/status`); return await api.get(`${BASE_URL_API}build/${flowId}/status`);
} }
@ -468,7 +468,7 @@ export async function updateUser(user_id: string, user: Users) {
export async function getApiKey() { export async function getApiKey() {
try { try {
const res = await api.get(`${BASE_URL_API}api_key`); const res = await api.get(`${BASE_URL_API}api_key/`);
if (res.status === 200) { if (res.status === 200) {
return res.data; return res.data;
} }
@ -480,7 +480,7 @@ export async function getApiKey() {
export async function createApiKey(name: string) { export async function createApiKey(name: string) {
try { try {
const res = await api.post(`${BASE_URL_API}api_key`, { name }); const res = await api.post(`${BASE_URL_API}api_key/`, { name });
if (res.status === 200) { if (res.status === 200) {
return res.data; return res.data;
} }

View file

@ -25,6 +25,7 @@ import { Textarea } from "../../components/ui/textarea";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants"; import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { getBuildStatus } from "../../controllers/API";
import { TabsState } from "../../types/tabs"; import { TabsState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils"; import { validateNodes } from "../../utils/reactflowUtils";
@ -155,9 +156,23 @@ export default function FormModal({
function handleOnClose(event: CloseEvent): void { function handleOnClose(event: CloseEvent): void {
if (isOpen.current) { if (isOpen.current) {
getBuildStatus(flow.id)
.then((response) => {
if (response.data.built) {
connectWS();
} else {
setErrorData({
title: "Please build the flow again before using the chat.",
});
}
})
.catch((error) => {
setErrorData({
title: error.data?.detail ? error.data.detail : error.message,
});
});
setErrorData({ title: event.reason }); setErrorData({ title: event.reason });
setTimeout(() => { setTimeout(() => {
connectWS();
setLockChat(false); setLockChat(false);
}, 1000); }, 1000);
} }
@ -175,7 +190,7 @@ export default function FormModal({
return `${ return `${
isDevelopment ? "ws" : webSocketProtocol isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}?token=${accessToken}`; }://${host}${chatEndpoint}?token=${encodeURIComponent(accessToken!)}`;
} }
function handleWsMessage(data: any) { function handleWsMessage(data: any) {
@ -260,7 +275,6 @@ export default function FormModal({
}; };
newWs.onmessage = (event) => { newWs.onmessage = (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
console.log("Received data:", data);
handleWsMessage(data); handleWsMessage(data);
//get chat history //get chat history
}; };
@ -268,7 +282,6 @@ export default function FormModal({
handleOnClose(event); handleOnClose(event);
}; };
newWs.onerror = (ev) => { newWs.onerror = (ev) => {
console.log(ev, "error");
if (flow.id === "") { if (flow.id === "") {
connectWS(); connectWS();
} else { } else {
@ -294,7 +307,6 @@ export default function FormModal({
useEffect(() => { useEffect(() => {
connectWS(); connectWS();
return () => { return () => {
console.log("unmount");
console.log(ws); console.log(ws);
if (ws.current) { if (ws.current) {
ws.current.close(); ws.current.close();

View file

@ -136,7 +136,11 @@ export default function GenericModal({
setSuccessData({ setSuccessData({
title: "Prompt is ready", title: "Prompt is ready",
}); });
if(JSON.stringify(apiReturn.data?.frontend_node)!==JSON.stringify({})) setNodeClass!(apiReturn.data?.frontend_node); if (
JSON.stringify(apiReturn.data?.frontend_node) !==
JSON.stringify({})
)
setNodeClass!(apiReturn.data?.frontend_node);
setModalOpen(closeModal); setModalOpen(closeModal);
setValue(inputValue); setValue(inputValue);
} }

View file

@ -4,6 +4,7 @@ import { useContext, useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent"; import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox"; import { Checkbox } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
@ -25,9 +26,8 @@ import {
} from "../../controllers/API"; } from "../../controllers/API";
import ConfirmationModal from "../../modals/ConfirmationModal"; import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal"; import UserManagementModal from "../../modals/UserManagementModal";
import { UserInputType } from "../../types/components";
import Header from "../../components/headerComponent";
import { Users } from "../../types/api"; import { Users } from "../../types/api";
import { UserInputType } from "../../types/components";
export default function AdminPage() { export default function AdminPage() {
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
@ -89,7 +89,7 @@ export default function AdminPage() {
if (input === "") { if (input === "") {
setFilterUserList(userList.current); setFilterUserList(userList.current);
} else { } else {
const filteredList = userList.current.filter((user:Users) => const filteredList = userList.current.filter((user: Users) =>
user.username.toLowerCase().includes(input.toLowerCase()) user.username.toLowerCase().includes(input.toLowerCase())
); );
setFilterUserList(filteredList); setFilterUserList(filteredList);
@ -183,249 +183,254 @@ export default function AdminPage() {
return ( return (
<> <>
<div className="flex flex-col"> <div className="flex flex-col">
<Header /> <Header />
{userData && ( {userData && (
<div className="main-page-panel"> <div className="main-page-panel">
<div className="m-auto flex h-full flex-row justify-center"> <div className="m-auto flex h-full flex-row justify-center">
<div className="basis-5/6"> <div className="basis-5/6">
<div className="m-auto flex h-full flex-col space-y-8 p-8 "> <div className="m-auto flex h-full flex-col space-y-8 p-8 ">
<div className="flex items-center justify-between space-y-2"> <div className="flex items-center justify-between space-y-2">
<div> <div>
<h2 className="text-2xl font-bold tracking-tight"> <h2 className="text-2xl font-bold tracking-tight">
Welcome back! Welcome back!
</h2> </h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Navigate through this section to efficiently oversee all Navigate through this section to efficiently oversee all
application users. From here, you can seamlessly manage application users. From here, you can seamlessly manage
user accounts. user accounts.
</p> </p>
</div>
<div className="flex items-center space-x-2"></div>
</div> </div>
<div className="flex items-center space-x-2"></div>
</div>
{userList.current.length === 0 && !loadingUsers && ( {userList.current.length === 0 && !loadingUsers && (
<>
<div className="flex items-center justify-between">
<h2>There's no users registered :)</h2>
</div>
</>
)}
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2>There's no users registered :)</h2> <div className="flex flex-1 items-center space-x-2">
</div> <Input
</> value={inputValue}
)} placeholder="Filter users..."
<> className="h-8 w-[150px] lg:w-[250px]"
<div className="flex items-center justify-between"> onChange={(e) => handleFilterUsers(e.target.value)}
<div className="flex flex-1 items-center space-x-2"> />
<Input {inputValue.length > 0 && (
value={inputValue} <Button
placeholder="Filter users..." onClick={() => {
className="h-8 w-[150px] lg:w-[250px]" setInputValue("");
onChange={(e) => handleFilterUsers(e.target.value)} setFilterUserList(userList.current);
/> }}
{inputValue.length > 0 && ( variant="ghost"
<Button className="h-8 px-2 lg:px-3"
onClick={() => { >
setInputValue(""); Reset
setFilterUserList(userList.current); <X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}} }}
variant="ghost"
className="h-8 px-2 lg:px-3"
> >
Reset <Button>New User</Button>
<X className="ml-2 h-4 w-4" /> </UserManagementModal>
</Button> </div>
)}
</div> </div>
<div> {loadingUsers && (
<UserManagementModal <div>
title="New User" <strong>Loading...</strong>
titleHeader={"Add a new user"} </div>
cancelText="Cancel" )}
confirmationText="Save" <div
icon={"UserPlus2"} className={
onConfirm={(index, user) => { "max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
handleNewUser(user); (loadingUsers ? " border-0" : "")
}} }
> >
<Button>New User</Button> <Table className={"table-fixed bg-muted outline-1"}>
</UserManagementModal> <TableHeader
</div> className={
</div> loadingUsers
{loadingUsers && ( ? "hidden"
<div> : "table-fixed bg-muted outline-1"
<strong>Loading...</strong> }
</div> >
)} <TableRow>
<div <TableHead className="h-10">Id</TableHead>
className={ <TableHead className="h-10">Username</TableHead>
"max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" + <TableHead className="h-10">Active</TableHead>
(loadingUsers ? " border-0" : "") <TableHead className="h-10">Superuser</TableHead>
} <TableHead className="h-10">Created At</TableHead>
> <TableHead className="h-10">Updated At</TableHead>
<Table className={"table-fixed bg-muted outline-1"}> <TableHead className="h-10 w-[100px] text-right"></TableHead>
<TableHeader </TableRow>
className={ </TableHeader>
loadingUsers {!loadingUsers && (
? "hidden" <TableBody>
: "table-fixed bg-muted outline-1" {filterUserList.map(
} (user: UserInputType, index) => (
> <TableRow key={index}>
<TableRow> <TableCell className="truncate py-2 font-medium">
<TableHead className="h-10">Id</TableHead> <ShadTooltip content={user.id}>
<TableHead className="h-10">Username</TableHead> <span className="cursor-default">
<TableHead className="h-10">Active</TableHead> {user.id}
<TableHead className="h-10">Superuser</TableHead> </span>
<TableHead className="h-10">Created At</TableHead>
<TableHead className="h-10">Updated At</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
{!loadingUsers && (
<TableBody>
{filterUserList.map((user:UserInputType, index) => (
<TableRow key={index}>
<TableCell className="truncate py-2 font-medium">
<ShadTooltip content={user.id}>
<span className="cursor-default">
{user.id}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="truncate py-2">
<ShadTooltip content={user.username}>
<span className="cursor-default">
{user.username}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<Checkbox
id="is_active"
checked={user.is_active}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleSuperUserEdit(
user.is_superuser,
user.id,
user
);
}}
>
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.id}`}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, editUser) => {
handleEditUser(user.id, editUser);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip> </ShadTooltip>
</UserManagementModal> </TableCell>
<TableCell className="truncate py-2">
<ConfirmationModal <ShadTooltip content={user.username}>
title="Delete" <span className="cursor-default">
titleHeader="Delete User" {user.username}
modalContentTitle="Attention!" </span>
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(user);
}}
>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip> </ShadTooltip>
</ConfirmationModal> </TableCell>
</div> <TableCell className="relative left-5 truncate py-2 text-align-last-left">
</TableCell> <ConfirmationModal
</TableRow> title="Edit"
))} titleHeader={`${user.username}`}
</TableBody> modalContentTitle="Attention!"
)} modalContent="Are you completely confident about the changes you are making to this user?"
</Table> cancelText="Cancel"
</div> confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<Checkbox
id="is_active"
checked={user.is_active}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
<ConfirmationModal
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
modalContent="Are you completely confident about the changes you are making to this user?"
cancelText="Cancel"
confirmationText="Confirm"
icon={"UserCog2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleSuperUserEdit(
user.is_superuser,
user.id,
user
);
}}
>
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
</ConfirmationModal>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at!)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.id}`}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, editUser) => {
handleEditUser(user.id, editUser);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<PaginatorComponent <ConfirmationModal
pageIndex={index} title="Delete"
pageSize={size} titleHeader="Delete User"
totalRowsCount={totalRowsCount} modalContentTitle="Attention!"
paginate={(pageIndex, pageSize) => { modalContent="Are you sure you want to delete this user? This action cannot be undone."
handleChangePagination(pageSize, pageIndex); cancelText="Cancel"
}} confirmationText="Delete"
></PaginatorComponent> icon={"UserMinus2"}
</> data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(user);
}}
>
<ShadTooltip
content="Delete"
side="top"
>
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
)
)}
</TableBody>
)}
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={totalRowsCount}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )}
)}
</div> </div>
</> </>
); );

View file

@ -421,7 +421,7 @@ export default function Page({
zoomOnScroll={!view} zoomOnScroll={!view}
zoomOnPinch={!view} zoomOnPinch={!view}
panOnDrag={!view} panOnDrag={!view}
proOptions={{hideAttribution: true}} proOptions={{ hideAttribution: true }}
> >
<Background className="" /> <Background className="" />
{!view && ( {!view && (

View file

@ -21,8 +21,7 @@ export default function ExtraSidebar(): JSX.Element {
const { data, templates } = useContext(typesContext); const { data, templates } = useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } = const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext); useContext(TabsContext);
const { setSuccessData, setErrorData } = const { setSuccessData, setErrorData } = useContext(alertContext);
useContext(alertContext);
const [dataFilter, setFilterData] = useState(data); const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const isPending = tabsState[tabId]?.isPending; const isPending = tabsState[tabId]?.isPending;
@ -101,9 +100,7 @@ export default function ExtraSidebar(): JSX.Element {
<div className="side-bar-button"> <div className="side-bar-button">
{flow && flow.data && ( {flow && flow.data && (
<ApiModal flow={flow} disable={!isBuilt}> <ApiModal flow={flow} disable={!isBuilt}>
<div <div className={classNames("extra-side-bar-buttons")}>
className={classNames("extra-side-bar-buttons")}
>
<IconComponent <IconComponent
name="Code2" name="Code2"
className={ className={

View file

@ -1,14 +1,33 @@
import { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import { CardComponent } from "../../components/cardComponent"; import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent"; import Header from "../../components/headerComponent";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage(): JSX.Element { export default function HomePage(): JSX.Element {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } = const {
useContext(TabsContext); flows,
setTabId,
downloadFlows,
uploadFlows,
addFlow,
removeFlow,
uploadFlow,
isLoading,
} = useContext(TabsContext);
const dropdownOptions = [
{
name: "Import from JSON",
onBtnClick: () =>
uploadFlow(true).then((id) => {
navigate("/flow/" + id);
}),
},
];
// Set a null id // Set a null id
useEffect(() => { useEffect(() => {
@ -16,6 +35,10 @@ export default function HomePage(): JSX.Element {
}, []); }, []);
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => {
console.log(isLoading);
}, [isLoading]);
// Personal flows display // Personal flows display
return ( return (
<> <>
@ -45,48 +68,55 @@ export default function HomePage(): JSX.Element {
<IconComponent name="Upload" className="main-page-nav-button" /> <IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection Upload Collection
</Button> </Button>
<Button <DropdownButton
variant="primary" firstButtonName="New Project"
onClick={() => { onFirstBtnClick={() => {
addFlow(null!, true).then((id) => { addFlow(null!, true).then((id) => {
navigate("/flow/" + id); navigate("/flow/" + id);
}); });
}} }}
> options={dropdownOptions}
<IconComponent name="Plus" className="main-page-nav-button" /> />
New Project
</Button>
</div> </div>
</div> </div>
<span className="main-page-description-text"> <span className="main-page-description-text">
Manage your personal projects. Download or upload your collection. Manage your personal projects. Download or upload your collection.
</span> </span>
<div className="main-page-flows-display"> <div className="main-page-flows-display">
{flows.map((flow, idx) => ( {isLoading && flows.length == 0 ? (
<CardComponent <>
key={idx} <SkeletonCardComponent />
flow={flow} <SkeletonCardComponent />
id={flow.id} <SkeletonCardComponent />
button={ <SkeletonCardComponent />
<Link to={"/flow/" + flow.id}> </>
<Button ) : (
variant="outline" flows.map((flow, idx) => (
size="sm" <CardComponent
className="whitespace-nowrap " key={idx}
> flow={flow}
<IconComponent id={flow.id}
name="ExternalLink" button={
className="main-page-nav-button" <Link to={"/flow/" + flow.id}>
/> <Button
Edit Flow variant="outline"
</Button> size="sm"
</Link> className="whitespace-nowrap "
} >
onDelete={() => { <IconComponent
removeFlow(flow.id); name="ExternalLink"
}} className="main-page-nav-button"
/> />
))} Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))
)}
</div> </div>
</div> </div>
</> </>

View file

@ -19,7 +19,8 @@ export default function LoginPage(): JSX.Element {
useState<loginInputStateType>(CONTROL_LOGIN_STATE); useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { password, username } = inputState; const { password, username } = inputState;
const { login, getAuthentication, setUserData, setIsAdmin } = useContext(AuthContext); const { login, getAuthentication, setUserData, setIsAdmin } =
useContext(AuthContext);
const navigate = useNavigate(); const navigate = useNavigate();
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
@ -89,6 +90,7 @@ export default function LoginPage(): JSX.Element {
<Form.Control asChild> <Form.Control asChild>
<Input <Input
type="username"
onChange={({ target: { value } }) => { onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } }); handleInput({ target: { name: "username", value } });
}} }}
@ -129,12 +131,14 @@ export default function LoginPage(): JSX.Element {
</div> </div>
<div className="w-full"> <div className="w-full">
<Form.Submit asChild> <Form.Submit asChild>
<Button className="mr-3 mt-6 w-full">Sign in</Button> <Button className="mr-3 mt-6 w-full" type="submit">
Sign in
</Button>
</Form.Submit> </Form.Submit>
</div> </div>
<div className="w-full"> <div className="w-full">
<Link to="/signup"> <Link to="/signup">
<Button className="w-full" variant="outline"> <Button className="w-full" variant="outline" type="button">
Don't have an account?&nbsp;<b>Sign Up</b> Don't have an account?&nbsp;<b>Sign Up</b>
</Button> </Button>
</Link> </Link>

View file

@ -84,6 +84,7 @@ export default function SignUp(): JSX.Element {
<Form.Control asChild> <Form.Control asChild>
<Input <Input
type="username"
onChange={({ target: { value } }) => { onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } }); handleInput({ target: { name: "username", value } });
}} }}
@ -157,6 +158,7 @@ export default function SignUp(): JSX.Element {
<div className="w-full"> <div className="w-full">
<Form.Submit asChild> <Form.Submit asChild>
<Button <Button
type="submit"
className="mr-3 mt-6 w-full" className="mr-3 mt-6 w-full"
onClick={() => { onClick={() => {
handleSignup(); handleSignup();

View file

@ -126,6 +126,18 @@
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm; @apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
} }
.skeleton-card {
@apply bg-background h-48 p-4 border rounded-lg flex flex-col gap-6;
}
.skeleton-card-wrapper {
@apply flex items-center space-x-4;
}
.skeleton-card-text {
@apply flex flex-col gap-3;
}
/* The same as primary-input but no-truncate */ /* The same as primary-input but no-truncate */
.textarea-primary { .textarea-primary {
@apply form-input block w-full rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm; @apply form-input block w-full rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;

View file

@ -36,3 +36,18 @@ pre {
.gradient-start { .gradient-start {
animation: gradient-motion-start 4s infinite forwards; animation: gradient-motion-start 4s infinite forwards;
} }
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
-webkit-text-fill-color: black;
-webkit-box-shadow: 0 0 0px 1000px #fff6d0 inset;
box-shadow: 0 0 0px 1000px #fff6d0 inset;
color: black;
}

View file

@ -268,7 +268,7 @@ export type UserInputType = {
is_superuser?: boolean; is_superuser?: boolean;
id?: string; id?: string;
create_at?: string; create_at?: string;
updated_at?:string; updated_at?: string;
}; };
export type ApiKeyType = { export type ApiKeyType = {
@ -544,3 +544,9 @@ export type fetchErrorComponentType = {
message: string; message: string;
description: string; description: string;
}; };
export type dropdownButtonPropsType = {
firstButtonName: string;
onFirstBtnClick: () => void;
options: Array<{ name: string; onBtnClick: () => void }>;
};

View file

@ -5,6 +5,7 @@ export type TabsContextType = {
saveFlow: (flow: FlowType) => Promise<void>; saveFlow: (flow: FlowType) => Promise<void>;
save: () => void; save: () => void;
tabId: string; tabId: string;
isLoading: boolean;
setTabId: (index: string) => void; setTabId: (index: string) => void;
flows: Array<FlowType>; flows: Array<FlowType>;
removeFlow: (id: string) => void; removeFlow: (id: string) => void;
@ -23,7 +24,7 @@ export type TabsContextType = {
uploadFlows: () => void; uploadFlows: () => void;
isBuilt: boolean; isBuilt: boolean;
setIsBuilt: (state: boolean) => void; setIsBuilt: (state: boolean) => void;
uploadFlow: (newFlow?: boolean, file?: File) => void; uploadFlow: (newFlow?: boolean, file?: File) => Promise<String | undefined>;
hardReset: () => void; hardReset: () => void;
getNodeId: (nodeType: string) => string; getNodeId: (nodeType: string) => string;
tabsState: TabsState; tabsState: TabsState;

View file

@ -5,6 +5,7 @@ import {
ChevronDown, ChevronDown,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
ChevronUp,
ChevronsLeft, ChevronsLeft,
ChevronsRight, ChevronsRight,
ChevronsUpDown, ChevronsUpDown,
@ -300,4 +301,5 @@ export const nodeIconsLucide: iconsType = {
UserCog2, UserCog2,
Key, Key,
Unplug, Unplug,
ChevronUp,
}; };

View file

@ -9,7 +9,13 @@ from langflow.services.database.models.user import UserUpdate
@pytest.fixture @pytest.fixture
def super_user(client, session): def super_user(client, session):
return create_super_user(session) settings_manager = get_settings_manager()
auth_settings = settings_manager.auth_settings
return create_super_user(
db=session,
username=auth_settings.FIRST_SUPERUSER,
password=auth_settings.FIRST_SUPERUSER_PASSWORD,
)
@pytest.fixture @pytest.fixture