✨ Release: Chat and Cache Improvements, Websocket Integration, and Dark Mode Enhancements 🌙 (#220)

This release brings a series of new features and improvements,
including:
- 💬 Chat and Cache Improvements:
- Refactored cache-related functions and moved them to a new base.py
module
  - Simplified the Chat component and added chat history support
  - Implemented the ability to send file responses in chat
  - Real-time Node validation for improved user experience
- CacheManager was added to share data between tools and display them in
the chat.
- 🌐 Websocket Integration:
  - Implemented websocket connection for the chat (WIP)
- 🌙 Dark Mode Enhancements:
  - Fixed dark mode for dropdown components
  - Improved dark mode styling for the chat interface
  - Updated thought icon for dark mode
- 🚀 Other Improvements:
  - Migrated chat logic to chat modal
  - Implemented unique IDs for flow management
  - Sorted sidebar items for better organization
  - Removed unused imports and optimized codebase

This release enhances the overall user experience and streamlines chat
and cache functionalities, ensuring a smoother and more efficient
workflow. 🎉
This commit is contained in:
anovazzi1 2023-04-29 14:03:41 -03:00 • committed by GitHub
commit 5c3f7acd7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 3773 additions and 1814 deletions

View file

@ -0,0 +1,15 @@
# LangFlow Demo Codespace Readme
These instructions will walk you through the process of running a LangFlow demo via GitHub Codespaces.
## Setup
### Create a Codespace in GitHub
To setup the demo, simply navigate to the Langflow repo, click the "+" button, and select "Create new Codespace". This will automatically create a new codespace in your browser, which you can use for the demo.
### Wait for everything to install
After the codespace is opened, you should see a new Terminal window in VS Code where langflow is installed. Once the install completes, `langflow` will launch the webserver and your application will be available via devcontainer port.
Note: VS Code should prompt you with a button to push once the port is available.

View file

@ -0,0 +1,32 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/universal
{
"name": "LangChain Demo Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:3.10",
"features": {
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {},
"ghcr.io/devcontainers/features/node": {}
},
"customizations": {
"vscode": {
"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"eamodio.gitlens"
]
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pipx install 'langflow>=0.0.33' && langflow --host 0.0.0.0"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View file

@ -1,11 +1,12 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/universal // README at: https://github.com/devcontainers/templates/tree/main/src/universal
{ {
"name": "Default Linux Universal", "name": "LangChain Dev Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/universal:2-linux", "image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": { "features": {
"ghcr.io/devcontainers/features/aws-cli:1": {} "ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {}
}, },
"customizations": { "customizations": {
"vscode": {"extensions": [ "vscode": {"extensions": [
@ -15,7 +16,7 @@
"sourcery.sourcery", "sourcery.sourcery",
"eamodio.gitlens" "eamodio.gitlens"
]} ]}
} },
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
// "features": {}, // "features": {},
@ -24,7 +25,7 @@
// "forwardPorts": [], // "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a", "postCreateCommand": "poetry install"
// Configure tool-specific properties. // Configure tool-specific properties.
// "customizations": {}, // "customizations": {},

View file

@ -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

View file

@ -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
View file

@ -0,0 +1,6 @@
{
"name": "reactFlow",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

488
poetry.lock generated
View file

@ -551,77 +551,77 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]] [[package]]
name = "clickhouse-connect" name = "clickhouse-connect"
version = "0.5.20" 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.20.tar.gz", hash = "sha256:5fc9a84849f3c3b6f6928b45a0df17fa63ebcf4e518b3a48ec70720957e18683"}, {file = "clickhouse-connect-0.5.22.tar.gz", hash = "sha256:cc8f01ff88d30b118cf2efc33ac34c89a1e332900396da249df7d0f36ac199d7"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c29cf8b2c90eed6b83366c13ab5ad471ff6ef2e334f35818729330854b9747ac"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09b461047dfc18c9b6ebfc94fb6022c5fc0ee7a343e1e691ee70294dce532909"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c03ded1b006fa2cf8f7d823f0ff9c6d294e442a123c96ca2a9ebc4b293bfb7f"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ac953ecd231d311f0c1a0576f164c1e6034358eb28cddcf5a2304d570a211d"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb0024160412d9c6079fa6982cb29abda4db8412b4f63918de7a1bde1dcb7aa"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02629304d233df14cd7df5dfb37dd5f87feaa95a7506f984a565e3b8fb185210"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:170bd258d21bc828557f8a55f23affe22cc4e671c93f645a6316ef874e359f8e"}, {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.20-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc70fee875fdba42c0a6f519fa376659a08253fd36d188b8b304f4ccda572177"}, {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.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:18837e06846797db475b6aee13f03928fb169f64d0efb268e2bb04e015990b5b"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5ad201756d2d2d7269c747835ba4f24da3d744bc2801e813f8b33fec897ffe10"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:76f7a7d2d41377e6f382a7ada825be594c2d316481f3194bfffd025727633258"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c577bcc2b7e1f71073cf4f87600fd6f06ebfc9cd6ae8a98b28ebed9ebcfb80dd"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3bac453f1199af29ec7292d2fd2a8cb0cc0e6692bec9c9da50ce5aec10ff0339"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83b4efc0f7db5b02348a97dba2b2fcd089d6eb45457049d49ed795a084dd7832"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-win32.whl", hash = "sha256:14983562d2687b18d03a35f27b4e7f28cf013c280ff4fee726501e03bae7528d"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-win32.whl", hash = "sha256:83b613dcab009ff1b18d5bd3cb9e8c2fe2cdd6b67cd1309389c04f11d3621713"},
{file = "clickhouse_connect-0.5.20-cp310-cp310-win_amd64.whl", hash = "sha256:3d618a9c15ee4d2facc7a79e59a646262da64e6ec39d2a1ac6a68167d52266bf"}, {file = "clickhouse_connect-0.5.22-cp310-cp310-win_amd64.whl", hash = "sha256:6954b98df0c2624f9933530eabdc1ad3bf143633e2c5328a3f1e167344f8a9c8"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bdfb74ba2bf5157230f576e16c7d708f20ffa7e4b19c54288d7db2b55ebcd17"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45972e1a37269b3d9ecbcd3eee4f7cbed4a1311d867ce45a6a30a707b31cb6b6"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fce7e54ad14b732479c5630948324f7088c3092a74a2442bf015a7cab4bc0a41"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436eadecdcf8685a5d1f8654b1a7f689f3933db2eae799f1ec6f183e91f2a5a9"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e6a2b6d123f5de362d49f079c509a0a43cfbaecae0130c860706ef738af12b7"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9610d103f42a45362d1c95dc7ef356c5da86359538f9d30e6298f83d407bb643"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a9391128387013755de8e420bb7e17c6c809f77ca3233fdc966a1df023fa85d"}, {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.20-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1df976816913675b46134e8dd9dee2cf315cc4bf42e258211f8036099b8fc280"}, {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.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f1ddeb651bc75b87ec5fa1fbe17fe3a589d00f42cad76d6e64918067f5025798"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8a3ad2a4d216572067611add9ba285b1aaaf464cd67d57a1d0980c2491bde361"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:caf60b4bfb7214d80455137eee45ca0943a370885d65f4298fafde0d431e837a"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6c6beacd96bf7eb0a0eb9e33c1d92b8b9dec642916a630332cc599f6c7441064"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c0bdcb72607244dc920f543ee6363a6094e836770aaac07f20556936af85813"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c82b5ea7a2814e19b01c192a32ae705d462be7c27fb8759623b6c03e117d4635"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-win32.whl", hash = "sha256:cc3f77df2b1cab2aa99b59f529aead2cc96beac1639ed18f7fd8dba392957623"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-win32.whl", hash = "sha256:1cc5fde42bbad4739a6c308bc75fba6d9b817529149447f4393c4092bce01aca"},
{file = "clickhouse_connect-0.5.20-cp311-cp311-win_amd64.whl", hash = "sha256:e44c3b7e40402ce0650f69cbc31f2f503073e2bec9f2b31befbd823150f2431d"}, {file = "clickhouse_connect-0.5.22-cp311-cp311-win_amd64.whl", hash = "sha256:f82269d18fdc25a2d92378ffd5ecb90658cb0748d14819f129a501c660cbdbe8"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ba78e7d270d78f9559e4a836c6c4f55ab54d9f2b6505c0d05db6260e8e2a4f6a"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0d135ae71723b28f3e250ff030583fd6f5f16af19b7d14bf6bec98be630c2e3b"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e8924824cd19b739cc920d867bf291a31a5da406637e0c575f6eb961cfb0557"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8fc12861461a5b88fd8826c500300ef48b18fa820b9c94be9233198475de67a"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:672c260c471fd18a87a4f5130e6d72590cd4f57289669c58feff5be934810d28"}, {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.20-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69887898f8f5ea6e70c30aa51c756f8a752ef0eb1df747d4aec7b7d10de5e103"}, {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.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c4da55465a52e0e440772e289e6959cc6acbb2efa0561a7ea4f9a7108159958d"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4beb12c6ca9bd5cbdf6661750d7d20b532abb360cd9e93d684114a2e4c0f9d"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2087b64ab47969e603cd9735e7c0433bdf15c6d83025abd00c50ca9a617ed39b"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4dcda1e33e64537d7b3969c0dec15bdf67a56a5f0801e85f7fd7cb0717a4ce45"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:28b72cabb1d4fc3f04392ed1f654bd925b6c950305869971186f73b2d13d835a"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9b1627800be5148ceee872de67b6483f89c89557b9032ea3c09b68b5fa5e1422"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-win32.whl", hash = "sha256:a481e13216de227aa624449f5f6ead9e51fe7c8f18bbd783c41e4b396919fa08"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-win32.whl", hash = "sha256:c0cc27d041c994d61b3a5065734b65c0fe9d578fb6dcf614e46f1893b4259e9f"},
{file = "clickhouse_connect-0.5.20-cp37-cp37m-win_amd64.whl", hash = "sha256:c1dc77bdc15240d6d4d375e098c77403aeabbc6f8b1c2ce524f4389a5d8c6d74"}, {file = "clickhouse_connect-0.5.22-cp37-cp37m-win_amd64.whl", hash = "sha256:5ea78955374abe704c10f0fcb012a954087d45513e3abceefd8d50311b91e72f"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4fe527b6b4306cad58dde934493d5f018166f78f5914f6abf6ed93750ca7ecbd"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f96ad0d9ab681b2c9d5b1ea622f1446c30267639dce452cf09733af8b70cc565"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c07b9ca21d302e843aa8c031ef15f85c86280c5730858edfe4eeb952d3991d1d"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c3bc819b930755599d9ef9d58f353f1b6e4013115b793870768de8027738846"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e427b3cd1f611bcb8315ea9bc17f0329329ca21043f1a5ef068e2903457b9b"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7125ad73427597fb0aecf9983eb69870242788dfb134dfec2e5bc24795b757ce"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9319037b437c8d1297b00d8bc3f92239cc2296db409b5bfc2ff22b05c5f3a26f"}, {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.20-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8c3c533fd2baff653dc40e7b88ca86ce9b8d0923c34fb33ce5ce1d1b7370fe6"}, {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.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c850bc0cf5a00bd144202a6926b646baa60fb4e6c449b62d46c230c548ec760a"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:621e2e49af7d8e62dca25eb0663db493af4b26fd25e7c3a4b30a4fdf51b08c87"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:632922c90cd71fcb8e1b7e6e2a9b4487dee2e67b91846dc1778cfd9d5198d047"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:00354463177fcf15ce075205b54ae2f8321f72fa5511e4894bfcc4aab430e6f4"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a6c7733b5754ea048bd7928b0cce6625d71c709570c97f1819ba36054850d915"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8d93b85eb6bc809a82b592bbe1244b63c2708478eb751a65f250203e6afa72e7"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-win32.whl", hash = "sha256:738b35e061a3c665e9a099a3b5cb50338bed89a6eee3ce29190cd525a1bc1892"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-win32.whl", hash = "sha256:56fdf98ccdd1a7536b3066479b832a339285e0c806c056e7192547d31d14cbb2"},
{file = "clickhouse_connect-0.5.20-cp38-cp38-win_amd64.whl", hash = "sha256:58da16eac95126d441f106d27c8e3ae931fcc784f263d7d916b5a8086bdcf757"}, {file = "clickhouse_connect-0.5.22-cp38-cp38-win_amd64.whl", hash = "sha256:ef0e233fcccd48398d378e631ca4582725e816e1cd87e38352703969da72815f"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9c57f6958021ec0b22eabaa02e567df3ff5f85fdfd9d052e3aface655bdf3d1"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e00a47fb565ed0f24e048457c67b4ab63676be5b0fe2334a7c43938e6d0a4b9"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9c9a2de183a85fc32ef70973cfad5c9af2a8d73733aa30b9523c1400b813c13"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:44dc02467f3a9c0d91174d09fcdc4c33ed03aa43cdacbb34fd5bb16db3d1b380"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50fd663b132c4edc1fc5dae33c5cbd2538dd2e0c94bd9fff5e98ca3ca12059a2"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f020238fbb7e9e0800ef0e3661dc220ef7427a5942732797b189fb5c563274"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a98b165fa2c8420e5219db244f0790b13f401a0932c6a7d5e5c1a959a26b80"}, {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.20-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9686bd02a16e3b6cbf976b2476e54bc7caaf1a95fd129fd44b2692d082dfcef6"}, {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.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d01a51871dde0cd0d24efafd61ab27c57293a0456a26ec7e8a5a585623239ab1"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:24292e0bb742d328c66f38838c3fe1654552e2817d470f8ad040a985b8d1a799"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2c1096ebad10964fcdd646f41228accf182d24b066cefd18d9b33f021e3017cd"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4c7baf4b535e9cb063568616aaac183c8bb611575ed515d1b55794122feda60b"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1f0407cc9ea9d2cf51edfe59993c536c256ae54c40c6b36fb7f738edd48f51b5"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f413cf98d7d99087be993573e8e0d441131fb8d30b62a50af5b6a75a6f7af66"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-win32.whl", hash = "sha256:184f7c119c9725b25ecaa3011420de8dc06530999653508a983b27c90894146c"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-win32.whl", hash = "sha256:76087148e9f7632321b39d582f13050820290d8d233721413ebd131d12e70e76"},
{file = "clickhouse_connect-0.5.20-cp39-cp39-win_amd64.whl", hash = "sha256:f7d2cbde4543cccddef8465afed221f81095eec3d3b763d7570c22ae99819ab4"}, {file = "clickhouse_connect-0.5.22-cp39-cp39-win_amd64.whl", hash = "sha256:6b69c9bd695e8129452bb9f0456f6e16caf12592eec3d4f677e626bdde42724a"},
{file = "clickhouse_connect-0.5.20-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f83a6e61b9832fc9184bf67e3f7bc041f3b940c066b8162bfadf02aa484b1c4"}, {file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:719fb63118147ae5c03db85bc5fa0b6b30ab1e51da7eb64bcbf5b7d03dc405b5"},
{file = "clickhouse_connect-0.5.20-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61b22a7038553813a8f5432cd3b1e57b6d94c629d599d775f57c64c4700a5df"}, {file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a04b409b7907cc0f9e70329f6f5ff42fb3ad5a345db9b56a05f55194071d0f1"},
{file = "clickhouse_connect-0.5.20-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbae752fadbd9fa9390f2246c5ce6e75a91225d03adb3451beb49bd3f1ea48f0"}, {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.20-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9da5c94be2255d6e07e255899411a5e009723f331d90359e5b21c66e8007630"}, {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.20-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:205a3dc992548891150d42856e418398d135d9dfa5f30f53bb7c3633d6b449d0"}, {file = "clickhouse_connect-0.5.22-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:13ed25c3173af67642cc1553e8bc6583a870227b685bbea5897efc3bc78addfd"},
{file = "clickhouse_connect-0.5.20-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5e0c42adc692f2fb285f5f898d166cf4ed9b5779e5f3effab8f612cd3362f004"}, {file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4f745384cd6d02d00c5186f9c36d2cd27634fd41d3de4705a7de59d47d6976d0"},
{file = "clickhouse_connect-0.5.20-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e8a2d9dfbfd7c3075f5d1c7011e32b5b62853000d16f93684fa69d8b8979a04"}, {file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b9cb52f22bb438326503d20e54d68b378076e842abc5345da0b1822a01a309b"},
{file = "clickhouse_connect-0.5.20-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f8bb09db27aba694193073137bd69f8404e53c2ee80f2dbf41c829c081175a"}, {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.20-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e07d91e3bcaf3989d698a4d9ad9b36f1dcf357673cc4c44a6663ab78581066"}, {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.20-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7832b2c4c4c4b316258bd078b54a82c84aeccd62c917eb986059de738b13b56b"}, {file = "clickhouse_connect-0.5.22-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a924e87f568eeac25dc39b97bba09ecf9fca2e57bf414bc3c5f2282358af7a67"},
{file = "clickhouse_connect-0.5.20-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e7dad00ce8df847f896c50aa9644c685259a995a15823fec788348e736fb893"}, {file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:89a0f649eb49398708c632336bcbc2f762e26e258a693a844570c003407d9869"},
{file = "clickhouse_connect-0.5.20-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34b6c4f16d8b4c5c458504da64e87fb2ec1390640ed7345bf051cfbba18526f4"}, {file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8abf10e0f5cb913df5d80c609cb95850aae436d31095208c3d938df2c4d0524a"},
{file = "clickhouse_connect-0.5.20-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ce3896158cbac451253bc3632140920a57bb775a82d68370de9ace97ce96a8"}, {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.20-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f1e552c4efdab1937ff824f062561fe0b6901044ea06b373a35c8a1a679cea"}, {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.20-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05d1cfd70fd90b5d7cdb4e93d603d34f74d34327811e8f573fbbd87838cfd4a3"}, {file = "clickhouse_connect-0.5.22-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2f444b4af4327747e43fe8abdd31af64e9fef425c7554a6d04ebca4acd114c71"},
] ]
[package.dependencies] [package.dependencies]
@ -701,6 +701,73 @@ lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (
test = ["pytest"] test = ["pytest"]
typing = ["mypy (>=0.990)"] typing = ["mypy (>=0.990)"]
[[package]]
name = "coverage"
version = "7.2.4"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "coverage-7.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e5eedde6e6e241ec3816f05767cc77e7456bf5ec6b373fb29917f0990e2078f"},
{file = "coverage-7.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c6c6e3b8fb6411a2035da78d86516bfcfd450571d167304911814407697fb7a"},
{file = "coverage-7.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7668a621afc52db29f6867e0e9c72a1eec9f02c94a7c36599119d557cf6e471"},
{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.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.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:700bc9fb1074e0c67c09fe96a803de66663830420781df8dc9fb90d7421d4ccb"},
{file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ac4861241e693e21b280f07844ae0e0707665e1dfcbf9466b793584984ae45c4"},
{file = "coverage-7.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3d6f3c5b6738a494f17c73b4aa3aa899865cc33a74aa85e3b5695943b79ad3ce"},
{file = "coverage-7.2.4-cp310-cp310-win32.whl", hash = "sha256:437da7d2fcc35bf45e04b7e9cfecb7c459ec6f6dc17a8558ed52e8d666c2d9ab"},
{file = "coverage-7.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:1d3893f285fd76f56651f04d1efd3bdce251c32992a64c51e5d6ec3ba9e3f9c9"},
{file = "coverage-7.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a17bf32e9e3333d78606ac1073dd20655dc0752d5b923fa76afd3bc91674ab4"},
{file = "coverage-7.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f7ffdb3af2a01ce91577f84fc0faa056029fe457f3183007cffe7b11ea78b23c"},
{file = "coverage-7.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89e63b38c7b888e00fd42ce458f838dccb66de06baea2da71801b0fc9070bfa0"},
{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.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.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bc47015fc0455753e8aba1f38b81b731aaf7f004a0c390b404e0fcf1d6c1d72f"},
{file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c122d120c11a236558c339a59b4b60947b38ac9e3ad30a0e0e02540b37bf536"},
{file = "coverage-7.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:50fda3d33b705b9c01e3b772cfa7d14de8aec2ec2870e4320992c26d057fde12"},
{file = "coverage-7.2.4-cp311-cp311-win32.whl", hash = "sha256:ab08af91cf4d847a6e15d7d5eeae5fead1487caf16ff3a2056dbe64d058fd246"},
{file = "coverage-7.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:876e4ef3eff00b50787867c5bae84857a9af4c369a9d5b266cd9b19f61e48ef7"},
{file = "coverage-7.2.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3fc9cde48de956bfbacea026936fbd4974ff1dc2f83397c6f1968f0142c9d50b"},
{file = "coverage-7.2.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12bc9127c8aca2f7c25c9acca53da3db6799b2999b40f28c2546237b7ea28459"},
{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.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.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f37ae1804596f13d811e0247ffc8219f5261b3565bdf45fcbb4fc091b8e9ff35"},
{file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdee9a77fd0ce000781680b6a1f4b721c567f66f2f73a49be1843ff439d634f3"},
{file = "coverage-7.2.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b65a6a5484b7f2970393d6250553c05b2ede069e0e18abe907fdc7f3528252e"},
{file = "coverage-7.2.4-cp37-cp37m-win32.whl", hash = "sha256:1a3e8697cb40f28e5bcfb6f4bda7852d96dbb6f6fd7cc306aba4ae690c9905ab"},
{file = "coverage-7.2.4-cp37-cp37m-win_amd64.whl", hash = "sha256:4078939c4b7053e14e87c65aa68dbed7867e326e450f94038bfe1a1b22078ff9"},
{file = "coverage-7.2.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:603a2b172126e3b08c11ca34200143089a088cd0297d4cfc4922d2c1c3a892f9"},
{file = "coverage-7.2.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72751d117ceaad3b1ea3bcb9e85f5409bbe9fb8a40086e17333b994dbccc0718"},
{file = "coverage-7.2.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f19ba9301e6fb0b94ba71fda9a1b02d11f0aab7f8e2455122a4e2921b6703c2f"},
{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.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.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:39747afc854a7ee14e5e132da7db179d6281faf97dc51e6d7806651811c47538"},
{file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60feb703abc8d78e9427d873bcf924c9e30cf540a21971ef5a17154da763b60f"},
{file = "coverage-7.2.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2becddfcbf3d994a8f4f9dd2b6015cae3a3eff50dedc6e4a17c3cccbe8f93d4"},
{file = "coverage-7.2.4-cp38-cp38-win32.whl", hash = "sha256:56a674ad18d6b04008283ca03c012be913bf89d91c0803c54c24600b300d9e51"},
{file = "coverage-7.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:ab08e03add2cf5793e66ac1bbbb24acfa90c125476f5724f5d44c56eeec1d635"},
{file = "coverage-7.2.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92b565c51732ea2e7e541709ccce76391b39f4254260e5922e08e00971e88e33"},
{file = "coverage-7.2.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8769a67e8816c7e94d5bf446fc0501641fde78fdff362feb28c2c64d45d0e9b1"},
{file = "coverage-7.2.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d74d6fbd5a98a5629e8467b719b0abea9ca01a6b13555d125c84f8bf4ea23d"},
{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.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.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fabd1f4d12dfd6b4f309208c2f31b116dc5900e0b42dbafe4ee1bc7c998ffbb0"},
{file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e41a7f44e73b37c6f0132ecfdc1c8b67722f42a3d9b979e6ebc150c8e80cf13a"},
{file = "coverage-7.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:864e36947289be05abd83267c4bade35e772526d3e9653444a9dc891faf0d698"},
{file = "coverage-7.2.4-cp39-cp39-win32.whl", hash = "sha256:ea534200efbf600e60130c48552f99f351cae2906898a9cd924c1c7f2fb02853"},
{file = "coverage-7.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:00f8fd8a5fe1ffc3aef78ea2dbf553e5c0f4664324e878995e38d41f037eb2b3"},
{file = "coverage-7.2.4-pp37.pp38.pp39-none-any.whl", hash = "sha256:856bcb837e96adede31018a0854ce7711a5d6174db1a84e629134970676c54fa"},
{file = "coverage-7.2.4.tar.gz", hash = "sha256:7283f78d07a201ac7d9dc2ac2e4faaea99c4d302f243ee5b4e359f3e170dc008"},
]
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
[[package]] [[package]]
name = "dataclasses-json" name = "dataclasses-json"
version = "0.5.7" version = "0.5.7"
@ -1506,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.1"
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.9"
files = [ files = [
{file = "ipython-8.12.0-py3-none-any.whl", hash = "sha256:1c183bf61b148b00bcebfa5d9b39312733ae97f6dad90d7e9b4d86c8647f498c"}, {file = "ipython-8.13.1-py3-none-any.whl", hash = "sha256:1c80d08f04144a1994cda25569eab07fbdc4989bd8d8793e3a4ff643065ccb51"},
{file = "ipython-8.12.0.tar.gz", hash = "sha256:a950236df04ad75b5bc7f816f9af3d74dc118fd42f2ff7e80e8e60ca1f182e2d"}, {file = "ipython-8.13.1.tar.gz", hash = "sha256:9c8487ac18f330c8a683fc50ab6d7bc0fcf9ef1d7a9f6ce7926938261067b81f"},
] ]
[package.dependencies] [package.dependencies]
@ -1641,32 +1708,38 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
[[package]] [[package]]
name = "langchain" name = "langchain"
version = "0.0.131" 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.131-py3-none-any.whl", hash = "sha256:3564a759e85095c9d71a78817da9cec1e2a8a0cda1bdd94ef8ac7008e432717a"}, {file = "langchain-0.0.152-py3-none-any.whl", hash = "sha256:f4ef223fc5783c34bc375ab5544a2b14c8ab5680bb8f0c8175176d1e8e19558c"},
{file = "langchain-0.0.131.tar.gz", hash = "sha256:61baf67fbec561ce38d187915a46e1c41139270826453600951760fde1a5d98a"}, {file = "langchain-0.0.152.tar.gz", hash = "sha256:bf0ccaf87139e404e2fd68c5144d5321ac7e5b4354e83a818e4a09edfc7d665f"},
] ]
[package.dependencies] [package.dependencies]
aiohttp = ">=3.8.3,<4.0.0" aiohttp = ">=3.8.3,<4.0.0"
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
dataclasses-json = ">=0.5.7,<0.6.0" dataclasses-json = ">=0.5.7,<0.6.0"
numexpr = ">=2.8.4,<3.0.0"
numpy = ">=1,<2" numpy = ">=1,<2"
openapi-schema-pydantic = ">=1.2,<2.0"
pydantic = ">=1,<2" pydantic = ">=1,<2"
PyYAML = ">=5.4.1" PyYAML = ">=5.4.1"
requests = ">=2,<3" requests = ">=2,<3"
SQLAlchemy = ">=1,<2" SQLAlchemy = ">1.3,<3"
tenacity = ">=8.1.0,<9.0.0" tenacity = ">=8.1.0,<9.0.0"
tqdm = ">=4.48.0"
[package.extras] [package.extras]
all = ["aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.4,<0.3.0)", "beautifulsoup4 (>=4,<5)", "boto3 (>=1.26.96,<2.0.0)", "cohere (>=3,<4)", "deeplake (>=3.2.9,<4.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "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)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "qdrant-client (>=1.1.1,<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,<2)", "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)"]
cohere = ["cohere (>=3,<4)"] cohere = ["cohere (>=3,<4)"]
llms = ["anthropic (>=0.2.4,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "torch (>=1,<2)", "transformers (>=4,<5)"] embeddings = ["sentence-transformers (>=2,<3)"]
llms = ["anthropic (>=0.2.6,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "torch (>=1,<3)", "transformers (>=4,<5)"]
openai = ["openai (>=0,<1)"] openai = ["openai (>=0,<1)"]
qdrant = ["qdrant-client (>=1.1.1,<2.0.0)"] qdrant = ["qdrant-client (>=1.1.2,<2.0.0)"]
[[package]] [[package]]
name = "lit" name = "lit"
@ -2250,6 +2323,49 @@ plot = ["matplotlib"]
tgrep = ["pyparsing"] tgrep = ["pyparsing"]
twitter = ["twython"] twitter = ["twython"]
[[package]]
name = "numexpr"
version = "2.8.4"
description = "Fast numerical expression evaluator for NumPy"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "numexpr-2.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a75967d46b6bd56455dd32da6285e5ffabe155d0ee61eef685bbfb8dafb2e484"},
{file = "numexpr-2.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db93cf1842f068247de631bfc8af20118bf1f9447cd929b531595a5e0efc9346"},
{file = "numexpr-2.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bca95f4473b444428061d4cda8e59ac564dc7dc6a1dea3015af9805c6bc2946"},
{file = "numexpr-2.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e34931089a6bafc77aaae21f37ad6594b98aa1085bb8b45d5b3cd038c3c17d9"},
{file = "numexpr-2.8.4-cp310-cp310-win32.whl", hash = "sha256:f3a920bfac2645017110b87ddbe364c9c7a742870a4d2f6120b8786c25dc6db3"},
{file = "numexpr-2.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:6931b1e9d4f629f43c14b21d44f3f77997298bea43790cfcdb4dd98804f90783"},
{file = "numexpr-2.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9400781553541f414f82eac056f2b4c965373650df9694286b9bd7e8d413f8d8"},
{file = "numexpr-2.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ee9db7598dd4001138b482342b96d78110dd77cefc051ec75af3295604dde6a"},
{file = "numexpr-2.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff5835e8af9a212e8480003d731aad1727aaea909926fd009e8ae6a1cba7f141"},
{file = "numexpr-2.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:655d84eb09adfee3c09ecf4a89a512225da153fdb7de13c447404b7d0523a9a7"},
{file = "numexpr-2.8.4-cp311-cp311-win32.whl", hash = "sha256:5538b30199bfc68886d2be18fcef3abd11d9271767a7a69ff3688defe782800a"},
{file = "numexpr-2.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:3f039321d1c17962c33079987b675fb251b273dbec0f51aac0934e932446ccc3"},
{file = "numexpr-2.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c867cc36cf815a3ec9122029874e00d8fbcef65035c4a5901e9b120dd5d626a2"},
{file = "numexpr-2.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:059546e8f6283ccdb47c683101a890844f667fa6d56258d48ae2ecf1b3875957"},
{file = "numexpr-2.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:845a6aa0ed3e2a53239b89c1ebfa8cf052d3cc6e053c72805e8153300078c0b1"},
{file = "numexpr-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:a38664e699526cb1687aefd9069e2b5b9387da7feac4545de446141f1ef86f46"},
{file = "numexpr-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eaec59e9bf70ff05615c34a8b8d6c7bd042bd9f55465d7b495ea5436f45319d0"},
{file = "numexpr-2.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b318541bf3d8326682ebada087ba0050549a16d8b3fa260dd2585d73a83d20a7"},
{file = "numexpr-2.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b076db98ca65eeaf9bd224576e3ac84c05e451c0bd85b13664b7e5f7b62e2c70"},
{file = "numexpr-2.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f12cc851240f7911a47c91aaf223dba753e98e46dff3017282e633602e76a7"},
{file = "numexpr-2.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c368aa35ae9b18840e78b05f929d3a7b3abccdba9630a878c7db74ca2368339"},
{file = "numexpr-2.8.4-cp38-cp38-win32.whl", hash = "sha256:b96334fc1748e9ec4f93d5fadb1044089d73fb08208fdb8382ed77c893f0be01"},
{file = "numexpr-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:a6d2d7740ae83ba5f3531e83afc4b626daa71df1ef903970947903345c37bd03"},
{file = "numexpr-2.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:77898fdf3da6bb96aa8a4759a8231d763a75d848b2f2e5c5279dad0b243c8dfe"},
{file = "numexpr-2.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df35324666b693f13a016bc7957de7cc4d8801b746b81060b671bf78a52b9037"},
{file = "numexpr-2.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ac9cfe6d0078c5fc06ba1c1bbd20b8783f28c6f475bbabd3cad53683075cab"},
{file = "numexpr-2.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df3a1f6b24214a1ab826e9c1c99edf1686c8e307547a9aef33910d586f626d01"},
{file = "numexpr-2.8.4-cp39-cp39-win32.whl", hash = "sha256:7d71add384adc9119568d7e9ffa8a35b195decae81e0abf54a2b7779852f0637"},
{file = "numexpr-2.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:9f096d707290a6a00b6ffdaf581ee37331109fb7b6c8744e9ded7c779a48e517"},
{file = "numexpr-2.8.4.tar.gz", hash = "sha256:d5432537418d18691b9115d615d6daa17ee8275baef3edf1afbbf8bc69806147"},
]
[package.dependencies]
numpy = ">=1.13.3"
[[package]] [[package]]
name = "numpy" name = "numpy"
version = "1.24.3" version = "1.24.3"
@ -2470,14 +2586,14 @@ files = [
[[package]] [[package]]
name = "openai" name = "openai"
version = "0.27.4" version = "0.27.5"
description = "Python client library for the OpenAI API" description = "Python client library for the OpenAI API"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7.1" python-versions = ">=3.7.1"
files = [ files = [
{file = "openai-0.27.4-py3-none-any.whl", hash = "sha256:3b82c867d531e1fd2003d9de2131e1c4bfd4c70b1a3149e0543a555b30807b70"}, {file = "openai-0.27.5-py3-none-any.whl", hash = "sha256:5b2121d8c0a4350626096fa482306d12e246a83b992530d54fd474f309f2882c"},
{file = "openai-0.27.4.tar.gz", hash = "sha256:9f9d27d26e62c6068f516c0729449954b5ef6994be1a6cbfe7dbefbc84423a04"}, {file = "openai-0.27.5.tar.gz", hash = "sha256:75778ca05759c77dde704985ee16976ba58804212f10e61f44356e68ffb8390e"},
] ]
[package.dependencies] [package.dependencies]
@ -2491,6 +2607,21 @@ dev = ["black (>=21.6b0,<22.0)", "pytest (>=6.0.0,<7.0.0)", "pytest-asyncio", "p
embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"] embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"]
wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"] wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"]
[[package]]
name = "openapi-schema-pydantic"
version = "1.2.4"
description = "OpenAPI (v3) specification schema as pydantic class"
category = "main"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "openapi-schema-pydantic-1.2.4.tar.gz", hash = "sha256:3e22cf58b74a69f752cc7e5f1537f6e44164282db2700cbbcd3bb99ddd065196"},
{file = "openapi_schema_pydantic-1.2.4-py3-none-any.whl", hash = "sha256:a932ecc5dcbb308950282088956e94dea069c9823c84e507d64f6b622222098c"},
]
[package.dependencies]
pydantic = ">=1.8.2"
[[package]] [[package]]
name = "openpyxl" name = "openpyxl"
version = "3.1.2" version = "3.1.2"
@ -2567,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"
@ -2704,14 +2850,14 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "3.3.0" version = "3.5.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "platformdirs-3.3.0-py3-none-any.whl", hash = "sha256:ea61fd7b85554beecbbd3e9b37fb26689b227ffae38f73353cbcc1cf8bd01878"}, {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"},
{file = "platformdirs-3.3.0.tar.gz", hash = "sha256:64370d47dc3fca65b4879f89bdead8197e93e05d696d6d1816243ebae8595da5"}, {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"},
] ]
[package.extras] [package.extras]
@ -3153,6 +3299,25 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "4.0.0"
description = "Pytest plugin for measuring coverage."
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
{file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
]
[package.dependencies]
coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.8.2" version = "2.8.2"
@ -3473,14 +3638,14 @@ files = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.28.2" version = "2.29.0"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7, <4" python-versions = ">=3.7"
files = [ files = [
{file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"},
{file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, {file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"},
] ]
[package.dependencies] [package.dependencies]
@ -3513,14 +3678,14 @@ idna2008 = ["idna"]
[[package]] [[package]]
name = "rich" name = "rich"
version = "13.3.4" version = "13.3.5"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
{file = "rich-13.3.4-py3-none-any.whl", hash = "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a"}, {file = "rich-13.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"},
{file = "rich-13.3.4.tar.gz", hash = "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"}, {file = "rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"},
] ]
[package.dependencies] [package.dependencies]
@ -3787,77 +3952,80 @@ files = [
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "1.4.47" version = "2.0.11"
description = "Database Abstraction Library" description = "Database Abstraction Library"
category = "main" category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" python-versions = ">=3.7"
files = [ files = [
{file = "SQLAlchemy-1.4.47-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:dcfb480bfc9e1fab726003ae00a6bfc67a29bad275b63a4e36d17fe7f13a624e"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e9069faea64d3390d90d16e5b2bc0652d8eb979ccdfd555822d96bc8d93afda1"},
{file = "SQLAlchemy-1.4.47-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28fda5a69d6182589892422c5a9b02a8fd1125787aab1d83f1392aa955bf8d0a"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8aea55b1754430449d43823c8c4da2d5c7621ccd1fcd4c36231417762542d4ef"},
{file = "SQLAlchemy-1.4.47-cp27-cp27m-win32.whl", hash = "sha256:45e799c1a41822eba6bee4e59b0e38764e1a1ee69873ab2889079865e9ea0e23"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ccd20b5a4e3511c2f0c889b7b79a7462b6c6aa2c06d0f4943c27a552e35e091"},
{file = "SQLAlchemy-1.4.47-cp27-cp27m-win_amd64.whl", hash = "sha256:10edbb92a9ef611f01b086e271a9f6c1c3e5157c3b0c5ff62310fb2187acbd4a"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dcfea87230e34d7d55f67959ed09d3e60e09b77c76996de151c32f1b780135"},
{file = "SQLAlchemy-1.4.47-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a4df53472c9030a8ddb1cce517757ba38a7a25699bbcabd57dcc8a5d53f324e"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a836f391d7dc1039f10d2ef58cdc6e271462d6898dacdae1bfabfc16ca295f2c"},
{file = "SQLAlchemy-1.4.47-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:511d4abc823152dec49461209607bbfb2df60033c8c88a3f7c93293b8ecbb13d"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25bbf89e6f171d37cf3a993dbeee18cb85abe37a421c40e78131bf339e48da9d"},
{file = "SQLAlchemy-1.4.47-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbe57f39f531c5d68d5594ea4613daa60aba33bb51a8cc42f96f17bbd6305e8d"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-win32.whl", hash = "sha256:0624852aec618438a4cd7a53ce00835435588506e6f8fbd60deaf9ac109f7cd0"},
{file = "SQLAlchemy-1.4.47-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca8ab6748e3ec66afccd8b23ec2f92787a58d5353ce9624dccd770427ee67c82"}, {file = "SQLAlchemy-2.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:d7eab7d668f95a1a2ef443da17154834adf9c5ac742a5992d5ebecbdca7d943e"},
{file = "SQLAlchemy-1.4.47-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299b5c5c060b9fbe51808d0d40d8475f7b3873317640b9b7617c7f988cf59fda"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa81761ff674d2e2d591fc88d31835d3ecf65bddb021a522f4eaaae831c584cf"},
{file = "SQLAlchemy-1.4.47-cp310-cp310-win32.whl", hash = "sha256:684e5c773222781775c7f77231f412633d8af22493bf35b7fa1029fdf8066d10"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21f447403a1bfeb832a7384c4ac742b7baab04460632c0335e020e8e2c741d4b"},
{file = "SQLAlchemy-1.4.47-cp310-cp310-win_amd64.whl", hash = "sha256:2bba39b12b879c7b35cde18b6e14119c5f1a16bd064a48dd2ac62d21366a5e17"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4d8d96c0a7265de8496250a2c2d02593da5e5e85ea24b5c54c2db028d74cf8c"},
{file = "SQLAlchemy-1.4.47-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:795b5b9db573d3ed61fae74285d57d396829e3157642794d3a8f72ec2a5c719b"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c4c5834789f718315cb25d1b95d18fde91b72a1a158cdc515d7f6380c1f02a3"},
{file = "SQLAlchemy-1.4.47-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:989c62b96596b7938cbc032e39431e6c2d81b635034571d6a43a13920852fb65"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f57965a9d5882efdea0a2c87ae2f6c7dbc14591dcd0639209b50eec2b3ec947e"},
{file = "SQLAlchemy-1.4.47-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b67bda733da1dcdccaf354e71ef01b46db483a4f6236450d3f9a61efdba35a"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0dd98b0be54503afc4c74e947720c3196f96fb2546bfa54d911d5de313c5463c"},
{file = "SQLAlchemy-1.4.47-cp311-cp311-win32.whl", hash = "sha256:9a198f690ac12a3a807e03a5a45df6a30cd215935f237a46f4248faed62e69c8"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-win32.whl", hash = "sha256:eec40c522781a58839df6a2a7a2d9fbaa473419a3ab94633d61e00a8c0c768b7"},
{file = "SQLAlchemy-1.4.47-cp311-cp311-win_amd64.whl", hash = "sha256:03be6f3cb66e69fb3a09b5ea89d77e4bc942f3bf84b207dba84666a26799c166"}, {file = "SQLAlchemy-2.0.11-cp311-cp311-win_amd64.whl", hash = "sha256:62835d8cd6713458c032466c38a43e56503e19ea6e54b0e73295c6ab281fc0b1"},
{file = "SQLAlchemy-1.4.47-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:16ee6fea316790980779268da47a9260d5dd665c96f225d28e7750b0bb2e2a04"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:216b9c4dbeaa143a36c9249f9e5a0fd7fa6549a1a3f9de9a2d30104f7e35d8b9"},
{file = "SQLAlchemy-1.4.47-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:557675e0befafa08d36d7a9284e8761c97490a248474d778373fb96b0d7fd8de"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7710fd24bcf33abed7ab7673dbb38ad48f20555835ff8c77258f07de46a87"},
{file = "SQLAlchemy-1.4.47-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb2797fee8a7914fb2c3dc7de404d3f96eb77f20fc60e9ee38dc6b0ca720f2c2"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:718c0a9f8509542d0674c15b01f362b2f10e8bc425db74444bda4e073e06e660"},
{file = "SQLAlchemy-1.4.47-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28297aa29e035f29cba6b16aacd3680fbc6a9db682258d5f2e7b49ec215dbe40"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2a5fb41db86f6d4892edcf30bd67418dd757eb0246242648e610fa2bca7533d4"},
{file = "SQLAlchemy-1.4.47-cp36-cp36m-win32.whl", hash = "sha256:998e782c8d9fd57fa8704d149ccd52acf03db30d7dd76f467fd21c1c21b414fa"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:352dcd93e5a0421eee59dbac0000f8f811203cf228334d85d77b3ef075707322"},
{file = "SQLAlchemy-1.4.47-cp36-cp36m-win_amd64.whl", hash = "sha256:dde4d02213f1deb49eaaf8be8a6425948963a7af84983b3f22772c63826944de"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-win32.whl", hash = "sha256:fb21777cc9205b94f51688cdcba0924bdecbeb23dcf81473ff8c5352211e6e38"},
{file = "SQLAlchemy-1.4.47-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e98ef1babe34f37f443b7211cd3ee004d9577a19766e2dbacf62fce73c76245a"}, {file = "SQLAlchemy-2.0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9268d7417467e9fde5f4364c71ce490b18a4b83a6543b0d55d1f83fce42bda"},
{file = "SQLAlchemy-1.4.47-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14a3879853208a242b5913f3a17c6ac0eae9dc210ff99c8f10b19d4a1ed8ed9b"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:125c41b3557179e9a514a1cfe2764433177ba6195b2264725ceaa7a2e8afcbde"},
{file = "SQLAlchemy-1.4.47-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7120a2f72599d4fed7c001fa1cbbc5b4d14929436135768050e284f53e9fbe5e"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19a03413cf36e86674857e519936b9c9e52059ba9f6e2ab0ec75d9a458277cb"},
{file = "SQLAlchemy-1.4.47-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:048509d7f3ac27b83ad82fd96a1ab90a34c8e906e4e09c8d677fc531d12c23c5"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e48d908695abe05435250e0a083416cc49bd5afd46bc16a7ec8725771aad8eac"},
{file = "SQLAlchemy-1.4.47-cp37-cp37m-win32.whl", hash = "sha256:6572d7c96c2e3e126d0bb27bfb1d7e2a195b68d951fcc64c146b94f088e5421a"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3728f7518aa70e5ce88fae4c68b5d7f25493f37d8d867e4a7d60905bd162cd0d"},
{file = "SQLAlchemy-1.4.47-cp37-cp37m-win_amd64.whl", hash = "sha256:a6c3929df5eeaf3867724003d5c19fed3f0c290f3edc7911616616684f200ecf"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1ab6ac214354957db83c72c65941af7e022d4c9324bdadc54d0266aa162a3828"},
{file = "SQLAlchemy-1.4.47-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71d4bf7768169c4502f6c2b0709a02a33703544f611810fb0c75406a9c576ee1"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:abadc6bf6b2c0a0be4370513221563afdbac3901d29fcdb7faf23b4e1ed26068"},
{file = "SQLAlchemy-1.4.47-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd45c60cc4f6d68c30d5179e2c2c8098f7112983532897566bb69c47d87127d3"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-win32.whl", hash = "sha256:78cbc8eba442c9b8dc2d90c43ac477f0ee27467617704cd82d741b2eb061afb2"},
{file = "SQLAlchemy-1.4.47-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fdbb8e9d4e9003f332a93d6a37bca48ba8095086c97a89826a136d8eddfc455"}, {file = "SQLAlchemy-2.0.11-cp38-cp38-win_amd64.whl", hash = "sha256:384fdde6bd628d1a882f04aa9a40aa6928840b02d595ff5bd08abeae4c25f867"},
{file = "SQLAlchemy-1.4.47-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f216a51451a0a0466e082e163591f6dcb2f9ec182adb3f1f4b1fd3688c7582c"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:737a70c994f5b34e437a6ca754957a7a0f6f76c59fa460fc59d1bd15b8f8cb32"},
{file = "SQLAlchemy-1.4.47-cp38-cp38-win32.whl", hash = "sha256:bd988b3362d7e586ef581eb14771bbb48793a4edb6fcf62da75d3f0f3447060b"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e53e4920cd5872280256ddf6ca843b5d1435e0302847992bcb90f84b744999f"},
{file = "SQLAlchemy-1.4.47-cp38-cp38-win_amd64.whl", hash = "sha256:32ab09f2863e3de51529aa84ff0e4fe89a2cb1bfbc11e225b6dbc60814e44c94"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:409cc6cd15d4db5c5af2c4e2d3a2137815c31d065cea9a77dec92cbe7cfcf448"},
{file = "SQLAlchemy-1.4.47-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:07764b240645627bc3e82596435bd1a1884646bfc0721642d24c26b12f1df194"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71dd742e3146be6fdded0b95a4b779f7d81595760eab32b0f718089573d3b86"},
{file = "SQLAlchemy-1.4.47-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e2a42017984099ef6f56438a6b898ce0538f6fadddaa902870c5aa3e1d82583"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d85ca17b070f7076ec2582324331cf3683c09146fd8bd2621e8d80d6c3a93bbf"},
{file = "SQLAlchemy-1.4.47-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6b6d807c76c20b4bc143a49ad47782228a2ac98bdcdcb069da54280e138847fc"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a785c30929a5d82f2fa1c60ec46d623d418b19981dc0c594da806d3901658e39"},
{file = "SQLAlchemy-1.4.47-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a94632ba26a666e7be0a7d7cc3f7acab622a04259a3aa0ee50ff6d44ba9df0d"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-win32.whl", hash = "sha256:66f24708cebe5a4e900e221574b50e102908f60f539fea30f1922705c0e97744"},
{file = "SQLAlchemy-1.4.47-cp39-cp39-win32.whl", hash = "sha256:f80915681ea9001f19b65aee715115f2ad310730c8043127cf3e19b3009892dd"}, {file = "SQLAlchemy-2.0.11-cp39-cp39-win_amd64.whl", hash = "sha256:5a2f95901e6bbed27b4ad5d59ab3f970eda0ce0b9ede3a67b6f9a914149ed71b"},
{file = "SQLAlchemy-1.4.47-cp39-cp39-win_amd64.whl", hash = "sha256:fc700b862e0a859a37faf85367e205e7acaecae5a098794aff52fdd8aea77b12"}, {file = "SQLAlchemy-2.0.11-py3-none-any.whl", hash = "sha256:1d28e8278d943d9111d44720f92cc338282e956ed68849bfcee053c06bde4f39"},
{file = "SQLAlchemy-1.4.47.tar.gz", hash = "sha256:95fc02f7fc1f3199aaa47a8a757437134cf618e9d994c84effd53f530c38586f"}, {file = "SQLAlchemy-2.0.11.tar.gz", hash = "sha256:c3cbff7cced3c42dbe71448ce6bf4202b4a2d305e78dd77e3f280ba6cd245138"},
] ]
[package.dependencies] [package.dependencies]
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""} greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
typing-extensions = ">=4.2.0"
[package.extras] [package.extras]
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"] asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
mssql = ["pyodbc"] mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"] mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"] mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"] mysql-connector = ["mysql-connector-python"]
oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] oracle = ["cx-oracle (>=7)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"] postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql", "pymysql (<1)"] pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3-binary"] sqlcipher = ["sqlcipher3-binary"]
[[package]] [[package]]
@ -4275,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"
@ -4289,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]
@ -4304,14 +4496,14 @@ types-urllib3 = "<1.27"
[[package]] [[package]]
name = "types-urllib3" name = "types-urllib3"
version = "1.26.25.10" version = "1.26.25.11"
description = "Typing stubs for urllib3" description = "Typing stubs for urllib3"
category = "dev" category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "types-urllib3-1.26.25.10.tar.gz", hash = "sha256:c44881cde9fc8256d05ad6b21f50c4681eb20092552351570ab0a8a0653286d6"}, {file = "types-urllib3-1.26.25.11.tar.gz", hash = "sha256:697102ddf4f781eed6f692353f40cee1098643526f5a8b99f49d2ede90fd3754"},
{file = "types_urllib3-1.26.25.10-py3-none-any.whl", hash = "sha256:12c744609d588340a07e45d333bf870069fc8793bcf96bae7a96d4712a42591d"}, {file = "types_urllib3-1.26.25.11-py3-none-any.whl", hash = "sha256:04235e792139cf3624b25d38faab593456738fbdb7439634046172e3b1339400"},
] ]
[[package]] [[package]]
@ -4802,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 = "f0a23552d8cbd3d38722a69698cf823cdd19a90b707538837da64a933f8d13b4" content-hash = "c8ba5d9e0208fc55dc2f00efffed26d7a2c7d325568dfd697cc6499d210304d9"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.0.58" version = "0.0.61"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -29,7 +29,7 @@ google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0" google-api-python-client = "^2.79.0"
typer = "^0.7.0" typer = "^0.7.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
langchain = "^0.0.131" langchain = "~0.0.150"
openai = "^0.27.2" openai = "^0.27.2"
types-pyyaml = "^6.0.12.8" types-pyyaml = "^6.0.12.8"
dill = "^0.3.6" dill = "^0.3.6"
@ -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"
@ -57,6 +58,10 @@ httpx = "^0.23.3"
pytest = "^7.2.2" pytest = "^7.2.2"
types-requests = "^2.28.11" types-requests = "^2.28.11"
requests = "^2.28.0" requests = "^2.28.0"
pytest-cov = "^4.0.0"
pandas-stubs = "^2.0.0.230412"
types-pillow = "^9.5.0.2"
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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

View 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())

View 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

View 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

View 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

View file

@ -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

View file

@ -0,0 +1 @@
from langflow.cache.manager import cache_manager # noqa

View file

@ -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
View 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()

View file

@ -15,6 +15,11 @@ CUSTOM_NODES = {
"utilities": { "utilities": {
"SQLDatabase": nodes.SQLDatabaseNode(), "SQLDatabase": nodes.SQLDatabaseNode(),
}, },
"chains": {
"SeriesCharacterChain": nodes.SeriesCharacterChainNode(),
"TimeTravelGuideChain": nodes.TimeTravelGuideChainNode(),
"MidJourneyPromptChain": nodes.MidJourneyPromptChainNode(),
},
} }

View file

@ -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):
# so we need to check if there is an attribute called run # func can be
if hasattr(result, "run"): # PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n')
result = result.run # type: ignore # so we need to check if there is an attribute called run
elif hasattr(result, "get_function"): if hasattr(result, "run"):
result = result.get_function() # type: ignore result = result.run # type: ignore
elif hasattr(result, "get_function"):
result = result.get_function() # type: ignore
elif inspect.iscoroutinefunction(result):
self.params["coroutine"] = result
else:
# turn result which is a function into a coroutine
# so that it can be awaited
self.params["coroutine"] = sync_to_async(result)
self.params[key] = result self.params[key] = result
elif isinstance(value, list) and all( elif isinstance(value, list) and all(

View file

@ -174,7 +174,7 @@ class SQLAgent(AgentExecutor):
def from_toolkit_and_llm(cls, llm: BaseLLM, database_uri: str, **kwargs: Any): def from_toolkit_and_llm(cls, llm: BaseLLM, database_uri: str, **kwargs: Any):
"""Construct a sql agent from an LLM and tools.""" """Construct a sql agent from an LLM and tools."""
db = SQLDatabase.from_uri(database_uri) db = SQLDatabase.from_uri(database_uri)
toolkit = SQLDatabaseToolkit(db=db) toolkit = SQLDatabaseToolkit(db=db, llm=llm)
# The right code should be this, but there is a problem with tools = toolkit.get_tools() # The right code should be this, but there is a problem with tools = toolkit.get_tools()
# related to `OPENAI_API_KEY` # related to `OPENAI_API_KEY`

View file

@ -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:

View file

@ -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):
@ -166,7 +186,9 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
allowed_tools = params["allowed_tools"] allowed_tools = params["allowed_tools"]
llm_chain = params["llm_chain"] llm_chain = params["llm_chain"]
tool_names = [tool.name for tool in allowed_tools] tool_names = [tool.name for tool in allowed_tools]
agent = agent_class(allowed_tools=tool_names, llm_chain=llm_chain) # Agent class requires an output_parser but Agent classes
# have a default output_parser.
agent = agent_class(allowed_tools=tool_names, llm_chain=llm_chain) # type: ignore
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, agent=agent,
tools=allowed_tools, tools=allowed_tools,

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -23,6 +23,7 @@ class TemplateFieldCreator(BaseModel, ABC):
options: list[str] = [] options: list[str] = []
name: str = "" name: str = ""
display_name: Optional[str] = None display_name: Optional[str] = None
advanced: bool = True
def to_dict(self): def to_dict(self):
result = self.dict() result = self.dict()
@ -228,6 +229,11 @@ class FrontendNode(BaseModel):
field.required = False field.required = False
if field.value is None: if field.value is None:
field.value = "" field.value = ""
if "kwargs" in field.name.lower():
field.advanced = True
field.required = False
field.show = False
# If the field.name contains api or api and key, then it might be an api key # 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 # 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(): if "api" in key.lower() and "key" in key.lower():

View file

@ -101,6 +101,108 @@ class PythonFunctionNode(FrontendNode):
return super().to_dict() return super().to_dict()
class MidJourneyPromptChainNode(FrontendNode):
name: str = "MidJourneyPromptChain"
template: Template = Template(
type_name="MidJourneyPromptChain",
fields=[
TemplateField(
field_type="BaseLanguageModel",
required=True,
placeholder="",
is_list=False,
show=True,
advanced=False,
multiline=False,
name="llm",
),
],
)
description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."
base_classes: list[str] = [
"LLMChain",
"BaseCustomChain",
"Chain",
"ConversationChain",
"MidJourneyPromptChain",
]
class TimeTravelGuideChainNode(FrontendNode):
name: str = "TimeTravelGuideChain"
template: Template = Template(
type_name="TimeTravelGuideChain",
fields=[
TemplateField(
field_type="BaseLanguageModel",
required=True,
placeholder="",
is_list=False,
show=True,
advanced=False,
multiline=False,
name="llm",
),
],
)
description: str = "Time travel guide chain to be used in the flow."
base_classes: list[str] = [
"LLMChain",
"BaseCustomChain",
"TimeTravelGuideChain",
"Chain",
"ConversationChain",
]
class SeriesCharacterChainNode(FrontendNode):
name: str = "SeriesCharacterChain"
template: Template = Template(
type_name="SeriesCharacterChain",
fields=[
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
advanced=False,
multiline=False,
name="character",
),
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
advanced=False,
multiline=False,
name="series",
),
TemplateField(
field_type="BaseLanguageModel",
required=True,
placeholder="",
is_list=False,
show=True,
advanced=False,
multiline=False,
name="llm",
),
],
)
description: str = "SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa
base_classes: list[str] = [
"LLMChain",
"BaseCustomChain",
"Chain",
"ConversationChain",
"SeriesCharacterChain",
"function",
]
class ToolNode(FrontendNode): class ToolNode(FrontendNode):
name: str = "Tool" name: str = "Tool"
template: Template = Template( template: Template = Template(
@ -216,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()
@ -418,17 +520,29 @@ class ChainFrontendNode(FrontendNode):
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name) FrontendNode.format_field(field, name)
field.advanced = False
if "key" in field.name: if "key" in field.name:
field.password = False field.password = False
field.show = False field.show = False
if field.name in ["input_key", "output_key"]: if field.name in ["input_key", "output_key"]:
field.required = True field.required = True
field.show = True field.show = 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
field.advanced = False
if field.name == "memory":
field.required = False
field.show = True
field.advanced = False
if field.name == "verbose":
field.required = False
field.show = True
field.advanced = True
class LLMFrontendNode(FrontendNode): class LLMFrontendNode(FrontendNode):
@ -438,7 +552,7 @@ class LLMFrontendNode(FrontendNode):
"huggingfacehub_api_token": "HuggingFace Hub API Token", "huggingfacehub_api_token": "HuggingFace Hub API Token",
} }
FrontendNode.format_field(field, name) FrontendNode.format_field(field, name)
SHOW_FIELDS = ["repo_id", "task", "model_kwargs"] SHOW_FIELDS = ["repo_id"]
if field.name in SHOW_FIELDS: if field.name in SHOW_FIELDS:
field.show = True field.show = True
@ -448,14 +562,21 @@ class LLMFrontendNode(FrontendNode):
# Required should be False to support # Required should be False to support
# loading the API key from environment variables # loading the API key from environment variables
field.required = False field.required = False
field.advanced = False
if field.name == "task": if field.name == "task":
field.required = True field.required = True
field.show = True field.show = True
field.is_list = True field.is_list = True
field.options = ["text-generation", "text2text-generation"] field.options = ["text-generation", "text2text-generation"]
field.advanced = True
if display_name := display_names_dict.get(field.name): if display_name := display_names_dict.get(field.name):
field.display_name = display_name field.display_name = display_name
if field.name == "model_kwargs": if field.name == "model_kwargs":
field.field_type = "code" field.field_type = "code"
field.advanced = True
field.show = True
elif field.name in ["model_name", "temperature"]:
field.advanced = False
field.show = True

View file

@ -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

View file

@ -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

View file

@ -1,5 +0,0 @@
FROM node:14-alpine
WORKDIR /app
COPY . /app
RUN npm install
RUN npm run build

View file

@ -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

View file

@ -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": "*"
} }

View file

@ -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"
} }

View file

@ -1,4 +1,4 @@
import { TrashIcon } from "@heroicons/react/24/outline"; import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/24/outline";
import { import {
classNames, classNames,
nodeColors, nodeColors,
@ -7,10 +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 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,
@ -21,7 +24,54 @@ export default function GenericNode({
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const showError = useRef(true); const showError = useRef(true);
const { types, deleteNode } = useContext(typesContext); const { types, deleteNode } = useContext(typesContext);
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({
@ -34,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"
)} )}
@ -51,17 +103,46 @@ export default function GenericNode({
/> />
<div className="truncate">{data.type}</div> <div className="truncate">{data.type}</div>
</div> </div>
<button <div className="flex gap-3">
onClick={() => { <button
deleteNode(data.id); className="relative"
}} onClick={(event) => {
> event.preventDefault();
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon> openPopUp(<NodeModal data={data} />);
</button> }}
>
<div className=" absolute text-red-600 -top-2 -right-1">
{Object.keys(data.node.template).some(
(t) =>
data.node.template[t].advanced &&
data.node.template[t].required
)
? " *"
: ""}
</div>
<Cog6ToothIcon
className={classNames(
Object.keys(data.node.template).some(
(t) => data.node.template[t].advanced && data.node.template[t].show
)
? ""
: "hidden",
"w-6 h-6 dark:text-gray-500 hover:animate-spin"
)}
></Cog6ToothIcon>
</button>
<button
onClick={() => {
deleteNode(data.id);
}}
>
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
</button>
</div>
</div> </div>
<div className="w-full h-full py-5"> <div className="w-full h-full py-5">
<div className="w-full text-gray-500 px-5 text-sm"> <div className="w-full text-gray-500 px-5 pb-3 text-sm">
{data.node.description} {data.node.description}
</div> </div>
@ -70,13 +151,15 @@ export default function GenericNode({
.filter((t) => t.charAt(0) !== "_") .filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => ( .map((t: string, idx) => (
<div key={idx}> <div key={idx}>
{idx === 0 ? ( {/* {idx === 0 ? (
<div <div
className={classNames( className={classNames(
"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"
: "" : ""
@ -86,8 +169,8 @@ export default function GenericNode({
</div> </div>
) : ( ) : (
<></> <></>
)} )} */}
{data.node.template[t].show ? ( {data.node.template[t].show && !data.node.template[t].advanced ? (
<ParameterComponent <ParameterComponent
data={data} data={data}
color={ color={
@ -117,9 +200,17 @@ export default function GenericNode({
)} )}
</div> </div>
))} ))}
<div className="px-5 py-2 mt-2 dark:text-white text-center"> <div
Output className={classNames(
Object.keys(data.node.template).length < 1 ? "hidden" : "",
"w-full flex justify-center"
)}
>
{" "}
</div> </div>
{/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
Output
</div> */}
<ParameterComponent <ParameterComponent
data={data} data={data}
color={nodeColors[types[data.type]] ?? nodeColors.unknown} color={nodeColors[types[data.type]] ?? nodeColors.unknown}

View file

@ -1,15 +1,23 @@
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({
const [show, setShow] = useState(true); dropItem,
const type = dropItem.type; removeAlert,
}: SingleAlertComponentType) {
const [show, setShow] = useState(true);
const type = dropItem.type;
return ( return (
<Transition <Transition
className="relative" className="relative"
show={show} show={show}
appear={true} appear={true}
@ -20,115 +28,127 @@ 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
<div className="flex-shrink-0"> className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
<XCircleIcon key={dropItem.id}
className="h-5 w-5 text-red-400" >
aria-hidden="true" <div className="flex-shrink-0">
/> <XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
{dropItem.title}
</h3>
{dropItem.list ? (
<div className="mt-2 text-sm text-red-700">
<ul className="list-disc space-y-1 pl-5">
{dropItem.list.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
</div>
) : (
<></>
)}
</div>
<div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5">
<button
type="button"
onClick={() => {
setShow(false);
setTimeout(() => {
removeAlert(dropItem.id);
}, 500);
}}
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
>
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div> </div>
<div className="ml-3"> </div>
<h3 className="text-sm font-medium text-red-800"> </div>
{dropItem.title} ) : type === "notice" ? (
</h3> <div
{dropItem.list ? ( className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
<div className="mt-2 text-sm text-red-700"> key={dropItem.id}
<ul className="list-disc space-y-1 pl-5"> >
{dropItem.list.map((item, idx) => ( <div className="flex-shrink-0">
<li key={idx}>{item}</li> <InformationCircleIcon
))} className="h-5 w-5 text-blue-400"
</ul> aria-hidden="true"
</div> />
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-blue-700">{dropItem.title}</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6">
{dropItem.link ? (
<Link
to={dropItem.link}
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
>
Details
</Link>
) : ( ) : (
<></> <></>
)} )}
</div> </p>
<div className="ml-auto pl-3"> </div>
<div className="-mx-1.5 -my-1.5"> <div className="ml-auto pl-3">
<button <div className="-mx-1.5 -my-1.5">
type="button" <button
onClick={() => { type="button"
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500); onClick={() => {
}} setShow(false);
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500" setTimeout(() => {
> removeAlert(dropItem.id);
<span className="sr-only">Dismiss</span> }, 500);
<XMarkIcon className="h-5 w-5" aria-hidden="true" /> }}
</button> className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
</div> >
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div> </div>
</div> </div>
:(type === "notice" ? </div>
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}> ) : (
<div className="flex-shrink-0"> <div
<InformationCircleIcon className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md"
className="h-5 w-5 text-blue-400" key={dropItem.id}
aria-hidden="true" >
/> <div className="flex-shrink-0">
</div> <CheckCircleIcon
<div className="ml-3 flex-1 md:flex md:justify-between"> className="h-5 w-5 text-green-400"
<p className="text-sm text-blue-700">{dropItem.title}</p> aria-hidden="true"
<p className="mt-3 text-sm md:mt-0 md:ml-6"> />
{dropItem.link ? ( </div>
<Link <div className="ml-3">
to={dropItem.link} <p className="text-sm font-medium text-green-800">
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600" {dropItem.title}
> </p>
Details </div>
</Link> <div className="ml-auto pl-3">
) : ( <div className="-mx-1.5 -my-1.5">
<></> <button
)} type="button"
</p> onClick={() => {
</div> setShow(false);
<div className="ml-auto pl-3"> setTimeout(() => {
<div className="-mx-1.5 -my-1.5"> removeAlert(dropItem.id);
<button }, 500);
type="button" }}
onClick={() => { className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500); >
}} <span className="sr-only">Dismiss</span>
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500" <XMarkIcon className="h-5 w-5" aria-hidden="true" />
> </button>
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</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-shrink-0">
<CheckCircleIcon
className="h-5 w-5 text-green-400"
aria-hidden="true"
/>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-green-800">
{dropItem.title}
</p>
</div>
<div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5">
<button
type="button"
onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
}}
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
>
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
)
}
</Transition> </Transition>
) );
}
}

View file

@ -5,73 +5,62 @@ 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 ( closePopUp();
componentRef.current && });
!componentRef.current.contains(event.target as Node)
) {
closePopUp();
}
}
// Bind the event listener const {
document.addEventListener("mousedown", handleClickOutside); notificationList,
clearNotificationList,
removeFromNotificationList,
} = useContext(alertContext);
// Cleanup the event listener when the component is unmounted return (
return () => { <div
document.removeEventListener("mousedown", handleClickOutside); ref={componentRef}
}; 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"
}, [componentRef]); >
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
const { Notifications
notificationList, <div className="flex gap-2 pr-3 ">
clearNotificationList, <button
removeFromNotificationList, className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
} = useContext(alertContext); onClick={() => {
closePopUp();
return ( setTimeout(clearNotificationList, 100);
<div }}
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" <TrashIcon className="w-[1.1rem] h-[1.1rem]" />
> </button>
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800"> <button
Notifications className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
<div className="flex gap-2 pr-3 "> onClick={closePopUp}
<button >
className="hover:text-red-500" <XMarkIcon className="h-5 w-5" />
onClick={() => { </button>
closePopUp(); </div>
setTimeout(clearNotificationList, 100); </div>
}} <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 ? (
<TrashIcon className="w-[1.1rem] h-[1.1rem]" /> notificationList.map((alertItem, index) => (
</button> <SingleAlert
<button className="hover:text-red-500" onClick={closePopUp}> key={alertItem.id}
<XMarkIcon className="h-5 w-5" /> dropItem={alertItem}
</button> removeAlert={removeFromNotificationList}
</div> />
</div> ))
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide"> ) : (
{notificationList.length !== 0 ? ( <div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
notificationList.map((alertItem, index) => ( No new notifications
<SingleAlert </div>
key={alertItem.id} )}
dropItem={alertItem} </div>
removeAlert={removeFromNotificationList} </div>
/> );
))
) : (
<div className="h-full w-full pb-16 text-gray-500 flex justify-center items-center">
No new notifications
</div>
)}
</div>
</div>
);
} }

View 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
}

View 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

View file

@ -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>)
}

View file

@ -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 };
) => { document.addEventListener("keydown", handleKeyDown);
let tabsChange = false; return () => {
setChatHistory((old) => { document.removeEventListener("keydown", handleKeyDown);
let newChat = _.cloneDeep(old); };
if (JSON.stringify(flow.chat) !== JSON.stringify(old)) { }, []);
tabsChange = true; return (
return old; <>
} <ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
if (thought) { <ChatTrigger open={open} setOpen={setOpen} flow={flow} />
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(() => {
updateFlow({ ..._.cloneDeep(flow), chat: chatHistory });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [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].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,
})
.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 (
<>
<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="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>
</>
);
} }

View file

@ -19,9 +19,24 @@ export default function CodeAreaComponent({
} }
}, [disabled, onChange]); }, [disabled, onChange]);
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"}> <div
className={
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
}
>
<div className="w-full flex items-center gap-3"> <div className="w-full flex items-center gap-3">
<span <span
onClick={() => {
openPopUp(
<CodeAreaModal
value={myValue}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
/>
);
}}
className={ className={
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + "truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
(disabled ? " bg-gray-200" : "") (disabled ? " bg-gray-200" : "")

View file

@ -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}

View file

@ -69,6 +69,7 @@ export default function InputFileComponent({
> >
<div className="w-full flex items-center gap-3"> <div className="w-full flex items-center gap-3">
<span <span
onClick={handleButtonClick}
className={ className={
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + "truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
(disabled ? " bg-gray-200" : "") (disabled ? " bg-gray-200" : "")

View file

@ -6,30 +6,61 @@ import TextAreaModal from "../../modals/textAreaModal";
import { TextAreaComponentType } from "../../types/components"; import { TextAreaComponentType } from "../../types/components";
import PromptAreaModal from "../../modals/promptModal"; import PromptAreaModal from "../../modals/promptModal";
export default function PromptAreaComponent({ value, onChange, disabled }:TextAreaComponentType) { export default function PromptAreaComponent({
const [myValue, setMyValue] = useState(value); value,
const { openPopUp } = useContext(PopUpContext); onChange,
useEffect(() => { disabled,
if (disabled) { }: TextAreaComponentType) {
setMyValue(""); const [myValue, setMyValue] = useState(value);
onChange(""); const { openPopUp } = useContext(PopUpContext);
} useEffect(() => {
}, [disabled, onChange]); if (disabled) {
return ( setMyValue("");
<div className={disabled ? "pointer-events-none cursor-not-allowed w-full" : " w-full"}> onChange("");
<div className="w-full flex items-center gap-3"> }
<span }, [disabled, onChange]);
className={ return (
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + <div
(disabled ? " bg-gray-200" : "") className={
} disabled ? "pointer-events-none cursor-not-allowed w-full" : " w-full"
> }
{myValue !== "" ? myValue : 'Text empty'} >
</span> <div className="w-full flex items-center gap-3">
<button onClick={()=>{openPopUp(<PromptAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}> <span
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600" /> onClick={() => {
</button> openPopUp(
</div> <PromptAreaModal
</div> value={myValue}
); setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
/>
);
}}
className={
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
(disabled ? " bg-gray-200" : "")
}
>
{myValue !== "" ? myValue : "Text empty"}
</span>
<button
onClick={() => {
openPopUp(
<PromptAreaModal
value={myValue}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
/>
);
}}
>
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600" />
</button>
</div>
</div>
);
} }

View file

@ -1,7 +1,6 @@
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import CodeAreaModal from "../../modals/codeAreaModal";
import TextAreaModal from "../../modals/textAreaModal"; import TextAreaModal from "../../modals/textAreaModal";
import { TextAreaComponentType } from "../../types/components"; import { TextAreaComponentType } from "../../types/components";
@ -17,7 +16,7 @@ export default function TextAreaComponent({ value, onChange, disabled }:TextArea
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> <div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<div className="w-full flex items-center gap-3"> <div className="w-full flex items-center gap-3">
<span <span onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}
className={ className={
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + "truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
(disabled ? " bg-gray-200" : "") (disabled ? " bg-gray-200" : "")

View file

@ -11,17 +11,15 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
return ( return (
<> <>
<DarkProvider> <DarkProvider>
<LocationProvider> <TypesProvider>
<AlertProvider> <LocationProvider>
<TabsProvider> <AlertProvider>
<PopUpProvider> <TabsProvider>
<TypesProvider> <PopUpProvider>{children}</PopUpProvider>
{children} </TabsProvider>
</TypesProvider> </AlertProvider>
</PopUpProvider> </LocationProvider>
</TabsProvider> </TypesProvider>
</AlertProvider>
</LocationProvider>
</DarkProvider> </DarkProvider>
</> </>
); );

View file

@ -1,33 +1,33 @@
import { createContext } from "react"; import { createContext } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
//context to set JSX element on the DOM // context to set JSX element on the DOM
export const PopUpContext = createContext({ export const PopUpContext = createContext({
openPopUp: (popUpElement: JSX.Element) => {}, openPopUp: (popUpElement: JSX.Element) => {},
closePopUp: () => {}, closePopUp: () => {},
}); });
interface PopUpProviderProps { interface PopUpProviderProps {
children: React.ReactNode; children: React.ReactNode;
} }
const PopUpProvider = ({ children }: PopUpProviderProps) => { const PopUpProvider = ({ children }: PopUpProviderProps) => {
const [popUpElement, setPopUpElement] = useState<JSX.Element | null>(null); const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
const openPopUp = (element: JSX.Element) => { const openPopUp = (element: JSX.Element) => {
setPopUpElement(element); setPopUpElements(prevPopUps => [element, ...prevPopUps]);
}; };
const closePopUp = () => { const closePopUp = () => {
setPopUpElement(null); setPopUpElements(prevPopUps => prevPopUps.slice(1));
}; };
return ( return (
<PopUpContext.Provider value={{ openPopUp, closePopUp }}> <PopUpContext.Provider value={{ openPopUp, closePopUp }}>
{children} {children}
{popUpElement} {popUpElements[0]}
</PopUpContext.Provider> </PopUpContext.Provider>
); );
}; };
export default PopUpProvider; export default PopUpProvider;

View file

@ -1,11 +1,21 @@
import { createContext, useEffect, useState, useRef, ReactNode, useContext } from "react"; import {
createContext,
useEffect,
useState,
useRef,
ReactNode,
useContext,
} from "react";
import { FlowType } from "../types/flow"; import { FlowType } from "../types/flow";
import { TabsContextType } from "../types/tabs"; import { LangFlowState, TabsContextType } from "../types/tabs";
import { normalCaseToSnakeCase } from "../utils"; import { normalCaseToSnakeCase, updateObject } from "../utils";
import { alertContext } from "./alertContext"; import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext";
import { TemplateVariableType } from "../types/api";
const { v4: uuidv4 } = require('uuid');
const TabsContextInitialValue: TabsContextType = { const TabsContextInitialValue: TabsContextType = {
save:()=>{}, save: () => {},
tabIndex: 0, tabIndex: 0,
setTabIndex: (index: number) => {}, setTabIndex: (index: number) => {},
flows: [], flows: [],
@ -13,11 +23,9 @@ const TabsContextInitialValue: TabsContextType = {
addFlow: (flowData?: any) => {}, addFlow: (flowData?: any) => {},
updateFlow: (newFlow: FlowType) => {}, updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => 0, incrementNodeId: () => 0,
downloadFlow: (flow:FlowType) => {}, downloadFlow: (flow: FlowType) => {},
uploadFlow: () => {}, uploadFlow: () => {},
lockChat: false, hardReset: () => {},
setLockChat:(prevState:boolean)=>{},
hardReset:()=>{},
}; };
export const TabsContext = createContext<TabsContextType>( export const TabsContext = createContext<TabsContextType>(
@ -25,51 +33,63 @@ export const TabsContext = createContext<TabsContextType>(
); );
export function TabsProvider({ children }: { children: ReactNode }) { 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 newNodeId = useRef(0); const newNodeId = useRef(0);
function incrementNodeId() { function incrementNodeId() {
newNodeId.current = newNodeId.current + 1; newNodeId.current = newNodeId.current + 1;
return newNodeId.current; return newNodeId.current;
} }
function save(){ function save() {
if (flows.length !== 0) if (flows.length !== 0)
window.localStorage.setItem( window.localStorage.setItem(
"tabsData", "tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
); );
} }
useEffect(() => { useEffect(() => {
//save tabs locally //save tabs locally
save() save();
}, [flows, id, tabIndex, newNodeId]); }, [flows, id, tabIndex, newNodeId]);
useEffect(() => { useEffect(() => {
//get tabs locally saved //get tabs locally saved
let cookie = window.localStorage.getItem("tabsData"); let cookie = window.localStorage.getItem("tabsData");
if (cookie) { if (cookie && Object.keys(templates).length > 0) {
let cookieObject = JSON.parse(cookie); let cookieObject: LangFlowState = JSON.parse(cookie);
cookieObject.flows.forEach((flow) => {
flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length>0) {
node.data.node.template = updateObject(
node.data.node.template as TemplateVariableType,
templates[node.data.type][
"template"
] as unknown as TemplateVariableType
);
}
});
});
setTabIndex(cookieObject.tabIndex); setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows); setFlows(cookieObject.flows);
setId(cookieObject.id); setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId; newNodeId.current = cookieObject.nodeId;
} }
}, []); }, [templates]);
function hardReset(){ function hardReset() {
newNodeId.current=0; newNodeId.current = 0;
setTabIndex(0);setFlows([]);setId(0); setTabIndex(0);
setFlows([]);
setId("");
} }
/** /**
* Downloads the current flow as a JSON file * Downloads the current flow as a JSON file
*/ */
function downloadFlow(flow:FlowType) { function downloadFlow(flow: FlowType) {
// create a data URI with the current flow data // create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flow) JSON.stringify(flow)
@ -82,7 +102,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// simulate a click on the link element to trigger the download // simulate a click on the link element to trigger the download
link.click(); link.click();
setNoticeData({title:"Warning: Critical data,JSON file may including API keys."}) setNoticeData({
title: "Warning: Critical data,JSON file may including API keys.",
});
} }
/** /**
@ -103,7 +125,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// read the file as text // read the file as text
file.text().then((text) => { file.text().then((text) => {
// parse the text into a JSON object // parse the text into a JSON object
addFlow(JSON.parse(text)); let flow: FlowType = JSON.parse(text);
flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length>0) {
node.data.node.template = updateObject(
node.data.node.template as TemplateVariableType,
templates[node.data.type][
"template"
] as unknown as TemplateVariableType
);
}
});
addFlow();
}); });
} }
}; };
@ -139,19 +173,18 @@ export function TabsProvider({ children }: { children: ReactNode }) {
function addFlow(flow?: FlowType) { function addFlow(flow?: FlowType) {
// Get data from the flow or set it to null if there's no flow provided. // Get data from the flow or set it to null if there's no flow provided.
const data = flow?.data ? flow.data : null; const data = flow?.data ? flow.data : null;
const description = flow?.description?flow.description:"" const description = flow?.description ? flow.description : "";
// Create a new flow with a default name if no flow is provided. // Create a new flow with a default name if no flow is provided.
let newFlow: FlowType = { let newFlow: FlowType = {
description, description,
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) => {
@ -171,10 +204,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id); const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
if (index !== -1) { if (index !== -1) {
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;
}); });
@ -185,8 +217,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
value={{ value={{
save, save,
hardReset, hardReset,
lockChat,
setLockChat,
tabIndex, tabIndex,
setTabIndex, setTabIndex,
flows, flows,

View file

@ -1,6 +1,8 @@
import { createContext, ReactNode, useState } from "react"; import { createContext, ReactNode, useEffect, useState } from "react";
import { Node} from "reactflow"; import { Node} from "reactflow";
import { typesContextType } from "../types/typesContext"; import { typesContextType } from "../types/typesContext";
import { getAll } from "../controllers/API";
import { APIKindType } from "../types/api";
//context to share types adn functions from nodes to flow //context to share types adn functions from nodes to flow
@ -10,6 +12,10 @@ const initialValue:typesContextType = {
deleteNode: () => {}, deleteNode: () => {},
types: {}, types: {},
setTypes: () => {}, setTypes: () => {},
templates: {},
setTemplates: () => {},
data:{},
setData:()=>{}
}; };
export const typesContext = createContext<typesContextType>(initialValue); export const typesContext = createContext<typesContextType>(initialValue);
@ -17,6 +23,42 @@ export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({ children }:{children:ReactNode}) { export function TypesProvider({ children }:{children:ReactNode}) {
const [types, setTypes] = useState({}); const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] = useState(null); const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [templates, setTemplates] = useState({});
const [data, setData] = useState({});
useEffect(() => {
async function getTypes(): Promise<void> {
// Make an asynchronous API call to retrieve all data.
let result = await getAll();
// Update the state of the component with the retrieved data.
setData(result.data);
setTemplates(
Object.keys(result.data).reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach((c: keyof APIKindType)=>{
acc[c] = result.data[curr][c]
})
return acc;
},{})
);
// Set the types by reducing over the keys of the result data and updating the accumulator.
setTypes(
Object.keys(result.data).reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
acc[c] = curr;
// Add the base classes to the accumulator as well.
result.data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
return acc;
}, {})
);
}
// Call the getTypes function.
getTypes();
}, [setTypes]);
function deleteNode(idx:string) { function deleteNode(idx:string) {
reactFlowInstance.setNodes( reactFlowInstance.setNodes(
reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx) reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx)
@ -31,6 +73,10 @@ export function TypesProvider({ children }:{children:ReactNode}) {
reactFlowInstance, reactFlowInstance,
setReactFlowInstance, setReactFlowInstance,
deleteNode, deleteNode,
setTemplates,
templates,
data,
setData
}} }}
> >
{children} {children}

View file

@ -1,40 +1,43 @@
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`);
} }
export async function sendAll(data:sendAllProps) { 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 =
const response = await axios.get(url); "https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
const response = await axios.get(url);
const jsonFiles = response.data.filter((file: any) => {
return file.name.endsWith('.json'); const jsonFiles = response.data.filter((file: any) => {
}); return file.name.endsWith(".json");
});
const contentsPromises = jsonFiles.map(async (file: any) => {
const contentResponse = await axios.get(file.download_url); const contentsPromises = jsonFiles.map(async (file: any) => {
return contentResponse.data; const contentResponse = await axios.get(file.download_url);
}); return contentResponse.data;
});
const contents = await Promise.all(contentsPromises);
const contents = await Promise.all(contentsPromises);
return contents;
} return contents;
}

View file

@ -0,0 +1,166 @@
import { useContext, useState } from "react";
import { TabsContext } from "../../../../contexts/tabsContext";
import InputListComponent from "../../../../components/inputListComponent";
import Dropdown from "../../../../components/dropdownComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import InputComponent from "../../../../components/inputComponent";
import ToggleComponent from "../../../../components/toggleComponent";
import FloatComponent from "../../../../components/floatComponent";
import IntComponent from "../../../../components/intComponent";
import InputFileComponent from "../../../../components/inputFileComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import { classNames } from "../../../../utils";
export default function ModalField({ data, title, required, id, name, type }) {
const { save } = useContext(TabsContext);
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
const display =
type === "str" ||
type === "int" ||
type === "prompt" ||
type === "bool" ||
type === "float" ||
type === "file" ||
type === "code";
return (
<div
className={classNames(
"flex flex-row w-full items-center justify-between",
display ? "" : "hidden"
)}
>
{display && (
<div>
<span className="mx-2">{title}</span>
<span className="text-red-600">{required ? " *" : ""}</span>
</div>
)}
{type === "str" && !data.node.template[name].options ? (
<div className="w-1/2">
{data.node.template[name].list ? (
<InputListComponent
disabled={false}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
save();
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
save();
}}
/>
) : (
<InputComponent
disabled={false}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
save();
}}
/>
)}
</div>
) : type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleComponent
disabled={false}
enabled={enabled}
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
save();
}}
/>
</div>
) : type === "float" ? (
<div className="w-1/2">
<FloatComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
save();
}}
/>
</div>
) : type === "str" && data.node.template[name].options ? (
<div className="w-1/2">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) => (data.node.template[name].value = newValue)}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : type === "int" ? (
<div className="w-1/2">
<IntComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
save();
}}
/>
</div>
) : type === "file" ? (
<div className="w-1/2">
<InputFileComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].content = t;
save();
}}
></InputFileComponent>
</div>
) : type === "prompt" ? (
<div className="w-1/2">
<PromptAreaComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
save();
}}
/>
</div>
) : type === "code" ? (
<div className="w-1/2">
<CodeAreaComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
save();
}}
/>
</div>
) : (
<div className="hidden"></div>
)}
</div>
);
}

View file

@ -0,0 +1,144 @@
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { NodeDataType } from "../../types/flow";
import { nodeColors, nodeIcons, snakeToNormalCase } from "../../utils";
import { typesContext } from "../../contexts/typesContext";
import ModalField from "./components/ModalField";
export default function NodeModal({ data }: { data: NodeDataType }) {
const [open, setOpen] = useState(true);
const { closePopUp } = useContext(PopUpContext);
const { types } = useContext(typesContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
const Icon = nodeIcons[types[data.type]];
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-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 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="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button
type="button"
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
<Icon
className="w-10 mt-4 h-10 p-1 rounded"
style={{
color:
nodeColors[types[data.type]] ?? nodeColors.unknown,
}}
/>
<div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
>
{data.type}
</Dialog.Title>
</div>
</div>
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="flex h-full w-full">
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
<div className="flex flex-col h-full gap-5">
{
Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_"&& data.node.template[t].advanced && data.node.template[t].show)
.map((t: string, idx) => {
return (
<ModalField
key={idx}
data={data}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? snakeToNormalCase(
data.node.template[t].name
)
: snakeToNormalCase(t)
}
required={data.node.template[t].required}
id={
data.node.template[t].type +
"|" +
t +
"|" +
data.id
}
name={t}
type={data.node.template[t].type}
/>
);
})
}
</div>
</div>
</div>
</div>
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
setModalOpen(false);
}}
>
Done
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View 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>
</>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View file

@ -1,9 +1,9 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { import {
XMarkIcon, XMarkIcon,
ArrowDownTrayIcon, ArrowDownTrayIcon,
DocumentDuplicateIcon, DocumentDuplicateIcon,
ComputerDesktopIcon, ComputerDesktopIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react"; import { Fragment, useContext, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
@ -12,165 +12,169 @@ import { TabsContext } from "../../contexts/tabsContext";
import { removeApiKeys } from "../../utils"; import { removeApiKeys } from "../../utils";
export default function ExportModal() { 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);
if (x === false) { if (x === false) {
setTimeout(() => { setTimeout(() => {
closePopUp(); closePopUp();
}, 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
as="div" as="div"
className="relative z-10" className="relative z-10"
onClose={setModalOpen} onClose={setModalOpen}
initialFocus={ref} initialFocus={ref}
> >
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <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"> <div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100" enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]"> <Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="button" type="button"
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
> >
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button> </button>
</div> </div>
<div className="h-full w-full flex flex-col justify-center items-center"> <div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm"> <div className="flex w-full pb-4 z-10 justify-center shadow-sm">
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10"> <div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
<ArrowDownTrayIcon <ArrowDownTrayIcon
className="h-6 w-6 text-blue-600" className="h-6 w-6 text-blue-600"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="mt-4 text-center sm:ml-4 sm:text-left"> <div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900" className="text-lg font-medium dark:text-white leading-10 text-gray-900"
> >
Export as Export as
</Dialog.Title> </Dialog.Title>
</div> </div>
</div> </div>
<div className="pt-16 flex flex-col items-start justify-start h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-16"> <div className="pt-16 flex flex-col items-start justify-start h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-16">
<div className="w-full"> <div className="w-full">
<label <label
htmlFor="name" htmlFor="name"
className="block mb-2 font-medium text-gray-700 dark:text-white" className="block mb-2 font-medium text-gray-700 dark:text-white"
> >
Name Name
</label> </label>
<input <input
onChange={(event) => { onChange={(event) => {
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" name="name"
name="name" 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 text-gray-900 dark:text-gray-100"
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" />
/> </div>
</div> <div className="w-full">
<div className="w-full"> <label
<label 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{" "}
Description <span className="text-gray-400 text-sm"> (optional)</span> <span className="text-gray-400 text-sm">
</label> {" "}
<textarea (optional)
name="description" </span>
id="description" </label>
onChange={(event) => { <textarea
let newFlow = flows[tabIndex]; name="description"
newFlow.description = event.target.value; id="description"
updateFlow(newFlow); onChange={(event) => {
}} let newFlow = flows[tabIndex];
value={flows[tabIndex].description ?? null} newFlow.description = event.target.value;
placeholder="Flow description" updateFlow(newFlow);
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" value={flows[tabIndex].description ?? null}
></textarea> placeholder="Flow description"
</div> rows={3}
<div> 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"
<label htmlFor="checkbox" className="flex items-center"> ></textarea>
<input </div>
onChange={(event) => {
setChecked(event.target.checked) <div>
}} <label htmlFor="checkbox" className="flex items-center">
checked={checked} <input
id="checkbox" onChange={(event) => {
type="checkbox" setChecked(event.target.checked);
className="h-4 w-4 text-blue-600 border-gray-300 rounded dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500" }}
/> checked={checked}
<span className="ml-2 font-medium text-gray-700 dark:text-white"> id="checkbox"
Save with my API keys type="checkbox"
</span> className="h-4 w-4 text-blue-600 border-gray-300 rounded dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
</label> />
</div> <span className="ml-2 font-medium text-gray-700 dark:text-white">
<div className="w-full flex justify-end"> Save with my API keys
<button </span>
onClick={() => { </label>
if(checked) downloadFlow(flows[tabIndex]); </div>
else downloadFlow(removeApiKeys(flows[tabIndex])) <div className="w-full flex justify-end">
}} <button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" onClick={() => {
> if (checked) downloadFlow(flows[tabIndex]);
Download Flow else downloadFlow(removeApiKeys(flows[tabIndex]));
</button> }}
</div> className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
</div> >
</div> Download Flow
</Dialog.Panel> </button>
</Transition.Child> </div>
</div> </div>
</div> </div>
</Dialog> </Dialog.Panel>
</Transition.Root> </Transition.Child>
); </div>
</div>
</Dialog>
</Transition.Root>
);
} }

View file

@ -2,43 +2,14 @@ import { Bars2Icon } from "@heroicons/react/24/outline";
import DisclosureComponent from "../DisclosureComponent"; import DisclosureComponent from "../DisclosureComponent";
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils"; import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { getAll } from "../../../../controllers/API";
import { typesContext } from "../../../../contexts/typesContext"; import { typesContext } from "../../../../contexts/typesContext";
import { import {
APIClassType, APIClassType,
APIKindType,
APIObjectType, APIObjectType,
} from "../../../../types/api"; } from "../../../../types/api";
export default function ExtraSidebar() { export default function ExtraSidebar() {
const [data, setData] = useState({}); const {data} = useContext(typesContext)
const { setTypes } = useContext(typesContext);
useEffect(() => {
async function getTypes(): Promise<void> {
// Make an asynchronous API call to retrieve all data.
let result = await getAll();
// Update the state of the component with the retrieved data.
setData(result.data);
// Set the types by reducing over the keys of the result data and updating the accumulator.
setTypes(
Object.keys(result.data).reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
acc[c] = curr;
// Add the base classes to the accumulator as well.
result.data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
return acc;
}, {})
);
}
// Call the getTypes function.
getTypes();
}, [setTypes]);
function onDragStart( function onDragStart(
event: React.DragEvent<any>, event: React.DragEvent<any>,
@ -51,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={{
@ -60,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
@ -84,7 +55,9 @@ export default function ExtraSidebar() {
</div> </div>
</div> </div>
))} ))}
{Object.keys(data[d]).length===0 && <div className="text-gray-400 text-center">Coming soon</div>} {Object.keys(data[d]).length === 0 && (
<div className="text-gray-400 text-center">Coming soon</div>
)}
</div> </div>
</DisclosureComponent> </DisclosureComponent>
))} ))}

View file

@ -5,116 +5,113 @@ import { TabsContext } from "../../../../contexts/tabsContext";
import FlowPage from "../.."; import FlowPage from "../..";
import { darkContext } from "../../../../contexts/darkContext"; import { darkContext } from "../../../../contexts/darkContext";
import { import {
ArrowDownTrayIcon, ArrowDownTrayIcon,
ArrowUpTrayIcon, ArrowUpTrayIcon,
BellIcon, BellIcon,
MoonIcon, MoonIcon,
SunIcon, SunIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { PopUpContext } from "../../../../contexts/popUpContext"; import { PopUpContext } from "../../../../contexts/popUpContext";
import AlertDropdown from "../../../../alerts/alertDropDown"; import AlertDropdown from "../../../../alerts/alertDropDown";
import { alertContext } from "../../../../contexts/alertContext"; import { alertContext } from "../../../../contexts/alertContext";
import ImportModal from "../../../../modals/importModal"; import ImportModal from "../../../../modals/importModal";
import ExportModal from "../../../../modals/exportModal"; import ExportModal from "../../../../modals/exportModal";
import { typesContext } from "../../../../contexts/typesContext";
export default function TabsManagerComponent() { export default function TabsManagerComponent() {
const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } =
useContext(TabsContext); useContext(TabsContext);
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
const {templates} = useContext(typesContext)
const AlertWidth = 256; const AlertWidth = 256;
const { dark, setDark } = useContext(darkContext); const { dark, setDark } = useContext(darkContext);
const { notificationCenter, setNotificationCenter } = const { notificationCenter, setNotificationCenter } =
useContext(alertContext); useContext(alertContext);
useEffect(() => { useEffect(() => {
//create the first flow //create the first flow
if (flows.length === 0) { if (flows.length === 0&& Object.keys(templates).length>0) {
addFlow(); addFlow();
} }
}, [addFlow, flows.length]); }, [addFlow, flows.length,templates]);
return ( return (
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">
<div className="w-full flex pr-2 flex-row text-center items-center bg-gray-100 dark:bg-gray-800 px-2"> <div className="w-full flex pr-2 flex-row text-center items-center bg-gray-100 dark:bg-gray-800 px-2">
{flows.map((flow, index) => { {flows.map((flow, index) => {
return ( return (
<TabComponent <TabComponent
onClick={() => setTabIndex(index)} onClick={() => setTabIndex(index)}
selected={index === tabIndex} selected={index === tabIndex}
key={index} key={index}
flow={flow} flow={flow}
/> />
); );
})} })}
<TabComponent <TabComponent
onClick={() => { onClick={() => {
addFlow(); addFlow();
}} }}
selected={false} selected={false}
flow={null} flow={null}
/> />
<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( className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
<ImportModal >
/> Import <ArrowUpTrayIcon className="w-5 h-5" />
) </button>
} <button
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500" onClick={() => openPopUp(<ExportModal />)}
> className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500"
Import <ArrowUpTrayIcon className="w-5 h-5" /> >
</button> Export <ArrowDownTrayIcon className="h-5 w-5" />
<button </button>
onClick={() =>openPopUp(<ExportModal/>)} <button
className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500" className="text-gray-600 hover:text-gray-500 "
> onClick={() => {
Export <ArrowDownTrayIcon className="h-5 w-5" /> setDark(!dark);
</button> }}
<button >
className="text-gray-600 hover:text-gray-500 " {dark ? (
onClick={() => { <SunIcon className="h-5 w-5" />
setDark(!dark); ) : (
}} <MoonIcon className="h-5 w-5" />
> )}
{dark ? ( </button>
<SunIcon className="h-5 w-5" /> <button
) : ( className="text-gray-600 hover:text-gray-500 relative"
<MoonIcon className="h-5 w-5" /> onClick={(event: React.MouseEvent<HTMLElement>) => {
)} setNotificationCenter(false);
</button> const top = (event.target as Element).getBoundingClientRect().top;
<button const left = (event.target as Element).getBoundingClientRect()
className="text-gray-600 hover:text-gray-500 relative" .left;
onClick={(event: React.MouseEvent<HTMLElement>) => { openPopUp(
setNotificationCenter(false); <div
const top = (event.target as Element).getBoundingClientRect().top; className="z-10 absolute"
const left = (event.target as Element).getBoundingClientRect() style={{ top: top + 20, left: left - AlertWidth }}
.left; >
openPopUp( <AlertDropdown />
<div </div>
className="z-10 absolute" );
style={{ top: top + 20, left: left - AlertWidth }} }}
> >
<AlertDropdown /> {notificationCenter && (
</div> <div className="absolute w-1.5 h-1.5 rounded-full bg-red-600 right-[3px]"></div>
); )}
}} <BellIcon className="h-5 w-5" aria-hidden="true" />
> </button>
{notificationCenter && ( </div>
<div className="absolute w-1.5 h-1.5 rounded-full bg-red-600 right-[3px]"></div> </div>
)} <div className="w-full h-full">
<BellIcon className="h-5 w-5" aria-hidden="true" /> <ReactFlowProvider>
</button> {flows[tabIndex] ? (
</div> <FlowPage flow={flows[tabIndex]}></FlowPage>
</div> ) : (
<div className="w-full h-full"> <></>
<ReactFlowProvider> )}
{flows[tabIndex] ? ( </ReactFlowProvider>
<FlowPage flow={flows[tabIndex]}></FlowPage> </div>
) : ( </div>
<></> );
)}
</ReactFlowProvider>
</div>
</div>
);
} }

View file

@ -1,15 +1,15 @@
import { useCallback, useContext, useEffect, useRef } from "react"; import { useCallback, useContext, useEffect, useRef } from "react";
import ReactFlow, { import ReactFlow, {
Background, Background,
Controls, Controls,
addEdge, addEdge,
useEdgesState, useEdgesState,
useNodesState, useNodesState,
useReactFlow, useReactFlow,
updateEdge, updateEdge,
EdgeChange, EdgeChange,
Connection, Connection,
Edge, Edge,
} from "reactflow"; } from "reactflow";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent"; import ExtraSidebar from "./components/extraSidebarComponent";
@ -24,7 +24,7 @@ import { APIClassType } from "../../types/api";
import { isValidConnection } from "../../utils"; import { isValidConnection } from "../../utils";
const nodeTypes = { const nodeTypes = {
genericNode: GenericNode, genericNode: GenericNode,
}; };
var _ = require("lodash"); var _ = require("lodash");
@ -32,143 +32,150 @@ var _ = require("lodash");
export default function FlowPage({ flow }:{flow:FlowType}) { export default function FlowPage({ flow }:{flow:FlowType}) {
let { updateFlow, incrementNodeId} = let { updateFlow, incrementNodeId} =
useContext(TabsContext); useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance } = const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext); useContext(typesContext);
const reactFlowWrapper = useRef(null); const reactFlowWrapper = useRef(null);
const { setExtraComponent, setExtraNavigation } = useContext(locationContext); const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState( const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? [] flow.data?.nodes ?? []
); );
const [edges, setEdges, onEdgesChange] = useEdgesState( const [edges, setEdges, onEdgesChange] = useEdgesState(
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) {
flow.data = reactFlowInstance.toObject(); flow.data = reactFlowInstance.toObject();
updateFlow(flow); updateFlow(flow);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]); }, [nodes, edges]);
//update flow when tabs change //update flow when tabs change
useEffect(() => { useEffect(() => {
setNodes(flow?.data?.nodes ?? []); setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []); setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) { if (reactFlowInstance) {
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 }); setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
} }
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]); }, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
//set extra sidebar //set extra sidebar
useEffect(() => { useEffect(() => {
setExtraComponent(<ExtraSidebar />); setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" }); setExtraNavigation({ title: "Components" });
}, [setExtraComponent, setExtraNavigation]); }, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback( const onEdgesChangeMod = useCallback(
(s:EdgeChange[]) => { (s: EdgeChange[]) => {
onEdgesChange(s); onEdgesChange(s);
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
return newX; return newX;
}); });
}, },
[onEdgesChange, setNodes] [onEdgesChange, setNodes]
); );
const onConnect = useCallback( const onConnect = useCallback(
(params:Connection) => { (params: Connection) => {
setEdges((eds) => setEdges((eds) =>
addEdge({ ...params, className: "animate-pulse" }, eds) addEdge({ ...params, className: "animate-pulse" }, eds)
); );
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
return newX; return newX;
}); });
}, },
[setEdges, setNodes] [setEdges, setNodes]
); );
const onDragOver = useCallback((event:React.DragEvent) => { const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
event.dataTransfer.dropEffect = "move"; event.dataTransfer.dropEffect = "move";
}, []); }, []);
const onDrop = useCallback( const onDrop = useCallback(
(event:React.DragEvent) => { (event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
// Helper function to generate a unique node ID
function getId() {
return `dndnode_` + incrementNodeId();
}
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
// 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"));
// 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 !== "chatInput" ||
(data.type === "chatInput" &&
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
) {
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// Generate a unique node ID
let newId = getId();
// Create a new node object
const newNode:NodeType = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
setNodes((nds) => nds.concat(newNode));
} else {
// If a chat input node already exists, set an error message
setErrorData({
title: "Error creating node",
list: ["There can't be more than one chat input."],
});
}
},
// Specify dependencies for useCallback
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
);
// Helper function to generate a unique node ID
const onDelete = (mynodes) => { function getId() {
setEdges(edges.filter((ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id))); return `dndnode_` + incrementNodeId();
} }
const onEdgeUpdateStart = useCallback(() => { // Get the current bounds of the ReactFlow wrapper element
edgeUpdateSuccessful.current = false; const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
}, []);
// Extract the data from the drag event and parse it as a JSON object
const onEdgeUpdate = useCallback((oldEdge:Edge, newConnection:Connection) => { let data: { type: string; node?: APIClassType } = JSON.parse(
if(isValidConnection(newConnection,reactFlowInstance)){ event.dataTransfer.getData("json")
edgeUpdateSuccessful.current = true; );
setEdges((els) => updateEdge(oldEdge, newConnection, els));
} // 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 !== "chatInput" ||
(data.type === "chatInput" &&
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
) {
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// Generate a unique node ID
let newId = getId();
// Create a new node object
const newNode: NodeType = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
setNodes((nds) => nds.concat(newNode));
} else {
// If a chat input node already exists, set an error message
setErrorData({
title: "Error creating node",
list: ["There can't be more than one chat input."],
});
}
},
// Specify dependencies for useCallback
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
);
const onDelete = (mynodes) => {
setEdges(
edges.filter(
(ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id)
)
);
};
const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false;
}, []);
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance)) {
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
[]
);
const onEdgeUpdateEnd = useCallback((_, edge) => { const onEdgeUpdateEnd = useCallback((_, edge) => {
if (!edgeUpdateSuccessful.current) { if (!edgeUpdateSuccessful.current) {
@ -180,7 +187,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
return ( return (
<div className="w-full h-full" ref={reactFlowWrapper}> <div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(types).length > 0 ? ( {Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
<> <>
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}

View file

@ -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
View file

@ -0,0 +1,4 @@
declare module "*.svg" {
const content: any;
export default content;
}

View file

@ -1,12 +1,29 @@
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";
title: string; title: string;
link?: string; link?: string;
list?: Array<string>; list?: Array<string>;
id: string; id: string;
}; };

View file

@ -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 sendAllProps={
nodes: Node[];
edges: Edge[];
name:string,
description:string;
viewport: Viewport;
message:string;
chatHistory:{message:string,isSend:boolean}[],
}; };
export type errorsTypeAPI={function:{errors:Array<string>},imports:{errors:Array<string>}} export type APIClassType = {
export type PromptTypeAPI = {input_variables:Array<string>} 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 = {
nodes: Node[];
edges: Edge[];
name: string;
description: string;
viewport: Viewport;
message: string;
chatHistory: { message: string; isSend: boolean }[];
};
export type errorsTypeAPI = {
function: { errors: Array<string> };
imports: { errors: Array<string> };
};
export type PromptTypeAPI = { input_variables: Array<string> };

View file

@ -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 }>;
};

View file

@ -1,81 +1,85 @@
import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react"; import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react";
import { NodeDataType } from "../flow/index"; import { NodeDataType } from "../flow/index";
export type InputComponentType = { export type InputComponentType = {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
onChange: (value: string) => void; onChange: (value: string) => void;
password: boolean; password: boolean;
}; };
export type ToggleComponentType = { export type ToggleComponentType = {
enabled: boolean; enabled: boolean;
setEnabled: (state: boolean) => void; setEnabled: (state: boolean) => void;
disabled: boolean; disabled: boolean;
}; };
export type DropDownComponentType = { export type DropDownComponentType = {
value: string; value: string;
options: string[]; options: string[];
onSelect: (value: string) => void; onSelect: (value: string) => void;
}; };
export type ParameterComponentType = { export type ParameterComponentType = {
data: NodeDataType; data: NodeDataType;
title: string; title: string;
id: string; id: string;
color: string; color: string;
left: boolean; left: boolean;
type: string; type: string;
required?: boolean; required?: boolean;
name?: string; name?: string;
tooltipTitle: string; tooltipTitle: string;
}; };
export type InputListComponentType = { export type InputListComponentType = {
value: string[]; value: string[];
onChange: (value: string[]) => void; onChange: (value: string[]) => void;
disabled: boolean; disabled: boolean;
}; };
export type TextAreaComponentType = { export type TextAreaComponentType = {
disabled: boolean; disabled: boolean;
onChange: (value: string[] | string) => void; onChange: (value: string[] | string) => void;
value: string; value: string;
}; };
export type FileComponentType = { export type FileComponentType = {
disabled: boolean; disabled: boolean;
onChange: (value: string[] | string) => void; onChange: (value: string[] | string) => void;
value: string; value: string;
suffixes:Array<string>; suffixes: Array<string>;
fileTypes:Array<string>; fileTypes: Array<string>;
onFileChange:(value: string) => void; onFileChange: (value: string) => void;
}; };
export type DisclosureComponentType = { export type DisclosureComponentType = {
children: ReactNode; children: ReactNode;
button: { button: {
title: string; title: string;
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>; Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
buttons?: { buttons?: {
Icon: ReactElement; Icon: ReactElement;
title: string; title: string;
onClick: (event?: React.MouseEvent) => void; onClick: (event?: React.MouseEvent) => void;
}[]; }[];
}; };
}; };
export type FloatComponentType = { export type FloatComponentType = {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
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";
};

View file

@ -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;
};

View file

@ -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 NodeDataType = {
type: string;
node?: APIClassType;
id: string;
value: any;
}; };
export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType}
export type NodeDataType = {type:string,node?:APIClassType,id:string,value:any}

View file

@ -1,17 +1,17 @@
import { FlowType } from "../flow"; import { FlowType } from "../flow";
export type TabsContextType = { export type TabsContextType = {
save:()=>void; save: () => void;
tabIndex: number; tabIndex: number;
setTabIndex: (index: number) => void; setTabIndex: (index: number) => void;
flows: Array<FlowType>; flows: Array<FlowType>;
removeFlow: (id: string) => void; removeFlow: (id: string) => void;
addFlow: (flowData?: FlowType) => void; addFlow: (flowData?: FlowType) => void;
updateFlow: (newFlow: FlowType) => void; updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => number; incrementNodeId: () => number;
downloadFlow: (flow:FlowType) => void; downloadFlow: (flow: FlowType) => void;
uploadFlow: () => void; uploadFlow: () => void;
lockChat:boolean; hardReset: () => void;
setLockChat:(prevState:boolean)=>void; };
hardReset:()=>void;
}; export type LangFlowState={ tabIndex:number, flows:FlowType[], id:string, nodeId:number }

View file

@ -0,0 +1,7 @@
const template:{[char: string]: string}={}
export type TemplateContextType = {
templates: typeof template;
setTemplates: (newState: {}) => void;
};

View file

@ -1,6 +1,9 @@
import { ReactFlowInstance } from "reactflow"; import { ReactFlowInstance } from "reactflow";
const types:{[char: string]: string}={} const types:{[char: string]: string}={};
const template:{[char: string]: string}={}
const data:{[char: string]: string}={}
export type typesContextType = { export type typesContextType = {
reactFlowInstance: ReactFlowInstance|null; reactFlowInstance: ReactFlowInstance|null;
@ -8,4 +11,8 @@ export type typesContextType = {
deleteNode: (idx: string) => void; deleteNode: (idx: string) => void;
types: typeof types; types: typeof types;
setTypes: (newState: {}) => void; setTypes: (newState: {}) => void;
}; templates: typeof template;
setTemplates: (newState: {}) => void;
data: typeof data;
setData: (newState: {}) => void;
};

View file

@ -14,13 +14,13 @@ 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(" ");
} }
@ -70,7 +70,7 @@ export const borderLColors = {
gray: "border-l-gray-500", gray: "border-l-gray-500",
}; };
export const nodeColors: {[char: string]: string} = { export const nodeColors: { [char: string]: string } = {
prompts: "#4367BF", prompts: "#4367BF",
llms: "#6344BE", llms: "#6344BE",
chains: "#FE7500", chains: "#FE7500",
@ -78,19 +78,19 @@ 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",
vectorstores: "#AA8742", vectorstores: "#AA8742",
textsplitters: "#B47CB5", textsplitters: "#B47CB5",
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 } = {
prompts: "Prompts", prompts: "Prompts",
llms: "LLMs", llms: "LLMs",
chains: "Chains", chains: "Chains",
@ -102,14 +102,18 @@ export const nodeNames:{[char: string]: string} = {
embeddings: "Embeddings", embeddings: "Embeddings",
documentloaders: "Document Loaders", documentloaders: "Document Loaders",
vectorstores: "Vector Stores", vectorstores: "Vector Stores",
toolkits:"Toolkits", toolkits: "Toolkits",
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,
@ -118,14 +122,14 @@ export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent<React.SV
tools: WrenchIcon, tools: WrenchIcon,
advanced: ComputerDesktopIcon, advanced: ComputerDesktopIcon,
chat: Bars3CenterLeftIcon, chat: Bars3CenterLeftIcon,
embeddings:FingerPrintIcon, embeddings: FingerPrintIcon,
documentloaders:PaperClipIcon, documentloaders: PaperClipIcon,
vectorstores: CircleStackIcon, vectorstores: CircleStackIcon,
toolkits:WrenchScrewdriverIcon, toolkits: WrenchScrewdriverIcon,
textsplitters:ScissorsIcon, textsplitters: ScissorsIcon,
wrappers:GiftIcon, wrappers: GiftIcon,
utilities:Squares2X2Icon, utilities: Squares2X2Icon,
unknown:QuestionMarkCircleIcon unknown: QuestionMarkCircleIcon,
}; };
export const bgColors = { export const bgColors = {
@ -218,7 +222,7 @@ export const taskTypeMap: { [key: string]: string } = {
MULTICLASS_CLASSIFICATION: "Multiclass Classification", MULTICLASS_CLASSIFICATION: "Multiclass Classification",
}; };
const charWidths:{[char: string]: number} = { const charWidths: { [char: string]: number } = {
" ": 0.2, " ": 0.2,
"!": 0.2, "!": 0.2,
'"': 0.3, '"': 0.3,
@ -260,7 +264,7 @@ for (let i = 97; i <= 122; i++) {
charWidths[String.fromCharCode(i)] = 0.5; charWidths[String.fromCharCode(i)] = 0.5;
} }
export function measureTextWidth(text: string, fontSize:number) { export function measureTextWidth(text: string, fontSize: number) {
let wordWidth = 0; let wordWidth = 0;
for (let j = 0; j < text.length; j++) { for (let j = 0; j < text.length; j++) {
let char = text[j]; let char = text[j];
@ -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(" ");
@ -319,19 +327,19 @@ export function snakeToNormalCase(str: string) {
.join(" "); .join(" ");
} }
export function normalCaseToSnakeCase(str:string){ export function normalCaseToSnakeCase(str: string) {
return str return str
.split(" ") .split(" ")
.map((word, index) => { .map((word, index) => {
if (index === 0) { if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase(); return word[0].toUpperCase() + word.slice(1).toLowerCase();
} }
return word.toLowerCase(); return word.toLowerCase();
}) })
.join("_"); .join("_");
} }
export function roundNumber(x:number, decimals:number) { export function roundNumber(x: number, decimals: number) {
return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals); return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals);
} }
@ -345,12 +353,15 @@ export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
} }
export function isValidConnection( export function isValidConnection(
{ source, target, sourceHandle, targetHandle }:Connection, { source, target, sourceHandle, targetHandle }: Connection,
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;
@ -375,16 +386,34 @@ export function isValidConnection(
return false; return false;
} }
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 {
let clonedObject = _.cloneDeep(objectToUpdate)
// Loop through each key in the object to update
for (const key in clonedObject) {
// If the key is not in the reference object, delete it
if (!(key in reference)) {
delete clonedObject[key];
}
}
// Loop through each key in the reference object
for (const key in reference) {
// If the key is not in the object to update, add it
if (!(key in clonedObject)) {
clonedObject[key] = reference[key];
}
}
return clonedObject;
} }

View file

@ -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':{ },
'-webkit-appearance': 'none', ".arrow-hide": {
'margin': 0 "&::-webkit-inner-spin-button": {
"-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')
], ],
} };

View file

@ -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")

View file

@ -36,6 +36,7 @@ def test_zero_shot_agent(client: TestClient):
"name": "llm_chain", "name": "llm_chain",
"type": "LLMChain", "type": "LLMChain",
"list": False, "list": False,
"advanced": True,
} }
assert template["allowed_tools"] == { assert template["allowed_tools"] == {
"required": False, "required": False,
@ -46,6 +47,7 @@ def test_zero_shot_agent(client: TestClient):
"name": "allowed_tools", "name": "allowed_tools",
"type": "Tool", "type": "Tool",
"list": True, "list": True,
"advanced": True,
} }
@ -68,6 +70,7 @@ def test_json_agent(client: TestClient):
"name": "toolkit", "name": "toolkit",
"type": "BaseToolkit", "type": "BaseToolkit",
"list": False, "list": False,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -78,6 +81,7 @@ def test_json_agent(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
"advanced": True,
} }
@ -104,6 +108,7 @@ def test_csv_agent(client: TestClient):
"type": "file", "type": "file",
"list": False, "list": False,
"content": None, "content": None,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -114,6 +119,7 @@ def test_csv_agent(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
"advanced": True,
} }
@ -124,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"] == {
@ -143,6 +149,7 @@ def test_initialize_agent(client: TestClient):
"name": "agent", "name": "agent",
"type": "str", "type": "str",
"list": True, "list": True,
"advanced": True,
} }
assert template["memory"] == { assert template["memory"] == {
"required": False, "required": False,
@ -153,6 +160,7 @@ def test_initialize_agent(client: TestClient):
"name": "memory", "name": "memory",
"type": "BaseChatMemory", "type": "BaseChatMemory",
"list": False, "list": False,
"advanced": True,
} }
assert template["tools"] == { assert template["tools"] == {
"required": False, "required": False,
@ -163,6 +171,7 @@ def test_initialize_agent(client: TestClient):
"name": "tools", "name": "tools",
"type": "Tool", "type": "Tool",
"list": True, "list": True,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -173,4 +182,5 @@ def test_initialize_agent(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
"advanced": True,
} }

View file

@ -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

View 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"}

View file

@ -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,
@ -31,16 +36,18 @@ def test_conversation_chain(client: TestClient):
"name": "memory", "name": "memory",
"type": "BaseMemory", "type": "BaseMemory",
"list": False, "list": False,
"advanced": False,
} }
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -51,6 +58,7 @@ def test_conversation_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
"advanced": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": True, "required": True,
@ -62,6 +70,7 @@ def test_conversation_chain(client: TestClient):
"name": "input_key", "name": "input_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": True, "required": True,
@ -73,6 +82,7 @@ def test_conversation_chain(client: TestClient):
"name": "output_key", "name": "output_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["_type"] == "ConversationChain" assert template["_type"] == "ConversationChain"
@ -91,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,
@ -102,17 +112,19 @@ def test_llm_chain(client: TestClient):
"name": "memory", "name": "memory",
"type": "BaseMemory", "type": "BaseMemory",
"list": False, "list": False,
"advanced": False,
} }
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": False, "value": False,
"password": False, "password": False,
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -123,6 +135,7 @@ def test_llm_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
"advanced": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": True, "required": True,
@ -134,6 +147,7 @@ def test_llm_chain(client: TestClient):
"name": "output_key", "name": "output_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
@ -145,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,
@ -156,17 +170,19 @@ def test_llm_checker_chain(client: TestClient):
"name": "memory", "name": "memory",
"type": "BaseMemory", "type": "BaseMemory",
"list": False, "list": False,
"advanced": False,
} }
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": False, "value": False,
"password": False, "password": False,
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -177,6 +193,7 @@ def test_llm_checker_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLLM", "type": "BaseLLM",
"list": False, "list": False,
"advanced": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": True, "required": True,
@ -188,6 +205,7 @@ def test_llm_checker_chain(client: TestClient):
"name": "input_key", "name": "input_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": True, "required": True,
@ -199,6 +217,7 @@ def test_llm_checker_chain(client: TestClient):
"name": "output_key", "name": "output_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["_type"] == "LLMCheckerChain" assert template["_type"] == "LLMCheckerChain"
@ -217,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,
@ -228,17 +247,19 @@ def test_llm_math_chain(client: TestClient):
"name": "memory", "name": "memory",
"type": "BaseMemory", "type": "BaseMemory",
"list": False, "list": False,
"advanced": False,
} }
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": False, "value": False,
"password": False, "password": False,
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
@ -249,6 +270,7 @@ def test_llm_math_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
"advanced": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": True, "required": True,
@ -260,6 +282,7 @@ def test_llm_math_chain(client: TestClient):
"name": "input_key", "name": "input_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": True, "required": True,
@ -271,6 +294,7 @@ def test_llm_math_chain(client: TestClient):
"name": "output_key", "name": "output_key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["_type"] == "LLMMathChain" assert template["_type"] == "LLMMathChain"
@ -291,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",
@ -298,35 +323,7 @@ def test_series_character_chain(client: TestClient):
"SeriesCharacterChain", "SeriesCharacterChain",
} }
template = chain["template"] template = chain["template"]
assert template["memory"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": False,
"value": {
"chat_memory": {"messages": []},
"output_key": None,
"input_key": None,
"return_messages": False,
"human_prefix": "Human",
"ai_prefix": "AI",
"memory_key": "history",
},
"password": False,
"name": "memory",
"type": "BaseMemory",
"list": False,
}
assert template["verbose"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "verbose",
"type": "bool",
"list": False,
}
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
"placeholder": "", "placeholder": "",
@ -336,50 +333,7 @@ def test_series_character_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
} "advanced": False,
assert template["input_key"] == {
"required": True,
"placeholder": "",
"show": True,
"multiline": False,
"value": "input",
"password": False,
"name": "input_key",
"type": "str",
"list": False,
}
assert template["output_key"] == {
"required": True,
"placeholder": "",
"show": True,
"multiline": False,
"value": "response",
"password": False,
"name": "output_key",
"type": "str",
"list": False,
}
assert template["template"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": True,
"value": "I want you to act like {character} from {series}.\nI want you to respond and answer like {character}. do not write any explanations. only answer like {character}.\nYou must know all of the knowledge of {character}.\nCurrent conversation:\n{history}\nHuman: {input}\n{character}:", # noqa: E501
"password": False,
"name": "template",
"type": "str",
"list": False,
}
assert template["ai_prefix_value"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"value": "character",
"password": False,
"name": "ai_prefix_value",
"type": "str",
"list": False,
} }
assert template["character"] == { assert template["character"] == {
"required": True, "required": True,
@ -390,6 +344,7 @@ def test_series_character_chain(client: TestClient):
"name": "character", "name": "character",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": False,
} }
assert template["series"] == { assert template["series"] == {
"required": True, "required": True,
@ -400,6 +355,7 @@ def test_series_character_chain(client: TestClient):
"name": "series", "name": "series",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": False,
} }
assert template["_type"] == "SeriesCharacterChain" assert template["_type"] == "SeriesCharacterChain"
@ -429,55 +385,7 @@ def test_mid_journey_prompt_chain(client: TestClient):
# Test the template object # Test the template object
template = chain["template"] template = chain["template"]
assert template["memory"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": False,
"value": {
"chat_memory": {"messages": []},
"output_key": None,
"input_key": None,
"return_messages": False,
"human_prefix": "Human",
"ai_prefix": "AI",
"memory_key": "history",
},
"password": False,
"name": "memory",
"type": "BaseMemory",
"list": False,
}
assert template["verbose"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "verbose",
"type": "bool",
"list": False,
}
# Continue with other template object assertions
assert template["prompt"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": False,
"value": {
"input_variables": ["history", "input"],
"output_parser": None,
"partial_variables": {},
"template": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n\nCurrent conversation:\n{history}\nHuman: {input}\nAI:", # noqa: E501
"template_format": "f-string",
"validate_template": True,
"_type": "prompt",
},
"password": False,
"name": "prompt",
"type": "BasePromptTemplate",
"list": False,
}
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
"placeholder": "", "placeholder": "",
@ -487,49 +395,7 @@ def test_mid_journey_prompt_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
} "advanced": False,
assert template["output_key"] == {
"required": True,
"placeholder": "",
"show": True,
"multiline": False,
"value": "response",
"password": False,
"name": "output_key",
"type": "str",
"list": False,
}
assert template["input_key"] == {
"required": True,
"placeholder": "",
"show": True,
"multiline": False,
"value": "input",
"password": False,
"name": "input_key",
"type": "str",
"list": False,
}
assert template["template"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": True,
"value": 'I want you to act as a prompt generator for Midjourney\'s artificial intelligence program.\n Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.\n Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.\n For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.\n The more detailed and imaginative your description, the more interesting the resulting image will be. Here is your first prompt:\n "A field of wildflowers stretches out as far as the eye can see, each one a different color and shape. In the distance, a massive tree towers over the landscape, its branches reaching up to the sky like tentacles."\n\n Current conversation:\n {history}\n Human: {input}\n AI:', # noqa: E501
"password": False,
"name": "template",
"type": "str",
"list": False,
}
assert template["ai_prefix_value"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "ai_prefix_value",
"type": "str",
"list": False,
} }
# Test the description object # Test the description object
assert ( assert (
@ -557,55 +423,7 @@ def test_time_travel_guide_chain(client: TestClient):
# Test the template object # Test the template object
template = chain["template"] template = chain["template"]
assert template["memory"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": False,
"value": {
"chat_memory": {"messages": []},
"output_key": None,
"input_key": None,
"return_messages": False,
"human_prefix": "Human",
"ai_prefix": "AI",
"memory_key": "history",
},
"password": False,
"name": "memory",
"type": "BaseMemory",
"list": False,
}
assert template["verbose"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "verbose",
"type": "bool",
"list": False,
}
assert template["prompt"] == {
"required": False,
"placeholder": "",
"show": True,
"multiline": False,
"value": {
"input_variables": ["history", "input"],
"output_parser": None,
"partial_variables": {},
"template": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n\nCurrent conversation:\n{history}\nHuman: {input}\nAI:", # noqa: E501
"template_format": "f-string",
"validate_template": True,
"_type": "prompt",
},
"password": False,
"name": "prompt",
"type": "BasePromptTemplate",
"list": False,
}
assert template["llm"] == { assert template["llm"] == {
"required": True, "required": True,
"placeholder": "", "placeholder": "",
@ -615,50 +433,7 @@ def test_time_travel_guide_chain(client: TestClient):
"name": "llm", "name": "llm",
"type": "BaseLanguageModel", "type": "BaseLanguageModel",
"list": False, "list": False,
} "advanced": False,
assert template["output_key"] == {
"required": True,
"placeholder": "",
"show": True,
"multiline": False,
"value": "response",
"password": False,
"name": "output_key",
"type": "str",
"list": False,
} }
assert template["input_key"] == { assert chain["description"] == "Time travel guide chain to be used in the flow."
"required": True,
"placeholder": "",
"show": True,
"multiline": False,
"value": "input",
"password": False,
"name": "input_key",
"type": "str",
"list": False,
}
assert template["template"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": True,
"value": "I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.\n Current conversation:\n {history}\n Human: {input}\n AI:", # noqa: E501
"password": False,
"name": "template",
"type": "str",
"list": False,
}
assert template["ai_prefix_value"] == {
"required": False,
"placeholder": "",
"show": False,
"multiline": False,
"password": False,
"name": "ai_prefix_value",
"type": "str",
"list": False,
}
assert chain["description"] == ""

View file

@ -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

View file

@ -28,6 +28,7 @@ def test_hugging_face_hub(client: TestClient):
"name": "cache", "name": "cache",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
@ -39,6 +40,7 @@ def test_hugging_face_hub(client: TestClient):
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["client"] == { assert template["client"] == {
"required": False, "required": False,
@ -49,6 +51,7 @@ def test_hugging_face_hub(client: TestClient):
"name": "client", "name": "client",
"type": "Any", "type": "Any",
"list": False, "list": False,
"advanced": True,
} }
assert template["repo_id"] == { assert template["repo_id"] == {
"required": False, "required": False,
@ -60,6 +63,7 @@ def test_hugging_face_hub(client: TestClient):
"name": "repo_id", "name": "repo_id",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["task"] == { assert template["task"] == {
"required": True, "required": True,
@ -71,6 +75,7 @@ def test_hugging_face_hub(client: TestClient):
"name": "task", "name": "task",
"type": "str", "type": "str",
"list": True, "list": True,
"advanced": True,
} }
assert template["model_kwargs"] == { assert template["model_kwargs"] == {
"required": False, "required": False,
@ -81,6 +86,7 @@ def test_hugging_face_hub(client: TestClient):
"name": "model_kwargs", "name": "model_kwargs",
"type": "code", "type": "code",
"list": False, "list": False,
"advanced": True,
} }
assert template["huggingfacehub_api_token"] == { assert template["huggingfacehub_api_token"] == {
"required": False, "required": False,
@ -92,6 +98,7 @@ def test_hugging_face_hub(client: TestClient):
"display_name": "HuggingFace Hub API Token", "display_name": "HuggingFace Hub API Token",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": False,
} }
@ -113,6 +120,7 @@ def test_openai(client: TestClient):
"name": "cache", "name": "cache",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["verbose"] == { assert template["verbose"] == {
"required": False, "required": False,
@ -123,6 +131,7 @@ def test_openai(client: TestClient):
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["client"] == { assert template["client"] == {
"required": False, "required": False,
@ -133,6 +142,7 @@ def test_openai(client: TestClient):
"name": "client", "name": "client",
"type": "Any", "type": "Any",
"list": False, "list": False,
"advanced": True,
} }
assert template["model_name"] == { assert template["model_name"] == {
"required": False, "required": False,
@ -151,6 +161,7 @@ def test_openai(client: TestClient):
"name": "model_name", "name": "model_name",
"type": "str", "type": "str",
"list": True, "list": True,
"advanced": False,
} }
# Add more assertions for other properties here # Add more assertions for other properties here
assert template["temperature"] == { assert template["temperature"] == {
@ -163,6 +174,7 @@ def test_openai(client: TestClient):
"name": "temperature", "name": "temperature",
"type": "float", "type": "float",
"list": False, "list": False,
"advanced": False,
} }
assert template["max_tokens"] == { assert template["max_tokens"] == {
"required": False, "required": False,
@ -174,6 +186,7 @@ def test_openai(client: TestClient):
"name": "max_tokens", "name": "max_tokens",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["top_p"] == { assert template["top_p"] == {
"required": False, "required": False,
@ -185,6 +198,7 @@ def test_openai(client: TestClient):
"name": "top_p", "name": "top_p",
"type": "float", "type": "float",
"list": False, "list": False,
"advanced": True,
} }
assert template["frequency_penalty"] == { assert template["frequency_penalty"] == {
"required": False, "required": False,
@ -196,6 +210,7 @@ def test_openai(client: TestClient):
"name": "frequency_penalty", "name": "frequency_penalty",
"type": "float", "type": "float",
"list": False, "list": False,
"advanced": True,
} }
assert template["presence_penalty"] == { assert template["presence_penalty"] == {
"required": False, "required": False,
@ -207,6 +222,7 @@ def test_openai(client: TestClient):
"name": "presence_penalty", "name": "presence_penalty",
"type": "float", "type": "float",
"list": False, "list": False,
"advanced": True,
} }
assert template["n"] == { assert template["n"] == {
"required": False, "required": False,
@ -218,6 +234,7 @@ def test_openai(client: TestClient):
"name": "n", "name": "n",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["best_of"] == { assert template["best_of"] == {
"required": False, "required": False,
@ -229,6 +246,7 @@ def test_openai(client: TestClient):
"name": "best_of", "name": "best_of",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["model_kwargs"] == { assert template["model_kwargs"] == {
"required": False, "required": False,
@ -239,6 +257,7 @@ def test_openai(client: TestClient):
"name": "model_kwargs", "name": "model_kwargs",
"type": "code", "type": "code",
"list": False, "list": False,
"advanced": True,
} }
assert template["openai_api_key"] == { assert template["openai_api_key"] == {
"required": False, "required": False,
@ -251,6 +270,7 @@ def test_openai(client: TestClient):
"display_name": "OpenAI API Key", "display_name": "OpenAI API Key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": False,
} }
assert template["batch_size"] == { assert template["batch_size"] == {
"required": False, "required": False,
@ -262,6 +282,7 @@ def test_openai(client: TestClient):
"name": "batch_size", "name": "batch_size",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["request_timeout"] == { assert template["request_timeout"] == {
"required": False, "required": False,
@ -272,6 +293,7 @@ def test_openai(client: TestClient):
"name": "request_timeout", "name": "request_timeout",
"type": "Union[float, Tuple[float, float], NoneType]", "type": "Union[float, Tuple[float, float], NoneType]",
"list": False, "list": False,
"advanced": True,
} }
assert template["logit_bias"] == { assert template["logit_bias"] == {
"required": False, "required": False,
@ -282,6 +304,7 @@ def test_openai(client: TestClient):
"name": "logit_bias", "name": "logit_bias",
"type": "code", "type": "code",
"list": False, "list": False,
"advanced": True,
} }
assert template["max_retries"] == { assert template["max_retries"] == {
"required": False, "required": False,
@ -293,6 +316,7 @@ def test_openai(client: TestClient):
"name": "max_retries", "name": "max_retries",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["streaming"] == { assert template["streaming"] == {
"required": False, "required": False,
@ -304,6 +328,7 @@ def test_openai(client: TestClient):
"name": "streaming", "name": "streaming",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
@ -326,6 +351,7 @@ def test_chat_open_ai(client: TestClient):
"name": "verbose", "name": "verbose",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["client"] == { assert template["client"] == {
"required": False, "required": False,
@ -336,6 +362,7 @@ def test_chat_open_ai(client: TestClient):
"name": "client", "name": "client",
"type": "Any", "type": "Any",
"list": False, "list": False,
"advanced": True,
} }
assert template["model_name"] == { assert template["model_name"] == {
"required": False, "required": False,
@ -348,6 +375,7 @@ def test_chat_open_ai(client: TestClient):
"name": "model_name", "name": "model_name",
"type": "str", "type": "str",
"list": True, "list": True,
"advanced": False,
} }
assert template["temperature"] == { assert template["temperature"] == {
"required": False, "required": False,
@ -359,6 +387,7 @@ def test_chat_open_ai(client: TestClient):
"name": "temperature", "name": "temperature",
"type": "float", "type": "float",
"list": False, "list": False,
"advanced": False,
} }
assert template["model_kwargs"] == { assert template["model_kwargs"] == {
"required": False, "required": False,
@ -369,6 +398,7 @@ def test_chat_open_ai(client: TestClient):
"name": "model_kwargs", "name": "model_kwargs",
"type": "code", "type": "code",
"list": False, "list": False,
"advanced": True,
} }
assert template["openai_api_key"] == { assert template["openai_api_key"] == {
"required": False, "required": False,
@ -381,6 +411,7 @@ def test_chat_open_ai(client: TestClient):
"display_name": "OpenAI API Key", "display_name": "OpenAI API Key",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": False,
} }
assert template["request_timeout"] == { assert template["request_timeout"] == {
"required": False, "required": False,
@ -392,6 +423,7 @@ def test_chat_open_ai(client: TestClient):
"name": "request_timeout", "name": "request_timeout",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["max_retries"] == { assert template["max_retries"] == {
"required": False, "required": False,
@ -403,6 +435,7 @@ def test_chat_open_ai(client: TestClient):
"name": "max_retries", "name": "max_retries",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["streaming"] == { assert template["streaming"] == {
"required": False, "required": False,
@ -414,6 +447,7 @@ def test_chat_open_ai(client: TestClient):
"name": "streaming", "name": "streaming",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
assert template["n"] == { assert template["n"] == {
"required": False, "required": False,
@ -425,6 +459,7 @@ def test_chat_open_ai(client: TestClient):
"name": "n", "name": "n",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["max_tokens"] == { assert template["max_tokens"] == {
@ -436,6 +471,7 @@ def test_chat_open_ai(client: TestClient):
"name": "max_tokens", "name": "max_tokens",
"type": "int", "type": "int",
"list": False, "list": False,
"advanced": True,
} }
assert template["_type"] == "ChatOpenAI" assert template["_type"] == "ChatOpenAI"
assert ( assert (

View file

@ -27,6 +27,7 @@ def test_prompt_template(client: TestClient):
"name": "input_variables", "name": "input_variables",
"type": "str", "type": "str",
"list": True, "list": True,
"advanced": True,
} }
assert template["output_parser"] == { assert template["output_parser"] == {
"required": False, "required": False,
@ -37,6 +38,7 @@ def test_prompt_template(client: TestClient):
"name": "output_parser", "name": "output_parser",
"type": "BaseOutputParser", "type": "BaseOutputParser",
"list": False, "list": False,
"advanced": True,
} }
assert template["partial_variables"] == { assert template["partial_variables"] == {
"required": False, "required": False,
@ -47,6 +49,7 @@ def test_prompt_template(client: TestClient):
"name": "partial_variables", "name": "partial_variables",
"type": "code", "type": "code",
"list": False, "list": False,
"advanced": True,
} }
assert template["template"] == { assert template["template"] == {
"required": True, "required": True,
@ -57,6 +60,7 @@ def test_prompt_template(client: TestClient):
"name": "template", "name": "template",
"type": "prompt", "type": "prompt",
"list": False, "list": False,
"advanced": True,
} }
assert template["template_format"] == { assert template["template_format"] == {
"required": False, "required": False,
@ -68,6 +72,7 @@ def test_prompt_template(client: TestClient):
"name": "template_format", "name": "template_format",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["validate_template"] == { assert template["validate_template"] == {
"required": False, "required": False,
@ -79,6 +84,7 @@ def test_prompt_template(client: TestClient):
"name": "validate_template", "name": "validate_template",
"type": "bool", "type": "bool",
"list": False, "list": False,
"advanced": True,
} }
@ -100,6 +106,7 @@ def test_few_shot_prompt_template(client: TestClient):
"name": "examples", "name": "examples",
"type": "prompt", "type": "prompt",
"list": True, "list": True,
"advanced": True,
} }
assert template["example_selector"] == { assert template["example_selector"] == {
"required": False, "required": False,
@ -110,6 +117,7 @@ def test_few_shot_prompt_template(client: TestClient):
"name": "example_selector", "name": "example_selector",
"type": "BaseExampleSelector", "type": "BaseExampleSelector",
"list": False, "list": False,
"advanced": True,
} }
assert template["example_prompt"] == { assert template["example_prompt"] == {
"required": True, "required": True,
@ -120,6 +128,7 @@ def test_few_shot_prompt_template(client: TestClient):
"name": "example_prompt", "name": "example_prompt",
"type": "PromptTemplate", "type": "PromptTemplate",
"list": False, "list": False,
"advanced": True,
} }
assert template["suffix"] == { assert template["suffix"] == {
"required": True, "required": True,
@ -130,6 +139,7 @@ def test_few_shot_prompt_template(client: TestClient):
"name": "suffix", "name": "suffix",
"type": "prompt", "type": "prompt",
"list": False, "list": False,
"advanced": True,
} }
assert template["example_separator"] == { assert template["example_separator"] == {
"required": False, "required": False,
@ -141,6 +151,7 @@ def test_few_shot_prompt_template(client: TestClient):
"name": "example_separator", "name": "example_separator",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["prefix"] == { assert template["prefix"] == {
"required": False, "required": False,
@ -152,6 +163,7 @@ def test_few_shot_prompt_template(client: TestClient):
"name": "prefix", "name": "prefix",
"type": "prompt", "type": "prompt",
"list": False, "list": False,
"advanced": True,
} }
@ -172,6 +184,7 @@ def test_zero_shot_prompt(client: TestClient):
"name": "prefix", "name": "prefix",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["suffix"] == { assert template["suffix"] == {
"required": True, "required": True,
@ -183,6 +196,7 @@ def test_zero_shot_prompt(client: TestClient):
"name": "suffix", "name": "suffix",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }
assert template["format_instructions"] == { assert template["format_instructions"] == {
"required": False, "required": False,
@ -194,4 +208,5 @@ def test_zero_shot_prompt(client: TestClient):
"name": "format_instructions", "name": "format_instructions",
"type": "str", "type": "str",
"list": False, "list": False,
"advanced": True,
} }

46
tests/test_websocket.py Normal file
View 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": [],
}