Chat_and_cache (#219)
This commit is contained in:
commit
f66c70f3e0
68 changed files with 2699 additions and 1397 deletions
11
Makefile
11
Makefile
|
|
@ -26,8 +26,15 @@ install_frontend:
|
||||||
run_frontend:
|
run_frontend:
|
||||||
cd src/frontend && npm start
|
cd src/frontend && npm start
|
||||||
|
|
||||||
run_backend:
|
frontend:
|
||||||
poetry run uvicorn langflow.main:app --port 5003 --reload
|
make install_frontend
|
||||||
|
make run_frontend
|
||||||
|
|
||||||
|
install_backend:
|
||||||
|
poetry install
|
||||||
|
|
||||||
|
backend:
|
||||||
|
poetry run uvicorn langflow.main:app --port 7860 --reload --log-level debug
|
||||||
|
|
||||||
build_frontend:
|
build_frontend:
|
||||||
cd src/frontend && CI='' npm run build
|
cd src/frontend && CI='' npm run build
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
cd src/frontend
|
|
||||||
docker build -t logspace/frontend_build -f build.Dockerfile .
|
|
||||||
cd ../backend
|
|
||||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
|
||||||
|
|
||||||
cd ../../
|
|
||||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
|
||||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION .
|
|
||||||
docker push ibiscp/langflow:$VERSION
|
|
||||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "reactFlow",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
297
poetry.lock
generated
297
poetry.lock
generated
|
|
@ -551,77 +551,77 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clickhouse-connect"
|
name = "clickhouse-connect"
|
||||||
version = "0.5.21"
|
version = "0.5.22"
|
||||||
description = "ClickHouse core driver, SqlAlchemy, and Superset libraries"
|
description = "ClickHouse core driver, SqlAlchemy, and Superset libraries"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "~=3.7"
|
python-versions = "~=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "clickhouse-connect-0.5.21.tar.gz", hash = "sha256:272d019b1a4c2b223129bf88fa05221e37fbd27610100b2cdd09a484606a7c55"},
|
{file = "clickhouse-connect-0.5.22.tar.gz", hash = "sha256:cc8f01ff88d30b118cf2efc33ac34c89a1e332900396da249df7d0f36ac199d7"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:deb49f01650c4feb8152b32887bc1b515272c5cb4ff4de64e2e8f49e6c1c9109"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09b461047dfc18c9b6ebfc94fb6022c5fc0ee7a343e1e691ee70294dce532909"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c3a4ca98850f4251492292cc187ca8bbebb2ff6938d6a8b4fec8b065ca8e320"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ac953ecd231d311f0c1a0576f164c1e6034358eb28cddcf5a2304d570a211d"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f3d314b25940012f0bfa7c36dcd45fd690f8b5610bd61060249142c243ff80e"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02629304d233df14cd7df5dfb37dd5f87feaa95a7506f984a565e3b8fb185210"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82bb77688e2102a3ec587a9cf98a82ce0904fa310fcac8913fac8a106e75f799"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:327452ed2622c1b6433ab6a1032813b3bf14a33959d47a2b896b2ab5e1a58e07"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc7c2bf3ec880cf697a94ad0cebb97bddb820c37019df0dfa7c7fb017c67d841"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7e9f5f9332c5ae51a0f1aecb99117222cc739a8b430b1961261e2cc9dca215"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ae159a5c8ab8c3e208f7b025433fab081f4027c1f0a6d4aa92d944262b4e68ba"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5ad201756d2d2d7269c747835ba4f24da3d744bc2801e813f8b33fec897ffe10"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:25e400d98930eb95c8f9b5eb8d52d8214fb0a4a52d48f4683e3473d32e3388fd"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c577bcc2b7e1f71073cf4f87600fd6f06ebfc9cd6ae8a98b28ebed9ebcfb80dd"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8903cdd2da35026e034ec5cb1a0985249b2f878d8c0652ca295be4c214610dc"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83b4efc0f7db5b02348a97dba2b2fcd089d6eb45457049d49ed795a084dd7832"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-win32.whl", hash = "sha256:9aeb4011ac45cb2b39418fc0624ea75ed28b7a1b5205a640e30fdba9be857543"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-win32.whl", hash = "sha256:83b613dcab009ff1b18d5bd3cb9e8c2fe2cdd6b67cd1309389c04f11d3621713"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp310-cp310-win_amd64.whl", hash = "sha256:13af3e11df586aa4ea354f27a2dc9f57fdae552d193605805874dcef80b0592a"},
|
{file = "clickhouse_connect-0.5.22-cp310-cp310-win_amd64.whl", hash = "sha256:6954b98df0c2624f9933530eabdc1ad3bf143633e2c5328a3f1e167344f8a9c8"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efcfabe4831ecc197c0164fc3f0b9516d703ea63436e292bc3456500d08088a7"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45972e1a37269b3d9ecbcd3eee4f7cbed4a1311d867ce45a6a30a707b31cb6b6"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:927dc6357a611b989bfd9c7671b8b726b40651384e2f6744e4c6fe45da06c271"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436eadecdcf8685a5d1f8654b1a7f689f3933db2eae799f1ec6f183e91f2a5a9"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bbc5e127eb6f86c813648414c3697b9b04f7f7c6f4a7a460ad78e46698ff0c6"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9610d103f42a45362d1c95dc7ef356c5da86359538f9d30e6298f83d407bb643"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f26302a0d67422524b026aba9f5bb22a23046ee5d267b9ba7df84740bb914a5"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25996d9868b79a4f1442ca4200a44882f192466399c767f010e169ca4328cd80"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9675f9b29adad540d1fdaed80945e078d4e87a871976783eac2740f09c054dce"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7562e357debe460dd29c8b5bc457fab9e3c1ab5ff20531aa2a43a0f05d429ed4"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c3f87261ba3f22453ba91fd5774a9170c21209d0a259d69943701be046e9d9ff"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8a3ad2a4d216572067611add9ba285b1aaaf464cd67d57a1d0980c2491bde361"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:90ddaf16fa77d5f8357b32d1e57fa7589b489851663e04610235cdfd02cf6fdc"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6c6beacd96bf7eb0a0eb9e33c1d92b8b9dec642916a630332cc599f6c7441064"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bee1960875300a96b72b861958c2b65e13737dfe55fc4bce24a8d8716a0bb013"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c82b5ea7a2814e19b01c192a32ae705d462be7c27fb8759623b6c03e117d4635"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-win32.whl", hash = "sha256:2bf3df46def2d7d5d3098cc5eadfc6023ae4dc8ec074d80ec53281e9964090a8"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-win32.whl", hash = "sha256:1cc5fde42bbad4739a6c308bc75fba6d9b817529149447f4393c4092bce01aca"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp311-cp311-win_amd64.whl", hash = "sha256:36bd43bb401541b89c458d82b8be7f352b3a7f3030ae4df277321f1ed01838bc"},
|
{file = "clickhouse_connect-0.5.22-cp311-cp311-win_amd64.whl", hash = "sha256:f82269d18fdc25a2d92378ffd5ecb90658cb0748d14819f129a501c660cbdbe8"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d914676a8d24b41d4e48317fdfed37737dde65eb4635b28b805cd99b427a928"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0d135ae71723b28f3e250ff030583fd6f5f16af19b7d14bf6bec98be630c2e3b"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76da72de0d69baab2b6bcddffd8450216ff0cfecf20254365aa4ea8604256273"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8fc12861461a5b88fd8826c500300ef48b18fa820b9c94be9233198475de67a"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b9e36ddebaf7580fe92e12d395f9a2d4bd3e84e3cf918ae3d3a6c7e28567758"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c83e8c0b7596ddb255b98e01a3bc9f7ed9f4bde4d072794be217a73d8c6e7c6"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94ca9490cb0799cee3bf4208f0b741df3ea11835736eafb20b80dab1678bb4c8"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92a903b8ee52dc65da422ecb2b23b27b469cd076c313a2dd69237f06f642e719"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:43c74116fa7a75d965d6be2760f3aaee9569d44de840a80fb21355a2a384207b"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4beb12c6ca9bd5cbdf6661750d7d20b532abb360cd9e93d684114a2e4c0f9d"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7156f2e4e7be7359a18f40ed168c44fc66d9f6359745b756c79e215545098313"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4dcda1e33e64537d7b3969c0dec15bdf67a56a5f0801e85f7fd7cb0717a4ce45"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:052a5a2e252e55b2f2f9c4ff13c3e61af990199eeec164779de9378dfd20427d"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9b1627800be5148ceee872de67b6483f89c89557b9032ea3c09b68b5fa5e1422"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-win32.whl", hash = "sha256:d9286b82d3729087ad44e46f5e432928a4fb6ffa1e0aee246364342ced694d3c"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-win32.whl", hash = "sha256:c0cc27d041c994d61b3a5065734b65c0fe9d578fb6dcf614e46f1893b4259e9f"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp37-cp37m-win_amd64.whl", hash = "sha256:449d566fc7f2e4eff3333f66c324ab920314f5a6674f75a86f26f92d18703017"},
|
{file = "clickhouse_connect-0.5.22-cp37-cp37m-win_amd64.whl", hash = "sha256:5ea78955374abe704c10f0fcb012a954087d45513e3abceefd8d50311b91e72f"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3666541ed9704894a4667360071ad02a455040d520e4f0180684c9a56690e7f7"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f96ad0d9ab681b2c9d5b1ea622f1446c30267639dce452cf09733af8b70cc565"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ffc8d31f176c2d8ea3fc373aa521475da17d353353709adf95544c039a7b2767"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c3bc819b930755599d9ef9d58f353f1b6e4013115b793870768de8027738846"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:faa0f06245288e05876626179232c07a54dadb4345eedde33902753062814d4c"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7125ad73427597fb0aecf9983eb69870242788dfb134dfec2e5bc24795b757ce"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed027fbad84cf955317082fd0dd0226acc05ba5fddd76ecbc8e5f872db0090a"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995567a46488b51b68070320c90c4dede2e1315b3ae99f8b519889ecff1e0497"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c86ea6f4e6689c0631b9bfbfd7961b739cea42ab05c35898c58e0d5ad7d4044"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e593a63bc6d081b2e11714de14a8b7509fb86e721489349034cf98412225f0"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b146d722f78d1920fa3382e347828addfd3b13dca409c0b382aacee42139e6f5"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:621e2e49af7d8e62dca25eb0663db493af4b26fd25e7c3a4b30a4fdf51b08c87"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:48c25ac5bd41bbb736a2fae221ee8e038a074ef8eb3f8e8eb2d1234fc6b60f6f"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:00354463177fcf15ce075205b54ae2f8321f72fa5511e4894bfcc4aab430e6f4"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39b04b3aeec095feabcac8eb81c7619d2e5076b76622de383c0fc661caa26b11"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8d93b85eb6bc809a82b592bbe1244b63c2708478eb751a65f250203e6afa72e7"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-win32.whl", hash = "sha256:2f5908b0ca6749c02ad9db1baf122e62d3feeffff858b46d4c141ab8e7e123fe"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-win32.whl", hash = "sha256:56fdf98ccdd1a7536b3066479b832a339285e0c806c056e7192547d31d14cbb2"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp38-cp38-win_amd64.whl", hash = "sha256:4315ff85b81c5b07eb177ad1ce5a00b60f4297baf6385335f8194e2b19797864"},
|
{file = "clickhouse_connect-0.5.22-cp38-cp38-win_amd64.whl", hash = "sha256:ef0e233fcccd48398d378e631ca4582725e816e1cd87e38352703969da72815f"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2897c1cd1289ef7bd43ffed0a698d1befb7ebfda1ecf4f2ac3b39efbc4ce242b"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e00a47fb565ed0f24e048457c67b4ab63676be5b0fe2334a7c43938e6d0a4b9"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:593529f6ee1460ce930ccaf052eee1a559c7a77fa2e1962bfb75a4f351937c8b"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:44dc02467f3a9c0d91174d09fcdc4c33ed03aa43cdacbb34fd5bb16db3d1b380"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21d69bf41b934a3d2321e1a07941309c65598ec57b83b13ac3b1443cb109b2e6"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f020238fbb7e9e0800ef0e3661dc220ef7427a5942732797b189fb5c563274"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:211eb11d3b0b147306ead0c20bf46574b195dc1c561156a389214f62ee8b7019"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251ce0491f06e0bb50f8bb006296fbe30efa111748b276b654fc39cb4fd6e4b4"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92a407252034fdd3d1595e485897115e7abe14cf8f889e3dc78feada0c61bc70"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88abb8ef9e7d48258c17f02d62f0d9a29f09073a0a1628c6024f68ed1670606b"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:450683ece1d428a89222ad4be1a6d52151ec319b2e37f879aa56debd6d5ba5d6"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:24292e0bb742d328c66f38838c3fe1654552e2817d470f8ad040a985b8d1a799"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:78c2288f16a3fa7cb484f49da650697bd2967a2fc64d6ee286b4689c638c8375"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4c7baf4b535e9cb063568616aaac183c8bb611575ed515d1b55794122feda60b"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2333f1250a94520b8015649342d95652dd421b1fa5f7eeadc72d591f702733"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f413cf98d7d99087be993573e8e0d441131fb8d30b62a50af5b6a75a6f7af66"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-win32.whl", hash = "sha256:fe96d3c9c6df935ea40ae79fdd15eacef8ac029a6bd3f3eb1170b88c9ddca736"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-win32.whl", hash = "sha256:76087148e9f7632321b39d582f13050820290d8d233721413ebd131d12e70e76"},
|
||||||
{file = "clickhouse_connect-0.5.21-cp39-cp39-win_amd64.whl", hash = "sha256:0bd4f86d58c02c4ad289d43f119eb52ba02bd9113f5368a920a5f3b7324c3360"},
|
{file = "clickhouse_connect-0.5.22-cp39-cp39-win_amd64.whl", hash = "sha256:6b69c9bd695e8129452bb9f0456f6e16caf12592eec3d4f677e626bdde42724a"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d4fded4a87ae67a90c92df350bb8e5e68e4a8cdc4e9b7435160f7f518a882d01"},
|
{file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:719fb63118147ae5c03db85bc5fa0b6b30ab1e51da7eb64bcbf5b7d03dc405b5"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bbf6ba502e2dedcc2195c16b7643141acae0c7404d7a8615d2bba05cd92870c"},
|
{file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a04b409b7907cc0f9e70329f6f5ff42fb3ad5a345db9b56a05f55194071d0f1"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e9c169dd12551c9677a93b509e78a5a0a6f90a2032658eabaaa0ae3adf8770f"},
|
{file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77fa7929b7859300be57a2e182aed51efcdba39c8995a19600c746c749d72e00"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03ce9d1bba707edef27463fa3b5db280d8d7dbcbd7486688b72448a355773309"},
|
{file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311921fb37625f885fa0c957490cd3189afdf2d9ea314d3fad61e26d8932d61b"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:50590c735fcb571bba12aa93e4066f067a51a1ad30c1a55365db26351d0e87bc"},
|
{file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:13ed25c3173af67642cc1553e8bc6583a870227b685bbea5897efc3bc78addfd"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:507cead1159f8f2c86d3f186ac6bd195b5c6a6e598128620ff8f1634cc923d26"},
|
{file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4f745384cd6d02d00c5186f9c36d2cd27634fd41d3de4705a7de59d47d6976d0"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79cf29a0bc84c666a9843d46a1f22a836a0bee32df3f45bc8b4bdbcffd91aed5"},
|
{file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b9cb52f22bb438326503d20e54d68b378076e842abc5345da0b1822a01a309b"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ed37d5df79801f542aff5b155bd1b24272bc143ef6e5707dd811b7903df7e"},
|
{file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fceb764177f24b9808039ef75e2b2f31e93971cf8d2bc9b8249a268a742190a"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50d94aad3be7032c9787500070b186a45826567b465a046e447a1473fe39eacd"},
|
{file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03ec3990efac7e11dcb66dff64456c255e5f2eaad7f12ae111c97915dd51a84c"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c11e050e06e48b8593c9caf986974944a075e419f1413499f999a8d17ff93fb3"},
|
{file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a924e87f568eeac25dc39b97bba09ecf9fca2e57bf414bc3c5f2282358af7a67"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92d2049467a86da7528a7ada83883475b2a3b7f87f9ae80d3c96406bb5bb1204"},
|
{file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:89a0f649eb49398708c632336bcbc2f762e26e258a693a844570c003407d9869"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a00775014cdaeb9d6562365ec0fcf3221f8ec54dc32910ec09497a19cca6cb"},
|
{file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8abf10e0f5cb913df5d80c609cb95850aae436d31095208c3d938df2c4d0524a"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:491461f2090cb5b281731da5dac7affb157f6c24da6353dc7757a2d36bb0cd8a"},
|
{file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06ee767384ab7c84f96a1825cdc4dd5dcd41c25dd69bf4a56233e2bbc05ee7b"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b38d4fa83cac4f07ad6d941f3ff52039c2263b41038cdc980517ffb3a8ae7252"},
|
{file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed4e041398246a474c295e55c10ccda42df6a2af9922cffe775c3c0dd93a50c8"},
|
||||||
{file = "clickhouse_connect-0.5.21-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d043e4a07a73b5eeec3357adec7570491ab5bd05bb74f88cf75a5e3fa4b60cf7"},
|
{file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2f444b4af4327747e43fe8abdd31af64e9fef425c7554a6d04ebca4acd114c71"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -703,63 +703,63 @@ typing = ["mypy (>=0.990)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.2.3"
|
version = "7.2.4"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"},
|
{file = "coverage-7.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e5eedde6e6e241ec3816f05767cc77e7456bf5ec6b373fb29917f0990e2078f"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"},
|
{file = "coverage-7.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c6c6e3b8fb6411a2035da78d86516bfcfd450571d167304911814407697fb7a"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"},
|
{file = "coverage-7.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7668a621afc52db29f6867e0e9c72a1eec9f02c94a7c36599119d557cf6e471"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"},
|
{file = "coverage-7.2.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfb53bef4b2739ff747ebbd76d6ac5384371fd3c7a8af08899074eba034d483"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"},
|
{file = "coverage-7.2.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5c4f2e44a2ae15fa6883898e756552db5105ca4bd918634cbd5b7c00e19e8a1"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"},
|
{file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:700bc9fb1074e0c67c09fe96a803de66663830420781df8dc9fb90d7421d4ccb"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"},
|
{file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ac4861241e693e21b280f07844ae0e0707665e1dfcbf9466b793584984ae45c4"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"},
|
{file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3d6f3c5b6738a494f17c73b4aa3aa899865cc33a74aa85e3b5695943b79ad3ce"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"},
|
{file = "coverage-7.2.4-cp310-cp310-win32.whl", hash = "sha256:437da7d2fcc35bf45e04b7e9cfecb7c459ec6f6dc17a8558ed52e8d666c2d9ab"},
|
||||||
{file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"},
|
{file = "coverage-7.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:1d3893f285fd76f56651f04d1efd3bdce251c32992a64c51e5d6ec3ba9e3f9c9"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"},
|
{file = "coverage-7.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a17bf32e9e3333d78606ac1073dd20655dc0752d5b923fa76afd3bc91674ab4"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"},
|
{file = "coverage-7.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f7ffdb3af2a01ce91577f84fc0faa056029fe457f3183007cffe7b11ea78b23c"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"},
|
{file = "coverage-7.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89e63b38c7b888e00fd42ce458f838dccb66de06baea2da71801b0fc9070bfa0"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"},
|
{file = "coverage-7.2.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4522dd9aeb9cc2c4c54ce23933beb37a4e106ec2ba94f69138c159024c8a906a"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"},
|
{file = "coverage-7.2.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29c7d88468f01a75231797173b52dc66d20a8d91b8bb75c88fc5861268578f52"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"},
|
{file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bc47015fc0455753e8aba1f38b81b731aaf7f004a0c390b404e0fcf1d6c1d72f"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"},
|
{file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c122d120c11a236558c339a59b4b60947b38ac9e3ad30a0e0e02540b37bf536"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"},
|
{file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:50fda3d33b705b9c01e3b772cfa7d14de8aec2ec2870e4320992c26d057fde12"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"},
|
{file = "coverage-7.2.4-cp311-cp311-win32.whl", hash = "sha256:ab08af91cf4d847a6e15d7d5eeae5fead1487caf16ff3a2056dbe64d058fd246"},
|
||||||
{file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"},
|
{file = "coverage-7.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:876e4ef3eff00b50787867c5bae84857a9af4c369a9d5b266cd9b19f61e48ef7"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"},
|
{file = "coverage-7.2.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3fc9cde48de956bfbacea026936fbd4974ff1dc2f83397c6f1968f0142c9d50b"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"},
|
{file = "coverage-7.2.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12bc9127c8aca2f7c25c9acca53da3db6799b2999b40f28c2546237b7ea28459"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"},
|
{file = "coverage-7.2.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2857894c22833d3da6e113623a9b7440159b2295280b4e0d954cadbfa724b85a"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"},
|
{file = "coverage-7.2.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4db4e6c115d869cd5397d3d21fd99e4c7053205c33a4ae725c90d19dcd178af"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"},
|
{file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f37ae1804596f13d811e0247ffc8219f5261b3565bdf45fcbb4fc091b8e9ff35"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"},
|
{file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdee9a77fd0ce000781680b6a1f4b721c567f66f2f73a49be1843ff439d634f3"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"},
|
{file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b65a6a5484b7f2970393d6250553c05b2ede069e0e18abe907fdc7f3528252e"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"},
|
{file = "coverage-7.2.4-cp37-cp37m-win32.whl", hash = "sha256:1a3e8697cb40f28e5bcfb6f4bda7852d96dbb6f6fd7cc306aba4ae690c9905ab"},
|
||||||
{file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"},
|
{file = "coverage-7.2.4-cp37-cp37m-win_amd64.whl", hash = "sha256:4078939c4b7053e14e87c65aa68dbed7867e326e450f94038bfe1a1b22078ff9"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"},
|
{file = "coverage-7.2.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:603a2b172126e3b08c11ca34200143089a088cd0297d4cfc4922d2c1c3a892f9"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"},
|
{file = "coverage-7.2.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72751d117ceaad3b1ea3bcb9e85f5409bbe9fb8a40086e17333b994dbccc0718"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"},
|
{file = "coverage-7.2.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f19ba9301e6fb0b94ba71fda9a1b02d11f0aab7f8e2455122a4e2921b6703c2f"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"},
|
{file = "coverage-7.2.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d784177a7fb9d0f58d24d3e60638c8b729c3693963bf67fa919120f750db237"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"},
|
{file = "coverage-7.2.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d2a9180beff1922b09bd7389e23454928e108449e646c26da5c62e29b0bf4e3"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"},
|
{file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:39747afc854a7ee14e5e132da7db179d6281faf97dc51e6d7806651811c47538"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"},
|
{file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60feb703abc8d78e9427d873bcf924c9e30cf540a21971ef5a17154da763b60f"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"},
|
{file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2becddfcbf3d994a8f4f9dd2b6015cae3a3eff50dedc6e4a17c3cccbe8f93d4"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"},
|
{file = "coverage-7.2.4-cp38-cp38-win32.whl", hash = "sha256:56a674ad18d6b04008283ca03c012be913bf89d91c0803c54c24600b300d9e51"},
|
||||||
{file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"},
|
{file = "coverage-7.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:ab08e03add2cf5793e66ac1bbbb24acfa90c125476f5724f5d44c56eeec1d635"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"},
|
{file = "coverage-7.2.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92b565c51732ea2e7e541709ccce76391b39f4254260e5922e08e00971e88e33"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"},
|
{file = "coverage-7.2.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8769a67e8816c7e94d5bf446fc0501641fde78fdff362feb28c2c64d45d0e9b1"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"},
|
{file = "coverage-7.2.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d74d6fbd5a98a5629e8467b719b0abea9ca01a6b13555d125c84f8bf4ea23d"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"},
|
{file = "coverage-7.2.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9f770c6052d9b5c9b0e824fd8c003fe33276473b65b4f10ece9565ceb62438e"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"},
|
{file = "coverage-7.2.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3023ce23e41a6f006c09f7e6d62b6c069c36bdc9f7de16a5ef823acc02e6c63"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"},
|
{file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fabd1f4d12dfd6b4f309208c2f31b116dc5900e0b42dbafe4ee1bc7c998ffbb0"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"},
|
{file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e41a7f44e73b37c6f0132ecfdc1c8b67722f42a3d9b979e6ebc150c8e80cf13a"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"},
|
{file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:864e36947289be05abd83267c4bade35e772526d3e9653444a9dc891faf0d698"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"},
|
{file = "coverage-7.2.4-cp39-cp39-win32.whl", hash = "sha256:ea534200efbf600e60130c48552f99f351cae2906898a9cd924c1c7f2fb02853"},
|
||||||
{file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"},
|
{file = "coverage-7.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:00f8fd8a5fe1ffc3aef78ea2dbf553e5c0f4664324e878995e38d41f037eb2b3"},
|
||||||
{file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"},
|
{file = "coverage-7.2.4-pp37.pp38.pp39-none-any.whl", hash = "sha256:856bcb837e96adede31018a0854ce7711a5d6174db1a84e629134970676c54fa"},
|
||||||
{file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"},
|
{file = "coverage-7.2.4.tar.gz", hash = "sha256:7283f78d07a201ac7d9dc2ac2e4faaea99c4d302f243ee5b4e359f3e170dc008"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -1573,14 +1573,14 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipython"
|
name = "ipython"
|
||||||
version = "8.12.0"
|
version = "8.13.0"
|
||||||
description = "IPython: Productive Interactive Computing"
|
description = "IPython: Productive Interactive Computing"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "ipython-8.12.0-py3-none-any.whl", hash = "sha256:1c183bf61b148b00bcebfa5d9b39312733ae97f6dad90d7e9b4d86c8647f498c"},
|
{file = "ipython-8.13.0-py3-none-any.whl", hash = "sha256:a0a8a30376cee8019c6e22bc0ab4320762f5f5e4d7abed0ea3ee4b95e3982ad5"},
|
||||||
{file = "ipython-8.12.0.tar.gz", hash = "sha256:a950236df04ad75b5bc7f816f9af3d74dc118fd42f2ff7e80e8e60ca1f182e2d"},
|
{file = "ipython-8.13.0.tar.gz", hash = "sha256:8d56026b882958db8eab089654f0c045d1237622313a1506da136fb0cce4270f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -1708,14 +1708,14 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain"
|
name = "langchain"
|
||||||
version = "0.0.151"
|
version = "0.0.152"
|
||||||
description = "Building applications with LLMs through composability"
|
description = "Building applications with LLMs through composability"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.1,<4.0"
|
python-versions = ">=3.8.1,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain-0.0.151-py3-none-any.whl", hash = "sha256:b832732b3ec95d2bf33bb11e36bac84ccda8529c9b470fbc375c787eabd8341c"},
|
{file = "langchain-0.0.152-py3-none-any.whl", hash = "sha256:f4ef223fc5783c34bc375ab5544a2b14c8ab5680bb8f0c8175176d1e8e19558c"},
|
||||||
{file = "langchain-0.0.151.tar.gz", hash = "sha256:e9c3cfb3712bce3f5b56de68bb4e8ad905c04b5e92433d06ce0882f6daf883a2"},
|
{file = "langchain-0.0.152.tar.gz", hash = "sha256:bf0ccaf87139e404e2fd68c5144d5321ac7e5b4354e83a818e4a09edfc7d665f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -1733,7 +1733,7 @@ tenacity = ">=8.1.0,<9.0.0"
|
||||||
tqdm = ">=4.48.0"
|
tqdm = ">=4.48.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.6,<0.3.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.3.0,<4.0.0)", "duckduckgo-search (>=2.8.6,<3.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "lancedb (>=0.1,<0.2)", "lark (>=1.1.5,<2.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "qdrant-client (>=1.1.2,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
|
all = ["aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.6,<0.3.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.3.0,<4.0.0)", "duckduckgo-search (>=2.8.6,<3.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "lancedb (>=0.1,<0.2)", "lark (>=1.1.5,<2.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "qdrant-client (>=1.1.2,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
|
||||||
azure = ["azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "openai (>=0,<1)"]
|
azure = ["azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "openai (>=0,<1)"]
|
||||||
cohere = ["cohere (>=3,<4)"]
|
cohere = ["cohere (>=3,<4)"]
|
||||||
embeddings = ["sentence-transformers (>=2,<3)"]
|
embeddings = ["sentence-transformers (>=2,<3)"]
|
||||||
|
|
@ -2698,6 +2698,21 @@ pytz = ">=2020.1"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
|
test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pandas-stubs"
|
||||||
|
version = "2.0.0.230412"
|
||||||
|
description = "Type annotations for pandas"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pandas_stubs-2.0.0.230412-py3-none-any.whl", hash = "sha256:311ab8b42ee574d9fea5061d1f63aeca297e472de6073ba84bf2a017c6cb1b6b"},
|
||||||
|
{file = "pandas_stubs-2.0.0.230412.tar.gz", hash = "sha256:016f567cb9947edd0067ea2665ab00b77fa47e73a65ce1a097de4f499b3485c0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
types-pytz = ">=2022.1.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parso"
|
name = "parso"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -4428,6 +4443,30 @@ dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2
|
||||||
doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"]
|
doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"]
|
||||||
test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
|
test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-pillow"
|
||||||
|
version = "9.5.0.2"
|
||||||
|
description = "Typing stubs for Pillow"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "types-Pillow-9.5.0.2.tar.gz", hash = "sha256:b3f9f621f259566c19c1deca21901017c8b1e3e200ed2e49e0a2d83c0a5175db"},
|
||||||
|
{file = "types_Pillow-9.5.0.2-py3-none-any.whl", hash = "sha256:58fdebd0ffa2353ecccdd622adde23bce89da5c0c8b96c34f2d1eca7b7e42d0e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-pytz"
|
||||||
|
version = "2023.3.0.0"
|
||||||
|
description = "Typing stubs for pytz"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"},
|
||||||
|
{file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-pyyaml"
|
name = "types-pyyaml"
|
||||||
version = "6.0.12.9"
|
version = "6.0.12.9"
|
||||||
|
|
@ -4442,14 +4481,14 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-requests"
|
name = "types-requests"
|
||||||
version = "2.28.11.17"
|
version = "2.29.0.0"
|
||||||
description = "Typing stubs for requests"
|
description = "Typing stubs for requests"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "types-requests-2.28.11.17.tar.gz", hash = "sha256:0d580652ce903f643f8c3b494dd01d29367ea57cea0c7ad7f65cf3169092edb0"},
|
{file = "types-requests-2.29.0.0.tar.gz", hash = "sha256:c86f4a955d943d2457120dbe719df24ef0924e11177164d10a0373cf311d7b4d"},
|
||||||
{file = "types_requests-2.28.11.17-py3-none-any.whl", hash = "sha256:cc1aba862575019306b2ed134eb1ea994cab1c887a22e18d3383e6dd42e9789b"},
|
{file = "types_requests-2.29.0.0-py3-none-any.whl", hash = "sha256:4cf6e323e856c779fbe8815bb977a5bf5d6c5034713e4c17ff2a9a20610f5b27"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -4955,4 +4994,4 @@ cffi = ["cffi (>=1.11)"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "49fd166dc3811d79f8344946ee40a62c28215db9fdd41f5f051ab109657d9a62"
|
content-hash = "c8ba5d9e0208fc55dc2f00efffed26d7a2c7d325568dfd697cc6499d210304d9"
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ fake-useragent = "^1.1.3"
|
||||||
docstring-parser = "^0.15"
|
docstring-parser = "^0.15"
|
||||||
psycopg2-binary = "^2.9.6"
|
psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^11.0.0"
|
pyarrow = "^11.0.0"
|
||||||
|
websockets = "^11.0.2"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
|
|
@ -58,6 +59,8 @@ pytest = "^7.2.2"
|
||||||
types-requests = "^2.28.11"
|
types-requests = "^2.28.11"
|
||||||
requests = "^2.28.0"
|
requests = "^2.28.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
|
pandas-stubs = "^2.0.0.230412"
|
||||||
|
types-pillow = "^9.5.0.2"
|
||||||
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
# `python-base` sets up all our shared environment variables
|
|
||||||
FROM python:3.10-slim
|
|
||||||
|
|
||||||
# python
|
|
||||||
ENV PYTHONUNBUFFERED=1 \
|
|
||||||
# prevents python creating .pyc files
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
\
|
|
||||||
# pip
|
|
||||||
PIP_NO_CACHE_DIR=off \
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
|
||||||
PIP_DEFAULT_TIMEOUT=100 \
|
|
||||||
\
|
|
||||||
# poetry
|
|
||||||
# https://python-poetry.org/docs/configuration/#using-environment-variables
|
|
||||||
POETRY_VERSION=1.4.0 \
|
|
||||||
# make poetry install to this location
|
|
||||||
POETRY_HOME="/opt/poetry" \
|
|
||||||
# make poetry create the virtual environment in the project's root
|
|
||||||
# it gets named `.venv`
|
|
||||||
POETRY_VIRTUALENVS_IN_PROJECT=true \
|
|
||||||
# do not ask any interactive question
|
|
||||||
POETRY_NO_INTERACTION=1 \
|
|
||||||
\
|
|
||||||
# paths
|
|
||||||
# this is where our requirements + virtual environment will live
|
|
||||||
PYSETUP_PATH="/opt/pysetup" \
|
|
||||||
VENV_PATH="/opt/pysetup/.venv"
|
|
||||||
|
|
||||||
# prepend poetry and venv to path
|
|
||||||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install --no-install-recommends -y \
|
|
||||||
# deps for installing poetry
|
|
||||||
curl \
|
|
||||||
# deps for building python deps
|
|
||||||
build-essential libpq-dev
|
|
||||||
|
|
||||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
|
||||||
|
|
||||||
# copy project requirement files here to ensure they will be cached.
|
|
||||||
WORKDIR /app
|
|
||||||
COPY poetry.lock pyproject.toml ./
|
|
||||||
COPY langflow/ ./langflow
|
|
||||||
|
|
||||||
# poetry install
|
|
||||||
RUN poetry install --without dev
|
|
||||||
|
|
||||||
# build wheel
|
|
||||||
RUN poetry build -f wheel
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
|
||||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
|
||||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION .
|
|
||||||
docker push ibiscp/langflow:$VERSION
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
from langflow.interface.loading import load_flow_from_json # noqa
|
from langflow.interface.loading import load_flow_from_json
|
||||||
|
from langflow.cache import cache_manager
|
||||||
|
|
||||||
|
__all__ = ["load_flow_from_json", "cache_manager"]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ from pydantic import BaseModel, validator
|
||||||
from langflow.graph.utils import extract_input_variables_from_prompt
|
from langflow.graph.utils import extract_input_variables_from_prompt
|
||||||
|
|
||||||
|
|
||||||
|
class CacheResponse(BaseModel):
|
||||||
|
data: dict
|
||||||
|
|
||||||
|
|
||||||
class Code(BaseModel):
|
class Code(BaseModel):
|
||||||
code: str
|
code: str
|
||||||
|
|
||||||
|
|
|
||||||
16
src/backend/langflow/api/callback.py
Normal file
16
src/backend/langflow/api/callback.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from typing import Any
|
||||||
|
from langchain.callbacks.base import AsyncCallbackHandler
|
||||||
|
|
||||||
|
from langflow.api.schemas import ChatResponse
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
||||||
|
class StreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
|
"""Callback handler for streaming LLM responses."""
|
||||||
|
|
||||||
|
def __init__(self, websocket):
|
||||||
|
self.websocket = websocket
|
||||||
|
|
||||||
|
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||||
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
|
await self.websocket.send_json(resp.dict())
|
||||||
18
src/backend/langflow/api/chat.py
Normal file
18
src/backend/langflow/api/chat.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from fastapi import APIRouter, WebSocket
|
||||||
|
|
||||||
|
from langflow.api.chat_manager import ChatManager
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
chat_manager = ChatManager()
|
||||||
|
|
||||||
|
|
||||||
|
@router.websocket("/chat/{client_id}")
|
||||||
|
async def websocket_endpoint(client_id: str, websocket: WebSocket):
|
||||||
|
"""Websocket endpoint for chat."""
|
||||||
|
try:
|
||||||
|
await chat_manager.handle_websocket(client_id, websocket)
|
||||||
|
except Exception as e:
|
||||||
|
# Log stack trace
|
||||||
|
logger.exception(e)
|
||||||
|
raise e
|
||||||
212
src/backend/langflow/api/chat_manager.py
Normal file
212
src/backend/langflow/api/chat_manager.py
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
import asyncio
|
||||||
|
from typing import Dict, List
|
||||||
|
from collections import defaultdict
|
||||||
|
from fastapi import WebSocket
|
||||||
|
import json
|
||||||
|
from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse
|
||||||
|
from langflow.cache.manager import Subject
|
||||||
|
from langflow.interface.run import (
|
||||||
|
get_result_and_steps,
|
||||||
|
load_or_build_langchain_object,
|
||||||
|
)
|
||||||
|
from langflow.interface.utils import pil_to_base64, try_setting_streaming_options
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from langflow.cache import cache_manager
|
||||||
|
|
||||||
|
|
||||||
|
class ChatHistory(Subject):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.history: Dict[str, List[ChatMessage]] = defaultdict(list)
|
||||||
|
|
||||||
|
def add_message(self, client_id: str, message: ChatMessage):
|
||||||
|
"""Add a message to the chat history."""
|
||||||
|
|
||||||
|
self.history[client_id].append(message)
|
||||||
|
|
||||||
|
if not isinstance(message, FileResponse):
|
||||||
|
self.notify()
|
||||||
|
|
||||||
|
def get_history(self, client_id: str, filter=True) -> List[ChatMessage]:
|
||||||
|
"""Get the chat history for a client."""
|
||||||
|
if history := self.history.get(client_id, []):
|
||||||
|
if filter:
|
||||||
|
return [msg for msg in history if msg.type not in ["start", "stream"]]
|
||||||
|
return history
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def empty_history(self, client_id: str):
|
||||||
|
"""Empty the chat history for a client."""
|
||||||
|
self.history[client_id] = []
|
||||||
|
|
||||||
|
|
||||||
|
class ChatManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.active_connections: Dict[str, WebSocket] = {}
|
||||||
|
self.chat_history = ChatHistory()
|
||||||
|
self.chat_history.attach(self.on_chat_history_update)
|
||||||
|
self.cache_manager = cache_manager
|
||||||
|
self.cache_manager.attach(self.update)
|
||||||
|
|
||||||
|
def on_chat_history_update(self):
|
||||||
|
"""Send the last chat message to the client."""
|
||||||
|
client_id = self.cache_manager.current_client_id
|
||||||
|
if client_id in self.active_connections:
|
||||||
|
chat_response = self.chat_history.get_history(client_id, filter=False)[-1]
|
||||||
|
if chat_response.is_bot:
|
||||||
|
# Process FileResponse
|
||||||
|
if isinstance(chat_response, FileResponse):
|
||||||
|
# If data_type is pandas, convert to csv
|
||||||
|
if chat_response.data_type == "pandas":
|
||||||
|
chat_response.data = chat_response.data.to_csv()
|
||||||
|
elif chat_response.data_type == "image":
|
||||||
|
# Base64 encode the image
|
||||||
|
chat_response.data = pil_to_base64(chat_response.data)
|
||||||
|
# get event loop
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
coroutine = self.send_json(client_id, chat_response)
|
||||||
|
asyncio.run_coroutine_threadsafe(coroutine, loop)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.cache_manager.current_client_id in self.active_connections:
|
||||||
|
self.last_cached_object_dict = self.cache_manager.get_last()
|
||||||
|
# Add a new ChatResponse with the data
|
||||||
|
chat_response = FileResponse(
|
||||||
|
message=None,
|
||||||
|
type="file",
|
||||||
|
data=self.last_cached_object_dict["obj"],
|
||||||
|
data_type=self.last_cached_object_dict["type"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.chat_history.add_message(
|
||||||
|
self.cache_manager.current_client_id, chat_response
|
||||||
|
)
|
||||||
|
|
||||||
|
async def connect(self, client_id: str, websocket: WebSocket):
|
||||||
|
await websocket.accept()
|
||||||
|
self.active_connections[client_id] = websocket
|
||||||
|
|
||||||
|
def disconnect(self, client_id: str):
|
||||||
|
del self.active_connections[client_id]
|
||||||
|
|
||||||
|
async def send_message(self, client_id: str, message: str):
|
||||||
|
websocket = self.active_connections[client_id]
|
||||||
|
await websocket.send_text(message)
|
||||||
|
|
||||||
|
async def send_json(self, client_id: str, message: ChatMessage):
|
||||||
|
websocket = self.active_connections[client_id]
|
||||||
|
await websocket.send_json(message.dict())
|
||||||
|
|
||||||
|
async def process_message(self, client_id: str, payload: Dict):
|
||||||
|
# Process the graph data and chat message
|
||||||
|
chat_message = payload.pop("message", "")
|
||||||
|
chat_message = ChatMessage(message=chat_message)
|
||||||
|
self.chat_history.add_message(client_id, chat_message)
|
||||||
|
|
||||||
|
graph_data = payload
|
||||||
|
start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
|
||||||
|
self.chat_history.add_message(client_id, start_resp)
|
||||||
|
|
||||||
|
is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0
|
||||||
|
# Generate result and thought
|
||||||
|
try:
|
||||||
|
logger.debug("Generating result and thought")
|
||||||
|
|
||||||
|
result, intermediate_steps = await process_graph(
|
||||||
|
graph_data=graph_data,
|
||||||
|
is_first_message=is_first_message,
|
||||||
|
chat_message=chat_message,
|
||||||
|
websocket=self.active_connections[client_id],
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Log stack trace
|
||||||
|
logger.exception(e)
|
||||||
|
self.chat_history.empty_history(client_id)
|
||||||
|
raise e
|
||||||
|
# Send a response back to the frontend, if needed
|
||||||
|
intermediate_steps = intermediate_steps or ""
|
||||||
|
history = self.chat_history.get_history(client_id, filter=False)
|
||||||
|
file_responses = []
|
||||||
|
if history:
|
||||||
|
# Iterate backwards through the history
|
||||||
|
for msg in reversed(history):
|
||||||
|
if isinstance(msg, FileResponse):
|
||||||
|
if msg.data_type == "image":
|
||||||
|
# Base64 encode the image
|
||||||
|
msg.data = pil_to_base64(msg.data)
|
||||||
|
file_responses.append(msg)
|
||||||
|
if msg.type == "start":
|
||||||
|
break
|
||||||
|
|
||||||
|
response = ChatResponse(
|
||||||
|
message=result or "",
|
||||||
|
intermediate_steps=intermediate_steps.strip(),
|
||||||
|
type="end",
|
||||||
|
files=file_responses,
|
||||||
|
)
|
||||||
|
self.chat_history.add_message(client_id, response)
|
||||||
|
|
||||||
|
async def handle_websocket(self, client_id: str, websocket: WebSocket):
|
||||||
|
await self.connect(client_id, websocket)
|
||||||
|
|
||||||
|
try:
|
||||||
|
chat_history = self.chat_history.get_history(client_id)
|
||||||
|
# iterate and make BaseModel into dict
|
||||||
|
chat_history = [chat.dict() for chat in chat_history]
|
||||||
|
await websocket.send_json(chat_history)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
json_payload = await websocket.receive_json()
|
||||||
|
try:
|
||||||
|
payload = json.loads(json_payload)
|
||||||
|
except TypeError:
|
||||||
|
payload = json_payload
|
||||||
|
if "clear_history" in payload:
|
||||||
|
self.chat_history.history[client_id] = []
|
||||||
|
continue
|
||||||
|
|
||||||
|
with self.cache_manager.set_client_id(client_id):
|
||||||
|
await self.process_message(client_id, payload)
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that might occur
|
||||||
|
logger.exception(e)
|
||||||
|
# send a message to the client
|
||||||
|
await self.send_message(client_id, str(e))
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
await self.active_connections[client_id].close(
|
||||||
|
code=1000, reason="Client disconnected"
|
||||||
|
)
|
||||||
|
self.disconnect(client_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def process_graph(
|
||||||
|
graph_data: Dict,
|
||||||
|
is_first_message: bool,
|
||||||
|
chat_message: ChatMessage,
|
||||||
|
websocket: WebSocket,
|
||||||
|
):
|
||||||
|
langchain_object = load_or_build_langchain_object(graph_data, is_first_message)
|
||||||
|
langchain_object = try_setting_streaming_options(langchain_object, websocket)
|
||||||
|
logger.debug("Loaded langchain object")
|
||||||
|
|
||||||
|
if langchain_object is None:
|
||||||
|
# Raise user facing error
|
||||||
|
raise ValueError(
|
||||||
|
"There was an error loading the langchain_object. Please, check all the nodes and try again."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate result and thought
|
||||||
|
try:
|
||||||
|
logger.debug("Generating result and thought")
|
||||||
|
result, intermediate_steps = get_result_and_steps(
|
||||||
|
langchain_object, chat_message.message or ""
|
||||||
|
)
|
||||||
|
logger.debug("Generated result and intermediate_steps")
|
||||||
|
return result, intermediate_steps
|
||||||
|
except Exception as e:
|
||||||
|
# Log stack trace
|
||||||
|
logger.exception(e)
|
||||||
|
raise e
|
||||||
40
src/backend/langflow/api/schemas.py
Normal file
40
src/backend/langflow/api/schemas.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from typing import Any, Union
|
||||||
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMessage(BaseModel):
|
||||||
|
"""Chat message schema."""
|
||||||
|
|
||||||
|
is_bot: bool = False
|
||||||
|
message: Union[str, None] = None
|
||||||
|
type: str = "human"
|
||||||
|
|
||||||
|
|
||||||
|
class ChatResponse(ChatMessage):
|
||||||
|
"""Chat response schema."""
|
||||||
|
|
||||||
|
intermediate_steps: str
|
||||||
|
type: str
|
||||||
|
is_bot: bool = True
|
||||||
|
files: list = []
|
||||||
|
|
||||||
|
@validator("type")
|
||||||
|
def validate_message_type(cls, v):
|
||||||
|
if v not in ["start", "stream", "end", "error", "info", "file"]:
|
||||||
|
raise ValueError("type must be start, stream, end, error, info, or file")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class FileResponse(ChatMessage):
|
||||||
|
"""File response schema."""
|
||||||
|
|
||||||
|
data: Any
|
||||||
|
data_type: str
|
||||||
|
type: str = "file"
|
||||||
|
is_bot: bool = True
|
||||||
|
|
||||||
|
@validator("data_type")
|
||||||
|
def validate_data_type(cls, v):
|
||||||
|
if v not in ["image", "csv"]:
|
||||||
|
raise ValueError("data_type must be image or csv")
|
||||||
|
return v
|
||||||
|
|
@ -7,6 +7,7 @@ from langflow.api.base import (
|
||||||
PromptValidationResponse,
|
PromptValidationResponse,
|
||||||
validate_prompt,
|
validate_prompt,
|
||||||
)
|
)
|
||||||
|
from langflow.interface.run import build_graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.validate import validate_code
|
from langflow.utils.validate import validate_code
|
||||||
|
|
||||||
|
|
@ -33,3 +34,20 @@ def post_validate_prompt(prompt: Prompt):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
|
# validate node
|
||||||
|
@router.post("/node/{node_id}", status_code=200)
|
||||||
|
def post_validate_node(node_id: str, data: dict):
|
||||||
|
try:
|
||||||
|
# build graph
|
||||||
|
graph = build_graph(data)
|
||||||
|
# validate node
|
||||||
|
node = graph.get_node(node_id)
|
||||||
|
if node is not None:
|
||||||
|
_ = node.build()
|
||||||
|
return str(node.params)
|
||||||
|
raise Exception(f"Node {node_id} not found")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
|
||||||
1
src/backend/langflow/cache/__init__.py
vendored
1
src/backend/langflow/cache/__init__.py
vendored
|
|
@ -0,0 +1 @@
|
||||||
|
from langflow.cache.manager import cache_manager # noqa
|
||||||
|
|
@ -2,14 +2,17 @@ import base64
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict
|
||||||
import dill # type: ignore
|
import dill # type: ignore
|
||||||
|
|
||||||
|
CACHE: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
|
||||||
def create_cache_folder(func):
|
def create_cache_folder(func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
149
src/backend/langflow/cache/manager.py
vendored
Normal file
149
src/backend/langflow/cache/manager.py
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import Any, Awaitable, Callable, List, Optional
|
||||||
|
from PIL import Image
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
class Subject:
|
||||||
|
"""Base class for implementing the observer pattern."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.observers: List[Callable[[], None]] = []
|
||||||
|
|
||||||
|
def attach(self, observer: Callable[[], None]):
|
||||||
|
"""Attach an observer to the subject."""
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def detach(self, observer: Callable[[], None]):
|
||||||
|
"""Detach an observer from the subject."""
|
||||||
|
self.observers.remove(observer)
|
||||||
|
|
||||||
|
def notify(self):
|
||||||
|
"""Notify all observers about an event."""
|
||||||
|
for observer in self.observers:
|
||||||
|
if observer is None:
|
||||||
|
continue
|
||||||
|
observer()
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncSubject:
|
||||||
|
"""Base class for implementing the async observer pattern."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.observers: List[Callable[[], Awaitable]] = []
|
||||||
|
|
||||||
|
def attach(self, observer: Callable[[], Awaitable]):
|
||||||
|
"""Attach an observer to the subject."""
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def detach(self, observer: Callable[[], Awaitable]):
|
||||||
|
"""Detach an observer from the subject."""
|
||||||
|
self.observers.remove(observer)
|
||||||
|
|
||||||
|
async def notify(self):
|
||||||
|
"""Notify all observers about an event."""
|
||||||
|
for observer in self.observers:
|
||||||
|
if observer is None:
|
||||||
|
continue
|
||||||
|
await observer()
|
||||||
|
|
||||||
|
|
||||||
|
class CacheManager(Subject):
|
||||||
|
"""Manages cache for different clients and notifies observers on changes."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.CACHE = {}
|
||||||
|
self.current_client_id = None
|
||||||
|
self.current_cache = {}
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def set_client_id(self, client_id: str):
|
||||||
|
"""
|
||||||
|
Context manager to set the current client_id and associated cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client_id (str): The client identifier.
|
||||||
|
"""
|
||||||
|
previous_client_id = self.current_client_id
|
||||||
|
self.current_client_id = client_id
|
||||||
|
self.current_cache = self.CACHE.setdefault(client_id, {})
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.current_client_id = previous_client_id
|
||||||
|
self.current_cache = self.CACHE.get(self.current_client_id, {})
|
||||||
|
|
||||||
|
def add(self, name: str, obj: Any, obj_type: str, extension: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Add an object to the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
obj (Any): The object to cache.
|
||||||
|
obj_type (str): The type of the object.
|
||||||
|
"""
|
||||||
|
object_extensions = {
|
||||||
|
"image": "png",
|
||||||
|
"pandas": "csv",
|
||||||
|
}
|
||||||
|
if obj_type in object_extensions:
|
||||||
|
_extension = object_extensions[obj_type]
|
||||||
|
else:
|
||||||
|
_extension = type(obj).__name__.lower()
|
||||||
|
self.current_cache[name] = {
|
||||||
|
"obj": obj,
|
||||||
|
"type": obj_type,
|
||||||
|
"extension": extension or _extension,
|
||||||
|
}
|
||||||
|
self.notify()
|
||||||
|
|
||||||
|
def add_pandas(self, name: str, obj: Any):
|
||||||
|
"""
|
||||||
|
Add a pandas DataFrame or Series to the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
obj (Any): The pandas DataFrame or Series object.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (pd.DataFrame, pd.Series)):
|
||||||
|
self.add(name, obj.to_csv(), "pandas", extension="csv")
|
||||||
|
else:
|
||||||
|
raise ValueError("Object is not a pandas DataFrame or Series")
|
||||||
|
|
||||||
|
def add_image(self, name: str, obj: Any, extension: str = "png"):
|
||||||
|
"""
|
||||||
|
Add a PIL Image to the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
obj (Any): The PIL Image object.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, Image.Image):
|
||||||
|
self.add(name, obj, "image", extension=extension)
|
||||||
|
else:
|
||||||
|
raise ValueError("Object is not a PIL Image")
|
||||||
|
|
||||||
|
def get(self, name: str):
|
||||||
|
"""
|
||||||
|
Get an object from the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The cached object associated with the given cache key.
|
||||||
|
"""
|
||||||
|
return self.current_cache[name]
|
||||||
|
|
||||||
|
def get_last(self):
|
||||||
|
"""
|
||||||
|
Get the last added item in the current client's cache.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The last added item in the cache.
|
||||||
|
"""
|
||||||
|
return list(self.current_cache.values())[-1]
|
||||||
|
|
||||||
|
|
||||||
|
cache_manager = CacheManager()
|
||||||
|
|
@ -4,16 +4,18 @@
|
||||||
# - Build each inner agent first, then build the outer agent
|
# - Build each inner agent first, then build the outer agent
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import inspect
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from langflow.cache import utils as cache_utils
|
from langflow.cache import base as cache_utils
|
||||||
from langflow.graph.constants import DIRECT_TYPES
|
from langflow.graph.constants import DIRECT_TYPES
|
||||||
from langflow.interface import loading
|
from langflow.interface import loading
|
||||||
from langflow.interface.listing import ALL_TYPES_DICT
|
from langflow.interface.listing import ALL_TYPES_DICT
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
|
|
@ -158,13 +160,21 @@ class Node:
|
||||||
continue
|
continue
|
||||||
result = value.build()
|
result = value.build()
|
||||||
# If the key is "func", then we need to use the run method
|
# If the key is "func", then we need to use the run method
|
||||||
if key == "func" and not isinstance(result, types.FunctionType):
|
if key == "func":
|
||||||
# func can be PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n')
|
if not isinstance(result, types.FunctionType):
|
||||||
|
# func can be
|
||||||
|
# PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n')
|
||||||
# so we need to check if there is an attribute called run
|
# so we need to check if there is an attribute called run
|
||||||
if hasattr(result, "run"):
|
if hasattr(result, "run"):
|
||||||
result = result.run # type: ignore
|
result = result.run # type: ignore
|
||||||
elif hasattr(result, "get_function"):
|
elif hasattr(result, "get_function"):
|
||||||
result = result.get_function() # type: ignore
|
result = result.get_function() # type: ignore
|
||||||
|
elif inspect.iscoroutinefunction(result):
|
||||||
|
self.params["coroutine"] = result
|
||||||
|
else:
|
||||||
|
# turn result which is a function into a coroutine
|
||||||
|
# so that it can be awaited
|
||||||
|
self.params["coroutine"] = sync_to_async(result)
|
||||||
|
|
||||||
self.params[key] = result
|
self.params[key] = result
|
||||||
elif isinstance(value, list) and all(
|
elif isinstance(value, list) and all(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,9 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
try:
|
try:
|
||||||
if name in get_custom_nodes(self.type_name).keys():
|
if name in get_custom_nodes(self.type_name).keys():
|
||||||
return get_custom_nodes(self.type_name)[name]
|
return get_custom_nodes(self.type_name)[name]
|
||||||
return build_template_from_class(name, self.type_to_loader_dict)
|
return build_template_from_class(
|
||||||
|
name, self.type_to_loader_dict, add_function=True
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Chain not found") from exc
|
raise ValueError("Chain not found") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from langchain.llms.loading import load_llm_from_config
|
||||||
|
|
||||||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||||
from langflow.interface.importing.utils import import_by_type
|
from langflow.interface.importing.utils import import_by_type
|
||||||
|
from langflow.interface.run import fix_memory_inputs
|
||||||
from langflow.interface.toolkits.base import toolkits_creator
|
from langflow.interface.toolkits.base import toolkits_creator
|
||||||
from langflow.interface.types import get_type_list
|
from langflow.interface.types import get_type_list
|
||||||
from langflow.interface.utils import load_file_into_dict
|
from langflow.interface.utils import load_file_into_dict
|
||||||
|
|
@ -33,6 +34,12 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
return custom_agent.initialize(**params) # type: ignore
|
return custom_agent.initialize(**params) # type: ignore
|
||||||
|
|
||||||
class_object = import_by_type(_type=base_type, name=node_type)
|
class_object = import_by_type(_type=base_type, name=node_type)
|
||||||
|
# check if it is a class before using issubclass
|
||||||
|
|
||||||
|
# if isinstance(class_object, type) and issubclass(class_object, BaseModel):
|
||||||
|
# # validate params
|
||||||
|
# fields = class_object.__fields__
|
||||||
|
# params = {key: value for key, value in params.items() if key in fields}
|
||||||
|
|
||||||
if base_type == "agents":
|
if base_type == "agents":
|
||||||
# We need to initialize it differently
|
# We need to initialize it differently
|
||||||
|
|
@ -65,6 +72,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
return load_toolkits_executor(node_type, loaded_toolkit, params)
|
return load_toolkits_executor(node_type, loaded_toolkit, params)
|
||||||
return loaded_toolkit
|
return loaded_toolkit
|
||||||
elif base_type == "embeddings":
|
elif base_type == "embeddings":
|
||||||
|
# ? Why remove model from params?
|
||||||
params.pop("model")
|
params.pop("model")
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
elif base_type == "vectorstores":
|
elif base_type == "vectorstores":
|
||||||
|
|
@ -106,7 +114,19 @@ def load_flow_from_json(path: str, build=True):
|
||||||
# Nodes, edges and root node
|
# Nodes, edges and root node
|
||||||
edges = data_graph["edges"]
|
edges = data_graph["edges"]
|
||||||
graph = Graph(nodes, edges)
|
graph = Graph(nodes, edges)
|
||||||
return graph.build() if build else graph
|
if build:
|
||||||
|
langchain_object = graph.build()
|
||||||
|
if hasattr(langchain_object, "verbose"):
|
||||||
|
langchain_object.verbose = True
|
||||||
|
|
||||||
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
|
# https://github.com/hwchase17/langchain/issues/2068
|
||||||
|
# Deactivating until we have a frontend solution
|
||||||
|
# to display intermediate steps
|
||||||
|
langchain_object.return_intermediate_steps = False
|
||||||
|
fix_memory_inputs(langchain_object)
|
||||||
|
return langchain_object
|
||||||
|
return graph
|
||||||
|
|
||||||
|
|
||||||
def replace_zero_shot_prompt_with_prompt_template(nodes):
|
def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import io
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
from chromadb.errors import NotEnoughElementsException # type: ignore
|
from chromadb.errors import NotEnoughElementsException # type: ignore
|
||||||
|
|
||||||
from langflow.cache.utils import compute_dict_hash, load_cache, memoize_dict
|
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
|
||||||
from langflow.graph.graph import Graph
|
from langflow.graph.graph import Graph
|
||||||
from langflow.interface import loading
|
from langflow.interface import loading
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -32,23 +32,23 @@ def load_or_build_langchain_object(data_graph, is_first_message=False):
|
||||||
return build_langchain_object_with_caching(data_graph)
|
return build_langchain_object_with_caching(data_graph)
|
||||||
|
|
||||||
|
|
||||||
@memoize_dict(maxsize=1)
|
@memoize_dict(maxsize=10)
|
||||||
def build_langchain_object_with_caching(data_graph):
|
def build_langchain_object_with_caching(data_graph):
|
||||||
"""
|
"""
|
||||||
Build langchain object from data_graph.
|
Build langchain object from data_graph.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.debug("Building langchain object")
|
logger.debug("Building langchain object")
|
||||||
nodes = data_graph["nodes"]
|
graph = build_graph(data_graph)
|
||||||
# Add input variables
|
|
||||||
# nodes = payload.extract_input_variables(nodes)
|
|
||||||
# Nodes, edges and root node
|
|
||||||
edges = data_graph["edges"]
|
|
||||||
graph = Graph(nodes, edges)
|
|
||||||
|
|
||||||
return graph.build()
|
return graph.build()
|
||||||
|
|
||||||
|
|
||||||
|
def build_graph(data_graph):
|
||||||
|
nodes = data_graph["nodes"]
|
||||||
|
edges = data_graph["edges"]
|
||||||
|
return Graph(nodes, edges)
|
||||||
|
|
||||||
|
|
||||||
def build_langchain_object(data_graph):
|
def build_langchain_object(data_graph):
|
||||||
"""
|
"""
|
||||||
Build langchain object from data_graph.
|
Build langchain object from data_graph.
|
||||||
|
|
@ -87,7 +87,7 @@ def process_graph(data_graph: Dict[str, Any]):
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, thought = get_result_and_thought_using_graph(langchain_object, message)
|
result, thought = get_result_and_steps(langchain_object, message)
|
||||||
logger.debug("Generated result and thought")
|
logger.debug("Generated result and thought")
|
||||||
|
|
||||||
# Save langchain_object to cache
|
# Save langchain_object to cache
|
||||||
|
|
@ -118,7 +118,7 @@ def process_graph_cached(data_graph: Dict[str, Any]):
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, thought = get_result_and_thought_using_graph(langchain_object, message)
|
result, thought = get_result_and_steps(langchain_object, message)
|
||||||
logger.debug("Generated result and thought")
|
logger.debug("Generated result and thought")
|
||||||
return {"result": str(result), "thought": thought.strip()}
|
return {"result": str(result), "thought": thought.strip()}
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ def fix_memory_inputs(langchain_object):
|
||||||
update_memory_keys(langchain_object, possible_new_mem_key)
|
update_memory_keys(langchain_object, possible_new_mem_key)
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought_using_graph(langchain_object, message: str):
|
def get_result_and_steps(langchain_object, message: str):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
|
|
@ -240,6 +240,61 @@ def get_result_and_thought_using_graph(langchain_object, message: str):
|
||||||
return result, thought
|
return result, thought
|
||||||
|
|
||||||
|
|
||||||
|
def async_get_result_and_steps(langchain_object, message: str):
|
||||||
|
"""Get result and thought from extracted json"""
|
||||||
|
try:
|
||||||
|
if hasattr(langchain_object, "verbose"):
|
||||||
|
langchain_object.verbose = True
|
||||||
|
chat_input = None
|
||||||
|
memory_key = ""
|
||||||
|
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
||||||
|
memory_key = langchain_object.memory.memory_key
|
||||||
|
|
||||||
|
if hasattr(langchain_object, "input_keys"):
|
||||||
|
for key in langchain_object.input_keys:
|
||||||
|
if key not in [memory_key, "chat_history"]:
|
||||||
|
chat_input = {key: message}
|
||||||
|
else:
|
||||||
|
chat_input = message # type: ignore
|
||||||
|
|
||||||
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
|
# https://github.com/hwchase17/langchain/issues/2068
|
||||||
|
# Deactivating until we have a frontend solution
|
||||||
|
# to display intermediate steps
|
||||||
|
langchain_object.return_intermediate_steps = False
|
||||||
|
|
||||||
|
fix_memory_inputs(langchain_object)
|
||||||
|
|
||||||
|
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||||
|
try:
|
||||||
|
# if hasattr(langchain_object, "acall"):
|
||||||
|
# output = await langchain_object.acall(chat_input)
|
||||||
|
# else:
|
||||||
|
output = langchain_object(chat_input)
|
||||||
|
except ValueError as exc:
|
||||||
|
# make the error message more informative
|
||||||
|
logger.debug(f"Error: {str(exc)}")
|
||||||
|
output = langchain_object.run(chat_input)
|
||||||
|
|
||||||
|
intermediate_steps = (
|
||||||
|
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
|
)
|
||||||
|
|
||||||
|
result = (
|
||||||
|
output.get(langchain_object.output_keys[0])
|
||||||
|
if isinstance(output, dict)
|
||||||
|
else output
|
||||||
|
)
|
||||||
|
if intermediate_steps:
|
||||||
|
thought = format_intermediate_steps(intermediate_steps)
|
||||||
|
else:
|
||||||
|
thought = output_buffer.getvalue()
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError(f"Error: {str(exc)}") from exc
|
||||||
|
return result, thought
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
|
def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
|
import base64
|
||||||
|
from io import BytesIO
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from PIL.Image import Image
|
||||||
|
from langchain.callbacks.base import AsyncCallbackManager
|
||||||
|
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
|
||||||
|
from langchain.llms import AzureOpenAI, OpenAI
|
||||||
|
from langflow.api.callback import StreamingLLMCallbackHandler
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
@ -20,3 +27,30 @@ def load_file_into_dict(file_path: str) -> dict:
|
||||||
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
|
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def pil_to_base64(image: Image) -> str:
|
||||||
|
buffered = BytesIO()
|
||||||
|
image.save(buffered, format="PNG")
|
||||||
|
img_str = base64.b64encode(buffered.getvalue())
|
||||||
|
return img_str.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def try_setting_streaming_options(langchain_object, websocket):
|
||||||
|
# If the LLM type is OpenAI or ChatOpenAI,
|
||||||
|
# set streaming to True
|
||||||
|
# First we need to find the LLM
|
||||||
|
llm = None
|
||||||
|
if hasattr(langchain_object, "llm"):
|
||||||
|
llm = langchain_object.llm
|
||||||
|
elif hasattr(langchain_object, "llm_chain") and hasattr(
|
||||||
|
langchain_object.llm_chain, "llm"
|
||||||
|
):
|
||||||
|
llm = langchain_object.llm_chain.llm
|
||||||
|
if isinstance(llm, (OpenAI, ChatOpenAI, AzureOpenAI, AzureChatOpenAI)):
|
||||||
|
llm.streaming = bool(hasattr(llm, "streaming"))
|
||||||
|
stream_handler = StreamingLLMCallbackHandler(websocket)
|
||||||
|
stream_manager = AsyncCallbackManager([stream_handler])
|
||||||
|
llm.callback_manager = stream_manager
|
||||||
|
|
||||||
|
return langchain_object
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from langflow.api.endpoints import router as endpoints_router
|
from langflow.api.endpoints import router as endpoints_router
|
||||||
from langflow.api.validate import router as validate_router
|
from langflow.api.validate import router as validate_router
|
||||||
|
from langflow.api.chat import router as chat_router
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|
@ -23,6 +24,7 @@ def create_app():
|
||||||
|
|
||||||
app.include_router(endpoints_router)
|
app.include_router(endpoints_router)
|
||||||
app.include_router(validate_router)
|
app.include_router(validate_router)
|
||||||
|
app.include_router(chat_router)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,10 +229,6 @@ class FrontendNode(BaseModel):
|
||||||
field.required = False
|
field.required = False
|
||||||
if field.value is None:
|
if field.value is None:
|
||||||
field.value = ""
|
field.value = ""
|
||||||
# If the field.name contains api or api and key, then it might be an api key
|
|
||||||
# other conditions are to make sure that it is not an input or output variable
|
|
||||||
if "api" in key.lower() and "key" in key.lower():
|
|
||||||
field.required = False
|
|
||||||
|
|
||||||
if "kwargs" in field.name.lower():
|
if "kwargs" in field.name.lower():
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,7 @@ class SeriesCharacterChainNode(FrontendNode):
|
||||||
"Chain",
|
"Chain",
|
||||||
"ConversationChain",
|
"ConversationChain",
|
||||||
"SeriesCharacterChain",
|
"SeriesCharacterChain",
|
||||||
|
"function",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -317,7 +318,7 @@ class InitializeAgentNode(FrontendNode):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = """Construct a json agent from an LLM and tools."""
|
description: str = """Construct a json agent from an LLM and tools."""
|
||||||
base_classes: list[str] = ["AgentExecutor"]
|
base_classes: list[str] = ["AgentExecutor", "function"]
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return super().to_dict()
|
return super().to_dict()
|
||||||
|
|
@ -529,7 +530,7 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
|
|
||||||
# Separated for possible future changes
|
# Separated for possible future changes
|
||||||
if field.name == "prompt":
|
if field.name == "prompt" and field.value is None:
|
||||||
# if no prompt is provided, use the default prompt
|
# if no prompt is provided, use the default prompt
|
||||||
field.required = False
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from functools import wraps
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
|
|
@ -301,3 +302,15 @@ def update_verbose(d: dict, new_value: bool) -> dict:
|
||||||
elif k == "verbose":
|
elif k == "verbose":
|
||||||
d[k] = new_value
|
d[k] = new_value
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def sync_to_async(func):
|
||||||
|
"""
|
||||||
|
Decorator to convert a sync function to an async function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
async def async_wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return async_wrapper
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
poetry remove langchain
|
|
||||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
|
||||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
|
||||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION .
|
|
||||||
docker run -p 5003:80 -d ibiscp/langflow:$VERSION
|
|
||||||
poetry add --editable ../../../langchain
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
FROM node:14-alpine
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . /app
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
# Read the contents of the JSON file
|
|
||||||
json=$(cat package.json)
|
|
||||||
|
|
||||||
# Extract the value of the "version" field using jq
|
|
||||||
VERSION=$(echo "$json" | jq -r '.version')
|
|
||||||
|
|
||||||
docker build -t logspace/frontend_build -f build.Dockerfile .
|
|
||||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow_frontend:$VERSION .
|
|
||||||
docker push ibiscp/langflow_frontend:$VERSION
|
|
||||||
28
src/frontend/package-lock.json
generated
28
src/frontend/package-lock.json
generated
|
|
@ -25,13 +25,14 @@
|
||||||
"ace-builds": "^1.16.0",
|
"ace-builds": "^1.16.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"axios": "^1.3.2",
|
"axios": "^1.3.2",
|
||||||
|
"base64-js": "^1.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"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.2",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^4.8.0",
|
||||||
"react-laag": "^2.0.5",
|
"react-laag": "^2.0.5",
|
||||||
"react-router-dom": "^6.8.1",
|
"react-router-dom": "^6.8.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
|
@ -5862,6 +5863,25 @@
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/batch": {
|
"node_modules/batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
|
|
@ -15040,9 +15060,9 @@
|
||||||
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
|
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
|
||||||
},
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "4.7.1",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
|
||||||
"integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==",
|
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,14 @@
|
||||||
"ace-builds": "^1.16.0",
|
"ace-builds": "^1.16.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"axios": "^1.3.2",
|
"axios": "^1.3.2",
|
||||||
|
"base64-js": "^1.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"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.2",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^4.8.0",
|
||||||
"react-laag": "^2.0.5",
|
"react-laag": "^2.0.5",
|
||||||
"react-router-dom": "^6.8.1",
|
"react-router-dom": "^6.8.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "NODE_ENV=development react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
|
@ -60,5 +61,5 @@
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"proxy": "http://backend:7860"
|
"proxy": "http://127.0.0.1:7860"
|
||||||
}
|
}
|
||||||
|
|
@ -7,12 +7,13 @@ import {
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import { useContext, useRef } from "react";
|
import { useContext, useState, useEffect, useRef } from "react";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import NodeModal from "../../modals/NodeModal";
|
import NodeModal from "../../modals/NodeModal";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
selected,
|
selected,
|
||||||
|
|
@ -25,6 +26,52 @@ export default function GenericNode({
|
||||||
const { types, deleteNode } = useContext(typesContext);
|
const { types, deleteNode } = useContext(typesContext);
|
||||||
const { openPopUp } = useContext(PopUpContext);
|
const { openPopUp } = useContext(PopUpContext);
|
||||||
const Icon = nodeIcons[types[data.type]];
|
const Icon = nodeIcons[types[data.type]];
|
||||||
|
const [validationStatus, setValidationStatus] = useState("idle");
|
||||||
|
// State for outline color
|
||||||
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
const {save} = useContext(TabsContext)
|
||||||
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
|
const [params, setParams] = useState([]);
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (reactFlowInstance) {
|
||||||
|
setParams(Object.values(reactFlowInstance.toObject()));
|
||||||
|
}
|
||||||
|
}, [save]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
fetch(`/validate/node/${data.id}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(reactFlowInstance.toObject()),
|
||||||
|
}).then((response) => {
|
||||||
|
console.log(response.status, response.body);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
setValidationStatus("success");
|
||||||
|
} else if (response.status === 500) {
|
||||||
|
setValidationStatus("error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error validating node:", error);
|
||||||
|
setValidationStatus("error");
|
||||||
|
}
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (validationStatus === "success") {
|
||||||
|
setIsValid(true);
|
||||||
|
} else {
|
||||||
|
setIsValid(false);
|
||||||
|
}
|
||||||
|
}, [validationStatus]);
|
||||||
|
|
||||||
if (!Icon) {
|
if (!Icon) {
|
||||||
if (showError.current) {
|
if (showError.current) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
|
|
@ -37,9 +84,11 @@ export default function GenericNode({
|
||||||
deleteNode(data.id);
|
deleteNode(data.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
isValid ? "animate-pulse-green" : "border-red-outline",
|
||||||
selected ? "border border-blue-500" : "border dark:border-gray-700",
|
selected ? "border border-blue-500" : "border dark:border-gray-700",
|
||||||
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
|
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
|
||||||
)}
|
)}
|
||||||
|
|
@ -108,7 +157,9 @@ export default function GenericNode({
|
||||||
"px-5 py-2 mt-2 dark:text-white text-center",
|
"px-5 py-2 mt-2 dark:text-white text-center",
|
||||||
Object.keys(data.node.template).filter(
|
Object.keys(data.node.template).filter(
|
||||||
(key) =>
|
(key) =>
|
||||||
!key.startsWith("_") && data.node.template[key].show
|
!key.startsWith("_") &&
|
||||||
|
data.node.template[key].show &&
|
||||||
|
!data.node.template[key].advanced
|
||||||
).length === 0
|
).length === 0
|
||||||
? "hidden"
|
? "hidden"
|
||||||
: ""
|
: ""
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
import { XCircleIcon, XMarkIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
XCircleIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
InformationCircleIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||||
|
|
||||||
export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponentType) {
|
export default function SingleAlert({
|
||||||
|
dropItem,
|
||||||
|
removeAlert,
|
||||||
|
}: SingleAlertComponentType) {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
const type = dropItem.type;
|
const type = dropItem.type;
|
||||||
|
|
||||||
|
|
@ -20,13 +28,13 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
leaveFrom={"transform translate-x-0"}
|
leaveFrom={"transform translate-x-0"}
|
||||||
leaveTo={"transform translate-x-[-100%]"}
|
leaveTo={"transform translate-x-[-100%]"}
|
||||||
>
|
>
|
||||||
{type === "error"?
|
{type === "error" ? (
|
||||||
<div className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" key={dropItem.id}>
|
<div
|
||||||
|
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
|
||||||
|
key={dropItem.id}
|
||||||
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon
|
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-red-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-red-800">
|
<h3 className="text-sm font-medium text-red-800">
|
||||||
|
|
@ -49,7 +57,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
setShow(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeAlert(dropItem.id);
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||||
>
|
>
|
||||||
|
|
@ -59,8 +70,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:(type === "notice" ?
|
) : type === "notice" ? (
|
||||||
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}>
|
<div
|
||||||
|
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
|
||||||
|
key={dropItem.id}
|
||||||
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<InformationCircleIcon
|
||||||
className="h-5 w-5 text-blue-400"
|
className="h-5 w-5 text-blue-400"
|
||||||
|
|
@ -87,7 +101,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
setShow(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeAlert(dropItem.id);
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
||||||
>
|
>
|
||||||
|
|
@ -97,8 +114,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:
|
) : (
|
||||||
<div className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md" key={dropItem.id}>
|
<div
|
||||||
|
className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md"
|
||||||
|
key={dropItem.id}
|
||||||
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircleIcon
|
||||||
className="h-5 w-5 text-green-400"
|
className="h-5 w-5 text-green-400"
|
||||||
|
|
@ -115,7 +135,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
setShow(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeAlert(dropItem.id);
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
||||||
>
|
>
|
||||||
|
|
@ -125,10 +148,7 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
</Transition>
|
</Transition>
|
||||||
)
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,29 +5,15 @@ import { TrashIcon } from "@heroicons/react/24/outline";
|
||||||
import SingleAlert from "./components/singleAlertComponent";
|
import SingleAlert from "./components/singleAlertComponent";
|
||||||
import { AlertDropdownType } from "../../types/alerts";
|
import { AlertDropdownType } from "../../types/alerts";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||||
export default function AlertDropdown({}: AlertDropdownType) {
|
export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const componentRef = useRef<HTMLDivElement>(null);
|
const componentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
// Use the custom hook
|
||||||
function handleClickOutside(event: MouseEvent) {
|
useOnClickOutside(componentRef, () => {
|
||||||
if (
|
|
||||||
componentRef.current &&
|
|
||||||
!componentRef.current.contains(event.target as Node)
|
|
||||||
) {
|
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Bind the event listener
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
|
|
||||||
// Cleanup the event listener when the component is unmounted
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [componentRef]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
notificationList,
|
notificationList,
|
||||||
|
|
@ -38,13 +24,13 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={componentRef}
|
ref={componentRef}
|
||||||
className="z-10 py-3 pb-4 rounded-md bg-white ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col"
|
className="z-10 py-3 pb-4 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col"
|
||||||
>
|
>
|
||||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800">
|
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
||||||
Notifications
|
Notifications
|
||||||
<div className="flex gap-2 pr-3 ">
|
<div className="flex gap-2 pr-3 ">
|
||||||
<button
|
<button
|
||||||
className="hover:text-red-500"
|
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
setTimeout(clearNotificationList, 100);
|
setTimeout(clearNotificationList, 100);
|
||||||
|
|
@ -52,12 +38,15 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||||
</button>
|
</button>
|
||||||
<button className="hover:text-red-500" onClick={closePopUp}>
|
<button
|
||||||
|
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||||
|
onClick={closePopUp}
|
||||||
|
>
|
||||||
<XMarkIcon className="h-5 w-5" />
|
<XMarkIcon className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide">
|
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
||||||
{notificationList.length !== 0 ? (
|
{notificationList.length !== 0 ? (
|
||||||
notificationList.map((alertItem, index) => (
|
notificationList.map((alertItem, index) => (
|
||||||
<SingleAlert
|
<SingleAlert
|
||||||
|
|
@ -67,7 +56,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full w-full pb-16 text-gray-500 flex justify-center items-center">
|
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
||||||
No new notifications
|
No new notifications
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
33
src/frontend/src/alerts/hooks/useOnClickOutside/index.ts
Normal file
33
src/frontend/src/alerts/hooks/useOnClickOutside/index.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export function useOnClickOutside(ref, handler) {
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (event) => {
|
||||||
|
// Do nothing if clicking ref's element or its children
|
||||||
|
if (!ref.current || ref.current.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach the listener to the document
|
||||||
|
document.addEventListener("mousedown", listener, { passive: true });
|
||||||
|
|
||||||
|
// Attach the listener to the react-flow instance
|
||||||
|
const reactFlowContainer = document.querySelector(".react-flow");
|
||||||
|
if (reactFlowContainer) {
|
||||||
|
reactFlowContainer.addEventListener("mousedown", listener, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the listener when the component is unmounted
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", listener);
|
||||||
|
if (reactFlowContainer) {
|
||||||
|
reactFlowContainer.removeEventListener("mousedown", listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [ref, handler]); // Rerun only if ref or handler changes
|
||||||
|
}
|
||||||
40
src/frontend/src/assets/Gooey Ring-5s-271px.svg
Normal file
40
src/frontend/src/assets/Gooey Ring-5s-271px.svg
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="271px" height="271px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||||
|
<defs>
|
||||||
|
<filter id="ldio-978hsxudfzl-filter" x="-100%" y="-100%" width="300%" height="300%" color-interpolation-filters="sRGB">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="3.6"></feGaussianBlur>
|
||||||
|
<feComponentTransfer result="cutoff">
|
||||||
|
<feFuncA type="table" tableValues="0 0 0 0 0 0 1 1 1 1 1"></feFuncA>
|
||||||
|
</feComponentTransfer>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g filter="url(#ldio-978hsxudfzl-filter)"><g transform="translate(50 50)">
|
||||||
|
<g>
|
||||||
|
<circle cx="8" cy="0" r="5" fill="#2edbb5">
|
||||||
|
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="5s" repeatCount="indefinite" begin="-0.2s"></animate>
|
||||||
|
</circle>
|
||||||
|
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="5s" repeatCount="indefinite" begin="0s"></animateTransform>
|
||||||
|
</g>
|
||||||
|
</g><g transform="translate(50 50)">
|
||||||
|
<g>
|
||||||
|
<circle cx="8" cy="0" r="5" fill="#1d99ff">
|
||||||
|
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="2.5s" repeatCount="indefinite" begin="-0.15000000000000002s"></animate>
|
||||||
|
</circle>
|
||||||
|
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="2.5s" repeatCount="indefinite" begin="-0.05s"></animateTransform>
|
||||||
|
</g>
|
||||||
|
</g><g transform="translate(50 50)">
|
||||||
|
<g>
|
||||||
|
<circle cx="8" cy="0" r="5" fill="#4f41ff">
|
||||||
|
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animate>
|
||||||
|
</circle>
|
||||||
|
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animateTransform>
|
||||||
|
</g>
|
||||||
|
</g><g transform="translate(50 50)">
|
||||||
|
<g>
|
||||||
|
<circle cx="8" cy="0" r="5" fill="#8400ff">
|
||||||
|
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.25s" repeatCount="indefinite" begin="-0.05s"></animate>
|
||||||
|
</circle>
|
||||||
|
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.25s" repeatCount="indefinite" begin="-0.15000000000000002s"></animateTransform>
|
||||||
|
</g>
|
||||||
|
</g></g>
|
||||||
|
<!-- [ldio] generated by https://loading.io/ --></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Transition } from "@headlessui/react";
|
||||||
|
import { Bars3CenterLeftIcon, ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { nodeColors } from "../../../utils";
|
||||||
|
import { PopUpContext } from "../../../contexts/popUpContext";
|
||||||
|
import { useContext } from "react";
|
||||||
|
import ChatModal from "../../../modals/chatModal";
|
||||||
|
|
||||||
|
export default function ChatTrigger({open, setOpen,flow}){
|
||||||
|
const {openPopUp} = useContext(PopUpContext)
|
||||||
|
return(<Transition
|
||||||
|
show={!open}
|
||||||
|
appear={true}
|
||||||
|
enter="transition ease-out duration-300"
|
||||||
|
enterFrom="translate-y-96"
|
||||||
|
enterTo="translate-y-0"
|
||||||
|
leave="transition ease-in duration-300"
|
||||||
|
leaveFrom="translate-y-0"
|
||||||
|
leaveTo="translate-y-96"
|
||||||
|
>
|
||||||
|
<div className="absolute bottom-2 right-3">
|
||||||
|
<div style={{backgroundColor:nodeColors['chat']}} className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full dark:bg-gray-800 dark:border-gray-600 dark:text-white">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<ChatBubbleBottomCenterTextIcon
|
||||||
|
className="h-6 w-6 mt-1"
|
||||||
|
style={{ color: "white" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>)
|
||||||
|
}
|
||||||
|
|
@ -1,291 +1,32 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
|
||||||
Bars3CenterLeftIcon,
|
import { ChatMessageType, ChatType } from "../../types/chat";
|
||||||
LockClosedIcon,
|
import ChatTrigger from "./chatTrigger";
|
||||||
PaperAirplaneIcon,
|
import ChatModal from "../../modals/chatModal";
|
||||||
XMarkIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import {
|
|
||||||
MouseEventHandler,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { sendAll } from "../../controllers/API";
|
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { classNames, nodeColors, snakeToNormalCase } from "../../utils";
|
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
|
||||||
import { ChatType } from "../../types/chat";
|
|
||||||
import ChatMessage from "./chatMessage";
|
|
||||||
import { NodeType } from "../../types/flow";
|
|
||||||
|
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
|
|
||||||
export default function Chat({ flow, reactFlowInstance }: ChatType) {
|
export default function Chat({ flow }: ChatType) {
|
||||||
const { updateFlow, lockChat, setLockChat, flows, tabIndex } =
|
const [open, setOpen] = useState(false);
|
||||||
useContext(TabsContext);
|
useEffect(() => {
|
||||||
const [saveChat, setSaveChat] = useState(false);
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
const [open, setOpen] = useState(true);
|
if (
|
||||||
const [chatValue, setChatValue] = useState("");
|
(event.key === "K" || event.key === "k") &&
|
||||||
const [chatHistory, setChatHistory] = useState(flow.chat);
|
(event.metaKey || event.ctrlKey)
|
||||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
) {
|
||||||
const addChatHistory = (
|
event.preventDefault();
|
||||||
message: string,
|
setOpen((oldState) => !oldState);
|
||||||
isSend: boolean,
|
|
||||||
thought?: string
|
|
||||||
) => {
|
|
||||||
let tabsChange = false;
|
|
||||||
setChatHistory((old) => {
|
|
||||||
let newChat = _.cloneDeep(old);
|
|
||||||
if (JSON.stringify(flow.chat) !== JSON.stringify(old)) {
|
|
||||||
tabsChange = true;
|
|
||||||
return old;
|
|
||||||
}
|
}
|
||||||
if (thought) {
|
|
||||||
newChat.push({ message, isSend, thought });
|
|
||||||
} else {
|
|
||||||
newChat.push({ message, isSend });
|
|
||||||
}
|
|
||||||
return newChat;
|
|
||||||
});
|
|
||||||
if (tabsChange) {
|
|
||||||
if (thought) {
|
|
||||||
updateFlow({
|
|
||||||
..._.cloneDeep(flow),
|
|
||||||
chat: [...flow.chat, { isSend, message, thought }],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateFlow({
|
|
||||||
..._.cloneDeep(flow),
|
|
||||||
chat: [...flow.chat, { isSend, message }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSaveChat((chat) => !chat);
|
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
updateFlow({ ..._.cloneDeep(flow), chat: chatHistory });
|
return () => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [saveChat]);
|
};
|
||||||
useEffect(() => {
|
}, []);
|
||||||
setChatHistory(flow.chat);
|
|
||||||
}, [flow]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}, [chatHistory]);
|
|
||||||
|
|
||||||
function validateNode(n: NodeType): Array<string> {
|
|
||||||
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
|
||||||
setNoticeData({
|
|
||||||
title:
|
|
||||||
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!",
|
|
||||||
});
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
type,
|
|
||||||
node: { template },
|
|
||||||
} = n.data;
|
|
||||||
|
|
||||||
return Object.keys(template).reduce(
|
|
||||||
(errors: Array<string>, t) =>
|
|
||||||
errors.concat(
|
|
||||||
(template[t].required) &&
|
|
||||||
(template[t].value===undefined || template[t].value === "") &&
|
|
||||||
!reactFlowInstance
|
|
||||||
.getEdges()
|
|
||||||
.some(
|
|
||||||
(e) =>
|
|
||||||
e.targetHandle.split("|")[1] === t &&
|
|
||||||
e.targetHandle.split("|")[2] === n.id
|
|
||||||
)
|
|
||||||
? [
|
|
||||||
`${type} is missing ${
|
|
||||||
template.display_name
|
|
||||||
? template.display_name
|
|
||||||
: snakeToNormalCase(template[t].name)
|
|
||||||
}.`,
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
),
|
|
||||||
[] as string[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateNodes() {
|
|
||||||
return reactFlowInstance
|
|
||||||
.getNodes()
|
|
||||||
.flatMap((n: NodeType) => validateNode(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
function sendMessage() {
|
|
||||||
if (chatValue !== "") {
|
|
||||||
let nodeValidationErrors = validateNodes();
|
|
||||||
if (nodeValidationErrors.length === 0) {
|
|
||||||
setLockChat(true);
|
|
||||||
let message = chatValue;
|
|
||||||
setChatValue("");
|
|
||||||
addChatHistory(message, true);
|
|
||||||
|
|
||||||
sendAll({
|
|
||||||
...reactFlowInstance.toObject(),
|
|
||||||
message,
|
|
||||||
chatHistory,
|
|
||||||
name: flow.name,
|
|
||||||
description: flow.description,
|
|
||||||
})
|
|
||||||
.then((r) => {
|
|
||||||
addChatHistory(r.data.result, false, r.data.thought);
|
|
||||||
setLockChat(false);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setErrorData({
|
|
||||||
title: error.message ?? "Unknown Error",
|
|
||||||
list: [error.response.data.detail],
|
|
||||||
});
|
|
||||||
setLockChat(false);
|
|
||||||
let lastMessage;
|
|
||||||
setChatHistory((chatHistory) => {
|
|
||||||
let newChat = chatHistory;
|
|
||||||
|
|
||||||
lastMessage = newChat.pop().message;
|
|
||||||
return newChat;
|
|
||||||
});
|
|
||||||
setChatValue(lastMessage);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: "Oops! Looks like you missed some required information:",
|
|
||||||
list: nodeValidationErrors,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: "Error sending message",
|
|
||||||
list: ["The message cannot be empty."],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function clearChat() {
|
|
||||||
setChatHistory([]);
|
|
||||||
updateFlow({ ..._.cloneDeep(flow), chat: [] });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Transition
|
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||||
show={open}
|
<ChatTrigger open={open} setOpen={setOpen} flow={flow} />
|
||||||
appear={true}
|
|
||||||
enter="transition ease-out duration-300"
|
|
||||||
enterFrom="translate-y-96"
|
|
||||||
enterTo="translate-y-0"
|
|
||||||
leave="transition ease-in duration-300"
|
|
||||||
leaveFrom="translate-y-0"
|
|
||||||
leaveTo="translate-y-96"
|
|
||||||
>
|
|
||||||
<div className="w-[340px] absolute bottom-0 right-1">
|
|
||||||
<div className="border dark:border-gray-700 h-full rounded-xl rounded-b-none bg-white dark:bg-gray-800 shadow">
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
className="flex justify-between cursor-pointer items-center px-5 py-2 border-b dark:border-b-gray-700"
|
|
||||||
>
|
|
||||||
<div className="flex gap-3 text-lg dark:text-white font-medium items-center">
|
|
||||||
<Bars3CenterLeftIcon
|
|
||||||
className="h-5 w-5 mt-1"
|
|
||||||
style={{ color: nodeColors["chat"] }}
|
|
||||||
/>
|
|
||||||
Chat
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="hover:text-blue-500 dark:text-white"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
clearChat();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="w-full h-[400px] flex gap-3 mb-auto overflow-y-auto scrollbar-hide flex-col bg-gray-50 dark:bg-gray-900 p-3 py-5">
|
|
||||||
{chatHistory.map((c, i) => (
|
|
||||||
<ChatMessage chat={c} key={i} />
|
|
||||||
))}
|
|
||||||
<div ref={ref}></div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex items-center justify-between p-3">
|
|
||||||
<div className="relative w-full mt-1 rounded-md shadow-sm">
|
|
||||||
<input
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === "Enter" && !lockChat) {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="text"
|
|
||||||
disabled={lockChat}
|
|
||||||
value={lockChat ? "Thinking..." : chatValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
setChatValue(e.target.value);
|
|
||||||
}}
|
|
||||||
className={classNames(
|
|
||||||
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700",
|
|
||||||
"form-input block w-full rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm"
|
|
||||||
)}
|
|
||||||
placeholder={"Send a message..."}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
||||||
<button disabled={lockChat} onClick={() => sendMessage()}>
|
|
||||||
{lockChat ? (
|
|
||||||
<LockClosedIcon
|
|
||||||
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<PaperAirplaneIcon
|
|
||||||
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<Transition
|
|
||||||
show={!open}
|
|
||||||
appear={true}
|
|
||||||
enter="transition ease-out duration-300"
|
|
||||||
enterFrom="translate-y-96"
|
|
||||||
enterTo="translate-y-0"
|
|
||||||
leave="transition ease-in duration-300"
|
|
||||||
leaveFrom="translate-y-0"
|
|
||||||
leaveTo="translate-y-96"
|
|
||||||
>
|
|
||||||
<div className="absolute bottom-0 right-1">
|
|
||||||
<div className="border flex justify-center align-center py-1 px-3 rounded-xl rounded-b-none bg-white dark:bg-gray-800 dark:border-gray-600 dark:text-white shadow">
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex gap-3 items-center">
|
|
||||||
<Bars3CenterLeftIcon
|
|
||||||
className="h-6 w-6 mt-1"
|
|
||||||
style={{ color: nodeColors["chat"] }}
|
|
||||||
/>
|
|
||||||
Chat
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,27 @@ import { Fragment, useState } from "react";
|
||||||
import { DropDownComponentType } from "../../types/components";
|
import { DropDownComponentType } from "../../types/components";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
|
|
||||||
export default function Dropdown({value, options, onSelect}:DropDownComponentType) {
|
export default function Dropdown({
|
||||||
let [internalValue,setInternalValue] = useState(value===""||!value?"Choose an option":value)
|
value,
|
||||||
|
options,
|
||||||
|
onSelect,
|
||||||
|
}: DropDownComponentType) {
|
||||||
|
let [internalValue, setInternalValue] = useState(
|
||||||
|
value === "" || !value ? "Choose an option" : value
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Listbox value={internalValue} onChange={(value)=>{
|
<Listbox
|
||||||
setInternalValue(value)
|
value={internalValue}
|
||||||
onSelect(value)
|
onChange={(value) => {
|
||||||
}}>
|
setInternalValue(value);
|
||||||
|
onSelect(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div className="relative mt-1">
|
<div className="relative mt-1">
|
||||||
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm">
|
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white dark:bg-gray-900 py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm">
|
||||||
<span className="block w-max truncate">{internalValue}</span>
|
<span className="block w-max truncate">{internalValue}</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon
|
<ChevronUpDownIcon
|
||||||
|
|
@ -32,14 +41,16 @@ export default function Dropdown({value, options, onSelect}:DropDownComponentTyp
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full dark:bg-gray-800 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
{options.map((option, id) => (
|
{options.map((option, id) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={id}
|
key={id}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
active ? "text-white bg-indigo-600" : "text-gray-900",
|
active
|
||||||
"relative cursor-default select-none py-2 pl-3 pr-9"
|
? "text-white bg-indigo-600 dark:bg-indigo-500"
|
||||||
|
: "text-gray-900",
|
||||||
|
"relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={option}
|
value={option}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { normalCaseToSnakeCase, updateObject } from "../utils";
|
||||||
import { alertContext } from "./alertContext";
|
import { alertContext } from "./alertContext";
|
||||||
import { typesContext } from "./typesContext";
|
import { typesContext } from "./typesContext";
|
||||||
import { TemplateVariableType } from "../types/api";
|
import { TemplateVariableType } from "../types/api";
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
const TabsContextInitialValue: TabsContextType = {
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
save: () => {},
|
save: () => {},
|
||||||
|
|
@ -24,8 +25,6 @@ const TabsContextInitialValue: TabsContextType = {
|
||||||
incrementNodeId: () => 0,
|
incrementNodeId: () => 0,
|
||||||
downloadFlow: (flow: FlowType) => {},
|
downloadFlow: (flow: FlowType) => {},
|
||||||
uploadFlow: () => {},
|
uploadFlow: () => {},
|
||||||
lockChat: false,
|
|
||||||
setLockChat: (prevState: boolean) => {},
|
|
||||||
hardReset: () => {},
|
hardReset: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,8 +36,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const { setNoticeData } = useContext(alertContext);
|
const { setNoticeData } = useContext(alertContext);
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(0);
|
const [id, setId] = useState("");
|
||||||
const [lockChat, setLockChat] = useState(false);
|
|
||||||
const { templates } = useContext(typesContext);
|
const { templates } = useContext(typesContext);
|
||||||
|
|
||||||
const newNodeId = useRef(0);
|
const newNodeId = useRef(0);
|
||||||
|
|
@ -47,7 +45,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
return newNodeId.current;
|
return newNodeId.current;
|
||||||
}
|
}
|
||||||
function save() {
|
function save() {
|
||||||
console.log("save");
|
|
||||||
if (flows.length !== 0)
|
if (flows.length !== 0)
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"tabsData",
|
"tabsData",
|
||||||
|
|
@ -86,7 +83,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
newNodeId.current = 0;
|
newNodeId.current = 0;
|
||||||
setTabIndex(0);
|
setTabIndex(0);
|
||||||
setFlows([]);
|
setFlows([]);
|
||||||
setId(0);
|
setId("");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -184,11 +181,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
name: flow?.name ?? "New Flow",
|
name: flow?.name ?? "New Flow",
|
||||||
id: id.toString(),
|
id: id.toString(),
|
||||||
data,
|
data,
|
||||||
chat: flow ? flow.chat : [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Increment the ID counter.
|
// Increment the ID counter.
|
||||||
setId((old) => old + 1);
|
setId(uuidv4());
|
||||||
|
|
||||||
// Add the new flow to the list of flows.
|
// Add the new flow to the list of flows.
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
|
|
@ -211,7 +207,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
newFlows[index].description = newFlow.description ?? "";
|
newFlows[index].description = newFlow.description ?? "";
|
||||||
newFlows[index].data = newFlow.data;
|
newFlows[index].data = newFlow.data;
|
||||||
newFlows[index].name = newFlow.name;
|
newFlows[index].name = newFlow.name;
|
||||||
newFlows[index].chat = newFlow.chat;
|
|
||||||
}
|
}
|
||||||
return newFlows;
|
return newFlows;
|
||||||
});
|
});
|
||||||
|
|
@ -222,8 +217,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
value={{
|
value={{
|
||||||
save,
|
save,
|
||||||
hardReset,
|
hardReset,
|
||||||
lockChat,
|
|
||||||
setLockChat,
|
|
||||||
tabIndex,
|
tabIndex,
|
||||||
setTabIndex,
|
setTabIndex,
|
||||||
flows,
|
flows,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { PromptTypeAPI, errorsTypeAPI } from './../../types/api/index';
|
import { PromptTypeAPI, errorsTypeAPI } from "./../../types/api/index";
|
||||||
import { APIObjectType, sendAllProps } from '../../types/api/index';
|
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
import { FlowType } from '../../types/flow';
|
import { FlowType } from "../../types/flow";
|
||||||
|
|
||||||
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||||
return await axios.get(`/all`);
|
return await axios.get(`/all`);
|
||||||
|
|
@ -11,22 +11,25 @@ export async function sendAll(data:sendAllProps) {
|
||||||
return await axios.post(`/predict`, data);
|
return await axios.post(`/predict`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkCode(code:string):Promise<AxiosResponse<errorsTypeAPI>>{
|
export async function checkCode(
|
||||||
|
code: string
|
||||||
return await axios.post('/validate/code',{code})
|
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||||
|
return await axios.post("/validate/code", { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkPrompt(template:string):Promise<AxiosResponse<PromptTypeAPI>>{
|
export async function checkPrompt(
|
||||||
|
template: string
|
||||||
return await axios.post('/validate/prompt',{template})
|
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||||
|
return await axios.post("/validate/prompt", { template });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExamples(): Promise<FlowType[]> {
|
export async function getExamples(): Promise<FlowType[]> {
|
||||||
const url = 'https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples';
|
const url =
|
||||||
|
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
|
||||||
const response = await axios.get(url);
|
const response = await axios.get(url);
|
||||||
|
|
||||||
const jsonFiles = response.data.filter((file: any) => {
|
const jsonFiles = response.data.filter((file: any) => {
|
||||||
return file.name.endsWith('.json');
|
return file.name.endsWith(".json");
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentsPromises = jsonFiles.map(async (file: any) => {
|
const contentsPromises = jsonFiles.map(async (file: any) => {
|
||||||
|
|
|
||||||
55
src/frontend/src/modals/chatModal/chatInput/index.tsx
Normal file
55
src/frontend/src/modals/chatModal/chatInput/index.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { LockClosedIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { classNames } from "../../../utils";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
export default function ChatInput({
|
||||||
|
lockChat,
|
||||||
|
chatValue,
|
||||||
|
sendMessage,
|
||||||
|
setChatValue,
|
||||||
|
}: {
|
||||||
|
lockChat: boolean;
|
||||||
|
chatValue: string;
|
||||||
|
sendMessage: Function;
|
||||||
|
setChatValue: Function;
|
||||||
|
}) {
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<textarea
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter" && !lockChat && !event.shiftKey) {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={inputRef}
|
||||||
|
disabled={lockChat}
|
||||||
|
style={{resize: "none" }}
|
||||||
|
value={lockChat ? "Thinking..." : chatValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setChatValue(e.target.value);
|
||||||
|
}}
|
||||||
|
className={classNames(
|
||||||
|
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700",
|
||||||
|
"form-input block w-full custom-scroll h-10 rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm"
|
||||||
|
)}
|
||||||
|
placeholder={"Send a message..."}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||||
|
<button disabled={lockChat} onClick={() => sendMessage()}>
|
||||||
|
{lockChat ? (
|
||||||
|
<LockClosedIcon
|
||||||
|
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PaperAirplaneIcon
|
||||||
|
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
82
src/frontend/src/modals/chatModal/chatMessage/index.tsx
Normal file
82
src/frontend/src/modals/chatModal/chatMessage/index.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
|
import { classNames } from "../../../utils";
|
||||||
|
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||||
|
import { UserIcon } from "@heroicons/react/24/solid";
|
||||||
|
import FileCard from "../fileComponent";
|
||||||
|
var Convert = require("ansi-to-html");
|
||||||
|
var convert = new Convert({ newline: true });
|
||||||
|
|
||||||
|
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
|
const [hidden, setHidden] = useState(true);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"w-full py-2 pl-2 flex",
|
||||||
|
chat.isSend ? "bg-white dark:bg-gray-800 " : "bg-gray-200 dark:bg-gray-700"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"rounded-full w-8 h-8 flex items-center my-3 justify-center",
|
||||||
|
chat.isSend ? "bg-gray-900" : "bg-gray-200"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!chat.isSend && <img className="scale-150" src={AiIcon} />}
|
||||||
|
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-200" />}
|
||||||
|
</div>
|
||||||
|
{!chat.isSend ? (
|
||||||
|
<div className="w-full text-start flex items-center">
|
||||||
|
<div className="w-full relative text-start inline-block text-gray-600 text-sm font-normal">
|
||||||
|
{hidden && chat.thought && chat.thought !== "" && (
|
||||||
|
<div
|
||||||
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
|
className="absolute -top-1 -left-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chat.thought && chat.thought !== "" && !hidden && (
|
||||||
|
<div
|
||||||
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
|
className=" text-start inline-block rounded-md h-full border border-gray-300
|
||||||
|
bg-gray-100 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: convert.toHtml(chat.thought),
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||||
|
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
||||||
|
<span className="dark:text-white">
|
||||||
|
{chat.message}
|
||||||
|
{chat.files && (
|
||||||
|
<div className="my-2 w-full">
|
||||||
|
{chat.files.map((file, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="my-2 w-full">
|
||||||
|
<FileCard
|
||||||
|
fileName={"Generated File"}
|
||||||
|
fileType={file.data_type}
|
||||||
|
content={file.data}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-full flex items-center">
|
||||||
|
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
|
||||||
|
{chat.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
src/frontend/src/modals/chatModal/fileComponent/index.tsx
Normal file
36
src/frontend/src/modals/chatModal/fileComponent/index.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||||
|
import * as base64js from 'base64-js';
|
||||||
|
|
||||||
|
export default function FileCard({ fileName, content, fileType }) {
|
||||||
|
const handleDownload = () => {
|
||||||
|
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||||
|
const blob = new Blob([byteArray], { type: 'application/octet-stream' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileName+".png";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleDownload}
|
||||||
|
className="bg-gray-100 shadow rounded w-1/2 text-gray-700 hover:drop-shadow-lg px-2 py-2 flex justify-between items-center border border-gray-300"
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 text-current items-center w-full mr-2">
|
||||||
|
{" "}
|
||||||
|
<DocumentIcon className="w-8 h-8" />
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
{" "}
|
||||||
|
<div className="truncate text-sm text-current">{fileName}</div>
|
||||||
|
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
373
src/frontend/src/modals/chatModal/index.tsx
Normal file
373
src/frontend/src/modals/chatModal/index.tsx
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import {
|
||||||
|
ChatBubbleOvalLeftEllipsisIcon,
|
||||||
|
LockClosedIcon,
|
||||||
|
PaperAirplaneIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
import { FlowType, NodeType } from "../../types/flow";
|
||||||
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
import { classNames, snakeToNormalCase } from "../../utils";
|
||||||
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
|
import ChatMessage from "./chatMessage";
|
||||||
|
import { FaEraser } from "react-icons/fa";
|
||||||
|
import { sendAllProps } from "../../types/api";
|
||||||
|
import { ChatMessageType, ChatType } from "../../types/chat";
|
||||||
|
import ChatInput from "./chatInput";
|
||||||
|
|
||||||
|
const _ = require("lodash");
|
||||||
|
|
||||||
|
export default function ChatModal({
|
||||||
|
flow,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: Function;
|
||||||
|
flow: FlowType;
|
||||||
|
}) {
|
||||||
|
const [chatValue, setChatValue] = useState("");
|
||||||
|
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||||
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
|
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||||
|
const [ws, setWs] = useState<WebSocket | null>(null);
|
||||||
|
const [lockChat, setLockChat] = useState(false);
|
||||||
|
const addChatHistory = (
|
||||||
|
message: string,
|
||||||
|
isSend: boolean,
|
||||||
|
thought?: string,
|
||||||
|
files?: Array<any>
|
||||||
|
) => {
|
||||||
|
setChatHistory((old) => {
|
||||||
|
let newChat = _.cloneDeep(old);
|
||||||
|
if (files) {
|
||||||
|
newChat.push({ message, isSend, files, thought });
|
||||||
|
} else if (thought) {
|
||||||
|
newChat.push({ message, isSend, thought });
|
||||||
|
} else {
|
||||||
|
newChat.push({ message, isSend });
|
||||||
|
}
|
||||||
|
return newChat;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function connectWS() {
|
||||||
|
console.log("conectou");
|
||||||
|
try {
|
||||||
|
const urlWs =
|
||||||
|
process.env.NODE_ENV === "development"
|
||||||
|
? `ws://localhost:7860/chat/${flow.id}`
|
||||||
|
: `wss://${window.location.host}/chat/${flow.id}`;
|
||||||
|
|
||||||
|
const newWs = new WebSocket(urlWs);
|
||||||
|
newWs.onopen = () => {
|
||||||
|
console.log("WebSocket connection established!");
|
||||||
|
};
|
||||||
|
newWs.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
setLockChat(false);
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
console.log("Received data:", data);
|
||||||
|
//get chat history
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
setChatHistory((_) => {
|
||||||
|
let newChatHistory: ChatMessageType[] = [];
|
||||||
|
data.forEach(
|
||||||
|
(chatItem: {
|
||||||
|
intermediate_steps?: "string";
|
||||||
|
is_bot: boolean;
|
||||||
|
message: string;
|
||||||
|
type: string;
|
||||||
|
files?: Array<any>;
|
||||||
|
}) => {
|
||||||
|
if (chatItem.message) {
|
||||||
|
newChatHistory.push(
|
||||||
|
chatItem.files
|
||||||
|
? {
|
||||||
|
isSend: !chatItem.is_bot,
|
||||||
|
message: chatItem.message,
|
||||||
|
thought: chatItem.intermediate_steps,
|
||||||
|
files: chatItem.files,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isSend: !chatItem.is_bot,
|
||||||
|
message: chatItem.message,
|
||||||
|
thought: chatItem.intermediate_steps,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return newChatHistory;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.type === "end") {
|
||||||
|
if (data.files) {
|
||||||
|
addChatHistory(
|
||||||
|
data.message,
|
||||||
|
false,
|
||||||
|
data.intermediate_steps,
|
||||||
|
data.files
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addChatHistory(data.message, false, data.intermediate_steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.type == "file") {
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (event.data !== "Error: 1005") {
|
||||||
|
setErrorData({ title: event.data });
|
||||||
|
newWs.close();
|
||||||
|
connectWS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
newWs.onclose = (_) => {
|
||||||
|
if (open) {
|
||||||
|
setLockChat(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
connectWS();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
newWs.onerror = (ev) => {
|
||||||
|
console.log(ev, "error");
|
||||||
|
};
|
||||||
|
setWs(newWs);
|
||||||
|
|
||||||
|
return newWs;
|
||||||
|
} catch {
|
||||||
|
setErrorData({
|
||||||
|
title: "There was an error on web connection, please: ",
|
||||||
|
list: [
|
||||||
|
"refresh the page",
|
||||||
|
"use a new flow tab",
|
||||||
|
"check if the backend is up",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ws && (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING)) {
|
||||||
|
let newWs = connectWS();
|
||||||
|
return () => {
|
||||||
|
console.log("trigger");
|
||||||
|
newWs.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [lockChat]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let newWs = connectWS();
|
||||||
|
return () => {
|
||||||
|
console.log("trigger");
|
||||||
|
newWs.close();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function sendAll(data: sendAllProps) {
|
||||||
|
try {
|
||||||
|
if (ws) {
|
||||||
|
ws.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setErrorData({
|
||||||
|
title: "There was an erro sending the message",
|
||||||
|
list: [error.message],
|
||||||
|
});
|
||||||
|
setChatValue(data.message);
|
||||||
|
connectWS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}, [chatHistory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ws && ws.readyState === ws.CLOSED) {
|
||||||
|
setLockChat(false);
|
||||||
|
}
|
||||||
|
}, [lockChat]);
|
||||||
|
|
||||||
|
function validateNode(n: NodeType): Array<string> {
|
||||||
|
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
||||||
|
setNoticeData({
|
||||||
|
title:
|
||||||
|
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!",
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
node: { template },
|
||||||
|
} = n.data;
|
||||||
|
|
||||||
|
return Object.keys(template).reduce(
|
||||||
|
(errors: Array<string>, t) =>
|
||||||
|
errors.concat(
|
||||||
|
template[t].required &&
|
||||||
|
template[t].show &&
|
||||||
|
(!template[t].value || template[t].value === "") &&
|
||||||
|
!reactFlowInstance
|
||||||
|
.getEdges()
|
||||||
|
.some(
|
||||||
|
(e) =>
|
||||||
|
e.targetHandle.split("|")[1] === t &&
|
||||||
|
e.targetHandle.split("|")[2] === n.id
|
||||||
|
)
|
||||||
|
? [
|
||||||
|
`${type} is missing ${
|
||||||
|
template.display_name
|
||||||
|
? template.display_name
|
||||||
|
: snakeToNormalCase(template[t].name)
|
||||||
|
}.`,
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
[] as string[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateNodes() {
|
||||||
|
return reactFlowInstance
|
||||||
|
.getNodes()
|
||||||
|
.flatMap((n: NodeType) => validateNode(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
if (chatValue !== "") {
|
||||||
|
let nodeValidationErrors = validateNodes();
|
||||||
|
if (nodeValidationErrors.length === 0) {
|
||||||
|
setLockChat(true);
|
||||||
|
let message = chatValue;
|
||||||
|
setChatValue("");
|
||||||
|
addChatHistory(message, true);
|
||||||
|
|
||||||
|
sendAll({
|
||||||
|
...reactFlowInstance.toObject(),
|
||||||
|
message,
|
||||||
|
chatHistory,
|
||||||
|
name: flow.name,
|
||||||
|
description: flow.description,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setErrorData({
|
||||||
|
title: "Oops! Looks like you missed some required information:",
|
||||||
|
list: nodeValidationErrors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error sending message",
|
||||||
|
list: ["The message cannot be empty."],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clearChat() {
|
||||||
|
setChatHistory([]);
|
||||||
|
ws.send(JSON.stringify({ clear_history: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
|
function setModalOpen(x: boolean) {
|
||||||
|
setOpen(x);
|
||||||
|
if (x === false) {
|
||||||
|
setTimeout(() => {
|
||||||
|
closePopUp();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
|
<Dialog
|
||||||
|
as="div"
|
||||||
|
className="relative z-10"
|
||||||
|
onClose={setModalOpen}
|
||||||
|
initialFocus={ref}
|
||||||
|
>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black backdrop-blur-sm dark:bg-gray-600 dark:bg-opacity-80 bg-opacity-80 transition-opacity" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||||
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className=" drop-shadow-2xl relative flex flex-col justify-between transform h-[95%] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[690px]">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<button
|
||||||
|
onClick={() => clearChat()}
|
||||||
|
className="absolute top-2 right-2 hover:text-red-500"
|
||||||
|
>
|
||||||
|
<FaEraser className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex-col flex items-center overflow-scroll scrollbar-hide">
|
||||||
|
{chatHistory.length > 0 ? (
|
||||||
|
chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />)
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle ">
|
||||||
|
<span>
|
||||||
|
👋{" "}
|
||||||
|
<span className="text-gray-600 text-lg">
|
||||||
|
LangFlow Chat
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200">
|
||||||
|
<span className="text-base text-gray-500">
|
||||||
|
Start a conversation and click the agent’s thoughts{" "}
|
||||||
|
<span>
|
||||||
|
<ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " />
|
||||||
|
</span>{" "}
|
||||||
|
to inspect the chaining process.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div ref={ref}></div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex-col flex items-center justify-between p-3">
|
||||||
|
<div className="relative w-full mt-1 rounded-md shadow-sm">
|
||||||
|
<ChatInput
|
||||||
|
chatValue={chatValue}
|
||||||
|
lockChat={lockChat}
|
||||||
|
sendMessage={sendMessage}
|
||||||
|
setChatValue={setChatValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ export default function ExportModal() {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const {setErrorData}= useContext(alertContext)
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
|
|
@ -25,8 +25,8 @@ export default function ExportModal() {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [checked,setChecked] = useState(true)
|
const [checked, setChecked] = useState(true);
|
||||||
const [name,setName] = useState(flows[tabIndex].name)
|
const [name, setName] = useState(flows[tabIndex].name);
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|
@ -101,11 +101,10 @@ export default function ExportModal() {
|
||||||
if (event.target.value != "") {
|
if (event.target.value != "") {
|
||||||
let newFlow = flows[tabIndex];
|
let newFlow = flows[tabIndex];
|
||||||
newFlow.name = event.target.value;
|
newFlow.name = event.target.value;
|
||||||
setName(event.target.value)
|
setName(event.target.value);
|
||||||
updateFlow(newFlow);
|
updateFlow(newFlow);
|
||||||
}
|
} else {
|
||||||
else{
|
setName(event.target.value);
|
||||||
setName(event.target.value)
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -113,7 +112,7 @@ export default function ExportModal() {
|
||||||
value={name ?? null}
|
value={name ?? null}
|
||||||
placeholder="File name"
|
placeholder="File name"
|
||||||
id="name"
|
id="name"
|
||||||
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
@ -121,7 +120,11 @@ export default function ExportModal() {
|
||||||
htmlFor="description"
|
htmlFor="description"
|
||||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||||
>
|
>
|
||||||
Description <span className="text-gray-400 text-sm"> (optional)</span>
|
Description{" "}
|
||||||
|
<span className="text-gray-400 text-sm">
|
||||||
|
{" "}
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -134,14 +137,15 @@ export default function ExportModal() {
|
||||||
value={flows[tabIndex].description ?? null}
|
value={flows[tabIndex].description ?? null}
|
||||||
placeholder="Flow description"
|
placeholder="Flow description"
|
||||||
rows={3}
|
rows={3}
|
||||||
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="checkbox" className="flex items-center">
|
<label htmlFor="checkbox" className="flex items-center">
|
||||||
<input
|
<input
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setChecked(event.target.checked)
|
setChecked(event.target.checked);
|
||||||
}}
|
}}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
id="checkbox"
|
id="checkbox"
|
||||||
|
|
@ -157,7 +161,7 @@ export default function ExportModal() {
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (checked) downloadFlow(flows[tabIndex]);
|
if (checked) downloadFlow(flows[tabIndex]);
|
||||||
else downloadFlow(removeApiKeys(flows[tabIndex]))
|
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
||||||
}}
|
}}
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export default function ExtraSidebar() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-1 w-full">
|
<div className="mt-1 w-full">
|
||||||
{Object.keys(data).map((d: keyof APIObjectType, i) => (
|
{Object.keys(data).sort().map((d: keyof APIObjectType, i) => (
|
||||||
<DisclosureComponent
|
<DisclosureComponent
|
||||||
key={i}
|
key={i}
|
||||||
button={{
|
button={{
|
||||||
|
|
@ -31,7 +31,7 @@ export default function ExtraSidebar() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-2 flex flex-col gap-2">
|
<div className="p-2 flex flex-col gap-2">
|
||||||
{Object.keys(data[d]).map((t: string, k) => (
|
{Object.keys(data[d]).sort().map((t: string, k) => (
|
||||||
<div key={k}>
|
<div key={k}>
|
||||||
<div
|
<div
|
||||||
draggable
|
draggable
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,7 @@ export default function TabsManagerComponent() {
|
||||||
/>
|
/>
|
||||||
<div className="ml-auto mr-2 flex gap-3">
|
<div className="ml-auto mr-2 flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => openPopUp(<ImportModal />)}
|
||||||
openPopUp(
|
|
||||||
<ImportModal
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
|
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
|
||||||
>
|
>
|
||||||
Import <ArrowUpTrayIcon className="w-5 h-5" />
|
Import <ArrowUpTrayIcon className="w-5 h-5" />
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
flow.data?.edges ?? []
|
flow.data?.edges ?? []
|
||||||
);
|
);
|
||||||
const { setViewport } = useReactFlow();
|
const { setViewport } = useReactFlow();
|
||||||
const edgeUpdateSuccessful = useRef(true)
|
const edgeUpdateSuccessful = useRef(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reactFlowInstance && flow) {
|
if (reactFlowInstance && flow) {
|
||||||
|
|
@ -110,7 +110,9 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
|
|
||||||
// Extract the data from the drag event and parse it as a JSON object
|
// Extract the data from the drag event and parse it as a JSON object
|
||||||
let data:{type:string,node?:APIClassType} = JSON.parse(event.dataTransfer.getData("json"));
|
let data: { type: string; node?: APIClassType } = JSON.parse(
|
||||||
|
event.dataTransfer.getData("json")
|
||||||
|
);
|
||||||
|
|
||||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||||
if (
|
if (
|
||||||
|
|
@ -153,22 +155,27 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
|
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const onDelete = (mynodes) => {
|
const onDelete = (mynodes) => {
|
||||||
setEdges(edges.filter((ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id)));
|
setEdges(
|
||||||
}
|
edges.filter(
|
||||||
|
(ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onEdgeUpdateStart = useCallback(() => {
|
const onEdgeUpdateStart = useCallback(() => {
|
||||||
edgeUpdateSuccessful.current = false;
|
edgeUpdateSuccessful.current = false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onEdgeUpdate = useCallback(
|
||||||
const onEdgeUpdate = useCallback((oldEdge:Edge, newConnection:Connection) => {
|
(oldEdge: Edge, newConnection: Connection) => {
|
||||||
if (isValidConnection(newConnection, reactFlowInstance)) {
|
if (isValidConnection(newConnection, reactFlowInstance)) {
|
||||||
edgeUpdateSuccessful.current = true;
|
edgeUpdateSuccessful.current = true;
|
||||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
||||||
if (!edgeUpdateSuccessful.current) {
|
if (!edgeUpdateSuccessful.current) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ReportHandler } from 'web-vitals';
|
import { ReportHandler } from "web-vitals";
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
getCLS(onPerfEntry);
|
getCLS(onPerfEntry);
|
||||||
getFID(onPerfEntry);
|
getFID(onPerfEntry);
|
||||||
getFCP(onPerfEntry);
|
getFCP(onPerfEntry);
|
||||||
|
|
|
||||||
4
src/frontend/src/svg.d.ts
vendored
Normal file
4
src/frontend/src/svg.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module "*.svg" {
|
||||||
|
const content: any;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,24 @@
|
||||||
export type ErrorAlertType = {title:string,list:Array<string>,id:string,removeAlert:(id:string)=>void}
|
export type ErrorAlertType = {
|
||||||
export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void}
|
title: string;
|
||||||
export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void}
|
list: Array<string>;
|
||||||
export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void}
|
id: string;
|
||||||
|
removeAlert: (id: string) => void;
|
||||||
|
};
|
||||||
|
export type NoticeAlertType = {
|
||||||
|
title: string;
|
||||||
|
link: string;
|
||||||
|
id: string;
|
||||||
|
removeAlert: (id: string) => void;
|
||||||
|
};
|
||||||
|
export type SuccessAlertType = {
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
removeAlert: (id: string) => void;
|
||||||
|
};
|
||||||
|
export type SingleAlertComponentType = {
|
||||||
|
dropItem: AlertItemType;
|
||||||
|
removeAlert: (index: string) => void;
|
||||||
|
};
|
||||||
export type AlertDropdownType = {};
|
export type AlertDropdownType = {};
|
||||||
export type AlertItemType = {
|
export type AlertItemType = {
|
||||||
type: "notice" | "error" | "success";
|
type: "notice" | "error" | "success";
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,40 @@
|
||||||
import { Node,Edge,Viewport } from "reactflow"
|
import { Node, Edge, Viewport } from "reactflow";
|
||||||
//kind and class are just representative names to represent the actual structure of the object received by the API
|
//kind and class are just representative names to represent the actual structure of the object received by the API
|
||||||
|
|
||||||
export type APIObjectType = {kind:APIKindType,[key:string]:APIKindType}
|
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
|
||||||
export type APIKindType= {class:APIClassType,[key:string]:APIClassType}
|
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
|
||||||
export type APITemplateType = {variable:TemplateVariableType,[key:string]:TemplateVariableType}
|
export type APITemplateType = {
|
||||||
export type APIClassType ={base_classes:Array<string>,description:string,template:APITemplateType,[key:string]:Array<string>|string|APITemplateType}
|
variable: TemplateVariableType;
|
||||||
export type TemplateVariableType = {type:string,required:boolean,placeholder?:string,list:boolean,show:boolean,multiline?:boolean,value?:any,[key:string]:any}
|
[key: string]: TemplateVariableType;
|
||||||
|
};
|
||||||
|
export type APIClassType = {
|
||||||
|
base_classes: Array<string>;
|
||||||
|
description: string;
|
||||||
|
template: APITemplateType;
|
||||||
|
[key: string]: Array<string> | string | APITemplateType;
|
||||||
|
};
|
||||||
|
export type TemplateVariableType = {
|
||||||
|
type: string;
|
||||||
|
required: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
list: boolean;
|
||||||
|
show: boolean;
|
||||||
|
multiline?: boolean;
|
||||||
|
value?: any;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
export type sendAllProps = {
|
export type sendAllProps = {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
name:string,
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
viewport: Viewport;
|
viewport: Viewport;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
||||||
chatHistory:{message:string,isSend:boolean}[],
|
chatHistory: { message: string; isSend: boolean }[];
|
||||||
};
|
};
|
||||||
export type errorsTypeAPI={function:{errors:Array<string>},imports:{errors:Array<string>}}
|
export type errorsTypeAPI = {
|
||||||
export type PromptTypeAPI = {input_variables:Array<string>}
|
function: { errors: Array<string> };
|
||||||
|
imports: { errors: Array<string> };
|
||||||
|
};
|
||||||
|
export type PromptTypeAPI = { input_variables: Array<string> };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { ReactFlowInstance } from 'reactflow';
|
import { ReactFlowInstance } from "reactflow";
|
||||||
import { FlowType } from "../flow";
|
import { FlowType } from "../flow";
|
||||||
|
|
||||||
export type ChatType = {flow:FlowType,reactFlowInstance:ReactFlowInstance}
|
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
|
||||||
export type ChatMessageType = { message: string; isSend: boolean, thought?:string }
|
export type ChatMessageType = {
|
||||||
|
message: string;
|
||||||
|
isSend: boolean;
|
||||||
|
thought?: string;
|
||||||
|
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -66,16 +66,20 @@ export type FloatComponentType = {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TooltipComponentType={children:ReactElement,title:string,placement?:
|
export type TooltipComponentType = {
|
||||||
| 'bottom-end'
|
children: ReactElement;
|
||||||
| 'bottom-start'
|
title: string;
|
||||||
| 'bottom'
|
placement?:
|
||||||
| 'left-end'
|
| "bottom-end"
|
||||||
| 'left-start'
|
| "bottom-start"
|
||||||
| 'left'
|
| "bottom"
|
||||||
| 'right-end'
|
| "left-end"
|
||||||
| 'right-start'
|
| "left-start"
|
||||||
| 'right'
|
| "left"
|
||||||
| 'top-end'
|
| "right-end"
|
||||||
| 'top-start'
|
| "right-start"
|
||||||
| 'top';}
|
| "right"
|
||||||
|
| "top-end"
|
||||||
|
| "top-start"
|
||||||
|
| "top";
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
import { HomeIcon } from "@heroicons/react/24/outline";
|
import { HomeIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
export type sidebarNavigationItemType = { name: string, href: string, icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>, current: boolean }
|
export type sidebarNavigationItemType = {
|
||||||
|
name: string;
|
||||||
|
href: string;
|
||||||
|
icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||||
|
current: boolean;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
import { ChatMessageType } from './../chat/index';
|
import { ChatMessageType } from "./../chat/index";
|
||||||
import { APIClassType } from '../api/index';
|
import { APIClassType } from "../api/index";
|
||||||
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
||||||
|
|
||||||
export type FlowType = {
|
export type FlowType = {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
data: ReactFlowJsonObject;
|
data: ReactFlowJsonObject;
|
||||||
chat: Array<ChatMessageType>;
|
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType}
|
export type NodeType = {
|
||||||
export type NodeDataType = {type:string,node?:APIClassType,id:string,value:any}
|
id: string;
|
||||||
|
type?: string;
|
||||||
|
position: XYPosition;
|
||||||
|
data: NodeDataType;
|
||||||
|
};
|
||||||
|
export type NodeDataType = {
|
||||||
|
type: string;
|
||||||
|
node?: APIClassType;
|
||||||
|
id: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ export type TabsContextType = {
|
||||||
incrementNodeId: () => number;
|
incrementNodeId: () => number;
|
||||||
downloadFlow: (flow: FlowType) => void;
|
downloadFlow: (flow: FlowType) => void;
|
||||||
uploadFlow: () => void;
|
uploadFlow: () => void;
|
||||||
lockChat:boolean;
|
|
||||||
setLockChat:(prevState:boolean)=>void;
|
|
||||||
hardReset: () => void;
|
hardReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LangFlowState={ tabIndex:number, flows:FlowType[], id:number, nodeId:number }
|
export type LangFlowState={ tabIndex:number, flows:FlowType[], id:string, nodeId:number }
|
||||||
|
|
@ -14,11 +14,11 @@ import {
|
||||||
FingerPrintIcon,
|
FingerPrintIcon,
|
||||||
ScissorsIcon,
|
ScissorsIcon,
|
||||||
CircleStackIcon,
|
CircleStackIcon,
|
||||||
Squares2X2Icon
|
Squares2X2Icon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
|
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
|
||||||
import { FlowType } from "./types/flow";
|
import { FlowType } from "./types/flow";
|
||||||
var _ = require('lodash')
|
var _ = require("lodash");
|
||||||
|
|
||||||
export function classNames(...classes: Array<string>) {
|
export function classNames(...classes: Array<string>) {
|
||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
|
|
@ -78,7 +78,7 @@ export const nodeColors: {[char: string]: string} = {
|
||||||
tools: "#FF3434",
|
tools: "#FF3434",
|
||||||
memories: "#F5B85A",
|
memories: "#F5B85A",
|
||||||
advanced: "#000000",
|
advanced: "#000000",
|
||||||
chat: "#454173",
|
chat: "#198BF6",
|
||||||
thought: "#272541",
|
thought: "#272541",
|
||||||
embeddings: "#42BAA7",
|
embeddings: "#42BAA7",
|
||||||
documentloaders: "#7AAE42",
|
documentloaders: "#7AAE42",
|
||||||
|
|
@ -87,7 +87,7 @@ export const nodeColors: {[char: string]: string} = {
|
||||||
toolkits: "#DB2C2C",
|
toolkits: "#DB2C2C",
|
||||||
wrappers: "#E6277A",
|
wrappers: "#E6277A",
|
||||||
utilities: "#31A3CC",
|
utilities: "#31A3CC",
|
||||||
unknown:"#9CA3AF"
|
unknown: "#9CA3AF",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nodeNames: { [char: string]: string } = {
|
export const nodeNames: { [char: string]: string } = {
|
||||||
|
|
@ -106,10 +106,14 @@ export const nodeNames:{[char: string]: string} = {
|
||||||
wrappers: "Wrappers",
|
wrappers: "Wrappers",
|
||||||
textsplitters: "Text Splitters",
|
textsplitters: "Text Splitters",
|
||||||
utilities: "Utilities",
|
utilities: "Utilities",
|
||||||
unknown:"Unknown"
|
unknown: "Unknown",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>} = {
|
export const nodeIcons: {
|
||||||
|
[char: string]: React.ForwardRefExoticComponent<
|
||||||
|
React.SVGProps<SVGSVGElement>
|
||||||
|
>;
|
||||||
|
} = {
|
||||||
agents: RocketLaunchIcon,
|
agents: RocketLaunchIcon,
|
||||||
chains: LinkIcon,
|
chains: LinkIcon,
|
||||||
memories: CpuChipIcon,
|
memories: CpuChipIcon,
|
||||||
|
|
@ -125,7 +129,7 @@ export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent<React.SV
|
||||||
textsplitters: ScissorsIcon,
|
textsplitters: ScissorsIcon,
|
||||||
wrappers: GiftIcon,
|
wrappers: GiftIcon,
|
||||||
utilities: Squares2X2Icon,
|
utilities: Squares2X2Icon,
|
||||||
unknown:QuestionMarkCircleIcon
|
unknown: QuestionMarkCircleIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bgColors = {
|
export const bgColors = {
|
||||||
|
|
@ -270,7 +274,11 @@ export function measureTextWidth(text: string, fontSize:number) {
|
||||||
return wordWidth;
|
return wordWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function measureTextHeight(text: string, width:number, fontSize:number) {
|
export function measureTextHeight(
|
||||||
|
text: string,
|
||||||
|
width: number,
|
||||||
|
fontSize: number
|
||||||
|
) {
|
||||||
const charHeight = fontSize;
|
const charHeight = fontSize;
|
||||||
const lineHeight = charHeight * 1.5;
|
const lineHeight = charHeight * 1.5;
|
||||||
const words = text.split(" ");
|
const words = text.split(" ");
|
||||||
|
|
@ -349,8 +357,11 @@ export function isValidConnection(
|
||||||
reactFlowInstance: ReactFlowInstance
|
reactFlowInstance: ReactFlowInstance
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
sourceHandle.split('|')[0] === targetHandle.split("|")[0] ||
|
sourceHandle.split("|")[0] === targetHandle.split("|")[0] ||
|
||||||
sourceHandle.split('|').slice(2).some((t) => t === targetHandle.split("|")[0]) ||
|
sourceHandle
|
||||||
|
.split("|")
|
||||||
|
.slice(2)
|
||||||
|
.some((t) => t === targetHandle.split("|")[0]) ||
|
||||||
targetHandle.split("|")[0] === "str"
|
targetHandle.split("|")[0] === "str"
|
||||||
) {
|
) {
|
||||||
let targetNode = reactFlowInstance.getNode(target).data.node;
|
let targetNode = reactFlowInstance.getNode(target).data.node;
|
||||||
|
|
@ -376,17 +387,16 @@ export function isValidConnection(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeApiKeys(flow: FlowType): FlowType {
|
export function removeApiKeys(flow: FlowType): FlowType {
|
||||||
let cleanFLow = _.cloneDeep(flow)
|
let cleanFLow = _.cloneDeep(flow);
|
||||||
cleanFLow.data.nodes.forEach(node=>{
|
cleanFLow.data.nodes.forEach((node) => {
|
||||||
for(const key in node.data.node.template)
|
for (const key in node.data.node.template) {
|
||||||
{
|
if (key.includes("api")) {
|
||||||
if(key.includes('api')){
|
console.log(node.data.node.template[key]);
|
||||||
console.log(node.data.node.template[key])
|
node.data.node.template[key].value = "";
|
||||||
node.data.node.template[key].value = ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return cleanFLow
|
return cleanFLow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateObject<T extends Record<string, any>>(reference: T, objectToUpdate: T): T {
|
export function updateObject<T extends Record<string, any>>(reference: T, objectToUpdate: T): T {
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,78 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
const plugin = require('tailwindcss/plugin')
|
const plugin = require("tailwindcss/plugin");
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{js,ts,tsx,jsx}"],
|
content: ["./src/**/*.{js,ts,tsx,jsx}"],
|
||||||
darkMode: 'class',
|
darkMode: "class",
|
||||||
important: true,
|
important: true,
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
borderColor: {
|
||||||
|
"red-outline": "rgba(255, 0, 0, 0.8)",
|
||||||
|
"green-outline": "rgba(72, 187, 120, 0.7)",
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
"red-outline": "0 0 5px rgba(255, 0, 0, 0.5)",
|
||||||
|
"green-outline": "0 0 5px rgba(72, 187, 120, 0.7)",
|
||||||
|
},
|
||||||
|
|
||||||
|
animation: {
|
||||||
|
"pulse-green": "pulseGreen 1s linear",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
pulseGreen: {
|
||||||
|
"0%": { boxShadow: "0 0 0 0 rgba(72, 187, 120, 0.7)" },
|
||||||
|
"100%": { boxShadow: "0 0 0 10px rgba(72, 187, 120, 0)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require("@tailwindcss/forms")({
|
require("@tailwindcss/forms")({
|
||||||
strategy: 'class', // only generate classes
|
strategy: "class", // only generate classes
|
||||||
}),
|
}),
|
||||||
plugin(function ({ addUtilities }) {
|
plugin(function ({ addUtilities }) {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
'.scrollbar-hide': {
|
".scrollbar-hide": {
|
||||||
/* IE and Edge */
|
/* IE and Edge */
|
||||||
'-ms-overflow-style': 'none',
|
"-ms-overflow-style": "none",
|
||||||
/* Firefox */
|
/* Firefox */
|
||||||
'scrollbar-width': 'none',
|
"scrollbar-width": "none",
|
||||||
/* Safari and Chrome */
|
/* Safari and Chrome */
|
||||||
'&::-webkit-scrollbar': {
|
"&::-webkit-scrollbar": {
|
||||||
display: 'none'
|
display: "none",
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'.arrow-hide':{
|
|
||||||
'&::-webkit-inner-spin-button':{
|
|
||||||
'-webkit-appearance': 'none',
|
|
||||||
'margin': 0
|
|
||||||
},
|
},
|
||||||
'&::-webkit-outer-spin-button':{
|
".arrow-hide": {
|
||||||
'-webkit-appearance': 'none',
|
"&::-webkit-inner-spin-button": {
|
||||||
'margin': 0
|
"-webkit-appearance": "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
"&::-webkit-outer-spin-button": {
|
||||||
|
"-webkit-appearance": "none",
|
||||||
|
margin: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'.password':{
|
'.password':{
|
||||||
"-webkit-text-security":"disc",
|
"-webkit-text-security":"disc",
|
||||||
"font-family": "text-security-disc"
|
"font-family": "text-security-disc"
|
||||||
|
|
||||||
|
},
|
||||||
|
'.custom-scroll':{
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
'width': '8px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-track': {
|
||||||
|
'backgroundColor': '#f1f1f1',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-thumb': {
|
||||||
|
'backgroundColor': '#ccc',
|
||||||
|
'borderRadius': '999px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-thumb:hover': {
|
||||||
|
'backgroundColor': '#bbb'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}),require('@tailwindcss/line-clamp')
|
}),require('@tailwindcss/line-clamp')
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import AsyncGenerator
|
||||||
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
@ -21,6 +25,15 @@ def get_text():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
async def async_client() -> AsyncGenerator:
|
||||||
|
from langflow.main import create_app
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
async with AsyncClient(app=app, base_url="http://testserver") as client:
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
# Create client fixture for FastAPI
|
# Create client fixture for FastAPI
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def client():
|
def client():
|
||||||
|
|
@ -30,3 +43,37 @@ def client():
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
def get_graph(_type="basic"):
|
||||||
|
"""Get a graph from a json file"""
|
||||||
|
from langflow.graph.graph import Graph
|
||||||
|
|
||||||
|
if _type == "basic":
|
||||||
|
path = pytest.BASIC_EXAMPLE_PATH
|
||||||
|
elif _type == "complex":
|
||||||
|
path = pytest.COMPLEX_EXAMPLE_PATH
|
||||||
|
elif _type == "openapi":
|
||||||
|
path = pytest.OPENAPI_EXAMPLE_PATH
|
||||||
|
|
||||||
|
with open(path, "r") as f:
|
||||||
|
flow_graph = json.load(f)
|
||||||
|
data_graph = flow_graph["data"]
|
||||||
|
nodes = data_graph["nodes"]
|
||||||
|
edges = data_graph["edges"]
|
||||||
|
return Graph(nodes, edges)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_graph():
|
||||||
|
return get_graph()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def complex_graph():
|
||||||
|
return get_graph("complex")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def openapi_graph():
|
||||||
|
return get_graph("openapi")
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ def test_initialize_agent(client: TestClient):
|
||||||
agents = json_response["agents"]
|
agents = json_response["agents"]
|
||||||
|
|
||||||
initialize_agent = agents["initialize_agent"]
|
initialize_agent = agents["initialize_agent"]
|
||||||
assert initialize_agent["base_classes"] == ["AgentExecutor"]
|
assert initialize_agent["base_classes"] == ["AgentExecutor", "function"]
|
||||||
template = initialize_agent["template"]
|
template = initialize_agent["template"]
|
||||||
|
|
||||||
assert template["agent"] == {
|
assert template["agent"] == {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from langflow.cache.utils import PREFIX, save_cache
|
from langflow.cache.base import PREFIX, save_cache
|
||||||
from langflow.interface.run import load_langchain_object
|
from langflow.interface.run import load_langchain_object
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
81
tests/test_cache_manager.py
Normal file
81
tests/test_cache_manager.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import pytest
|
||||||
|
from PIL import Image
|
||||||
|
import pandas as pd
|
||||||
|
from io import StringIO
|
||||||
|
from langflow.cache.manager import CacheManager
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def cache_manager():
|
||||||
|
return CacheManager()
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_manager_attach_detach_notify(cache_manager):
|
||||||
|
observer_called = False
|
||||||
|
|
||||||
|
def observer():
|
||||||
|
nonlocal observer_called
|
||||||
|
observer_called = True
|
||||||
|
|
||||||
|
cache_manager.attach(observer)
|
||||||
|
cache_manager.notify()
|
||||||
|
|
||||||
|
assert observer_called
|
||||||
|
|
||||||
|
observer_called = False
|
||||||
|
cache_manager.detach(observer)
|
||||||
|
cache_manager.notify()
|
||||||
|
|
||||||
|
assert not observer_called
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_manager_client_context(cache_manager):
|
||||||
|
with cache_manager.set_client_id("client1"):
|
||||||
|
cache_manager.add("foo", "bar", "string")
|
||||||
|
assert cache_manager.get("foo") == {
|
||||||
|
"obj": "bar",
|
||||||
|
"type": "string",
|
||||||
|
"extension": "str",
|
||||||
|
}
|
||||||
|
|
||||||
|
with cache_manager.set_client_id("client2"):
|
||||||
|
cache_manager.add("baz", "qux", "string")
|
||||||
|
assert cache_manager.get("baz") == {
|
||||||
|
"obj": "qux",
|
||||||
|
"type": "string",
|
||||||
|
"extension": "str",
|
||||||
|
}
|
||||||
|
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
cache_manager.get("foo")
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_manager_add_pandas(cache_manager):
|
||||||
|
df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
|
||||||
|
|
||||||
|
with cache_manager.set_client_id("client1"):
|
||||||
|
cache_manager.add_pandas("test_df", df)
|
||||||
|
cached_df = cache_manager.get("test_df")
|
||||||
|
assert cached_df["type"] == "pandas"
|
||||||
|
assert cached_df["extension"] == "csv"
|
||||||
|
read_df = pd.read_csv(StringIO(cached_df["obj"]), index_col=0)
|
||||||
|
pd.testing.assert_frame_equal(df, read_df)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_manager_add_image(cache_manager):
|
||||||
|
img = Image.new("RGB", (50, 50), color="red")
|
||||||
|
|
||||||
|
with cache_manager.set_client_id("client1"):
|
||||||
|
cache_manager.add_image("test_image", img)
|
||||||
|
cached_img = cache_manager.get("test_image")
|
||||||
|
assert cached_img["type"] == "image"
|
||||||
|
assert cached_img["extension"] == "png"
|
||||||
|
assert isinstance(cached_img["obj"], Image.Image)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_manager_get_last(cache_manager):
|
||||||
|
with cache_manager.set_client_id("client1"):
|
||||||
|
cache_manager.add("foo", "bar", "string")
|
||||||
|
cache_manager.add("baz", "qux", "string")
|
||||||
|
last_item = cache_manager.get_last()
|
||||||
|
assert last_item == {"obj": "qux", "type": "string", "extension": "str"}
|
||||||
|
|
@ -20,7 +20,12 @@ def test_conversation_chain(client: TestClient):
|
||||||
chain = chains["ConversationChain"]
|
chain = chains["ConversationChain"]
|
||||||
|
|
||||||
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
||||||
assert set(chain["base_classes"]) == {"LLMChain", "ConversationChain", "Chain"}
|
assert set(chain["base_classes"]) == {
|
||||||
|
"function",
|
||||||
|
"LLMChain",
|
||||||
|
"ConversationChain",
|
||||||
|
"Chain",
|
||||||
|
}
|
||||||
template = chain["template"]
|
template = chain["template"]
|
||||||
assert template["memory"] == {
|
assert template["memory"] == {
|
||||||
"required": False,
|
"required": False,
|
||||||
|
|
@ -96,7 +101,7 @@ def test_llm_chain(client: TestClient):
|
||||||
chain = chains["LLMChain"]
|
chain = chains["LLMChain"]
|
||||||
|
|
||||||
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
||||||
assert set(chain["base_classes"]) == {"LLMChain", "Chain"}
|
assert set(chain["base_classes"]) == {"function", "LLMChain", "Chain"}
|
||||||
template = chain["template"]
|
template = chain["template"]
|
||||||
assert template["memory"] == {
|
assert template["memory"] == {
|
||||||
"required": False,
|
"required": False,
|
||||||
|
|
@ -154,7 +159,7 @@ def test_llm_checker_chain(client: TestClient):
|
||||||
chain = chains["LLMCheckerChain"]
|
chain = chains["LLMCheckerChain"]
|
||||||
|
|
||||||
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
||||||
assert set(chain["base_classes"]) == {"LLMCheckerChain", "Chain"}
|
assert set(chain["base_classes"]) == {"function", "LLMCheckerChain", "Chain"}
|
||||||
template = chain["template"]
|
template = chain["template"]
|
||||||
assert template["memory"] == {
|
assert template["memory"] == {
|
||||||
"required": False,
|
"required": False,
|
||||||
|
|
@ -231,7 +236,7 @@ def test_llm_math_chain(client: TestClient):
|
||||||
chain = chains["LLMMathChain"]
|
chain = chains["LLMMathChain"]
|
||||||
|
|
||||||
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
||||||
assert set(chain["base_classes"]) == {"LLMMathChain", "Chain"}
|
assert set(chain["base_classes"]) == {"function", "LLMMathChain", "Chain"}
|
||||||
template = chain["template"]
|
template = chain["template"]
|
||||||
assert template["memory"] == {
|
assert template["memory"] == {
|
||||||
"required": False,
|
"required": False,
|
||||||
|
|
@ -310,6 +315,7 @@ def test_series_character_chain(client: TestClient):
|
||||||
|
|
||||||
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
# Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects
|
||||||
assert set(chain["base_classes"]) == {
|
assert set(chain["base_classes"]) == {
|
||||||
|
"function",
|
||||||
"LLMChain",
|
"LLMChain",
|
||||||
"BaseCustomChain",
|
"BaseCustomChain",
|
||||||
"Chain",
|
"Chain",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
from typing import Type, Union
|
from typing import Type, Union
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -15,7 +14,7 @@ from langflow.graph.nodes import (
|
||||||
ToolNode,
|
ToolNode,
|
||||||
WrapperNode,
|
WrapperNode,
|
||||||
)
|
)
|
||||||
from langflow.interface.run import get_result_and_thought_using_graph
|
from langflow.interface.run import get_result_and_steps
|
||||||
from langflow.utils.payload import build_json, get_root_node
|
from langflow.utils.payload import build_json, get_root_node
|
||||||
|
|
||||||
# Test cases for the graph module
|
# Test cases for the graph module
|
||||||
|
|
@ -24,38 +23,6 @@ from langflow.utils.payload import build_json, get_root_node
|
||||||
# BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH
|
# BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH
|
||||||
|
|
||||||
|
|
||||||
def get_graph(_type="basic"):
|
|
||||||
"""Get a graph from a json file"""
|
|
||||||
if _type == "basic":
|
|
||||||
path = pytest.BASIC_EXAMPLE_PATH
|
|
||||||
elif _type == "complex":
|
|
||||||
path = pytest.COMPLEX_EXAMPLE_PATH
|
|
||||||
elif _type == "openapi":
|
|
||||||
path = pytest.OPENAPI_EXAMPLE_PATH
|
|
||||||
|
|
||||||
with open(path, "r") as f:
|
|
||||||
flow_graph = json.load(f)
|
|
||||||
data_graph = flow_graph["data"]
|
|
||||||
nodes = data_graph["nodes"]
|
|
||||||
edges = data_graph["edges"]
|
|
||||||
return Graph(nodes, edges)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def basic_graph():
|
|
||||||
return get_graph()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def complex_graph():
|
|
||||||
return get_graph("complex")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def openapi_graph():
|
|
||||||
return get_graph("openapi")
|
|
||||||
|
|
||||||
|
|
||||||
def get_node_by_type(graph, node_type: Type[Node]) -> Union[Node, None]:
|
def get_node_by_type(graph, node_type: Type[Node]) -> Union[Node, None]:
|
||||||
"""Get a node by type"""
|
"""Get a node by type"""
|
||||||
return next((node for node in graph.nodes if isinstance(node, node_type)), None)
|
return next((node for node in graph.nodes if isinstance(node, node_type)), None)
|
||||||
|
|
@ -441,7 +408,7 @@ def test_get_result_and_thought(basic_graph):
|
||||||
# now build again and check if FakeListLLM was used
|
# now build again and check if FakeListLLM was used
|
||||||
|
|
||||||
# Get the result and thought
|
# Get the result and thought
|
||||||
result, thought = get_result_and_thought_using_graph(langchain_object, message)
|
result, thought = get_result_and_steps(langchain_object, message)
|
||||||
# The result should be a str
|
# The result should be a str
|
||||||
assert isinstance(result, str)
|
assert isinstance(result, str)
|
||||||
# The thought should be a Thought
|
# The thought should be a Thought
|
||||||
|
|
|
||||||
46
tests/test_websocket.py
Normal file
46
tests/test_websocket.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
|
def test_websocket_connection(client: TestClient):
|
||||||
|
with client.websocket_connect("/chat/test_client") as websocket:
|
||||||
|
assert websocket.scope["client"] == ["testclient", 50000]
|
||||||
|
assert websocket.scope["path"] == "/chat/test_client"
|
||||||
|
|
||||||
|
|
||||||
|
def test_chat_history(client: TestClient):
|
||||||
|
# Mock the process_graph function to return a specific value
|
||||||
|
with patch("langflow.api.chat_manager.process_graph") as mock_process_graph:
|
||||||
|
mock_process_graph.return_value = ("Hello, I'm a mock response!", "")
|
||||||
|
|
||||||
|
with client.websocket_connect("/chat/test_client") as websocket:
|
||||||
|
# First message should be the history
|
||||||
|
history = websocket.receive_json()
|
||||||
|
assert history == [] # Empty history
|
||||||
|
# Send a message
|
||||||
|
payload = {"message": "Hello"}
|
||||||
|
websocket.send_json(json.dumps(payload))
|
||||||
|
|
||||||
|
# Receive the response from the server
|
||||||
|
response = websocket.receive_json()
|
||||||
|
assert response == {
|
||||||
|
"is_bot": True,
|
||||||
|
"message": None,
|
||||||
|
"type": "start",
|
||||||
|
"intermediate_steps": "",
|
||||||
|
"files": [],
|
||||||
|
}
|
||||||
|
# Send another message
|
||||||
|
payload = {"message": "How are you?"}
|
||||||
|
websocket.send_json(json.dumps(payload))
|
||||||
|
|
||||||
|
# Receive the response from the server
|
||||||
|
response = websocket.receive_json()
|
||||||
|
assert response == {
|
||||||
|
"is_bot": True,
|
||||||
|
"message": "Hello, I'm a mock response!",
|
||||||
|
"type": "end",
|
||||||
|
"intermediate_steps": "",
|
||||||
|
"files": [],
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue